<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Aral Balkan</title>
    <link>https://ar.al/</link>
    <description>Recent content on Aral Balkan</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>mail@ar.al (Aral Balkan)</managingEditor>
    <webMaster>mail@ar.al (Aral Balkan)</webMaster>
    <copyright>This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License</copyright>
    <lastBuildDate>Mon, 20 Feb 2023 09:59:56 +0000</lastBuildDate>
    <image>
      <url>https://ar.al/apple-touch-icon-144x144.png</url>
      <title>Aral Balkan</title>
      <link>https://ar.al/</link>
      <width>144</width>
      <height>144</height>
    </image>
    
        <atom:link href="https://ar.al/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>End-to-end encrypted Kitten Chat</title>
      <link>https://ar.al/2023/02/20/end-to-end-encrypted-kitten-chat/</link>
      <pubDate>Mon, 20 Feb 2023 09:59:56 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2023/02/20/end-to-end-encrypted-kitten-chat/</guid>
      <description>&lt;figure&gt;
    &lt;video id=&#34;small-is-beautiful&#34; controls=&#34;&#34; preload=&#34;none&#34;
      src=&#34;https://player.vimeo.com/progressive_redirect/playback/799597015/rendition/1080p/file.mp4?loc=external&amp;signature=77550654f53ecbc203211f93710a9c96d8d510ae0d62824c5b441ed25dde2701#t=25&#34;
      poster=&#34;/2023/02/20/end-to-end-encrypted-kitten-chat/poster.jpg&#34; width=&#34;800&#34;&gt;
      &lt;track label=&#34;English captions&#34; kind=&#34;subtitles&#34; srclang=&#34;en&#34; src=&#34;./auto_generated_captions_edited.vtt&#34; default&gt;
      &lt;img src=&#34;https://ar.al/2023/02/20/end-to-end-encrypted-kitten-chat//2023/02/20/end-to-end-encrypted-kitten-chat/poster.jpg&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
      &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
          href=&#34;https://player.vimeo.com/progressive_redirect/download/799597015/rendition/1080p/small_is_beautiful_end-to-end_encrypted_kitten_chat_-_feb_16,_2023%20%281080p%29.mp4?loc=external&amp;signature=5aa64bb92cee5c59b97c02741e5970420ee42cf386c50147a8ff29537e654966&#34;&gt;download
          Small Is Beautiful #27 directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
    &lt;/video&gt;
    &lt;figcaption&gt;Small Is Beautiful (Feb, 2023): End-to-end encrypted Kitten Chat (an example peer-to-peer &lt;a href=&#39;https://ar.al/2020/08/07/what-is-the-small-web/&#39;&gt;Small Web&lt;/a&gt; app using &lt;a href=&#39;https://codeberg.org/kitten/app&#39;&gt;Kitten&lt;/a&gt;. &lt;a href=&#39;https://codeberg.org/kitten/app#kitten-chat&#39;&gt;Follow the tutorial&lt;/a&gt; to build it yourself from scratch or &lt;a href=&#39;https://codeberg.org/kitten/app/src/branch/main/examples/end-to-end-encrypted-kitten-chat&#39;&gt;browse the source code&lt;/a&gt;).&lt;/p&gt;
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;p&gt;In this hour-and-a-half long &lt;a href=&#34;https://small-tech.org/events/&#34;&gt;Small is Beautiful&lt;/a&gt; live stream recording, I show you how &lt;a href=&#34;https://codeberg.org/kitten/app#kitten-chat&#34;&gt;WebSockets&lt;/a&gt;, &lt;a href=&#34;https://codeberg.org/kitten/app#project-specific-secret&#34;&gt;project-specific secrets&lt;/a&gt;, and &lt;a href=&#34;https://codeberg.org/kitten/app#authenticated-routes&#34;&gt;authenticated routes&lt;/a&gt; work in &lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt; and migrate &lt;a href=&#34;https://codeberg.org/kitten/app#persistent-kitten-chat&#34;&gt;a centralised WebSocket chat application&lt;/a&gt; to &lt;a href=&#34;https://codeberg.org/kitten/app#end-to-end-encrypted-kitten-chat&#34;&gt;an end-to-end-encrypted peer-to-peer Small Web chat application&lt;/a&gt; in Kitten.&lt;/p&gt;
&lt;p&gt;The example also makes use of the native support for &lt;a href=&#34;https://htmx.org&#34;&gt;htmx&lt;/a&gt; and &lt;a href=&#34;https://alpinejs.dev&#34;&gt;Alpine.js&lt;/a&gt; in Kitten.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&#34;https://owncast.small-web.org/&#34;&gt;follow Small is Beautiful&lt;/a&gt; from the &lt;a href=&#34;https://fedi.tips/&#34;&gt;fediverse&lt;/a&gt; (we stream it using our own &lt;a href=&#34;https://owncast.online&#34;&gt;Owncast&lt;/a&gt; instance) to be notified of future streams.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Hint: you can install Owncast using &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;transcript&#34;&gt;Transcript&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/videos/small-is-beautiful-27/auto_generated_transcript_edited.txt&#34;&gt;Auto-generated transcript from the captions.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;links-to-other-things-mentioned-or-shown-during-the-stream&#34;&gt;Links to other things mentioned or shown during the stream:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://codeberg.org/domain/app&#34;&gt;Domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitlab.gnome.org/raggesilver/blackbox&#34;&gt;Black Box Terminal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://codeberg.org/small-tech/lipstick&#34;&gt;Lipstick on a Pig&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://helix-editor.com&#34;&gt;Helix Editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/gokcehan/lf&#34;&gt;lf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mhgolkar/Weasel&#34;&gt;WebSocket Weasel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/RESTEDClient/RESTED&#34;&gt;RESTED&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/skipto-landmarks-headings/browser-extension&#34;&gt;SkipTo Landmarks &amp;amp; Headings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://openswitcher.org/&#34;&gt;Open Switcher Control&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Streamed using our own &lt;a href=&#34;https://owncast.online&#34;&gt;Owncast&lt;/a&gt; instance. (Hint: you can install Owncast using &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you like this livestream, please help support out work at Small Technology Foundation with a &lt;a href=&#34;https://ar.al/fund-us&#34;&gt;patronage or donation&lt;/a&gt;, or share our videos with your friends!&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Installing Helix Editor Language Servers</title>
      <link>https://ar.al/2022/11/14/installing-helix-editor-language-servers/</link>
      <pubDate>Mon, 14 Nov 2022 11:18:37 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/11/14/installing-helix-editor-language-servers/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/11/14/installing-helix-editor-language-servers/./helix-screenshot.png&#34;
         alt=&#34;Screenshot of the Helix Editor symbol picker with the temporaryDirectory variable selected in the left-hand-side of a horizontally-split interface and the relevant code section displayed on the right.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Helix Editor using the Bash Language Server to show the symbols in the script included in this post.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;style&gt;figcaption { margin-top: -1.5em }&lt;/style&gt;
&lt;h2 id=&#34;helix-editor&#34;&gt;Helix Editor&lt;/h2&gt;
&lt;p&gt;I’ve been using &lt;a href=&#34;https://helix-editor.com&#34;&gt;Helix Editor&lt;/a&gt; as my daily driver for web development for most of this year and – while it has some outstanding issues&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; (what doesn’t?) – I’m really enjoying it.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;One of the issues that might trip new folks is that while it has &lt;a href=&#34;https://microsoft.github.io/language-server-protocol/&#34;&gt;Language Server Protocol (LSP)&lt;/a&gt; support, and while &lt;a href=&#34;https://docs.helix-editor.com/languages.html&#34;&gt;there are default Language Servers configured&lt;/a&gt;, installing Helix Editor doesn’t actually install those language servers.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Another issue is that since many of the language servers are written in Node.js and installed as global modules, if you update your version of Node (e.g., using &lt;a href=&#34;https://github.com/jorgebucaran/nvm.fish&#34;&gt;nvm.fish&lt;/a&gt; on &lt;a href=&#34;https://ar.al/2021/07/25/fish-shell/&#34;&gt;fish shell&lt;/a&gt;), you will also have to reinstall your language servers.&lt;/p&gt;
&lt;p&gt;Needless to say, this can get tedious so here’s a little script I hacked together today for myself to easily install (and reinstall) the Language Servers I use for web development (mostly HTML, JS, CSS, and Node.js with &lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt; these days) in Helix Editor.&lt;/p&gt;
&lt;p&gt;I’m sharing it below in case it helps anyone else. Please feel free to adapt and use it for yourself.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Copy &lt;a href=&#34;#code&#34;&gt;the code&lt;/a&gt; into a file named, e.g., &lt;em&gt;install-helix-language-servers&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make that file executable. e.g.:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;chmod +x install-helix-language-servers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adapt it for your own needs (&lt;a href=&#34;#notes&#34;&gt;see notes&lt;/a&gt;) and enjoy!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Only tested on Linux.&lt;/li&gt;
&lt;li&gt;Requires &lt;a href=&#34;https://nodejs.org/&#34;&gt;Node.js&lt;/a&gt;, &lt;a href=&#34;https://www.rust-lang.org/&#34;&gt;Rust&lt;/a&gt;, &lt;a href=&#34;https://en.wikipedia.org/wiki/Bash_(Unix_shell)&#34;&gt;Bash&lt;/a&gt;, &lt;a href=&#34;https://www.gnu.org/software/wget/&#34;&gt;wget&lt;/a&gt;, &lt;a href=&#34;https://www.gnu.org/software/gzip/&#34;&gt;gunzip&lt;/a&gt;, and &lt;a href=&#34;https://www.gnu.org/software/tar/&#34;&gt;tar&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re running this on &lt;a href=&#34;https://getfedora.org/en/silverblue/&#34;&gt;Fedora Silverblue&lt;/a&gt; or other &lt;a href=&#34;https://github.com/castrojo/awesome-immutable&#34;&gt;immutable Linux distribution&lt;/a&gt;,
make sure you do so from a mutable container (e.g., via &lt;a href=&#34;https://fedoramagazine.org/a-quick-introduction-to-toolbox-on-fedora/&#34;&gt;Toolbox&lt;/a&gt; or &lt;a href=&#34;https://distrobox.privatedns.org/&#34;&gt;Distrobox&lt;/a&gt;)
that has the Rust toolchain installed so that the Language Servers that are
installed by &lt;a href=&#34;https://doc.rust-lang.org/book/ch01-03-hello-cargo.html&#34;&gt;Cargo&lt;/a&gt; (e.g., &lt;a href=&#34;https://toml.io/en/&#34;&gt;TOML&lt;/a&gt;) can be compiled properly.&lt;/p&gt;
&lt;p&gt;Note: TOML Language Server requires latest Rust to compile. Tested to work on 1.65.0
(does not work on 1.60.0). See &lt;a href=&#34;https://github.com/tamasfe/taplo/issues/349&#34;&gt;https://github.com/tamasfe/taplo/issues/349&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you’re on a non-Linux platform, please modify the script accordingly.&lt;/p&gt;
&lt;p&gt;You can find installation instructions for all Language Servers supported by Helix Editor at:
&lt;a href=&#34;https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers&#34;&gt;https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;BINARY_HOME&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/.local/bin&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;DATA_HOME&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;XDG_DATA_HOME&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;:-&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;/.local/share&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;

&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Installing Language Servers for Helix Editor:&amp;#34;&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Work in a throwaway temporary directory so as not to pollute the file system.&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;temporaryDirectory&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;/tmp/helix-editor-language-server-installer&amp;#34;&lt;/span&gt;
mkdir -p &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;temporaryDirectory&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;pushd&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;temporaryDirectory&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Bash&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;  • Bash (bash-language-server)&amp;#34;&lt;/span&gt;
npm i -g bash-language-server

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# HTML, JSON, JSON schema&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;  • HTML, JSON, and JSON schema (vscode-langservers-extracted)&amp;#34;&lt;/span&gt;
npm i -g vscode-langservers-extracted

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# JavaScript (via TypeScript)&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;  • JavaScript (typescript, typescript-language-server)&amp;#34;&lt;/span&gt;
npm install -g typescript typescript-language-server

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Markdown (via ltex-ls. Note: this has excellent features like&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# spelling and grammar check but is a ~269MB download).&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;  • Markdown (ltex-ls)&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsVersion&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;15.2.0
&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBinaryPath&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;BINARY_HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/ltex-ls&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBaseFileName&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;ltex-ls-&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsVersion&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsFileNameWithPlatform&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBaseFileName&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;-linux-x64&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsAppDirectory&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;DATA_HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBaseFileName&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;
rm &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBinaryPath&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;
rm -rf &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsAppDirectory&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;
wget &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;https://github.com/valentjn/ltex-ls/releases/download/&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsVersion&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsFileNameWithPlatform&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;.tar.gz&amp;#34;&lt;/span&gt;
gunzip &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsFileNameWithPlatform&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;.tar.gz&amp;#34;&lt;/span&gt;
tar xf &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsFileNameWithPlatform&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;.tar&amp;#34;&lt;/span&gt;
mv &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBaseFileName&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;DATA_HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;
ln -s &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsAppDirectory&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/bin/ltex-ls&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;ltexLsBinaryPath&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt; 

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# TOML&lt;/span&gt;
cargo install taplo-cli --locked --features lsp

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Clean up.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;popd&lt;/span&gt;
rm -rf &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;temporaryDirectory&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;

&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Done.&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Soft wrap doesn’t exist yet, for example, which makes it nigh on impossible to edit Markdown in it. For that, I’m currently using &lt;a href=&#34;https://github.com/marktext/marktext&#34;&gt;MarkText&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is not something I thought I’d ever say for a modal editor but Helix gets a lot of things right. While there is definitely a learning curve, I feel like I now think about my code as code instead of as lines of text and characters as I’m working and that’s a good feeling. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is understandable as not everyone needs or wants to install every language server. Especially considering that language servers can be very large. For example, the ltex-ls language server for Markdown is roughtly a 265MB download. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Is the fediverse about to get Fryed? (Or, “Why every toot is also a potential denial of service attack”)</title>
      <link>https://ar.al/2022/11/09/is-the-fediverse-about-to-get-fryed-or-why-every-toot-is-also-a-potential-denial-of-service-attack/</link>
      <pubDate>Wed, 09 Nov 2022 19:00:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/11/09/is-the-fediverse-about-to-get-fryed-or-why-every-toot-is-also-a-potential-denial-of-service-attack/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/11/09/is-the-fediverse-about-to-get-fryed-or-why-every-toot-is-also-a-potential-denial-of-service-attack/./stephen-fry-mastodon-banner.png&#34;
         alt=&#34;Screenshot of Stephen Fry’s Mastodon account banner on mastodonapp.uk&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Stephen is a big fish to fry. (I’m here all week.)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Warning: the fediverse is about to get Fryed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.stephenfry.com/&#34;&gt;Stephen Fry&lt;/a&gt;ed, that is.&lt;/p&gt;
&lt;p&gt;Following the recent takeover of Twitter by a proto-fascist billionaire man-baby, people have been fleeing&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; to the fediverse&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Among them are folks who, on Twitter, at least, had millions of followers like &lt;a href=&#34;https://mastodon.nu/@gretathunberg&#34;&gt;Greta Thunberg&lt;/a&gt; and, more recently, &lt;a href=&#34;https://mastodonapp.uk/@stephenfry&#34;&gt;Stephen Fry.&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;“Well, surely that’s a good thing? It’ll get everyone talking about the fediverse, decentralisation, and maybe even that &lt;a href=&#34;&#34;&gt;Small Web&lt;/a&gt; thing you keep harping on about all the time, Aral, no?”&lt;/p&gt;
&lt;p&gt;Well, yes and no… you see, there is such a thing as &lt;strong&gt;too much of a good thing.&lt;/strong&gt; And, on the fediverse today, that appears to be “engagement when you’re popular.” In fact, it could be deadly (to Mastodon instances, that is).&lt;/p&gt;
&lt;p&gt;Read on and I’ll try to explain what I mean by using my own account as an example.&lt;/p&gt;
&lt;h2 id=&#34;how-to-kill-a-mastodon-hint-by-being-chatty-when-youre-popular&#34;&gt;How to kill a Mastodon (hint: by being chatty when you’re popular)&lt;/h2&gt;
&lt;p&gt;Needless to say, I’m not a celebrity.&lt;/p&gt;
&lt;p&gt;And yet, on the fediverse, I find myself in a somewhat unique situation where:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I have my own personal Mastodon instance, just for me.&lt;/strong&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I’m followed by quite a number of people.&lt;/strong&gt; Over 22,000, to be exact.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I follow a lot of people and I genuinely enjoy having conversations with them.&lt;/strong&gt; (I believe this is what the cool kids call “engagement”.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unfortunately, the combination of these three factors creates a perfect storm&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; which means that now, every time I post something that gets lots of engagement, I essentially end up carrying out a &lt;a href=&#34;https://en.wikipedia.org/wiki/Denial-of-service_attack&#34;&gt;denial of service attack&lt;/a&gt; on myself.&lt;/p&gt;
&lt;h2 id=&#34;mastodon-denial-of-service-as-a-service&#34;&gt;Mastodon: denial-of-service as a service?&lt;/h2&gt;
&lt;p&gt;Yesterday was my birthday.&lt;/p&gt;
&lt;p&gt;So, of course, I posted about it on my Mastodon instance.&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/109307717300565748/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;p&gt;It got quite a few replies. And, because it’s only polite, I started replying to everyone with thank-you messages.&lt;/p&gt;
&lt;p&gt;Oh, no, you poor, naïve man, you. What were you thinking?!…&lt;/p&gt;
&lt;p&gt;I’ll let my friend &lt;a href=&#34;https://hugo.gameiro.pt/&#34;&gt;Hugo Gameiro&lt;/a&gt;, who runs &lt;a href=&#34;https://masto.host&#34;&gt;masto.host&lt;/a&gt; and hosts my instance, explain what happened next:&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You just get a lot of engagement and that requires a ton of &lt;a href=&#34;https://sidekiq.org/&#34;&gt;Sidekiq&lt;/a&gt; power to process.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s look at your birthday post …  besides requiring thousands of Sidekiq jobs to spread your post through all their servers (you have 23K followers, let&amp;rsquo;s assume 3K different servers&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;), as soon as you create the post 3K Sidekiq jobs are created. At your current plan you have 12 Sidekiq threads, so to process 3K jobs it will take a while because it can only deal with 12 at a time.&lt;/p&gt;
&lt;p&gt;Then, for each reply you receive to that post, 3K jobs are created, so your
followers can see that reply without leaving their server or looking at
your profile. Then you reply to the reply you got, another 3K jobs are
created and so on. &lt;/p&gt;
&lt;p&gt;If you replied to the 100 replies you got on that post in 10 minutes (and assuming my 3K servers math is right). You created 300K jobs in Sidekiq. That&amp;rsquo;s why you get those queues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So what does that mean if you’re not into the technical mumbo-jumbo?&lt;/p&gt;
&lt;p&gt;It means I was too chatty while being somewhat popular.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/11/09/is-the-fediverse-about-to-get-fryed-or-why-every-toot-is-also-a-potential-denial-of-service-attack/./sidekiq-stats-during-my-birthday-post.png&#34;
         alt=&#34;Screenshot of my Sidekiq stats, showing 175,082 enqueued tasks as I was replying to folks on my birthday post&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;What a traffic jam looks like in Mastodon.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So, what’s the solution?&lt;/p&gt;
&lt;p&gt;Well, there’s only one thing you can do when you find yourself in such a pickle: scale up your Mastodon instance.&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; The problem with that? It starts getting expensive.&lt;/p&gt;
&lt;p&gt;Prior to the latest Twitter migration&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;, I was paying around €280/year (or a little over €20/month) for my Mastodon instance on a custom plan I had with Hugo from the early days. This week, I upped that to a roughly €50/month plan. And that’s still not enough as my birthday post just showed so Hugo, kindly, has suggested he might have to come up with a custom plan for me.&lt;/p&gt;
&lt;p&gt;And yet, the problem is not one that will go away. We can only kick the ball down the road, as it were.&lt;/p&gt;
&lt;p&gt;(Unless I piss everyone off with this post, that is.)&lt;/p&gt;
&lt;p&gt;Thankfully, by running my own instance, the only person I’m burdening with this additional expense is me. But what if I’d been on a public instance run by someone else instead?&lt;/p&gt;
&lt;h2 id=&#34;musk-you&#34;&gt;Musk you?&lt;/h2&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/109281944476827542/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;p&gt;If Elon Musk wanted to destroy &lt;a href=&#34;https://mastodon.social&#34;&gt;mastodon.social&lt;/a&gt;, the flagship Mastodon instance, all he’d have to do is join it.&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Thank goodness Elon isn’t that smart.&lt;/p&gt;
&lt;p&gt;I jest, of course… &lt;a href=&#34;https://en.wikipedia.org/wiki/Eugen_Rochko&#34;&gt;Eugen&lt;/a&gt; would likely ban his account the moment he saw it. But it does illustrate a problem: Elon’s easy to ban. Stephen, not so much. He’s a national treasure for goodness’ sake. One does not simply ban Stephen Fry.&lt;/p&gt;
&lt;p&gt;And yet Stephen can similarly (yet unwittingly) cause untold expense to the folks running Mastodon instances just by joining one.&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The solution, for Stephen at least, is simple: &lt;a href=&#34;https://mastodon.ar.al/@aral/109311046067489334&#34;&gt;he should run his own personal instance.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Or get someone else to run it for him, like I do.)&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Running his own instance would also give Stephen one additional benefit: he’d automatically get verified.&lt;/p&gt;
&lt;p&gt;After all, if you’re talking to, say, &lt;em&gt;@stephen@social.stephenfry.com&lt;/em&gt;, you can be sure it’s really him because you know he owns the domain.&lt;/p&gt;
&lt;h2 id=&#34;personal-instances-to-the-rescue&#34;&gt;Personal instances to the rescue&lt;/h2&gt;
&lt;style&gt;
/* Because CSS sucks. Hack courtesy of https://css-tricks.com/NetMag/FluidWidthVideo/Article-FluidWidthVideo.php */

.videoWrapper {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 */
  padding-top: 25px;
  height: 0;
}

.videoWrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
&lt;/style&gt;
&lt;figure&gt;
&lt;div class=&#39;videoWrapper&#39;&gt;
  &lt;iframe sandbox=&#34;allow-same-origin allow-scripts&#34; src=&#34;https://video.lqdn.fr/videos/embed/70f2128c-8c06-4cc4-8a5a-bf77e765c8fd?title=0&amp;warningTitle=0&#34; frameborder=&#34;0&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;figcaption&gt;&lt;p style=&#34;margin-top: 2em; font-weight: 700;&#34;&gt;My speech at the European Parliament on the problem with Big Tech and the different approaches provided by Mastodon, the fediverse, and Small Web.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Wait, I’m confused… didn’t you say that personal instances were part of the problem?&lt;/p&gt;
&lt;p&gt;Yes and no: they are and they shouldn’t be.&lt;/p&gt;
&lt;p&gt;If ActivityPub (the protocol) and Mastodon (a server that adheres to that protocol) were designed to incentivise decentralisation, having more instances in the network would not be a problem. In fact, it would be the sign of a healthy, decentralised network.&lt;/p&gt;
&lt;p&gt;However, ActivityPub and Mastodon are designed the same way Big Tech/Big Web is: to encourage services that host as many “users”&lt;sup id=&#34;fnref:14&#34;&gt;&lt;a href=&#34;#fn:14&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;14&lt;/a&gt;&lt;/sup&gt; as they can.&lt;/p&gt;
&lt;p&gt;This design is both complex (which makes it difficult and expensive to self-host) and works beautifully for Big Tech (where things are centralised and scale vertically and where the goal is to get/own/control/exploit as many users as possible).&lt;/p&gt;
&lt;p&gt;In Big Tech, the initial cost of obtaining such scale is subsidised by vast amounts of venture capital (rich people investing in exploitative and extractive new businesses – which Silicon Valley calls Startups™ – in an effort to get even richer) and it leads to the amassing of the centres&lt;sup id=&#34;fnref:15&#34;&gt;&lt;a href=&#34;#fn:15&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;15&lt;/a&gt;&lt;/sup&gt; we know today as the Googles, Facebooks, and Twitters of the world.&lt;/p&gt;
&lt;p&gt;However, unlike Big Tech, the stated goal of the fediverse is to decentralise things, not centralise them. Yet how likely is it we can achieve the opposite of Big Tech’s goals while adopting its same fundamental design?&lt;/p&gt;
&lt;p&gt;When you adopt the design of a thing, you also inherit the success criteria that led to the evolution of that design. If that success criteria does not align with your own goals, you have a problem on your hands.&lt;/p&gt;
&lt;p&gt;What I’m trying to say is:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mastodon.ar.al/@aral/109274437122830092&#34;&gt;Do not adopt the success criteria of Big Tech lest you should become Big Tech.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;bigger-is-not-better&#34;&gt;Bigger is not better&lt;/h2&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/109273487203534361/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;p&gt;Today, we equate the size of mastodon.social (the instance run by Eugen) with how successful Mastodon (the software created by Eugen) is. This is very dangerous. The larger mastodon.social gets, the more it will become like Twitter.&lt;/p&gt;
&lt;p&gt;I can almost hear you shout, “But Aral, it’s federated! At least there’s no lock-in to mastodon.social!”&lt;/p&gt;
&lt;p&gt;This is true.&lt;/p&gt;
&lt;p&gt;You know what else is federated? Email.&lt;/p&gt;
&lt;p&gt;Have you ever heard of a little old email instance called Gmail? (Or perhaps the term &lt;a href=&#34;https://en.wikipedia.org/wiki/Embrace,_extend,_and_extinguish&#34;&gt;“embrace, extend, extinguish?”&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Do you know what happens to your email if Google says (rightly or wrongly) that you’re spam? No one sees your email.&lt;/p&gt;
&lt;p&gt;You know what happens if mastodon.social blocks your instance? Hundreds of thousands of people (soon, millions?) do not get a choice in whether they see your posts or not.&lt;/p&gt;
&lt;p&gt;What happens when your instance of one blocks mastodon.social? Nothing, really.&lt;/p&gt;
&lt;p&gt;That’s quite a power imbalance.&lt;/p&gt;
&lt;h2 id=&#34;decentralisation-begins-at-decentring-yourself&#34;&gt;Decentralisation begins at decentring yourself&lt;/h2&gt;
&lt;p&gt;Mastodon is a not-for-profit, and I have no reason to believe that Eugen has anything but the best of intentions.&lt;/p&gt;
&lt;p&gt;However, &lt;a href=&#34;https://ar.al/2022/02/16/decentralisation-begins-at-decentring-yourself/&#34;&gt;decentralisation begins at decentring yourself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s in the interests of the fediverse that mastodon.social sets a good example by limiting its size voluntarily.&lt;/p&gt;
&lt;p&gt;In fact, this should be built right into the software. Mastodon instances should be limited from growing beyond a certain size. Instances that are already too large should have ways of encouraging people to migrate to smaller ones.&lt;/p&gt;
&lt;p&gt;As a community we should approach large instances as tumours: how do we break them up so they are no longer a threat to the organism?&lt;/p&gt;
&lt;p&gt;If you take this approach to its logical conclusion, you will arrive at the concept of the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt;; a web where we each own and control our own place (or places).&lt;/p&gt;
&lt;figure&gt;
  &lt;video id=&#34;small-is-beautiful&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/762676594/rendition/1080p/file.mp4?loc=external&amp;signature=6f327f42324b157a97add4fd4c532c69e0cdd29b206ff93f79f4cdae570ed922#t=185&#34;
    poster=&#34;https://small-tech.org/videos/small-is-beautiful-23/poster.jpg&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/2022/11/09/is-the-fediverse-about-to-get-fryed-or-why-every-toot-is-also-a-potential-denial-of-service-attack/https://small-tech.org/videos/small-is-beautiful-23/poster.jpg&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/playback/762676594/rendition/1080p/file.mp4?loc=external&amp;signature=6f327f42324b157a97add4fd4c532c69e0cdd29b206ff93f79f4cdae570ed922#t=27&#34;&gt;download
        Small Is Beautiful #23 directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;&lt;a href=&#39;https://owncast.small-web.org&#39;&gt;Small Is Beautiful&lt;/a&gt; (Oct, 2022): What is the Small Web and why do we need it?&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;tweet-tweet&#34;&gt;Tweet, tweet?&lt;/h2&gt;
&lt;p&gt;I’m not saying that the current fediverse protocols and apps can, will, or even necessarily &lt;em&gt;should&lt;/em&gt; evolve into the Small Web.&lt;sup id=&#34;fnref:16&#34;&gt;&lt;a href=&#34;#fn:16&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;16&lt;/a&gt;&lt;/sup&gt; In the here and now, the fediverse is an invaluable stopgap that provides a safer haven than the centralised cesspits of Silicon Valley.&lt;/p&gt;
&lt;p&gt;How long the stopgap lasts will depend on how successful we are at resisting centralisation. Protocol and server designs that incentivise vertical scale will not necessarily make this easy. However, there are social pressures we can use to counter their effects.&lt;/p&gt;
&lt;p&gt;The last thing you want is a handful of mini Zuckerbergs running the fediverse. Or worse, to find yourself having become one of those mini Zuckerbergs.&lt;/p&gt;
&lt;p&gt;I love that the fediverse exists. And I have the utmost respect for the gargantuan effort that’s going into it.&lt;/p&gt;
&lt;p&gt;And yet, I am also very concerned&lt;sup id=&#34;fnref:17&#34;&gt;&lt;a href=&#34;#fn:17&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;17&lt;/a&gt;&lt;/sup&gt; that the design decisions that have been made incentivise centralisation, not decentralisation. I implore us to acknowledge this, to mitigate the risks as best we can, to strive to learn from our mistakes, and to do even better going forward.&lt;/p&gt;
&lt;p&gt;So to the ActivityPub and Mastodon folks, I say:&lt;/p&gt;
&lt;p&gt;Consider me your canary in the coal mine…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;«Chirp! Chirp! Chirp!»&lt;/strong&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;After 16 years on Twitter, even I finally &lt;a href=&#34;https://mastodon.ar.al/@aral/109285512320737864&#34;&gt;deactivated my account and asked for all by data to be deleted&lt;/a&gt; last week. It was easy to do as I’ve been on the fediverse for over five years and I’d basically stopped using the &lt;a href=&#34;https://ar.al/2021/05/10/hell-site/&#34;&gt;hell site&lt;/a&gt; almost entirely for over a year. I was just keeping my account active so as not to break over a decade-and-a-half of web links. (Let this be a lesson to you: if you care about not breaking links/content on the web, make sure that &lt;em&gt;you&lt;/em&gt; own them instead of Startup Of The Week, Inc.) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you’re wondering what the fediverse is, it’s likely what you call &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;. I could write a whole other blog post about why Mastodon is not the fediverse but, thankfully, &lt;a href=&#34;https://fedi.tips/mastodon-and-the-fediverse-beginners-start-here/&#34;&gt;others have done a great job of explaining the basic concepts already&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Greta joined the &lt;a href=&#34;https://mastodon.nu&#34;&gt;mastodon.nu&lt;/a&gt; instance and already has over 44,000 followers (and is following 50) and Stephen has joined &lt;a href=&#34;&#34;&gt;mastodonapp.uk&lt;/a&gt; and amassed 27,000 followers in a day or so (and isn‘t following anyone in return at the moment). &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s what I’d call a “personal instance”, an “instance of one”, or “a single-tenant instance.” It’s basically a server where only you have an account. Because Mastodon is federated using the ActivityPub protocol, you can communicate with anyone on any other instance but being on an instance of one is very different to being on an instance of hundreds of thousands, like mastodon.social, for example. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On Twitter, my follower count was at roughly 42,000 people before I deactivated my account. Keep in mind that that was after being on the site for 16 years. Similarly, I’ve been on the fediverse for over five years, basically since the beginning, and Stephen Fry has amassed more followers in a single day. This is important to remember as we use my experience to forecast what the instance he joined – mastodonapp.uk – will begin to (or has already begun to) experience. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Given the design decisions underpinning the &lt;a href=&#34;https://activitypub.rocks/&#34;&gt;ActivityPub protocol&lt;/a&gt; and Mastodon server. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;When I asked Hugo if I could quote him for this post, he said yes but with the following caveat, which I’m including here so you don’t go off complaining to him (if you have any issues with this post, you can complain to me instead): “You can quote my explanation but as an illustration because there are several aspects that are not 100% accurate and others that I probably don&amp;rsquo;t even know. For example, if a post/reply includes an image, are two Sidekiq jobs created or only one? If it includes multiple images? If there is a custom emoji that has not federated yet in those posts/replies, etc. But the explanation is probably not very far from the reality in terms of the general functionality of the protocol.” &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;After posting this article from my instance, I watched the Sidekiq queue during the original posting and subsequent replies and it would appear that, currently, the actual number of unique instances my followers are on is ~1,377. &lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or start blocking followers, or unfollowing people, or staying quiet. None of which are viable options. (Especially the last one… I mean, do you even &lt;em&gt;know&lt;/em&gt; me?) &lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Somewhat ironically, &lt;a href=&#34;https://extratone.com/mastodon-eugen-rochko-federated-social-network&#34;&gt;I’m apparently responsible for one of the first Twitter migrations&lt;/a&gt; (although on a much smaller scale than today’s), back when Mastodon was just starting out. I guess you could say all this is just karma. &lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;He’d then get his followers on Twitter to join as many different Mastodon instances as they could and follow him. Finally, with his bootlicker army of incels in place, he’d just have to get chatty with them and watch &lt;a href=&#34;https://en.wikipedia.org/wiki/Eugen_Rochko&#34;&gt;Eugen&lt;/a&gt;’s instance burn to the ground under the weight of it all. &lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On an instance run by donations, for example, this would mean those donations would go to subsidise his account far more than the other accounts on the instance. That is, if they even managed to cover the cost of it to begin with. In Big Tech, like Twitter, the burden placed on the system by celebrities and other exceedingly popular accounts (journalists, etc.) is just a cost of doing business. The cost is subsidised by the corporation because these accounts are the bait that attracts the true assets of the business: you. In Big Tech, &lt;a href=&#34;https://mastodon.ar.al/@aral/109281944476827542&#34;&gt;you are the livestock being farmed&lt;/a&gt;. They just have to keep you distracted enough with enough sparkly things that you don’t realise you’re being farmed.Also, there are economies of scale present in centralised Big Tech systems that simply do not (and should not) exist in decentralised systems. &lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:13&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Unlike me, I don’t think Stephen would have to sweat the cost of the server although it will be considerably more than the heavily-subsidised $8/month that Elon would have been charging him. &lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:14&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The term “user” is an othering. &lt;a href=&#34;https://mastodon.ar.al/@aral/109290545572206366&#34;&gt;In Small Tech, we call people “people.”&lt;/a&gt; &lt;a href=&#34;#fnref:14&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:15&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Some would say “tumours”. Hello, I am Some. &lt;a href=&#34;#fnref:15&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:16&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If anything, the design decisions behind servers like Mastodon show that we need radically different approaches and first-principles design optimised for single-tenant servers and peer-to-peer connections if we want to nurture a Small Web. &lt;a href=&#34;#fnref:16&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:17&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://mastodon.social/@aral/2160843&#34;&gt;And I have been from the start.&lt;/a&gt; &lt;a href=&#34;#fnref:17&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>NLnet Grant Application for Domain Rejected</title>
      <link>https://ar.al/2022/10/20/nlnet-grant-application-for-domain-rejected/</link>
      <pubDate>Thu, 20 Oct 2022 11:08:59 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/10/20/nlnet-grant-application-for-domain-rejected/</guid>
      <description>&lt;p&gt;On October 12th, 2022, we recevied the following form letter, informing us that our NLnet Grant Application (&lt;a href=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/&#34;&gt;original application&lt;/a&gt;, &lt;a href=&#34;https://ar.al/2022/09/28/nlnet-grant-application-for-domain-first-update-questions-and-answers/&#34;&gt;follow-up questions and answers&lt;/a&gt;) for &lt;a href=&#34;https://codeberg.org/domain/app&#34;&gt;Domain&lt;/a&gt; has been rejected.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;Dear Aral,

I&amp;#39;m sorry to have to inform you that your project &amp;#34;Domain&amp;#34; (2022-08-099) unfortunately was not selected for a grant.

This in no way means that we do not see the value of the work you proposed. We get many more excellent proposals than we can grant with our limited means, so competition is extremely tough. There are unfortunately many talented independent researchers, developers and community leaders that need funding. In addition to that, the specific scope and rules of play of each call pose (sometimes artificial) limits in our selection that mean excellent projects leave empty-handed - because they just do not fit well enough within a certain fund.

Again, we are very sorry that we cannot offer you support for your good efforts. We hope you are not discouraged, and are able to secure funding elsewhere for the project, for instance from any of these organisations:

 https://NLnet.nl/foundation/network.html

or through other calls from the Next Generation Internet:

 https://www.ngi.eu/opencalls

And do trust that we have your funding need and the outline of your project in the back of our head from now on, and so we might come back to you if an opportunity arises (unless you asked us to destroy your contact details in the application form, in which case we will do so).

If you should have any questions, please let us know. And in case you have another good project in mind in the future, do not hesitate to submit again!

Kind regards,
on behalf of NLnet foundation,

Michiel Leenaars
Strategy Director
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means that, to date, we have received and continue to receive no European Union funding for our work at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m also done wasting time writing grant proposals to organisations that clearly do not care about supporting the work we do.&lt;/p&gt;
&lt;p&gt;Instead, there is code to write and we will continue to work for the common good – as we have been doing for almost the past decade – even though we are not funded from the common purse.&lt;/p&gt;
&lt;p&gt;If you’d like to help us continue to exist, please feel free to &lt;a href=&#34;https://small-tech.org/fund-us&#34;&gt;become a patron or make a donation&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;style&gt;
pre, pre code {
  background-color: white !important;
}
&lt;/style&gt;
</description>
    </item>
    
    <item>
      <title>Nlnet Grant Application for Domain – First Update: Questions and Answers</title>
      <link>https://ar.al/2022/09/28/nlnet-grant-application-for-domain-first-update-questions-and-answers/</link>
      <pubDate>Wed, 28 Sep 2022 09:31:40 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/09/28/nlnet-grant-application-for-domain-first-update-questions-and-answers/</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;This is the first update to our &lt;a href=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/&#34;&gt;NLnet Grant Application for Domain&lt;/a&gt;.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You applied to the 2022-08 open call from NLnet. We have some questions regarding your project proposal Domain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thank you for getting back to us. Please find the answers, inline, below.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You requested 50000 euro, equivalent of one year of effort. Can you provide some more detail on how you arrived at this time estimate? Could you provide a breakdown of the main tasks, and the associated effort? What rates did you use?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a funding body, I can see why the question of funding amount is the primary question. However, as someone who has eschewed mainstream income and other benefits to work for the common good, it is the least important one for me so I’ll leave this to the end and tackle your other questions first.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can you compare Domain to chatons.org and Libre.sh (note the former community also already uses the word Kitten&amp;hellip;)?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let me tackle them separately:&lt;/p&gt;
&lt;h2 id=&#34;domain-vs-chatons&#34;&gt;Domain vs. Chatons&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chatons.org/en/presentation&#34;&gt;CHATONS&lt;/a&gt; is an excellent initiative by the lovely folks at &lt;a href=&#34;https://framasoft.org/en/&#34;&gt;Framasoft&lt;/a&gt; – who have supported our work in the past with &lt;a href=&#34;https://framablog.org/tag/smalltech/&#34;&gt;translations of our articles into French&lt;/a&gt; – to create “a collective of small structures offering online services (e.g. email, web hosting, collaborative tools, communication tools, etc.)”. They call these structures “hosters.”&lt;/p&gt;
&lt;p&gt;Within the CHATONS, model, Domain is a tool to enable the creation of more hosters. Everyday people can then use the Domain instances of those hosters to set up their own small web places.&lt;/p&gt;
&lt;p&gt;Once Domain is ready for use, CHATONS is a collective that I can see us considering becoming a part of with Domain and Kitten (after all, as you point out, they’re already called Kittens so even that fits).&lt;/p&gt;
&lt;h2 id=&#34;domain-vs-libresh&#34;&gt;Domain vs. Libre.sh&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://libre.sh/&#34;&gt;Libre.sh&lt;/a&gt;, from what I can tell based on their web site, is a tool for people with technical knowledge to set up web sites using Kubernites or Docker Compose. These are enterprise technologies used by Big Tech and involve a high level of complexity. In the &lt;a href=&#34;https://k8s.libre.sh/gettingstarted/&#34;&gt;Getting Started guide for the Kubernites version of libre.sh&lt;/a&gt;, you are shown how to deploy a cluster with 9 machines: “3 masters, 3 ingresses, 3 compute”. Needless to say, this is a completely different use case than Domain.&lt;/p&gt;
&lt;p&gt;The goal of Domain is to enable organisations to become small web hosts, where they can provide a simple interface for everyday people without technical knowledge to set up their own small web places (sites/apps). As such, you can think of it as “Digital Ocean in a box” for anyone who wants to run their own small web host.&lt;/p&gt;
&lt;p&gt;Domain provides a holistic solution that integrates the necessary components of provisioning a VPS, managing subdomains (DNS), installing web applications, and (optionally) charging for the service (or otherwise controlling access to resources).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://k8s.libre.sh/problemtosolve/&#34;&gt;The problem libre.sh solves&lt;/a&gt; is how do we “host free software at scale?” while the problem Domain solves is “how do we enable people to host free software that doesn’t scale?” (in other words small web places). And, crucially, how do we make it possible for any organisation to become such a host? And easy for people without technical knowledge to set up small web places using it?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;if one would use Domain to install e.g. Yunohost or Sandstorm, what would be the added advantage compared to deploy a preconfigured image of these directly at a VPS hosting company (which is already on offer with some hosters).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You &lt;em&gt;wouldn’t&lt;/em&gt; use Domain to install &lt;a href=&#34;https://yunohost.org/&#34;&gt;Yunohost&lt;/a&gt; or &lt;a href=&#34;https://sandstorm.io/&#34;&gt;Sandstorm&lt;/a&gt; as they are different approaches to solving similar problems. Both Yunohost and Sandstorm offer dashboards for installing existing web applications. These are applications that, for the most part, have been created in the traditional multi-user, Big Tech design, with out-of-process databases, etc.&lt;/p&gt;
&lt;p&gt;Domain, on the other hand, is for hosting single-tenant &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; applications. The kind that you can create, for example, with &lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Domain does not aim to support every type of web application but a very specific type (single tenant, small web application). This is its greatest strength as this focus and control means that we can simplify the experience and reduce the system requirements throughout the stack and lifecycle (e.g., when it comes to automatic updates and maintenance).&lt;/p&gt;
&lt;p&gt;Yunohost, Sandstorm and similar projects are great in that they allow more people to install and use existing free/open source “multi-user” web apps that have almost entirely been designed with Big Web architectures.&lt;/p&gt;
&lt;p&gt;Domain aims to do the same thing for Small Web apps while reducing complexity and the need for technical knowledge and improving the overall experience.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Domain approach could be interpreted as poor-mans hosting and/or entry level shared hosting (which is already a very competitively priced economic area). In order to cut prices ,there are definite significant tradeoffs which may bite the user in the longer term.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wouldn’t call Domain’s approach to democratising hosting by enabling independent organisations to become small web hosts “poor man’s hosting” any more than I would call the platform cooperative movement “poor man’s corporations”. The goal is to democratise not just the ownership of small web places but to do so without centralising them at a single host (e.g., us).&lt;/p&gt;
&lt;p&gt;While price is an important factor for commercial providers (in that there is likely a psychological number beyond which it would be deemed too expensive for a small web place), it is definitely not the primary focus. As mentioned in the original funding application, Domain can be run as a private instance (e.g., internally for organisations, neighbourhoods, families, etc.) or using a token-based system (where, for example, a municipality can issue tokens that citizens can exchange for the own small web places instead of using legal tender).&lt;/p&gt;
&lt;p&gt;Finally, the type of hosting integrated into Domain is Virtual Private Server (VPS) not shared hosting.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Borrowing a subdomain from someone doesn&amp;rsquo;t offer much legal standing, whether it is on the public suffix list or not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Borrowing a subdomain from an organisation offers the exact same legal standing as borrowing a sudomain from a commercial top-level domain (like .com, etc.) provider in that they are both subject to contract law. So whatever terms are in the contract are the legal standing that is offered.&lt;/p&gt;
&lt;p&gt;The difference between a commercial top-level domain provider and organisations that will be running domain (like our not-for-profit Small Technology Foundation) is the reason why the domains are being offered. In the former, it is to make a profit. For us, it’s to provide people with a location that they can be easily reached on the Web with.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When a host folds the entire reputation of its user base is immediately destroyed (unlike the case of a TLD, which has an escrow arrangement enforced by their ICANN contract). When someone forgets to backup and things crash, users have little resort. And small payments are expensive to process, eating further into the &amp;ldquo;Domain&amp;rdquo; hosters margins. What countermeasures do you propose for such scenario&amp;rsquo;s to give users peace of mind? Is there some form of service portability?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed, if a host shuts down, this would take down everyone they host. This is a problem inherent with decentralisation and we see it when a fediverse server goes down too. This is not to say that the solution is then to ensure that only a handful of mutli-billion-dollar commercial hosts should exist. Just as the solution to fediverse servers being shut down is not to say that everyone should use Twitter instead because we can trust it’ll be around in one form or other.&lt;/p&gt;
&lt;p&gt;Instead, possible mitigations for this fact of life include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supporting small web hosts from the commons&lt;/li&gt;
&lt;li&gt;Encouraging as many small web hosts as possible so that if one goes away the damage is limited&lt;/li&gt;
&lt;li&gt;Ensuring that it is as easy as possible to move between small web hosts (portability)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the first one would involve political will, the second and third are issues that Domain exists to tackle.&lt;/p&gt;
&lt;p&gt;On the topic of payments, Domain initially supports Stripe which, to the best of my knowledge, does not impose higher fees for the sorts of amounts we’re talking about. Microtransactions (which is not what this is) might be a different matter but do not concern our use case for Domain.&lt;/p&gt;
&lt;p&gt;Finally, while the initial setup is on a subdomain, it will be easy to configure the site to use any domain (on a regular TLD) after the fact. The initial setup using a subdomain is a design decision to both enable people to have a small web place without paying separately for a commercial domain name and to keep the setup time as quick as possible.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If people are expected to pay 10 euro&amp;rsquo;s a month, that puts them in the realm of CPanel and Plesk/managed services which are already common with hosting companies for decades - and at prices going down to below 1 euro per month for e.g. Wordpress hosting for which tens of thousands of tutorials and extensions to make everything possible. What planned features are supposed to lure people over from that competition (and the marketing power of players like Strato)?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;First off, to clarify: the €10/month number is an initial estimate. It’s based on our own research into what could make Small Technology Foundation sustainable by hosting a Domain instance at &lt;em&gt;small-web.org&lt;/em&gt;. It’s also based on my belief that it’s likely the maximum amount you could charge that still feels “small.”&lt;/p&gt;
&lt;p&gt;And, again, Domain instances can be private and token-based. The commercial payment aspect is a sad necessity for organisations like ours that need to survive under capitalism today while hopefully creating systems that will mean that eventually we will have kinder and fairer systems tomorrow where we better understand the value of the commons and support it accordingly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If cheap is the main target, would it not be more convenient to point people to dot.tk et al, which gives them a &amp;lsquo;real&amp;rsquo; domain name (discussion about their security aside) at no cost, conveniently exposed by an API that allows for automation?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cheap really isn’t the main target. If anything, affordable is. And, ideally, in time, we’d love to see small web hosts supported from the common purse for the common good. But until that time, we aim to prove, even within the success criteria of the current system, that we can be sustainable.&lt;/p&gt;
&lt;p&gt;If someone wants to use a dot.tk name (not linking to them as their site doesn’t use TLS which doesn’t give me a lot of confidence about them), they will be able to.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You state that a domain on the public suffix list would &amp;ldquo;provide all the privacy and security guarantees that any other top-level domain can&amp;rdquo;. Is that actually true? i&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, like the other statements in our funding application and that we make in general, it is true :)&lt;/p&gt;
&lt;p&gt;(If we wanted to get into lying, we’d be working in the mainstream. You can make a lot of money there doing that. It’s actually quite a sought-after skill.)&lt;/p&gt;
&lt;p&gt;To reiterate, from &lt;a href=&#34;https://publicsuffix.org/&#34;&gt;the official description&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It allows browsers to, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avoid privacy-damaging &amp;ldquo;supercookies&amp;rdquo; being set for high-level domain name suffixes&lt;/li&gt;
&lt;li&gt;Highlight the most important part of a domain name in the user interface&lt;/li&gt;
&lt;li&gt;Accurately sort history entries by site&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;When a domain is on the &lt;a href=&#34;https://publicsuffix.org/&#34;&gt;Public Suffix List&lt;/a&gt;, it is, for all intents and purposes, a top-level domain and is treated as such by browsers. This is a very useful hack for creating domains that are not part of the commercial top-level domain business and is one of the fundamental design decisions in Domain that sets it apart from other initiatives in the area.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A lot of information can already be learned just from the DNS requests the second level nameservers would see. How do you intend to deal with e.g. TLSA records, DNSSEC, SPF, etc? What kind of alternative services will be delivered (for instance e-mail? ) How will e.g. spam and blacklisting impact sibling Domain-hosted efforts?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a project from a not-for-profit that exists to protect privacy, Domain includes privacy policies for hosts based on data minimisation. While the initial service providers for DNS, VPS, TLS (DNSimple, Hetzner, Let’s Encrpypt), etc., do have the means to gather data, this is true in general (not specific to Domain) and they are bound by the rules of GDPR.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because of ACME, meanwhile there has been a lot of work on automation of DNS challenges (&lt;a href=&#34;https://github.com/acmesh-official/acme.sh/tree/master/dnsapi)&#34;&gt;https://github.com/acmesh-official/acme.sh/tree/master/dnsapi)&lt;/a&gt;. Would it not make sense to seek a similar approach for the DNS management, where one does not depend on hardcoding a single US based domain name company (DNSimple) as exclusive provider?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DNSimple is not used for TLS certificates. &lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt; uses &lt;a href=&#34;https://codeberg.org/small-tech/auto-encrypt&#34;&gt;Auto Encrypt&lt;/a&gt; (another of our free and open source libraries) to automatically provision TLS certificates using Let’s Encrypt’s HTTP-01 challenges.&lt;/p&gt;
&lt;p&gt;DNSimple is used for setting up the domain records for subdomains.&lt;/p&gt;
&lt;p&gt;And, again, DNSimple is simply the first provider we are building support for to get Domain up and running. The goal is to support others that have APIs and to thus abstract out the APIs, hopefully commoditising these services to some degree in the process.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How would applications be packaged and maintained/security hardened? Is this something you have experience with?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Applications are “packaged” (insofar as we can use that term for it) in Domain using &lt;a href=&#34;https://cloud-init.io/&#34;&gt;cloud-init&lt;/a&gt;. Currently, this is on standard Ubuntu 22.04 instances with unattended security updates enabled. However, we are keeping our eyes on immutable operating systems like CoreOS to aid in OS updates in the future.&lt;/p&gt;
&lt;p&gt;Much of the security of Kitten and Small Web sites comes from their simplicity and small attack surface. There are fewer moving parts, less code, and basic standards being used.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can you elaborate on deployment and maintenance requirements after initial deployment - which tend to forms a continuous drain on resources at the project level and the user level?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Small Web sites/apps built on Kitten and deployed with Domain will be entirely self-maintaining.&lt;/p&gt;
&lt;p&gt;Kitten’s installation process is based on git and it also &lt;a href=&#34;https://codeberg.org/kitten/app#deployment&#34;&gt;clones git repositories to deploy apps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is also work underway to implement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatic updates for the deployed app (via git)&lt;/li&gt;
&lt;li&gt;Automatic updates for Kitten itself (again via git)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually, when immutable server operating systems become available, the goal is to have automatic major version operating system updates as well for completely hands-off maintenance. (In the meanwhile we will be deploying to LTS versions of Ubuntu which will give us quite a few years of headroom on this.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You mention that setup can be done in under one minute, but surely beyond that someone will have to do the hard work?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No, that’s the whole idea: there isn’t. When you install a small web app and, say, 45 seconds later, it’s ready, it’s actually ready. You hit your domain. You start using it. That’s all there is to it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Is there a threat analysis that you&amp;rsquo;ve considered during the design phase - if one of the fellow hosted community members doesn&amp;rsquo;t update their version of X, how do you prevent compromise of the rest? Is configuration and user management separate from delivering bits? Would Domain packages offer reproducibility, like Nix/Nixpkgs (which has a vast collection of software, see repology.org)?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The security model of Domain follows, in the words of Joanna Rutkowska (of Qubes, etc.) “security through distrust”.&lt;/p&gt;
&lt;p&gt;This results in some core design decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Domains are on the Public Suffix List (so no one can set supercookies, etc.)&lt;/li&gt;
&lt;li&gt;Every person gets their own virtual private server (we don’t use shared hosting)&lt;/li&gt;
&lt;li&gt;Every site is automatically protected by TLS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And, to reiterate, some related properties even if they may not seem to immediately be security-related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can use your own domain.&lt;/li&gt;
&lt;li&gt;You can easily move away from a host.&lt;/li&gt;
&lt;li&gt;All code is free and open.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regarding reproducibility, since small web apps are installed via git, there isn’t necessarily a build process involved. Where one is (e.g., via &lt;code&gt;npm install&lt;/code&gt;), it would be up to the apps themselves to ensure that dependencies are locked and loaded from trusted sources and that commits and tags are signed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You requested 50000 euro, equivalent of one year of effort. Can you provide some more detail on how you arrived at this time estimate? Could you provide a breakdown of the main tasks, and the associated effort? What rates did you use?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, to return to the first question: I work full time at Small Technology Foundation and my work these days is full-time on Kitten and Domain. So, let’s say I work 40 hours a week on average (it can vary and I often work on the weekends too):&lt;/p&gt;
&lt;p&gt;€50K/yr comes down to about ~€26/hour.&lt;/p&gt;
&lt;p&gt;Let’s put this into perspective: over a decade ago, when I doing regular development work as a contractor, I was charging €100/hr. My partner, who is &lt;a href=&#34;https://laurakalbag.com/working-with-stately/&#34;&gt;contracting with Stately&lt;/a&gt; (a startup), makes more than double this amount a year and this is the main source of income that is currently sustaining Small Technology Foundation (the two of us and our dog). Previously, I’ve sold two family homes in Turkey and we’ve relied on a combination of sales of our tracker blocker (&lt;a href=&#34;https://better.fyi&#34;&gt;Better, now retired&lt;/a&gt;) and fees from conference speaking to scrape by.&lt;/p&gt;
&lt;p&gt;All this to say that I don’t actually care how much funding we get. (I wonder if this is why they don’t usually let me write the funding applications?) Whether it’s €50K or €0 or something in between, we will keep working on Kitten and Domain and we will keep working on our vision for a Small Web owned and controlled by people, not corporations. We’ve always found a way and we will continue to do so.&lt;/p&gt;
&lt;p&gt;What would be nice, however, is to feel like we are supported. We have been working for the common good for almost a decade now and it would be nice to have it funded from the common purse to some degree at least. And it would also be nice to have some stability so we can keep working on this without worrying about how we’re going to exist in X months time.&lt;/p&gt;
&lt;p&gt;While I understand that the funding from ngi/NLnet is mostly project (and maybe even feature)-based, I do believe we need to think longer term and support folks like ourselves who are essentially carrying out research and development.&lt;/p&gt;
&lt;p&gt;Silicon Valley understands the value of funding teams and then allowing them to pivot, etc., as they explore a problem domain and learn more about it. Sadly, it does so with the worst possible success criteria (how to best farm you for your data and monetise it). As I mentioned during one of my talks at the European Parliament, I hope that we can do the same thing for folks working on technology for the common good.&lt;/p&gt;
&lt;p&gt;So I’m not going to give you an hour-by-hour breakdown of tasks because I don’t know what those are going to be beyond a few days’ time. You can keep track of the current ones on the issue trackers of the Kitten and Domain projects.&lt;/p&gt;
&lt;p&gt;Apart from that, I get up in the morning and work on Kitten and Domain and that’s what I’ll be doing for the foreseeable future.&lt;/p&gt;
&lt;p&gt;Please support us financially if you feel that what we’re working on should exist and you’d like us to exist long enough to make sure that it does.&lt;/p&gt;
&lt;p&gt;If you have any other questions, please don’t hesitate to get in touch.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Small Is Beautiful Episode 22 Small Web Kitten Dweb Camp Braid</title>
      <link>https://ar.al/2022/09/16/small-is-beautiful-episode-22-small-web-kitten-dweb-camp-braid/</link>
      <pubDate>Fri, 16 Sep 2022 10:49:35 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/09/16/small-is-beautiful-episode-22-small-web-kitten-dweb-camp-braid/</guid>
      <description></description>
    </item>
    
    <item>
      <title>Dear Linux, Privileged Ports Must Die</title>
      <link>https://ar.al/2022/08/30/dear-linux-privileged-ports-must-die/</link>
      <pubDate>Tue, 30 Aug 2022 14:30:53 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/08/30/dear-linux-privileged-ports-must-die/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/08/30/dear-linux-privileged-ports-must-die/./toffs.jpg&#34;
         alt=&#34;A photo of two actors playing Boris Johnson and some other toff popping champagne.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Privileged ports, toffs of the Linux world.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://codeberg.org/kitten/app#kitten&#34;&gt;Kitten&lt;/a&gt; is a &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;small web&lt;/a&gt; server that runs as a user-level service and would never need elevated privileges if it wasn’t for one archaic anti-security feature in Linux that dates back to the mainframe era: privileged ports.&lt;/p&gt;
&lt;h2 id=&#34;back-to-the-future&#34;&gt;Back to the future&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://utcc.utoronto.ca/~cks/space/blog/unix/BSDRcmdsAndPrivPorts&#34;&gt;As it was in Unix in the 1980s&lt;/a&gt;, so it is now, that any process that wants to bind to a port less than 1024 must have elevated privileges. These ports are known as “privileged ports.” While this was a security feature in the days of dumb terminals, &lt;strong&gt;in the age of the World Wide Web, it is a security vulnerability.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Privileged ports lead to dangerous security practices, like server processes forgetting to drop privileges and being run as root.&lt;/p&gt;
&lt;p&gt;They’ve been &lt;a href=&#34;https://www.linuxquestions.org/linux/articles/Technical/Why_can_only_root_listen_to_ports_below_1024&#34;&gt;obsolete for quite a while&lt;/a&gt;, &lt;a href=&#34;https://news.ycombinator.com/item?id=18302380&#34;&gt;macOS removed them as of Mojave&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, and there’s no comparable concept on Windows either. Heck, &lt;a href=&#34;http://adamierymenko.com/ports.html&#34;&gt;they apparently even cause climate change&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not to mention, &lt;a href=&#34;https://stackoverflow.com/questions/413807/is-there-a-way-for-non-root-processes-to-bind-to-privileged-ports-on-linux&#34;&gt;they’re a world of unnecessary hurt to work around&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, even &lt;a href=&#34;https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.1cBSD/a/sys/netinet/in_pcb.c&#34;&gt;their original implementation in BSD&lt;/a&gt; was a hack:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (lport) {
  u_short aport &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; htons(lport);
  &lt;span style=&#34;color:#902000&#34;&gt;int&lt;/span&gt; wild &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;;

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* GROSS */&lt;/span&gt;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (aport &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; IPPORT_RESERVED &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; u.u_uid &lt;span style=&#34;color:#666&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;)
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; (EACCES);
  &lt;span style=&#34;&#34;&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;The BSD people knew this was a hack; they just did it anyway, probably because it was a very handy hack in their trusted local network environment. Unix has quietly inherited it ever since.&lt;/p&gt;
&lt;p&gt;— &lt;a href=&#34;https://utcc.utoronto.ca/~cks/space/blog/unix/BSDRcmdsAndPrivPorts&#34;&gt;The BSD r* commands and the history of privileged TCP ports&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Listen to what some other folks have to say on the subject:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“So we have web servers, and all other servers whose standard ports happen to fall below 1024, expecting to be started as root, bind(2) to their service address, and then &amp;ldquo;drop privileges&amp;rdquo;. &lt;strong&gt;At this point,  you must think I’m shitting you, but I’m not. This is for real.&lt;/strong&gt;  Failure to drop privileges after binding to listen port is a whole  category of vulnerability. It&amp;rsquo;s like throwing egg on the stairs every morning, and several times later each day carefully classifying various accidents in the accident log as &amp;ldquo;slipped on eggy stairs&amp;rdquo;. Stop throwing egg on the stairs!”&lt;/p&gt;
&lt;p&gt;— &lt;a href=&#34;https://wibblement.blogspot.com/2021/11/the-persistent-idiocy-of-privileged.html&#34;&gt; The Persistent Idiocy of &amp;ldquo;Privileged Ports&amp;rdquo; on Unix&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This port 1024 limit is a security measure. But it is based on an obsolete security model and today &lt;strong&gt;it only gives a false sense of security and contributes to security holes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;— &lt;a href=&#34;https://www.staldal.nu/tech/2007/10/31/why-can-only-root-listen-to-ports-below-1024/&#34;&gt;Why can only root listen to ports below 1024?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;These are the reasons why I suspect the 1024 limit was imposed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don&amp;rsquo;t let users run system-level services on the mainframe.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t let the users hog ports for important services on the mainframe.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t let the users run a bogus service to steal logins on the mainframe.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;hellip;and &lt;strong&gt;here&amp;rsquo;s why I think these aren&amp;rsquo;t relevant anymore:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;- The mainframe is now the desktop.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;– &lt;a href=&#34;https://icculus.org/finger/icculus?date=2006-08-30&#34;&gt;…I really don&amp;rsquo;t get it&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;the-workaround&#34;&gt;The workaround&lt;/h2&gt;
&lt;p&gt;On modern Linux systems, you can configure privileged ports using &lt;code&gt;sysctl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;sudo sysctl -w net.ipv4.ip_unprivileged_port_start&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, that setting does not survive a reboot.&lt;/p&gt;
&lt;p&gt;You can also configure the setting in a persistent way using a configuration file. e.g., by creating a file called &lt;em&gt;/etc/sysctl.d/99-reduce-unprivileged-port-start-to-80.conf&lt;/em&gt; with the following content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;net.ipv4.ip_unprivileged_port_start=&lt;span style=&#34;color:#40a070&#34;&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To remove all privileged ports, you can set that value to 0. For our needs, 80 will do as it means our web server can bind to ports 80 and 443 without requiring superuser privileges.&lt;/p&gt;
&lt;p&gt;In fact, &lt;a href=&#34;https://codeberg.org/kitten/app/src/branch/main/install#L153&#34;&gt;the Kitten installer&lt;/a&gt; does just this.&lt;/p&gt;
&lt;p&gt;But even that has issues.&lt;/p&gt;
&lt;p&gt;For one thing, it doesn’t work in rootless containers (e.g., if you’re running on an “immutable” Linux distribution like &lt;a href=&#34;https://silverblue.fedoraproject.org/&#34;&gt;Fedora Silverblue&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;) because the configuration file gets added to the container, which doesn’t have &lt;em&gt;systemd&lt;/em&gt; running &lt;em&gt;sysctl.d&lt;/em&gt; so the configuration setting doesn’t get applied at boot.&lt;/p&gt;
&lt;p&gt;So we’re back to having to gain temporary privileges and drop them just to alter this configuration setting every time the server is run.&lt;/p&gt;
&lt;p&gt;What a pain in the ass.&lt;/p&gt;
&lt;h2 id=&#34;the-fix&#34;&gt;The fix&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The fix is easy: ship Linux distributions so that privileged ports start from 80 to begin with.&lt;/strong&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;net.ipv4.ip_unprivileged_port_start=&lt;span style=&#34;color:#40a070&#34;&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sure, this could also be set to zero but setting it to 80 would fix the number one use case today, which is to allow web servers to bind to ports 80 and 443 without requiring superuser privileges. So I’d be happy with either solution.&lt;/p&gt;
&lt;p&gt;And for the three folks in Finland who administer multi-user Linux instances and rely on privileged ports for their mainframe-era security properties, they can always run &lt;code&gt;sysctl&lt;/code&gt; and set their port limit to 1024 as it was before.&lt;/p&gt;
&lt;p&gt;Yay, everyone’s happy!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is such an easy fix and one that would improve security across the board that I hope Linux distributions will start implementing it as soon as possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All it takes is one major distribution to start the trend and the rest will follow. Please feel free to open issues in your distribution of choice and talk to folks you know to get them to do this.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;#Linux #PrivilegedPortsMustDie&lt;/em&gt; is what I’m saying.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s time to move Linux out of the mainframe era… kicking and screaming, if need be.&lt;/strong&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m just waiting for someone to tell me the folks at Apple don’t understand security and that macOS is less secure now because they removed privileged ports. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And these so-called “immutable” distributions are the future of Linux, not just on the desktop but on servers also (see, for example, &lt;a href=&#34;https://getfedora.org/coreos/&#34;&gt;Fedora CoreOS&lt;/a&gt;, etc.) So the sooner we remove the archaic security anti-feature that is privileged ports, the better. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I mean, ideally, the kernel could implement this fix and be done with it for &lt;em&gt;every&lt;/em&gt; Linux distribution but I have no idea how to get the kernel folks to implement something. I have a feeling it involves a lot of text-only emails and being told how dumb I am in no uncertain terms by multiple people. So, yeah, if anyone else wants to take that one, please be my guest ;) &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Lipstick on a Pig: learning the most important lesson in design</title>
      <link>https://ar.al/2022/08/17/lipstick-on-a-pig/</link>
      <pubDate>Wed, 17 Aug 2022 16:15:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/08/17/lipstick-on-a-pig/</guid>
      <description>&lt;p&gt;I just released a little tool called &lt;a href=&#34;https://codeberg.org/small-tech/lipstick&#34;&gt;Lipstick on a Pig&lt;/a&gt; that helps keep the visual appearance of supported command-line applications in sync with the current light/dark mode setting (colour scheme) of your system in GNOME.&lt;/p&gt;
&lt;p&gt;But why is this tool even necessary to begin with?&lt;/p&gt;
&lt;p&gt;Let’s start at the beginning…&lt;/p&gt;
&lt;h2 id=&#34;getting-to-gnome-you&#34;&gt;Getting to GNOME you&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.gnome.org/&#34;&gt;The GNOME display environment&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, since version 42, implements support for light and dark appearance styles (aka colour schemes).&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2022/08/17/lipstick-on-a-pig/images/gnome-appearance-settings.png&#34; alt=&#34;Screenshot of GNOME’s appearance settings showing light and dark style thumbnails with the currently-selected wallpapers for each&#34;&gt;&lt;/p&gt;
&lt;p&gt;With a GNOME extension like &lt;a href=&#34;&#34;&gt;Night Theme Switcher&lt;/a&gt;, you can even automate the switch from light mode to dark mode based on time of day (or toggle it manually using a button in the system bar).&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2022/08/17/lipstick-on-a-pig/images/night-theme-switcher.png&#34; alt=&#34;Screenshot of Night Theme Switcher settings&#34;&gt;&lt;/p&gt;
&lt;p&gt;Furthermore, newer terminal applications like &lt;a href=&#34;https://gitlab.gnome.org/raggesilver/blackbox#black-box&#34;&gt;Black Box&lt;/a&gt; follow GNOME’s colour scheme and automatically switch between their light and dark themes for the console and its content.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2022/08/17/lipstick-on-a-pig/images/black-box-settings.png&#34; alt=&#34;Screenshot of Black Box terminal’s setting showing the light and dark theme tabs, with the former selected and showing thumbnail previews of the four included light themes&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is all great and it means that you can, for example, have your computer switch automatically to dark mode at sunset and have your terminal adapt to it automatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2022/08/17/lipstick-on-a-pig/images/black-box.png&#34; alt=&#34;Screenshot of light and dark modes on Black Box terminal&#34;&gt;&lt;/p&gt;
&lt;p&gt;The immediately-obvious problem is that very few command-line applications adhere to the terminal’s theme, choosing to override it with their own theme settings, which, rarely (if ever) take the system’s colour scheme setting (light/dark mode) into consideration.&lt;/p&gt;
&lt;p&gt;But is this the right problem to solve?&lt;/p&gt;
&lt;p&gt;And is the command-line application layer the right place to solve it?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Surely, I must think so because that’s the problem Lipstick on a Pig solves, right? Not really, no, read on…)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;solving-the-wrong-problem&#34;&gt;Solving the wrong problem&lt;/h2&gt;
&lt;p&gt;Think about it: The only reason command-line apps implement theme support is because they cannot rely on terminal apps to do so consistently.&lt;/p&gt;
&lt;p&gt;If every terminal app in the world had consistent support for themes, command-line apps could just render their output, happy in the knowledge that it would get rendered in the person’s chosen theme.&lt;/p&gt;
&lt;p&gt;So is the terminal app layer the correct place to define our problem?&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;The fact that every terminal app would have to implement theme support should be a hint that it’s not. That’s a lot of duplicated effort. (And last I checked there wasn’t a standards body for terminal apps, nor do I think we’ll see one anytime soon given they’re not exactly – unlike the web, say – a huge industry.)&lt;/p&gt;
&lt;p&gt;So let’s look for the problem one layer higher in the stack, in the display environment/operating system layer.&lt;/p&gt;
&lt;p&gt;Is the core problem that the display environment/operating system does not have support for text themes?&lt;/p&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;p&gt;How do we know that this is the right layer to define our problem at? We know because if we define it at this layer, solving it will also solve it at the terminal app and command-line app layers.&lt;/p&gt;
&lt;p&gt;Now that we know where to solve it, articulating the design problem becomes much easier:&lt;/p&gt;
&lt;p&gt;“How do we ensure consistent syntax highlighting of text on an operating system that adheres to system colour scheme changes.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So the ideal solution to this design problem would be for the theme settings that currently reside in a terminal app such as &lt;a href=&#34;https://gitlab.gnome.org/raggesilver/blackbox#black-box&#34;&gt;Black Box&lt;/a&gt; to instead exist in the Appearance settings of GNOME itself and for every app running on the system, including terminal apps, to use them when rendering syntax-highlighted text.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2022/08/17/lipstick-on-a-pig/images/gnome-appearance-settings-with-text-themes.png&#34; alt=&#34;Render of GNOME Appearance Settings page showing a potential Text theme section under Style&#34;&gt;&lt;/p&gt;
&lt;p&gt;Were this to be implemented, it would mean terminal apps and command-line apps would no longer have to implement custom theme support themselves.&lt;/p&gt;
&lt;p&gt;Which brings us to…&lt;/p&gt;
&lt;h2 id=&#34;the-most-important-lesson-in-design&#34;&gt;The most important lesson in design&lt;/h2&gt;
&lt;video id=&#34;superheroes--villains-in-design&#34; controls=&#34;&#34; src=&#34;//player.vimeo.com/external/133430959.hd.mp4?s=8a96e7ede72482a65add5610be0271eb&amp;amp;profile_id=119&#34; poster=&#34;images/poster-design-talk.jpg&#34; width=&#34;800&#34;&gt;
  &lt;img src=&#34;https://ar.al/2022/08/17/lipstick-on-a-pig/poster-ux-talk.jpg&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
  &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a href=&#34;//player.vimeo.com/external/133430959.hd.mp4?s=8a96e7ede72482a65add5610be0271eb&amp;amp;profile_id=119&#34;&gt;download Superheroes &amp;amp; Villains in Design directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
&lt;/video&gt;
&lt;p&gt;&lt;strong&gt;The most important lesson in design is that design does not begin at solving a problem. Design begins at understanding what &lt;em&gt;the right problem to solve&lt;/em&gt; is. Which means we must uncover &lt;em&gt;where&lt;/em&gt; the problem actually is.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Most times, bad design (which results in tools that are unusable, overly complicated, etc.) doesn’t arise from solving a problem badly (although this can, of course, happen) but from solving the wrong problem.&lt;/p&gt;
&lt;p&gt;This was the central thesis of my talk, Superheroes and Villains in Design, that I gave at Thinking Digital Conference in Newcastle almost a decade ago. I’d highly recommend you watch it, especially if you’re working in free technology. It’s more important than ever that the free and open alternatives we build today are not only ethically-sound but accessible, usable, and maybe even – heck, why not? – delightful. That’s the only way to ensure that people will actually use them. The &lt;a href=&#34;https://ind.ie/ethical-design/&#34;&gt;Ethical Design Manifesto&lt;/a&gt; and &lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;Small Tech Principles&lt;/a&gt; can guide you in achieving this.&lt;/p&gt;
&lt;p&gt;In the talk, I recount my less-than-delightful experiences trying to buy a ticket on the San Francisco Bay Area Rapid Transit (BART) system. While you can watch the video for all the hilarious details, suffice to say the folks who designed the BART ticketing system solved the wrong problem. They solved the problem of “How do we sell a ticket?” not the problem of “How do we get someone from point A to point B as effortlessly as possible.” If they had solved the latter problem, the confusing ticket machines would either look very different or perhaps not even exist at all.&lt;/p&gt;
&lt;p&gt;Another example I give in the talk is that of a whole class of everyday devices that solve the wrong problem: washing machines. Nearly all washing machines today solve the problem of “washing clothes.” This is a problem nearly no one has (unless you wash clothes for a living or have a particular enthusiasm for the craft). However, nearly all of us have the problem of having dirty clothes we want to be clean. While they may at first appear to be the same problem, they’re not. These are two fundamentally different problems. Conflating them is the reason we have washing machines today with a plethora of settings that almost no one really understands instead of machines with almost no controls that you dump your clothes into and get clean clothes out of.  (Hands up if you set your machine to the one or two settings you know work well enough and leave it at that.)&lt;/p&gt;
&lt;p&gt;I won’t go into it again here as, in the talk, I sketch out what a washing machine would look like if it solved the right problem.&lt;/p&gt;
&lt;p&gt;Spoiler alert: it comes out looking nothing like any washing machine you’ve seen.&lt;/p&gt;
&lt;h2 id=&#34;solving-the-right-problem&#34;&gt;Solving the right problem&lt;/h2&gt;
&lt;p&gt;So, to get back to the case in hand, the correct design problem we should be solving is how to ensure that any app on a system that renders syntax highlighted text does so consistently with every other app and in accordance with the system’s colour scheme settings.&lt;/p&gt;
&lt;p&gt;Understanding the problem and understanding what level a problem should be solved at are inextricably linked.&lt;/p&gt;
&lt;p&gt;We now understand that this problem cannot be solved at the level that Lipstick on a Pig solves it. (Now do you understand why I chose the name?) Therefore, we know that Lipstick on a Pig is not a design solution. Lipstick on a Pig is a hack; a pragmatic short-term workaround; a stopgap.&lt;/p&gt;
&lt;p&gt;It’s very important that we don’t conflate hacks and pragmatic workarounds with actual solutions. The former are not without value. In fact, they might keep things ticking along for quite a while. But the moment we forget that they’re just temporary band-aids, we run the risk of incorporating them into our stacks and thereby weakening the foundations of those stacks. Where Murphie’s Hierarchy of Hacks goes, Murphy’s Law is usually not far behind. We eventually end up with brittle structures that can no longer be evolved or iterated upon without other parts of them collapsing, leading to complete rewrites or failure and abandonment of the systems for better alternatives.&lt;/p&gt;
&lt;h2 id=&#34;lipstick-on-a-pig&#34;&gt;Lipstick on a Pig&lt;/h2&gt;
&lt;p&gt;The problem Lipstick on a Pig solves is “how can we synchronise the visual appearance of command-line apps that implement their own theme systems with system colour scheme changes to provide people with a consistent/legible/readable experience.”&lt;/p&gt;
&lt;p&gt;This complexity of the problem statement itself is the first smell that we’re solving the wrong problem at the wrong place.&lt;/p&gt;
&lt;p&gt;And I’ve already baked in so many incorrect assumptions based on my belief (right or wrong, as it might be) on the difficulty of effecting change in the other layers in the stack.&lt;/p&gt;
&lt;p&gt;I know that command-line apps &lt;em&gt;should not&lt;/em&gt; be implementing their own theme systems (the operating system/desktop environment should). And I know that terminal apps should not be doing this either (because, again, the operating system/desktop environment should). I also know that by solving the right problem at the right layer in the stack&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, it would create less work for – i.e., solve the problem for – all lower layers in the stack.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;And yet I’m releasing Lipstick on a Pig today instead of doing any of those other things.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because getting people to care about consistency is hard.&lt;/p&gt;
&lt;p&gt;And getting people to care about consistency in free/open source is even harder.&lt;/p&gt;
&lt;h2 id=&#34;cultural-challenges&#34;&gt;Cultural challenges&lt;/h2&gt;
&lt;p&gt;We still have &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5545&#34;&gt;an uphill battle even to get a desktop environment like GNOME to adhere to its own colour scheme settings&lt;/a&gt;. Why? Mostly because some folks like how something looks, consistency be damned.&lt;/p&gt;
&lt;p&gt;GNOME has even formalised this inconsistency by not just having a light and dark mode but &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5545#note_1471117&#34;&gt;a “mixed mode.”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What’s mixed mode?&lt;/p&gt;
&lt;p&gt;Quite bluntly, it’s an example of bad design (complexity and inconsistency) born out of a culture where design is still, to some extent, not practised from first principles but is subject to the aesthetic whims and fancies of individual developers and the compromises they (often times reluctantly) reach between themselves.&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;It’s important to understand that these issues do not merely concern aesthetics and that they must not be driven by vanity. If someone has their system set to light or dark mode, there may well be &lt;a href=&#34;https://www.nngroup.com/articles/dark-mode/&#34;&gt;important accessibility-and-usability-related reasons&lt;/a&gt; for doing so. So, beyond consistency (which itself lowers the cognitive load of the entire system), not respecting a system setting like light or dark mode is a perfect example of not respecting the people who use the tools we make. We must constantly remind ourselves that our preferences as developers do not override those of the people using the things we make.&lt;/p&gt;
&lt;h2 id=&#34;on-pragmatism-and-landfills&#34;&gt;On pragmatism and landfills&lt;/h2&gt;
&lt;p&gt;While I hope that the solution to the correct design problem in this case can be implemented at the correct layer in the stack, we must also be pragmatic. Sometimes all we can do is to implement a sub-optimal workaround (like Lipstick on a Pig) at the wrong layer in the stack.&lt;/p&gt;
&lt;p&gt;Which layer?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One that we, ourselves, have control over.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I believe folks call this “pragmatism.”&lt;/p&gt;
&lt;p&gt;(This is a concept I am trying more heartily to embrace these days in lieu of exotic expeditions to explore the spiralling depths of confrontation and bike-shedding and regular visits to the chasms of utter hopelessness and despair.)&lt;/p&gt;
&lt;p&gt;And yet, even as I write this, I’m painfully aware that such “pragmatism”, alone, explains so much of how bad design comes about and why sometimes it‘s easier to start from scratch rather than to continue building on a system that is comprised of a landfill of hacks and workarounds.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/X_Window_System&#34;&gt;X11&lt;/a&gt;, I’m looking at you, kid.&lt;/p&gt;
&lt;p&gt;It also should hold a candle to what you can do in your own organisations to ensure that you don’t end up with a landfill on your hands: ensure designers have the responsibility and authority to affect change at any layer of the organisational stack.&lt;/p&gt;
&lt;p&gt;And create a culture where design conversations take place from first principles, not personal preferences.&lt;/p&gt;
&lt;h2 id=&#34;a-map-of-solutions&#34;&gt;A map of solutions&lt;/h2&gt;
&lt;p&gt;So Lipstick on a Pig tackles the wrong problem at the most pragmatic and most sub-optimal level possible.&lt;/p&gt;
&lt;p&gt;(Yippee! Don’t we all feel better now?)&lt;/p&gt;
&lt;p&gt;It requires hacks to be implemented for individual command-line applications and thus has a potentially infinite problem space. This is literally the worst possible place to fix this problem and yet it is also the only place where I can fix it without engaging in (the often times frustrating process of) getting large open source projects to implement design changes.&lt;/p&gt;
&lt;p&gt;And yet, if I didn’t believe that these things can change, I wouldn’t be writing this (admittedly rather lengthy, at this point, apologies…) treatise to accompany the launch of a simple little tool.&lt;/p&gt;
&lt;p&gt;I do hope that in writing this I might influence those who have control over the more optimal layers of the stack to implement better solutions there. But I don’t have the time or wherewithal to fight those battles myself. I have my own projects – &lt;a href=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/&#34;&gt;like Domain&lt;/a&gt; and &lt;a href=&#34;https://codeberg.org/nodekit/app/src/branch/kitten#kitten&#34;&gt;Kitten&lt;/a&gt; – that I’m working on as a solo developer and they take up almost all my time. Even writing this feels like a luxury I can scantly afford right now while &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;bigger problems&lt;/a&gt; remain unsolved.&lt;/p&gt;
&lt;p&gt;With that goal in mind, here is a map of solutions, organised by whom it would require buy-in from.&lt;/p&gt;
&lt;h3 id=&#34;1-terminal-app-developers&#34;&gt;1. Terminal app developers&lt;/h3&gt;
&lt;p&gt;One step up from Lipstick on a Pig would be to fix the problem at the terminal app layer while still essentially implementing the same technique Lipstick on a Pig uses with light and dark mode configurations for individual command-line apps.&lt;/p&gt;
&lt;p&gt;A terminal app like Black Box, for example, could listen for system colour scheme changes and call the corresponding light or dark script. This would remove the need for Lipstick on a Pig and reduce the problem to one of configuration (you would still have to keep your light and dark scripts updated for the command-line apps you use).&lt;/p&gt;
&lt;p&gt;Still suboptimal, but better.&lt;/p&gt;
&lt;h3 id=&#34;2-terminal-app-developers-and-command-line-app-developers&#34;&gt;2. Terminal app developers and command-line app developers&lt;/h3&gt;
&lt;p&gt;If we’re willing to open up the social effort required to implement a solution, we can stay at the terminal app layer and implement something better.&lt;/p&gt;
&lt;p&gt;The terminal app could set an environment variable (maybe one that’s even eventually &lt;a href=&#34;https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html&#34;&gt;standardised by freedesktop.org&lt;/a&gt;) like &lt;code&gt;$XDG_TERMINAL_HANDLES_THEME&lt;/code&gt;. Command-line apps could, then, easily implement support for light/dark mode by checking for this environment variable and, if it exists, disabling their own theme support to use the system theme.&lt;/p&gt;
&lt;p&gt;This solution is much better in that it could, eventually, lead to command-line apps not having to implement themes themselves if enough terminal apps adhered to the standard. This is the second-best solution, and likely the best compromise between purity and pragmatism, given how difficult it would probably be to get GNOME to implement the optimal solution. (If past performance is any indication of future behaviour, I’m likely going to get chided just for writing this. &lt;em&gt;Mais, c’est la vie, non, mes amis?&lt;/em&gt;)&lt;/p&gt;
&lt;h3 id=&#34;3-gnome-developers&#34;&gt;3. GNOME developers&lt;/h3&gt;
&lt;p&gt;Finally, to reiterate, the ideal solution would be for the operating system/desktop environment to implement a solution that can be used not just by terminal apps but by any app that display text with syntax highlighting (e.g., code editors with graphical interfaces).&lt;/p&gt;
&lt;p&gt;Until then – or until one of the more optimal solutions outlined above are implemented by others – I hope you’ll find &lt;a href=&#34;https://codeberg.org/small-tech/lipstick&#34;&gt;Lipstick on a Pig&lt;/a&gt; useful as a stopgap.&lt;/p&gt;
&lt;p&gt;And, before you rush off to solve a design problem, remember to always ask yourself the most important question in design:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Am I solving the right problem at the right place?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Used by operating systems like Ubuntu, Fedora, etc. (Linux is confusing like that.) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5545&#34;&gt;GNOME’s implementation of colour schemes is currently not holistic.&lt;/a&gt; Which is a nice way of saying it’s inconsistent and not very well supported by interfaces within GNOME shell itself. For example, if you want to create your own custom wallpapers for light and dark mode, you have to use &lt;a href=&#34;https://wiki.gnome.org/Apps/Tweaks&#34;&gt;GNOME Tweaks&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The ability to schedule colour scheme changes based on time of day, having colour scheme changes animate smoothly, and having an easy to reach control for changing the colour scheme manually – in other words, basically everything the Night Theme Switcher extension does – are features that should really be added to GNOME itself and presented with intelligent defaults (e.g., switching to dark mode at sunset). &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Note that “the stack” does not necessarily just mean a technology stack. It includes socio-economic layers. While we’re mostly looking up and down the technological stack here, it’s quite possible that a wider analysis of a problem will reveal that the correct design solution must be implement in a non-technological layer. It may require, for example, changing the business model of an organisation. If you reach the top of the technology stack and you still cannot find an optimal solution to the problem, that’s a pretty good tell that the design problem (and therefore the solution) is at some higher, non-technological layer. In many ways, these are the hardest design problems to solve. Mostly because, in most organisations, problems at these levels are not seen as design problems and designers do not have the necessary authority or responsibility to tackle them. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Were GNOME to implement system themes for syntax-highlighted text for light and dark modes, all apps – including terminal apps and all command-line applications – could consistently adhere to them without doing anything at all. Think of all the code (and thus complexity) removed from the equation were that to happen. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Although this has gotten immeasurably better in recent years with the effect of folks like &lt;a href=&#34;https://blogs.gnome.org/tbernard/&#34;&gt;Tobias Bernard&lt;/a&gt;. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Make Helix Editor follow the system colour scheme</title>
      <link>https://ar.al/2022/08/01/make-helix-editor-follow-the-system-colour-scheme/</link>
      <pubDate>Mon, 01 Aug 2022 18:26:49 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/08/01/make-helix-editor-follow-the-system-colour-scheme/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/08/01/make-helix-editor-follow-the-system-colour-scheme/./black-box.png&#34;
         alt=&#34;Montage of light mode and dark mode screenshots of a terminal app.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Black Box adheres to the system colour scheme in GNOME.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is a niche post for those of you using a Terminal application that adheres to system light/dark mode settings&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; (like the excellent &lt;a href=&#34;https://gitlab.gnome.org/raggesilver/blackbox&#34;&gt;Black Box&lt;/a&gt; by &lt;a href=&#34;https://gitlab.gnome.org/raggesilver&#34;&gt;Paulo Queiroz&lt;/a&gt;) and &lt;a href=&#34;https://helix-editor.com/&#34;&gt;Helix Editor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The problem is that &lt;a href=&#34;https://github.com/helix-editor/helix/issues/2158&#34;&gt;Helix editor currently does not support separate light and dark themes&lt;/a&gt; or switching between them automatically when your system does. This is, however, something we can fix with a little bit of scripting.&lt;/p&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Make sure your Helix configuration is at &lt;em&gt;~/.config/helix/config.toml&lt;/em&gt; (or change the paths in the scripts accordingly) and that you have a line in it that sets the theme:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;theme = &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;something&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(What it’s set to is not important as long the line is there so it can be changed by the update script we’re going to write next.)&lt;/p&gt;
&lt;h2 id=&#34;the-scripts&#34;&gt;The scripts&lt;/h2&gt;
&lt;p&gt;First, create a folder to hold the scripts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;mkdir -p ~/.config/helix/scripts/synchronise-with-system-colour-scheme
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then create the following scripts and ensure they’re executable (&lt;code&gt;chmod +x &amp;lt;name-of-file&amp;gt;&lt;/code&gt;):&lt;/p&gt;
&lt;h3 id=&#34;monitorsh&#34;&gt;monitor.sh&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
gsettings monitor org.gnome.desktop.interface color-scheme &lt;span style=&#34;color:#4070a0;font-weight:bold&#34;&gt;\
&lt;/span&gt;&lt;span style=&#34;color:#4070a0;font-weight:bold&#34;&gt;&lt;/span&gt;  | xargs -L1 bash -c &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;source &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/.config/helix/scripts/synchronise-with-system-colour-scheme/update.sh&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;updatesh&#34;&gt;update.sh&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Fail early, fail often.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;set&lt;/span&gt; -eu -o pipefail

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;[[&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$1&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;then&lt;/span&gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Update Helix Editor config to use light theme.&lt;/span&gt;
  sed -i &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s/theme = &amp;#34;.*&amp;#34;/theme = &amp;#34;onelight&amp;#34;/&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;/.config/helix/config.toml
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Update Helix Editor config to use dark theme.&lt;/span&gt;
  sed -i &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s/theme = &amp;#34;.*&amp;#34;/theme = &amp;#34;dracula&amp;#34;/&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;HOME&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;/.config/helix/config.toml
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Edit the script to change the light and dark themes to the ones you want. You can see the ones available on your system using the &lt;code&gt;:theme&lt;/code&gt; command in Helix.&lt;/p&gt;
&lt;p&gt;You need to keep the monitor script running in the background for this to work. You can do this by simply running &lt;code&gt;./monitor.sh &amp;amp;&lt;/code&gt;, or (recommended), you can run it as a systemd user service.&lt;/p&gt;
&lt;p&gt;Before creating the systemd service, create two more helper scripts to enable and disable it:&lt;/p&gt;
&lt;h3 id=&#34;enablesh&#34;&gt;enable.sh&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Enables the service to start on boot and also starts it up immediately.&lt;/span&gt;
systemctl --user &lt;span style=&#34;color:#007020&#34;&gt;enable&lt;/span&gt; helix-system-colour-scheme-synchronisation.service
systemctl --user start helix-system-colour-scheme-synchronisation.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;disablesh&#34;&gt;disable.sh&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Stop the service immediately and disables it from starting on boot.&lt;/span&gt;
systemctl --user stop helix-system-colour-scheme-synchronisation.service
systemctl --user disable helix-system-colour-scheme-synchronisation.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And, finally, create the systemd user service (in _~/.config/systemd/user and make sure it is executable.)&lt;/p&gt;
&lt;h3 id=&#34;helix-system-colour-scheme-synchronisationservice&#34;&gt;helix-system-colour-scheme-synchronisation.service&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;[Unit]&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;Helix System Colour Scheme Synchronisation Service&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;Documentation&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;https://github.com/helix-editor/helix/issues/2158&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;After&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;gnome-session.target&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;[Service]&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;simple&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;RestartSec&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;5&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;Restart&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;always&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;%h/.config/helix/scripts/synchronise-with-system-colour-scheme/monitor.sh&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;[Install]&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;gnome-session.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;Run: &lt;code&gt;./enable&lt;/code&gt; in your scripts folder to enable the service and &lt;code&gt;./disable&lt;/code&gt; to disable it.&lt;/p&gt;
&lt;p&gt;Helix currently doesn’t automatically reload its configuration when it changes so you’ll have to restart existing instances manually.&lt;/p&gt;
&lt;p&gt;I look forward to the day when we can see a smooth fade from a light theme to a dark theme and back when the system theme changes. 🤓👍&lt;/p&gt;
&lt;p&gt;Until then, I hope you find this useful.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you’re using GNOME Console or GNOME Terminal, this post won’t help you as those apps do not adhere to the system colour scheme. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>NLnet Grant Application for Domain</title>
      <link>https://ar.al/2022/07/29/nlnet-grant-application-for-domain/</link>
      <pubDate>Fri, 29 Jul 2022 11:04:33 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/07/29/nlnet-grant-application-for-domain/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is my &lt;a href=&#34;https://nlnet.nl/propose/&#34;&gt;application&lt;/a&gt; to get &lt;a href=&#34;https://nlnet.nl/news/2022/20220601-call.html&#34;&gt;NLnet&lt;/a&gt; funding to work on Domain as part of the &lt;a href=&#34;https://nlnet.nl/useroperated/&#34;&gt;User-Operated Internet Fund&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I feel it’s important that such grant applications are made public so everyone has visibility into the process. This will allow us to collectively learn from the experience and perhaps even to improve the process itself.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;As such, I’ll be making my end of the process as public as possible by not only sharing my original grant application but any subsequent communication I receive during the process. And I urge you to do the same on your own site when applying for similar grants.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Public money, &lt;em&gt;&lt;strong&gt;public process&lt;/strong&gt;&lt;/em&gt;, public code.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;timeline&#34;&gt;Timeline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Saturday, July 30, 2022: Proposal submitted to NLnet.&lt;/li&gt;
&lt;li&gt;Tuesday, September 27, 2022: Questions received from NLnet.&lt;/li&gt;
&lt;li&gt;Wednesday, September 28, 2022: &lt;a href=&#34;https://ar.al/2022/09/28/nlnet-grant-application-for-domain-first-update-questions-and-answers/&#34;&gt;Answers to questions sent to NLnet.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wednesday, October 12, 2022: &lt;a href=&#34;https://ar.al/2022/10/20/nlnet-grant-application-for-domain-rejected/&#34;&gt;Our proposal is rejected.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;call-topic&#34;&gt;Call topic&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Thematic call:&lt;/strong&gt; User-Operated Internet Fund&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;contact-information&#34;&gt;Contact information&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Your name:&lt;/strong&gt; Aral Balkan&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email address:&lt;/strong&gt; &lt;a href=&#34;mailto:aral@small-tech.org&#34;&gt;aral@small-tech.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phone numbers:&lt;/strong&gt; &lt;em&gt;(not including it here for privacy reasons)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organisation:&lt;/strong&gt; &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Country:&lt;/strong&gt; Ireland&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;general-project-information&#34;&gt;General project information&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project name:&lt;/strong&gt; Domain&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Website/wiki:&lt;/strong&gt; &lt;a href=&#34;https://codeberg.org/domain/app&#34;&gt;https://codeberg.org/domain/app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;abstract-can-you-explain-the-whole-project-and-its-expected-outcomes-1200-characters&#34;&gt;Abstract: Can you explain the whole project and its expected outcome(s). (1200 characters)&lt;/h2&gt;
&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/1375672201-8c04d975c07e410a3612f1fc2ad99c6ff29485f47e2f7f3a6821d612c71e216a-d?mw=2500&amp;mh=1406&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/progressive_redirect/playback/678826485/rendition/1080p/file.mp4?loc=external&amp;signature=6f4b206a51efd52219677699c959dc0ace079d2c7f5ddb5da5f98d6f55684182#t=00:53:24&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Domain creating an Owncast server in under a minute (live stream demo).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Domain aims to democratise the ownership of web sites and commoditise the gatekeepers.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The best thing about the internet is that … [f]rom the edges inwards, users themselves &lt;strong&gt;can&lt;/strong&gt; collectively own, operate and rewrite every aspect of the technology and infrastructure they depend on.” – &lt;a href=&#34;https://nlnet.nl/useroperated/&#34;&gt;User-Operated Internet Fund&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, we &lt;strong&gt;can&lt;/strong&gt;. But it is &lt;strong&gt;technically difficult&lt;/strong&gt; to do so.&lt;/p&gt;
&lt;p&gt;This is the problem Domain aims to solve.&lt;/p&gt;
&lt;p&gt;Think about what you have to do today to have your own web site (or self-hosted web application) running on your own server:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Buy a domain name from a commercial domain name registrar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sign up with a commercial web host (e.g., DigitalOcean, Hetzner, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provision a server (e.g., virtual private server) with your web host.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure your Domain Name System (DNS) records to point to your server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install or configure a web server, the site (or app) you want to host, as well as any other required software (database, etc.).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure your server is sufficiently secure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintain your server by installing updates to your operating system and other related software on a regular basis.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While none of this is terribly difficult for seasoned developers or system administrators to tackle, it is not a process you would expect someone without technical skills to undertake without a considerable investment of time and effort.&lt;/p&gt;
&lt;p&gt;And we want everyone to have their own place on the Web, not just those of us who have the relevant technical knowledge.&lt;/p&gt;
&lt;p&gt;Furthermore, it can easily take at least half an hour even for technically-savvy folks to go through all those steps to set up a simple site or app (and longer still if you have to configure databases, message queues, etc.) I once watched an experienced software engineer struggle to set up his own &lt;a href=&#34;https://joinmastodon.org/&#34;&gt;Mastodon&lt;/a&gt; instance for an afternoon before giving up. ’Nuff said.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal of Domain is to reduce this process to under a minute.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(So we’re talking about an order of magnitude difference or more.)&lt;/p&gt;
&lt;p&gt;Here’s &lt;a href=&#34;https://vimeo.com/678826485#t=53m24s&#34;&gt;a video of Domain creating and running a new Owncast server in under a minute&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;how-does-it-work&#34;&gt;How does it work?&lt;/h3&gt;
&lt;p&gt;Domain is being built for use by two different audiences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hosts:&lt;/strong&gt; Organisations/technically-savvy individuals who run domain as a service for their communities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Everyday people:&lt;/strong&gt; Who use the Domain instances set up by hosts to commission, own, and control their own web sites and apps.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;for-hosts&#34;&gt;For hosts&lt;/h4&gt;
&lt;p&gt;Domain enables organisations and individuals to provide web hosting services for small (personal) web sites and apps to the general public.&lt;/p&gt;
&lt;p&gt;As a prerequisite, hosts need to sign up for the following constituent services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A web hosting provider (initially only &lt;a href=&#34;https://www.hetzner.com/cloud&#34;&gt;Hetzner&lt;/a&gt; will be supported&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A DNS provider (initially only &lt;a href=&#34;https://dnsimple.com&#34;&gt;DNSimple&lt;/a&gt; will be supported).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A domain name that they’ve added to the &lt;a href=&#34;https://publicsuffix.org/&#34;&gt;Public Suffix List&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally, an account with a payment provider if they plan to charge for hosting (initially only &lt;a href=&#34;https://stripe.com&#34;&gt;Stripe&lt;/a&gt; will be supported).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/screenshots/domain-public-suffix-list.png&#34;
         alt=&#34;Screenshot of the Domain administration panel showing the Public Suffix List (PSL) tab active. The domain small-web.org is on the PSL.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Domain Administration Panel.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once a host has acquired the required accounts and their domain is on the Public Suffix List, they will use the simple online administration interface provided by Domain to enter the relevant API keys for each of these services.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/screenshots/domain-vps-settings.png&#34;
         alt=&#34;Screenshot of the Domain administration panel showing the VPS Host Settings screen with API Token and SSH Key Name input boxes.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The VPS Host Settings screen.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Domain combines these fundamental services into a simple and seamless flow to enable everyday people to sign up for their own sites and apps at their own domain.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/screenshots/domain-apps-1.png&#34;
         alt=&#34;Screenshot of Domain administration panel showing the App Settings screen. Available choices are “Site.js”, “Owncast”… Owncast is selected.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The App Settings screen.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Hosts can decide which apps and sites people can install. These apps are defined in the online interface by their installation and configuration instructions in a standard format known as &lt;a href=&#34;https://github.com/canonical/cloud-init#cloud-init&#34;&gt;cloud-init&lt;/a&gt;. It is currently possible in the Domain prototype to install &lt;a href=&#34;https://owncast.online&#34;&gt;Owncast&lt;/a&gt;, for example, as well as a generic &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; web site.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/screenshots/domain-apps-2.png&#34;
         alt=&#34;Screenshot of Domain administration panel showing lower part of App Settings screen with the Owncast cloud-init configuration&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;App Settings: Owncast cloud-init configuration.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Domain will democratise the process of being a web host and enable small organisations and even individuals to host sites and apps for their local communities.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; design of Domain sets natural limits on how large a host can grow. Our goal is to encourage a community of lots of small,  independent, and ideally not-for-profit hosts around the world. Each of which, of course, can customise Domain for their own needs and culture, given that it is released under AGPL version 3. This license also means that any improvements to Domain must be shared back into the commons, thereby strengthening it for everyone. (And it prevents, for example, a venture-capital-funded startup in the future from taking the software, spending a few million dollars to improve it, and then keeping those improvements to themselves to create fragmentation and lock-in.)&lt;/p&gt;
&lt;p&gt;Domain is being built with an integrated web server and web development platform called &lt;a href=&#34;https://codeberg.org/nodekit/app/src/branch/kitten#kitten&#34;&gt;Kitten&lt;/a&gt; (previously known as &lt;a href=&#34;https://codeberg.org/nodekit/app/src/branch/main#nodekit-logo-nodekit-logo-svg-nodekit&#34;&gt;NodeKit&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Kitten is being developed alongside Domain, with Domain’s requirements determining its features.&lt;/p&gt;
&lt;p&gt;Kitten will also be used to create and serve small web sites that can be installed with Domain and will be a useful tool itself in lowering the barrier of entry to developing and hosting your own web site or app. While Kitten is not the focus of this grant application, it is important to understand that it is impossible to separate the two as they are being developed in parallel and any support for our development work will benefit both of these intertwined projects.&lt;/p&gt;
&lt;h4 id=&#34;for-everyday-people&#34;&gt;For everyday people&lt;/h4&gt;
&lt;p&gt;For everyday people, Domain presents a very simple interface:&lt;/p&gt;
&lt;p&gt;Choose a domain and select an app to install. Optionally, provide payment information and, within a minute, you’re up and running with your own site at your own domain.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/screenshots/domain-everyday-people.png&#34;
         alt=&#34;Screenshot of Domain’s public-facing page. Interface reads “I want my own (editable field) Place at (editable field) domain .small-web.org for €10/month. A green button reads “Get Started.”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Domain interface for everyday people.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As far as I’m aware, there is currently nothing that approaches this in terms of ease of use.&lt;/p&gt;
&lt;p&gt;The speed and ease with which everyday people can set up their own sites and apps at their own servers is &lt;em&gt;&lt;strong&gt;at least an order of magnitude faster&lt;/strong&gt;&lt;/em&gt; than what’s available today. We believe that this will have a substantial impact in democratising the Web by enabling more people to own and control their own place on it.&lt;/p&gt;
&lt;h2 id=&#34;have-you-been-involved-with-projects-or-organisations-relevant-to-this-project-before-and-if-so-can-you-tell-us-a-bit-about-your-contributions&#34;&gt;Have you been involved with projects or organisations relevant to this project before? And if so, can you tell us a bit about your contributions?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;“Technology should be a commons for everyone to enjoy and contribute to, without gatekeepers or persistent threats.” – &lt;a href=&#34;https://nlnet.nl/useroperated/&#34;&gt;User-Operated Internet Fund&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We formed &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; in 2013 and have been working towards this very ideal ever since. In the intervening years, as our understanding of the problem grew, we honed our focus and limited resources on effecting change were we feel it will be most impactful and least likely to be threatened by proprietary systems and corporate control: the open web.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/07/29/nlnet-grant-application-for-domain/https://s3-eu-central-1.amazonaws.com/mastodon-aral/media_attachments/files/000/390/722/original/97e592d88621df42.jpeg&#34;
         alt=&#34;Screenshot of magazine: “…relying so heavily on the “free” data-slurping tech giants. But some people are trying to change the dynamic. Aral Balkan is an activist and co-founder of Indie, which develops privacy-minded tools and services. He is working with the Belgian city of Ghent to provide an alternative to social media sites. Citizens will be able to. sign up for their own .gent website, which will be able to follow and update other .gent sites. The sites will also connect to other like-minded services such as Mastodon, a privacy-respecting alternative to Twitter (see “Ditch and switch”, right). The idea is that it will work much like a Facebook profile, but each person will own their own site there is no central authority hoovering up your data. On the face of it, that sounds a lot like the old web, where people created simple pages hosted on computers they controlled. The crucial difference is that it used to be difficult to put things online without technical know-how —which is partly why easy-to-use services like Facebook are popular. Balkan wants the .gent project to be simple to use, with plans for the webhosting and domain registration to happen in the background. “We solve that problem, and that’s where we change the game,” he says. The project is being funded by the city of Ghent, which Balkan says is a model for the way forward. “We need to start funding these ethical alternatives from the commons, for the common good,” he says. He&amp;#39;s not calling for social media to be run by the government or for Facebook to be nationalised, he says - the potential for surveillance is too high. The Chinese government plans to use personal data to rate individual citizens, for example. Instead, the taxpayer could fund online services, which are kept at arm&amp;#39;s length from the state.”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Indienet project as covered in New Scientist&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In 2017-2018 we worked with the City of Ghent, in Belgium, on prototyping the &lt;a href=&#34;https://mastodon.ar.al/@aral/99841075595555746&#34;&gt;Indienet&lt;/a&gt; project. The goal was to demonstrate how citizens could have their own web sites (at a .gent domain) and own and control their own data when interacting among themselves and with the municipality. Sadly, a conservative local government was elected and our funding for the project was cut.&lt;/p&gt;
&lt;p&gt;The Indienet project was the spiritual predecessor to Domain.&lt;/p&gt;
&lt;p&gt;The Indienet project taught me several things: first, not to rely on public funding which might be taken away at any moment when the winds of politics change and, secondly, that we must build our own tools and frameworks at all levels of the stack to radically simplify the development and deployment of personal web sites and apps.&lt;/p&gt;
&lt;p&gt;As part of our research and development efforts, I’ve been iterating on a personal web server and web development platform since 2019, when I first launched &lt;a href=&#34;https://ar.al/2019/03/08/https-server-now-with-seamless-lets-encrypt-support/&#34;&gt;HTTPS Server&lt;/a&gt; which became &lt;a href=&#34;https://ar.al/2019/03/10/indie-web-server/&#34;&gt;Indie Web Server&lt;/a&gt;, and finally &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;. The latter currently powers all our sites, including &lt;a href=&#34;https://small-tech.org&#34;&gt;https://small-tech.org&lt;/a&gt;, my blog at &lt;a href=&#34;https://ar.al&#34;&gt;https://ar.al&lt;/a&gt;, and, of course, &lt;a href=&#34;https://sitejs.org&#34;&gt;https://sitejs.org&lt;/a&gt; itself.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://codeberg.org/nodekit/app/src/branch/kitten#kitten&#34;&gt;Kitten&lt;/a&gt;, the server/framework I am building in parallel to Domain, is the next iteration of these efforts. It makes modern web development about as simple as it can get by eschewing a complicated build process and heavyweight JavaScript frameworks and enabling people to create web sites using plain HTML, CSS, and JavaScript and, optionally, enhancing them with HTML-over-the-wire via &lt;a href=&#34;https://htmx.org&#34;&gt;htmx&lt;/a&gt; and simple interactivity using &lt;a href=&#34;https://hyperscript.org&#34;&gt;hyperscript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Domain is being written in and will be powered by Kitten.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Kitten (and thereby Domain) benefits from components that I created for Site.js, including the &lt;a href=&#34;https://source.small-tech.org/site.js/lib/auto-encrypt&#34;&gt;auto-encrypt&lt;/a&gt; library for simplifying secure sites with automatic Let’s Encrypt certificates and &lt;a href=&#34;https://source.small-tech.org/site.js/lib/jsdb&#34;&gt;JavaScript Database (JSDB)&lt;/a&gt;, a simple in-memory, streaming write-on-update JavaScript database for Small Web use that persists to a JavaScript transaction log.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(An earlier iteration of Kitten was called &lt;a href=&#34;https://codeberg.org/nodekit/app/src/branch/main#nodekit-logo-nodekit-logo-svg-nodekit&#34;&gt;NodeKit&lt;/a&gt; and used &lt;a href=&#34;https://svelte.dev/&#34;&gt;Svelte&lt;/a&gt; instead of htmx. The current prototype of Domain in written in NodeKit and is being ported over to Kitten. The current prototype is able to deploy sites using Site.js. In fact, it’s how we set up our streaming server for Small Technology Foundation at &lt;a href=&#34;https://owncast.small-web.org&#34;&gt;https://owncast.small-web.org&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;requested-support&#34;&gt;Requested support&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Requested amount:&lt;/strong&gt; €50,000&lt;/p&gt;
&lt;h3 id=&#34;explain-what-the-requested-budget-will-be-used-for-does-the-project-have-other-funding-sources-both-past-and-present-if-you-want-you-can-in-addition-attach-a-budget-at-the-bottom-of-the-form&#34;&gt;Explain what the requested budget will be used for. Does the project have other funding sources, both past and present? (If you want, you can in addition attach a budget at the bottom of the form):&lt;/h3&gt;
&lt;p&gt;The requested budget will be used, after taxes, to pay me, a software developer with over two decades of professional development experience, to work on the project for the next 12 months (at a rate that’s less than half of what I was earning working in the mainstream two decades ago).&lt;/p&gt;
&lt;p&gt;Our funding sources for Small Technology Foundation are and have been:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(Past) An initial crowdfunding round in 2013.&lt;/li&gt;
&lt;li&gt;(Past) My selling two family homes in Turkey.&lt;/li&gt;
&lt;li&gt;(Past and present) &lt;a href=&#34;https://small-tech.org/fund-us&#34;&gt;Patrons and donors&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;(Present) &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura’s&lt;/a&gt; contracting work with &lt;a href=&#34;https://stately.ai/&#34;&gt;Stately&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;compare-your-own-project-with-existing-or-historical-efforts&#34;&gt;Compare your own project with existing or historical efforts.&lt;/h3&gt;
&lt;p&gt;The closest existing tools and projects to Domain are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hosting providers like DigitalOcean, Hetzner, etc. when combined with commercial domain name registration services.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These suffer from the complexity problem mentioned above and are profit-oriented corporate initiatives that do not have any sort of social mission to democratise the Web or technology.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free and open source self-hosting platforms like &lt;a href=&#34;https://yunohost.org&#34;&gt;Yunohost&lt;/a&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These systems provide web site/app installation functionality but they must themselves be hosted (which requires all the steps that Domain automates and streamlines). To illustrate, you could use Domain to create a server running Yunohost.&lt;/p&gt;
&lt;h3 id=&#34;what-are-significant-technical-challenges-you-expect-to-solve-during-the-project-if-any&#34;&gt;What are significant technical challenges you expect to solve during the project, if any?)&lt;/h3&gt;
&lt;p&gt;In terms of feasibility, I don’t believe there are any major questions left unexplored (as I have been working on the underlying infrastructure and design for several years now and I already have a functional prototype of Domain).&lt;/p&gt;
&lt;p&gt;In the coming days, I will be porting Domain from NodeKit to Kitten and evolving Kitten to handle any unmet requirements this process might uncover in it. I feel very good about this as the replacement of Svelte with htmx has removed a large chunk of complexity from (the already lightweight) framework and developing with it feels excellent so far.&lt;/p&gt;
&lt;p&gt;Now that the architectural decisions have been locked down, I expect a rather straightforward and sustained development effort with ongoing releases of both Kitten and Domain in the coming year.&lt;/p&gt;
&lt;h3 id=&#34;describe-the-ecosystem-of-the-project-and-how-you-will-engage-with-relevant-actors-and-promote-the-outcomes&#34;&gt;Describe the ecosystem of the project, and how you will engage with relevant actors and promote the outcomes?&lt;/h3&gt;
&lt;p&gt;Initially, my goal is to launch our own Domain instance at &lt;a href=&#34;https://small-web.org&#34;&gt;https://small-web.org&lt;/a&gt; to demonstrate how it works and so it can start bringing in income for Small Technology Foundation. (Our goal is to become sustainable so we can keep working on the Small Web.) I envision that the initial people who play with it to set up their own sites and apps will be people who hear about it because they &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;follow me on the fediverse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope that once other developers see that it works, they might want to start becoming hosts themselves (either individually or with their organisations). My goal is to eventually see a healthy ecosystem of independent (and ideally not-for-profit hosts) around the world using Domain to empower their communities to own and control their own places on the World Wide Web.&lt;/p&gt;
&lt;p&gt;I would love to see community groups, cooperatives, municipalities, and other organisations experimenting with alternative means of sustaining such efforts. The token ‘payment’ system in Domain, for example, could be used by a city to mail out codes to all its citizens that they can then use in lieu of payment to set up their own web sites and apps. (This is the model we were exploring with the City of Ghent.)&lt;/p&gt;
&lt;p&gt;Finally, although Kitten is not the focus of this grant proposal, since it is being developed in parallel with Domain (and given that Domain is being written in it and will be powered by it), I believe that Kitten will also open up web development to a wider group of people by removing the complexities inherent in current corporate stacks. I foresee it being used to teach web development (at the very least, I foresee myself using it to teach web development).&lt;/p&gt;
&lt;h3 id=&#34;attachments&#34;&gt;Attachments&lt;/h3&gt;
&lt;p&gt;None.&lt;/p&gt;
&lt;p&gt;(Please see links in the body of the proposal.)&lt;/p&gt;
&lt;h3 id=&#34;how-may-we-handle-your-information&#34;&gt;How may we handle your information&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(If you haven&amp;rsquo;t, please check our privacy policy).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;When your project gets selected, we will legally need to retain your information for compliance purposes for at least seven years. Also, in case you are submitting to one of the NGI related funds, at that point we will need to share some information with our (not-for-profit) partner organisations in order so they may assist you with mentoring as well as complementary services e.g. accessibility, documentation, localisation, packaging, etc. By submitting a proposal you grant us permission to handle the data in the proposal in the manner described.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What should we do in the other case, e.g. when your project is not immediately selected?&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I allow NLnet Foundation to keep the information I submit on record, should future funding opportunities arise&lt;/li&gt;
&lt;li&gt;Send me a copy of this application.&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I do wish they had called it the Person-Operated or Human-Operated Internet Fund (or even just the Personal Internal Fund) instead of using the Silicon Valley word ‘user’, which is inherently colonial/ethnographic in nature. This is why &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/#fn:13&#34;&gt;we refer to people as ‘people’ in small technology&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;While initially only a single web host, DNS provider, and payment provider will be supported, our hope is to add other providers so that, eventually, Domain can commoditise these services. The greatest challenge there is that commercial providers usually have custom Application Programming Interfaces (APIs) as part of their business model of locking people into their own services. So our goal will be to work towards better interoperability between service providers as well as abstracting away the differences on our end. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Domain makes use of the Public Suffix List in order to bypass the commercial domain name system and enable fully-functional sites to be set up at their own domains in under a minute.&lt;/p&gt;
&lt;p&gt;A domain like small-web.org – where Small Technology Foundation will be running our own instance of Domain – which is on the Public Suffix List is, for all intents and purposes, equivalent to a top-level domain and can provide all the privacy and security guarantees that any other top-level domain can (e.g., .com, .org, etc.) &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Using bound functions to unit test EcmaScript Modules</title>
      <link>https://ar.al/2022/03/23/using-bound-functions-to-unit-test-ecmascript-modules/</link>
      <pubDate>Wed, 23 Mar 2022 15:47:53 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/03/23/using-bound-functions-to-unit-test-ecmascript-modules/</guid>
      <description>&lt;p&gt;Imagine you have the following EcmaScript module you want to unit test:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// untestable.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; configurationValue &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;something&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; functionToTest () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; configurationValue
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The problem is you can’t test it for different values of &lt;code&gt;configurationValue&lt;/code&gt; (which, in a real example, might be based on a runtime quality of your app). You can’t because configuration value is private to the module.&lt;/p&gt;
&lt;p&gt;However, if you rewrite your function like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// testable.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; context &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {
  configurationValue&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;something&amp;#39;&lt;/span&gt;
}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; functionToTest &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.configurationValue
}).bind(context)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can test it by providing different values for &lt;code&gt;configurationValue&lt;/code&gt; like this&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// tests/index.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; test from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/tape-with-promises&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { context, functionToTest } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;../testable.js&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; something &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;something&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; somethingElse &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;something else&amp;#39;&lt;/span&gt;

test(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;index-testable&amp;#39;&lt;/span&gt;, t =&amp;gt; {
  t.equals(functionToTest(), something)

  context.configurationValue &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; somethingElse

  t.equals(functionToTest(), somethingElse)
  t.end()
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This works (both tests pass) because exports are &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export&#34;&gt;live bindings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Why not just use a class? Because sometimes you can’t. Case in point: I came up with this method to test my &lt;a href=&#34;https://nodejs.org/docs/latest-v16.x/api/esm.html#loaders&#34;&gt;ES Module Loader&lt;/a&gt; in &lt;a href=&#34;https://github.com/small-tech/nodekit&#34;&gt;NodeKit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2021/05/27/make-anything-a-javascript-module-using-node.js-esm-module-loaders/&#34;&gt;ES Module Loaders&lt;/a&gt; are meant to export &lt;code&gt;resolve()&lt;/code&gt; and &lt;code&gt;load()&lt;/code&gt; functions and I was configuring them using module-level variables.&lt;/p&gt;
&lt;p&gt;I didn’t want to change the function signatures to inject values or introduce conditional logic to support unit tests.&lt;/p&gt;
&lt;p&gt;This method allows me to inject the values I need during tests. The only change to the code is exporting a bound function instead of an unbound function and referring to any context variables using &lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;mocking-whole-modules&#34;&gt;Mocking whole modules&lt;/h2&gt;
&lt;p&gt;You can also use this method to mock imported modules.&lt;/p&gt;
&lt;p&gt;For example, if the module you want to test looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// also-testable.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; importedFunction from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./other-module.js&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; context &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {
  importedFunction
}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; functionToTest &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.importedFunction()
}).bind(context)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this is the module we want to mock:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// other-module.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;actual value&amp;#39;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can mock it like this in our test:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; test from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/tape-with-promises&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { context, functionToTest } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;../also-testable.js&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; mockFunction &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;mock value&amp;#39;&lt;/span&gt;
}

test(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;index-testable&amp;#39;&lt;/span&gt;, t =&amp;gt; {
  t.equals(functionToTest(), &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;actual value&amp;#39;&lt;/span&gt;)

  context.importedFunction &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; mockFunction

  t.equals(functionToTest(), &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;mock value&amp;#39;&lt;/span&gt;)
  t.end()
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hope this adds another tool to your belt should you need it when unit testing EcmaScript Modules.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you’re going to try this out, first &lt;a href=&#34;https://github.com/small-tech/tape-with-promises&#34;&gt;&lt;code&gt;npm i --save-dev @small-tech/tape-with-promises&lt;/code&gt;&lt;/a&gt;. You can run it with &lt;code&gt;npx tape tests/index.js&lt;/code&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Decentralisation begins at decentring yourself</title>
      <link>https://ar.al/2022/02/16/decentralisation-begins-at-decentring-yourself/</link>
      <pubDate>Wed, 16 Feb 2022 13:34:21 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/02/16/decentralisation-begins-at-decentring-yourself/</guid>
      <description>&lt;figure&gt;
  &lt;div class=&#39;video&#39; style=&#34;padding:56.25% 0 0 0;position:relative;&#34;&gt;&lt;iframe src=&#34;https://player.vimeo.com/video/678148583?h=7c9e3e052e&#34; frameborder=&#34;0&#34; allow=&#34;autoplay; fullscreen; picture-in-picture&#34; allowfullscreen style=&#34;position:absolute;top:0;left:0;width:100%;height:100%;&#34; title=&#34;How to send an email&#34;&gt;&lt;/iframe&gt;&lt;/div&gt;
    &lt;figcaption&gt;&lt;p&gt;My talk at the Department of Art &amp; Media Technology, University of Southampton, on Wednesday, February 16th, 2022.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This morning, I was invited to gave a talk at the Department of Art &amp;amp; Media Technology, University of Southampton.&lt;/p&gt;
&lt;p&gt;I was invited by Adam Procter, a long-time ally who heads up the department, as part of a new series of talks to help shape department policy on digital and networked technologies.&lt;/p&gt;
&lt;p&gt;The talk covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Corporate vs human futures and topologies.&lt;/li&gt;
&lt;li&gt;Big Web vs Small Web (and web3 vs web0).&lt;/li&gt;
&lt;li&gt;Decentralised and decolonial approaches to technology.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Links mentioned during the talk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/small-tech/domain&#34;&gt;Domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https:/sitejs.org&#34;&gt;Site.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/small-tech/nodekit&#34;&gt;NodeKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cyborgrights.eu&#34;&gt;Universal Declaration of Cyborg Rights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://small-web.org&#34;&gt;Small Web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>NodeKit extension for Codium</title>
      <link>https://ar.al/2022/02/15/nodekit-extension-for-codium/</link>
      <pubDate>Tue, 15 Feb 2022 11:03:27 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/02/15/nodekit-extension-for-codium/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/02/15/nodekit-extension-for-codium/./nodekit-extension-1.png&#34;
         alt=&#34;Screenshot of the NodeKit for Codium extension’s settings page in VSCodium.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;NodeKit for Codium: a reluctant fork of Svelte Language Tools.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This past week, I cobbled together&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; a very basic &lt;a href=&#34;https://github.com/small-tech/nodekit/&#34;&gt;NodeKit&lt;/a&gt; extension for &lt;a href=&#34;https://vscodium.com/&#34;&gt;Codium&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;download&#34;&gt;Download&lt;/h2&gt;
&lt;p&gt;&lt;svg width=&#34;1em&#34; height=&#34;1.1em&#34;&gt;&lt;text x=&#34;0&#34; y=&#34;1em&#34;&gt;📦&lt;/text&gt;&lt;/svg&gt; &lt;a href=&#34;./nodekit-for-codium-0.5.0.vsix&#34;&gt;nodekit-for-codium-0.5.0.vsix&lt;/a&gt;&lt;/p&gt;
&lt;!-- Note: making this an SVG so it’s not affected by my dark mode filters. --&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;svg width=&#34;1em&#34; height=&#34;1.1em&#34;&gt;&lt;text x=&#34;0&#34; y=&#34;1em&#34;&gt;💡&lt;/text&gt;&lt;/svg&gt; What’s NodeKit? &lt;a href=&#34;https://ar.al/2022/01/26/nodekit-update/&#34;&gt;Watch the NodeKit video introduction&lt;/a&gt; to find out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;p&gt;Either drag the downloaded package to the Extensions panel of Codium or, in your terminal, run the following command from the folder you downloaded the package to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;code --install-extension ./nodekit-for-codium-0.5.0.vsix
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;svg width=&#34;1em&#34; height=&#34;1.1em&#34;&gt;&lt;text x=&#34;0&#34; y=&#34;1em&#34;&gt;💡&lt;/text&gt;&lt;/svg&gt; If you have Svelte Language Tools installed, this extension will likely clash with it so please disable the other extension while playing with NodeKit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/02/15/nodekit-extension-for-codium/./nodekit-extension-2.png&#34;
         alt=&#34;Screenshot of index.page open in VSCodium. Code: &amp;lt;data&amp;gt;let count = 1; export default () =&amp;gt; { return {count: count&amp;#43;&amp;#43;} }&amp;lt;/data&amp;gt; &amp;lt;script&amp;gt;export let data&amp;lt;/script&amp;gt;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;I’ve greeted you {data.count} time{data.count &amp;gt; 1 ? &amp;#39;s&amp;#39; : &amp;#39;&amp;#39;}.&amp;lt;/p&amp;gt;&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The NodeKit code in the example keeps a simple ephemeral server-side count and displays it in the browser when the page loads.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;about&#34;&gt;About&lt;/h2&gt;
&lt;p&gt;The NodeKit for Codium extension is a fork of &lt;a href=&#34;https://github.com/sveltejs/language-tools&#34;&gt;Svelte Language Tools&lt;/a&gt; that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hides the &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; section of a &lt;code&gt;.page&lt;/code&gt; from Svelte so it doesn’t complain about it.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides basic JavaScript syntax highlighting for &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; blocks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides &lt;em&gt;very&lt;/em&gt; basic JavaScript completions (not Node-specific) for &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; blocks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides full JavaScript syntax highlighting and language intelligence for &lt;em&gt;.data&lt;/em&gt;, &lt;em&gt;.get&lt;/em&gt;, &lt;em&gt;.post&lt;/em&gt;, and &lt;em&gt;.socket&lt;/em&gt; files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;good-perfect-and-all-that&#34;&gt;Good, perfect and all that&lt;/h2&gt;
&lt;p&gt;I’m not happy with the current state of this extension but you know what they say about perfect being the enemy of the good.&lt;/p&gt;
&lt;p&gt;Basically, without this extension, Codium gets in your way when you add the NodeKit &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; block to your code. With it, it doesn’t and you get proper Svelte language intelligence for your page as well as basic syntax highlighting for your &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;Initially, I did try creating &lt;a href=&#34;https://github.com/small-tech/nodekit-codium-extension&#34;&gt;a separate tiny extension&lt;/a&gt; that uses &lt;a href=&#34;https://code.visualstudio.com/api/language-extensions/embedded-languages&#34;&gt;request forwarding&lt;/a&gt; to forward requests for the &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; block and the rest of the document separately but &lt;a href=&#34;https://github.com/microsoft/vscode/issues/142734&#34;&gt;that didn’t work&lt;/a&gt;. And, even if it had worked, being a client-side solution, it would have been Codium-specific.&lt;/p&gt;
&lt;h2 id=&#34;a-forking-shame&#34;&gt;A forking shame&lt;/h2&gt;
&lt;p&gt;So, like it or not, if I want other editors to support language intelligence for NodeKit in the future, I currently find myself in the unenviable position of having to fork the Svelte language server.&lt;/p&gt;
&lt;p&gt;It is sad that there isn’t a cleaner way to basically say, at the language server level, “hey, see this &lt;data&gt;…&lt;/data&gt; region? Treat it as JavaScript.”&lt;/p&gt;
&lt;p&gt;I mean this is essentially what the Svelte language server itself does by forwarding different regions of Svelte files to different language services and using source maps to translate the results back but it is not trivial to implement (&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;I’ll likely eventually end up modifying the Svelte language server far more extensively to add proper support for &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; and other NodeScript blocks (like &lt;code&gt;&amp;lt;post&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;socket&amp;gt;&lt;/code&gt;, etc.)&lt;/p&gt;
&lt;p&gt;Right now, however, given the extension is currently good enough – if not anywhere near perfect – I’m going to get back to developing the core of NodeKit as I build &lt;a href=&#34;https://small-tech.org/videos/small-is-beautiful-11/&#34;&gt;Domain&lt;/a&gt; using it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you’re playing with NodeKit at this very early stage, I hope the existence of this extension makes your life a little easier. I’m going to keep thinking about tooling around NodeKit to try and balance not recreating the wheel with having a first-rate developer experience.&lt;/em&gt;&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or, “Bled for a week to try and get something better together within the limitations of the Language Server Protocol while wading through The Incredible Machine that is the Svelte Language Tools monorepo but finally settled on something that works for now even if it’s not perfect.” &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;At one point it even transpiles the Svelte source to TSX to make use of the TypeScript language service, which is a pragmatic move but it does end up creating the aforementioned Incredible Machine to decipher, especially when you’re first diving through the code. There is, however, a helpful &lt;a href=&#34;https://github.com/sveltejs/language-tools/blob/master/docs/internal/overview.md&#34;&gt;overview of the language-tools and how things work together&lt;/a&gt; document that will make your life easier. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Everyone Hates Facebook (but this is more than just about Facebook)</title>
      <link>https://ar.al/2022/02/07/everyone-hates-facebook-but-this-is-more-than-just-about-facebook/</link>
      <pubDate>Mon, 07 Feb 2022 10:15:01 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/02/07/everyone-hates-facebook-but-this-is-more-than-just-about-facebook/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/02/07/everyone-hates-facebook-but-this-is-more-than-just-about-facebook/./dislikes.jpg&#34;
         alt=&#34;Minimalistic vector illustration: a flock of Facebook thumbs-down dislike icons flies across a blue sky with fluffy clouds in the foreground.&#34;/&gt; 
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;“And my timelines lit up in a rare unity, the left and right, the rich and the poor, the healthy and the sick, all pleading and nodding and saying, yes, yes please, please do that.” - &lt;a href=&#34;https://twitter.com/RevPriest/status/1490280782073667589&#34;&gt;Adam Dalliance&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, it’s official, &lt;a href=&#34;https://twitter.com/aral/status/1490268690532737026/retweets/with_comments&#34;&gt;everybody hates Facebook&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But why we hate it matters. As does what we intend to do about it.&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&#39;video&#39; style=&#34;padding:56.25% 0 0 0;position:relative;&#34;&gt;&lt;iframe src=&#34;https://player.vimeo.com/video/116683861?h=6e4ebc5c71&#34; frameborder=&#34;0&#34; allow=&#34;autoplay; fullscreen; picture-in-picture&#34; allowfullscreen style=&#34;position:absolute;top:0;left:0;width:100%;height:100%;&#34; title=&#34;How to send an email&#34;&gt;&lt;/iframe&gt;&lt;/div&gt;
    &lt;figcaption&gt;&lt;p&gt;For more good reasons to hate Facebook and other people farmers, watch my talk, The Camera Panopticon.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;good-reasons-to-hate-facebook&#34;&gt;Good reasons to hate Facebook:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Because it is &lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;bad for democracy&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because it is &lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;bad for personhood&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;bad-reasons-to-hate-facebook&#34;&gt;Bad reasons to hate Facebook:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Because it doesn’t censor the things your government wants it to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because it did censor your favourite neo-Nazi.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because you want to create the next Facebook and be just as evil as they are but they’re standing in your way. (I’m looking at you VCs and startups, you know who you are.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, we all agree that Facebook is a problem.&lt;/p&gt;
&lt;p&gt;Some for the right reasons, some for the wrong ones…&lt;/p&gt;
&lt;p&gt;But this is not just about Facebook: it’s about any corporation that has the same business model as Facebook. The business model I call “people farming.”&lt;/p&gt;
&lt;p&gt;So it’s about Google too. And Snapchat. And TikTok. And, and, and, and… (this is the business model of mainstream technology today.)&lt;/p&gt;
&lt;p&gt;So we have a bigger – systemic ­– problem on our hands. (Ooh, fun!) And it seems everyone has some idea or other about how we should do things differently moving forward.&lt;/p&gt;
&lt;h2 id=&#34;bad-ways-forward&#34;&gt;Bad ways forward&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Recreate Facebook but in Europe.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Recreate Facebook but in &lt;a href=&#34;https://web0.small-web.org&#34;&gt;fucking web3&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make Facebook share its data with other people farmers so more people farmers can farm you for your data (try saying that quicky five times).&lt;/p&gt;
&lt;p&gt;And yes, &lt;a href=&#34;https://www.techzine.eu/news/trends/39436/vestager-regulate-data-access-instead-of-splitting-up-tech-giants/&#34;&gt;this really is exactly the EU Commission’s current hair-brained strategy&lt;/a&gt; because they can’t think beyond markets and antitrust.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;good-ways-forward&#34;&gt;Good ways forward&lt;/h2&gt;
&lt;style&gt;
/* Because CSS sucks. Hack courtesy of https://css-tricks.com/NetMag/FluidWidthVideo/Article-FluidWidthVideo.php */

.videoWrapper {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 */
  padding-top: 25px;
  height: 0;
}

.videoWrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

figcaption {
  margin-top: 1em;
}
&lt;/style&gt;
&lt;figure&gt;
&lt;div class=&#39;videoWrapper&#39;&gt;
  &lt;iframe sandbox=&#34;allow-same-origin allow-scripts&#34; src=&#34;https://video.lqdn.fr/videos/embed/70f2128c-8c06-4cc4-8a5a-bf77e765c8fd?title=0&amp;warningTitle=0&#34; frameborder=&#34;0&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;figcaption&gt;Watch my speech at the European Parliament in which I summarise the problem and suggest a solution.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Support current non-commercial federated alternatives (the “fediverse”), where there already are &lt;a href=&#34;https://switching.software/list/fediverse/&#34;&gt;viable alternatives to Twitter, YouTube, and Instagram&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support current non-commercial and single-tenant alternatives for individuals like &lt;a href=&#34;https://owncast.online&#34;&gt;Owncast&lt;/a&gt; for online video streaming.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support the &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;research and development&lt;/a&gt; of the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; – a non-commercial, human-scale web made up of spaces owned and controlled by individuals, not corporations.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://small-tech.org/videos/&#34;&gt;Watch the recordings of Small is Beautiful&lt;/a&gt;, our monthly live stream at Small Technology Foundation, to find out more about the current state of my work on the Small Web.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hating Facebook is all well and good but please let’s not forget that this is not just about Facebook. It’s about people farming in general.&lt;/p&gt;
&lt;p&gt;If Facebook goes away tomorrow but another Facebook takes its place, we won’t have won anything.&lt;/p&gt;
&lt;p&gt;So please let’s make sure we understand the differences between the various alternatives and pick the ones that will result in meaningful progress in protecting our personhood and democracy.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Hint: look at the &lt;em&gt;intent&lt;/em&gt; behind an organisation/project. Is it to make a billion dollars or to protect human rights and democracy? And yes, no matter what the capitalists tell you, the two goals &lt;em&gt;are&lt;/em&gt; diametrically opposed and mutually exclusive.)&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>NodeKit Update</title>
      <link>https://ar.al/2022/01/26/nodekit-update/</link>
      <pubDate>Wed, 26 Jan 2022 11:40:33 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/01/26/nodekit-update/</guid>
      <description>&lt;figure&gt;
  &lt;div class=&#39;video&#39; style=&#34;padding:56.25% 0 0 0;position:relative;&#34;&gt;&lt;iframe src=&#34;https://player.vimeo.com/video/670175367?&amp;amp;badge=0&amp;amp;autopause=0&amp;amp;player_id=0&amp;amp;app_id=58479&#34; frameborder=&#34;0&#34; allow=&#34;autoplay; fullscreen; picture-in-picture&#34; allowfullscreen style=&#34;position:absolute;top:0;left:0;width:100%;height:100%;&#34; title=&#34;How to send an email&#34;&gt;&lt;/iframe&gt;&lt;/div&gt;
    &lt;figcaption&gt;&lt;p&gt;Since the initial demo of &lt;a href=&#39;https://github.com/small-tech/nodekit&#39;&gt;NodeKit&lt;/a&gt; at &lt;a href=&#39;https://small-tech.org/videos/small-is-beautiful-15/&#39;&gt;last week’s Small Is Beautiful&lt;/a&gt;, we now actually have a nodekit command, the server now does naïve restarts on route changes, and routes are now lazily loaded the first time they’re hit.
&lt;p&gt;I wanted to record this before jumping down the rabbit hole of implementing some form of hot module reloading/replacement using WebSockets and &lt;a href=&#39;https://github.com/patrick-steele-idem/morphdom&#39;&gt;MorphDOM&lt;/a&gt;.&lt;/p&gt;&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>The web0 manifesto</title>
      <link>https://ar.al/2022/01/06/the-web0-manifesto-a-technical-review/</link>
      <pubDate>Thu, 06 Jan 2022 10:55:19 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2022/01/06/the-web0-manifesto-a-technical-review/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2022/01/06/the-web0-manifesto-a-technical-review/./web0-manifesto.png&#34;
         alt=&#34;(Start blackboard with equation) web0 manifesto: web3 = decentralisation &amp;#43; blockchain &amp;#43; NFTs &amp;#43; metaverse, web0 = web3 - blockchain - NFTs - metaverse, web0 = decentralisation. (End blackboard with equation). web0 is the decentralised web. In other words, web0 is web3 without all the corporate right-libertarian Silicon Valley bullshit.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The web0 manifesto: a short and sweet middle finger to web3.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;is-it-covid-or-bullshit-allergy&#34;&gt;Is it Covid or bullshit allergy?&lt;/h2&gt;
&lt;p&gt;Towards the end of 2021, I started noticing more and more &lt;a href=&#34;https://duckduckgo.com/?q=web3&amp;amp;t=ffab&amp;amp;iar=news&amp;amp;ia=news&#34;&gt;“articles” on “web3”&lt;/a&gt; cropping up, each one more grating than the last.&lt;/p&gt;
&lt;p&gt;It was as if &lt;strike&gt;journalists&lt;/strike&gt; public relations hacks&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; were in a race to the death to see who could best parrot the latest right-libertarian techbro bullshit from bullshit press releases ejected out the fetid bowels of bullshit startups in Silicon Valley, the world’s capital of bullshit.&lt;/p&gt;
&lt;p&gt;So, anyway…&lt;/p&gt;
&lt;p&gt;I felt we could all use a little antihistamine – not to mention, a different term we could use to differentiate ourselves from the bullshit – so, over the holidays, I created the &lt;a href=&#34;https://web0.small-web.org&#34;&gt;web0 manifesto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I write this, it has been online for a little under six days and &lt;a href=&#34;https://web0.small-web.org/#signatories&#34;&gt;signed by 639 people, projects, and organisations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The site is entirely hand-rolled, doesn’t use any third-party APIs, and runs on &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;. If you’re interested in some technical trivia, &lt;a href=&#34;https://web0.small-web.org/#trivia&#34;&gt;read the footer&lt;/a&gt;, &lt;a href=&#34;https://web0.small-web.org/privacy&#34;&gt;glance at the privacy policy&lt;/a&gt;, and &lt;a href=&#34;https://github.com/small-tech/web0&#34;&gt;view the source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also, of course, &lt;a href=&#34;https://web0.small-web.org&#34;&gt;sign it.&lt;/a&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;To these fine individuals I say, you can stop worrying: you do not have imposter syndrome; you &lt;em&gt;are&lt;/em&gt; imposters. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to send an email</title>
      <link>https://ar.al/2021/12/20/how-to-send-an-email/</link>
      <pubDate>Mon, 20 Dec 2021 18:07:03 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/12/20/how-to-send-an-email/</guid>
      <description>&lt;figure&gt;
  &lt;div class=&#39;video&#39; style=&#34;padding:56.25% 0 0 0;position:relative;&#34;&gt;&lt;iframe src=&#34;https://player.vimeo.com/video/658683141?h=ffec8ea47b&amp;amp;badge=0&amp;amp;autopause=0&amp;amp;player_id=0&amp;amp;app_id=58479&#34; frameborder=&#34;0&#34; allow=&#34;autoplay; fullscreen; picture-in-picture&#34; allowfullscreen style=&#34;position:absolute;top:0;left:0;width:100%;height:100%;&#34; title=&#34;How to send an email&#34;&gt;&lt;/iframe&gt;&lt;/div&gt;
    &lt;figcaption&gt;&lt;p&gt;How to send an email by speaking SMTP interactively to an email server. (This is how email works under the hood and it’s simpler than you might think. Play the video and follow along.)&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>The Three Laws of Personal Devices</title>
      <link>https://ar.al/2021/12/18/the-three-laws-of-personal-devices/</link>
      <pubDate>Sat, 18 Dec 2021 13:21:15 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/12/18/the-three-laws-of-personal-devices/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://cyborgrights.eu&#34;&gt;The Universal Declaration of Cyborg Rights&lt;/a&gt; states that we extend our selves using digital and networked technologies and that this extended self must be protected under human rights law.&lt;/p&gt;
&lt;p&gt;As the primary means by which we extend ourselves today are through our everyday personal devices – computers, mobile phones, the so-called “Internet of Things” and “smart” homes, cars, etc. – we must enshrine the human rights that pertain to our extended selves within concrete laws that protect personhood in the digital network age.&lt;/p&gt;
&lt;p&gt;I propose we need no more or less than the following three:&lt;/p&gt;
&lt;h2 id=&#34;law-1&#34;&gt;Law 1&lt;/h2&gt;
&lt;p&gt;Your devices must work in your interests and your interests alone.&lt;/p&gt;
&lt;h2 id=&#34;law-2&#34;&gt;Law 2&lt;/h2&gt;
&lt;p&gt;When a feature &lt;em&gt;can&lt;/em&gt; be built so that algorithms and data are kept exclusively on the person’s own device, it &lt;em&gt;must&lt;/em&gt; be built that way.&lt;/p&gt;
&lt;p&gt;If a feature cannot be built in this manner, all data must be end-to-end encrypted and the owner of the device must be the exclusive holder of the private key.&lt;/p&gt;
&lt;h2 id=&#34;law-3&#34;&gt;Law 3&lt;/h2&gt;
&lt;p&gt;The hardware, software, and services must be free and open.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Saying goodbye to an old friend</title>
      <link>https://ar.al/2021/12/16/saying-goodbye-to-an-old-friend/</link>
      <pubDate>Thu, 16 Dec 2021 12:18:14 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/12/16/saying-goodbye-to-an-old-friend/</guid>
      <description>&lt;style&gt;
  /* Optical adjustment, screenshot needs a bit more whitespace at the bottom. */
  figure:first-of-type img {
    background-color: white;
    padding-bottom: 0.65em;
  }
&lt;/style&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/12/16/saying-goodbye-to-an-old-friend/better.png&#34;
         alt=&#34;Screenshot of the Better web site, showing the Better badge in front of clouds. Reads: Better 1996-2021&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;How do you like them apples? We didn’t and poor Better paid the price.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Trillion-dollar corporations are not your friend.&lt;/p&gt;
&lt;p&gt;This isn’t a revelation to me. I’ve been saying it for some time now. And yet, over the years, I’ve taken my share of flack for seeing Apple as &lt;a href=&#34;https://ar.al/notes/apple-vs-google-on-privacy-a-tale-of-absolute-competitive-advantage/&#34;&gt;a viable mainstream stopgap based on its business model.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Well, that position has become untenable ever since Apple &lt;a href=&#34;https://arstechnica.com/tech-policy/2021/08/apple-explains-how-iphones-will-scan-photos-for-child-sexual-abuse-images/&#34;&gt;announced&lt;/a&gt;, &lt;a href=&#34;https://www.lawfareblog.com/bugs-our-pockets-risks-client-side-scanning&#34;&gt;got slammed for&lt;/a&gt;, &lt;a href=&#34;https://www.wsj.com/articles/apple-executive-defends-tools-to-fight-child-porn-acknowledges-privacy-backlash-11628859600&#34;&gt;doubled-down on&lt;/a&gt;, &lt;a href=&#34;https://www.lawfareblog.com/apple-client-side-scanning-takes-pause&#34;&gt;delayed&lt;/a&gt;, and finally, with iOS 15.2, &lt;a href=&#34;https://www.forbes.com/sites/zakdoffman/2021/11/13/apples-billion-iphone-users-shock-imessage-update-after-security-warnings/?sh=37f2954b2a98&#34;&gt;started drip feeding us the changes&lt;/a&gt; in an attempt to &lt;a href=&#34;https://en.wikipedia.org/wiki/Boiling_frog&#34;&gt;slowly boil frogs&lt;/a&gt; as it continues with its plans to implement &lt;a href=&#34;https://ar.al/2021/08/08/apple-is-trying-to-redefine-what-it-means-to-violate-your-privacy-we-must-not-let-it/&#34;&gt;client-side scanning on iPhones, iPads, and Macs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apple didn’t come up with the client-side scanning idea, by the way, &lt;a href=&#34;https://cyberlaw.stanford.edu/blog/2019/10/william-barr-and-winnie-pooh&#34;&gt;William Barr and Trump’s justice department did&lt;/a&gt; (&lt;a href=&#34;http://cyberlaw.stanford.edu/blog/2020/05/client-side-scanning-and-winnie-pooh-redux-plus-some-thoughts-zoom&#34;&gt;also read the follow-up&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The suddenness of this new push is alarming. Also noteworthy is that suddenly the main reason to demonize encryption is CSAM, with terrorism and other ills playing second fiddle. Even as recently as late July 2019, when Barr &lt;a href=&#34;http://cyberlaw.stanford.edu/blog/2019/07/william-barr-despises-your-power-keep-him-snooping-you&#34;&gt;revived&lt;/a&gt; his predecessors’ habit of castigating encrypted service providers, it was drug cartels he invoked. But CSAM is the dominant focus now, suddenly and thoroughly.&lt;/p&gt;
&lt;p&gt;It is beyond question that CSAM is a real and serious problem … It is radioactive, it is illegal everywhere, and no legitimate company wants it on their servers. Nevertheless, this new single-minded focus on CSAM in the revived anti-encryption push feels like an exceedingly cynical move on the part of the U.S. government. Out of the Four Horsemen of the Infocalypse (terrorism, drug trafficking, CSAM, and organized crime), terrorism didn’t work to turn public opinion against encryption, so the government has switched horse(men) midstream.&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;One proposal for enabling law enforcement access is to build a system where the provider … would check content, such as a photo attached to a message, before it’s encrypted and transmitted to another user &amp;ndash; i.e. while the content is on the sender’s device, not traveling through the provider’s server &amp;ndash; to try to figure out whether that content is or might be abusive content such as CSAM.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;a-long-time-coming&#34;&gt;A long time coming&lt;/h2&gt;
&lt;p&gt;My disenchantment with Apple has been a long time coming.&lt;/p&gt;
&lt;p&gt;It hasn’t helped that since we moved to Ireland and set up &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;, &lt;a href=&#34;https://localhost/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/&#34;&gt;we haven’t been able to get Apple to move our developer account over from our old organisation based in the UK to the new one here in Ireland&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, &lt;a href=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/&#34;&gt;we were going to discontinue Better then&lt;/a&gt; as we couldn’t afford to keep two organisations going but &lt;a href=&#34;https://ar.al/2020/01/20/a-happy-ending-to-the-better-blocker-saga/&#34;&gt;we received an offer from Apple to sort things out&lt;/a&gt;. I naïvely took them at their word and announced it as “a happy ending.”&lt;/p&gt;
&lt;p&gt;Right afterwards, Apple went entirely silent on us.&lt;/p&gt;
&lt;p&gt;We haven’t heard a single word from them over the last two years even after we closed down the old organisation (that our Apple developer account is still technically linked to) and even though we contacted their offices in Ireland to follow up.&lt;/p&gt;
&lt;p&gt;So there’s that.&lt;/p&gt;
&lt;p&gt;(Understand that this is something they happily do for large companies. It’s just that &lt;a href=&#34;https://small-tech.org&#34;&gt;a two-person not-for-profit&lt;/a&gt; is next to worthless in their eyes.)&lt;/p&gt;
&lt;p&gt;As I wrote &lt;a href=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/&#34;&gt;at the time&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[T]his is just how life is when you’re dealing with trillion-dollar faceless corporations. It’s just one reason why it’s so important that we fund and develop human-scale &lt;a href=&#34;https://small-tech.org&#34;&gt;small tech&lt;/a&gt; as an alternative to the strangehold of big tech on our lives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, &lt;a href=&#34;https://ar.al/2020/03/25/apple-just-killed-offline-web-apps-while-purporting-to-protect-your-privacy-why-thats-a-bad-thing-and-why-you-should-care/&#34;&gt;they went and killed offline web apps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And then &lt;a href=&#34;https://mastodon.ar.al/@aral/103908353592269604&#34;&gt;they started using your data to enable third parties to target you&lt;/a&gt; (but said it was OK because all the data was on your own device&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;And then &lt;a href=&#34;https://ar.al/2020/04/11/how-apple-and-google-will-cure-covid-19-and-how-you-can-opt-into-it-if-you-want-to-keep-your-job/&#34;&gt;they continued collecting more and more data about us&lt;/a&gt; that we were assured would stay on our own devices unless we wanted to share it.&lt;/p&gt;
&lt;p&gt;And then they reneged on the last bit with &lt;a href=&#34;https://ar.al/2021/08/08/apple-is-trying-to-redefine-what-it-means-to-violate-your-privacy-we-must-not-let-it/&#34;&gt;their plans to implement client-side scanning&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And then I recently had a chilling chat with an ex-Apple employee and realised that the stuff we hear is just the tip of the iceberg. (Hey, Tim Cook, if you say “privacy is a human right” but withhold it from your own employees, does that mean you really don’t believe that it’s a human right or that you don’t believe your employees are human beings?)&lt;/p&gt;
&lt;p&gt;Anyway, none of this makes me want to write another line of code for or spend another cent on the devices of such a company.&lt;/p&gt;
&lt;p&gt;This apple is rotten to its core and I’m done with it.&lt;/p&gt;
&lt;h2 id=&#34;so-what-next&#34;&gt;So, what next?&lt;/h2&gt;
&lt;p&gt;We build the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We &lt;a href=&#34;https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/&#34;&gt;support alternative operating systems&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We encourage and support folks like &lt;a href=&#34;https://pine64.org&#34;&gt;Pine64&lt;/a&gt;, &lt;a href=&#34;https://starlabs.systems&#34;&gt;Starlabs&lt;/a&gt;, &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt;, and &lt;a href=&#34;https://system76.com&#34;&gt;System76&lt;/a&gt; who are building alternatives.&lt;/p&gt;
&lt;p&gt;We envision and create a world where your only alternative is not to be shackled to the whims of fucking trillion-dollar corporations.&lt;/p&gt;
&lt;p&gt;Who’s with me?&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Your own device tracking and profiling you is &lt;strong&gt;not&lt;/strong&gt; OK even if the data never leaves your device if it is used not in your interests but in the interests of a third party. Apple News has been doing that for some time now. Does it matter whether Apple, Inc. has the data if some advertiser can still attempt to influence you based on your current emotional and mental state? No. Either your device works in your interests and your interests alone or it is not &lt;em&gt;your&lt;/em&gt; device. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Comet</title>
      <link>https://ar.al/2021/12/11/comet/</link>
      <pubDate>Sat, 11 Dec 2021 18:06:46 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/12/11/comet/</guid>
      <description>&lt;style&gt;
  /* Tweak: the images have drop shadows that create too much spacing; reduce this. */
  p img {
    margin-bottom: -1em;
  }
&lt;/style&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/12/11/comet/comet.jpg&#34;
         alt=&#34;Illustation of the Little Prince flying through space with a comet he has captured.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Comet. Wallpaper illustation by Margo de Weerdt&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After several months of work, &lt;a href=&#34;https://comet.small-web.org&#34;&gt;Comet version 1.0 is now available to install&lt;/a&gt; on &lt;a href=&#34;https://elementary.io&#34;&gt;elementary OS 6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Comet is one of the artefacts from &lt;a href=&#34;https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/&#34;&gt;my three-month-long elementary OS 6 upgrade adventure&lt;/a&gt; and I will be writing more about the process of creating an app according to the elementary OS guidelines – what worked, what didn’t, and what can be improved – later.&lt;/p&gt;
&lt;p&gt;I am also incorporating the lessons learned, workflows, etc., into &lt;a href=&#34;https://github.com/small-tech/watson&#34;&gt;Watson&lt;/a&gt;, my elementary OS 6 best-practices application template.&lt;/p&gt;
&lt;img src=&#39;comet-128.svg&#39; alt=&#39;Comet logo: a flat illustation of an orange comet with a round yellow centre&#39; style=&#39;width: 128px; margin-left: auto; margin-right: auto; margin-top: 2em; display: block;&#39;&gt;
&lt;h2 id=&#34;features&#34;&gt;Features&lt;/h2&gt;
&lt;h3 id=&#34;distraction-free-interface&#34;&gt;Distraction-free interface&lt;/h3&gt;
&lt;p&gt;Separates your commit message from the comment generated by Git and helps you focus on writing better commit messages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/12/11/comet/https://raw.githubusercontent.com/small-tech/comet-screenshots/main/en/comet-basic-usage.png&#34; alt=&#34;Screenshot of the editor with one half displaying in light style and the other in dark style. The first line of the message reads: “This is the summary line of your Git commit message; make sure it isn’t too long” with the words “too long” highlighted in yellow. The second line of the message reads “You can change the suggested length in the Settings Menu. The Git comment for an initial commit with a new file called a.txt to be committed is displayed in a separate area. There is a Cancel and Commit button on the window.”&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;spell-check&#34;&gt;Spell check&lt;/h3&gt;
&lt;p&gt;Highlights spelling mistakes and offers suggestions. Automatically uses your current system language (you may need to &lt;a href=&#34;https://github.com/small-tech/comet/#about-spell-check&#34;&gt;install the dictionary for your language if it doesn’t come with the system.&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/12/11/comet/https://raw.githubusercontent.com/small-tech/comet-screenshots/main/en/comet-spell-check.png&#34; alt=&#34;Screenshot of the editor with one half displaying in light style and the other in dark style. The first line of the message reads: “This is the summary line of your Git commit message; make sure it isn’t too long” with the words “too long” highlighted in yellow. The second line of the message reads “You can change the suggested length in the Settings Menu. The Git comment for an initial commit with a new file called a.txt to be committed is displayed in a separate area. There is a Cancel and Commit button on the window.”&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;emoji-support&#34;&gt;Emoji support&lt;/h3&gt;
&lt;p&gt;Express yourself with emoji. Heck, it &lt;a href=&#34;https://ar.al/2021/10/31/how-to-count-unicode-glyphs-in-vala-using-gtk/&#34;&gt;can even count emoji properly&lt;/a&gt; (what, you think such things are easy?) 🤪️&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/12/11/comet/https://raw.githubusercontent.com/small-tech/comet-screenshots/main/en/comet-emoji.png&#34; alt=&#34;Screenshot of editor with the message “Press Control + . (period) to insert emoji” and the emoji picker popover showing. The Git comment for an initial commit with a new file called a.txt to be committed is partially visible in a separate area. There is a Cancel button (partially visible) and a Commit button on the window.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;configurable&#34;&gt;Configurable&lt;/h3&gt;
&lt;p&gt;Configure your first line character limit with common defaults or a custom value according to the conventions used by you or your team.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/12/11/comet/https://raw.githubusercontent.com/small-tech/comet-screenshots/main/en/comet-settings.png&#34; alt=&#34;Screenshot of the editor with the Settings Menu open. A numeric stepper control inside it with the label “First line character limit” is set to 50. Underneath it, there are three buttons, labelled “Dogmatic (50)”, “GitHub truncation (72)”, and “GitLab truncation (100)”. In the editor, the message, partly obscured by the Settings Menu, reads “Dogma (n): A settled opinion”. The Git comment for an initial commit with a new file called a.txt to be committed is displayed in a separate area. There is a Cancel and Commit button on the window.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;low-effort&#34;&gt;Low effort&lt;/h3&gt;
&lt;p&gt;Can update your Git configuration for you. Remembers your previous Git commit message editor so it can restore it if you disable Comet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/12/11/comet/https://raw.githubusercontent.com/small-tech/comet-screenshots/main/en/comet-welcome.png&#34; alt=&#34;Screenshot of Welcome Screen. Screen contents: Text: “Comet: Write better Git commit messages.” Button (selected) with greyed-out comet icon and label that reads “Disable Comet: Revert to using your previous editor for Git commit messages.” Button with question mark in speech bubble icon and label that reads “Help. Having trouble? Get help and report issues.” Status message at bottom has green check mark and the text “Comet is enabled as your editor for Git commit messages.&#34;&gt;&lt;/p&gt;
&lt;p&gt;I hope you enjoy it!&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://comet.small-web.org&#34;&gt;Install Comet from comet.small-web.org.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Comet is currently not available on the elementary OS AppCenter due to confusion around AppCenter policies regarding what constitutes a sandboxed app and what doesn’t as well as whether or not apps that are not sandboxed should be allowed. Follow &lt;a href=&#34;https://github.com/elementary/appcenter-reviews/pull/225&#34;&gt;this pull request&lt;/a&gt; and &lt;a href=&#34;&#34;&gt;this issue&lt;/a&gt; for the latest news.&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Ireland’s two-tier health insurance system</title>
      <link>https://ar.al/2021/11/29/irelands-two-tier-health-insurance-system/</link>
      <pubDate>Mon, 29 Nov 2021 16:34:17 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/11/29/irelands-two-tier-health-insurance-system/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/29/irelands-two-tier-health-insurance-system/animal-farm-still.png&#34; alt=&#34;Still from an animated film of George Orwell’s Animal Farm with the words “All animals are equal but some animals are more equal than others” painted on the barn with a pig looking out from behind a window at the top.&#34;&gt;&lt;/p&gt;
&lt;figure style=&#34;width: 100%;&#34;&gt;
  &lt;audio
  style=&#34;width: 100%;&#34;
    controls
    src=&#34;vhi-wmare-waived-waiting-periods-preexisting-conditions-recording-excerpt.mp3&#34;
    &gt;
    &lt;p&gt;Sorry, your browser does not support embedded audio. &lt;a href=&#39;vhi-wmare-waived-waiting-periods-preexisting-conditions-recording-excerpt.mp3&#39;&gt;Download the recording.&lt;/a&gt;&lt;/p&gt;
  &lt;/audio&gt;
  &lt;figcaption&gt;An excerpt from an almost ten-minute phone call with a Vhi corporate advisor. &lt;a href=&#34;#transcript&#34;&gt;(Transcript.)&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;question&#34;&gt;Question&lt;/h2&gt;
&lt;p&gt;How do you get pre-existing conditions and waiting periods waived when getting health insurance from &lt;a href=&#34;https://www.vhi.ie&#34;&gt;Vhi&lt;/a&gt; in Ireland?&lt;/p&gt;
&lt;h2 id=&#34;answer&#34;&gt;Answer:&lt;/h2&gt;
&lt;p&gt;You start working at a multi-billion dollar American Big Tech company like &lt;a href=&#34;https://benefits.vmware.com/ie/health-plans/healthcare/&#34;&gt;VMWare&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;were-all-equal&#34;&gt;We’re all equal…&lt;/h2&gt;
&lt;p&gt;In Ireland, we are told that &lt;a href=&#34;https://www.hia.ie/frequently-asked-questions&#34;&gt;by law everyone has access to the same health insurance plans, including company plans&lt;/a&gt; negotiated by Big Tech and other multinationals.&lt;/p&gt;
&lt;p&gt;In reality, however, we have a two-tier system where the gentry who work at our Silicon Valley corporate overlords don’t have to put up with petty annoyances like waiting periods or pre-existing conditions while the rest of us hoi polloi do.&lt;/p&gt;
&lt;p&gt;So, I can sign up for the same &lt;a href=&#34;https://www.vhi.ie/health-insurance/plandescription?coverset=LIFCPM18&amp;amp;profileId=individuals&#34;&gt;PMI 18 11&lt;/a&gt; plan a VMWare employee can sign up for but with one crucial difference: the VMWare employee doesn’t have pre-existing conditions or waiting periods to worry about as they’re both waived.&lt;/p&gt;
&lt;h2 id=&#34;but-some-are-more-equal-than-others&#34;&gt;…but some are more equal than others.&lt;/h2&gt;
&lt;p&gt;I first noticed this when, overwhelmed by trying to get my head around the sheer number of cryptic company plans, I started procrastinating by &lt;a href=&#34;https://gist.github.com/aral/f5359c5ce48e99c43db3c9a975b05bd4&#34;&gt;researching which companies they belonged to&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using search engines, I was only able to uncover two: PayPal and VMWare. But it was on &lt;a href=&#34;https://benefits.vmware.com/ie/health-plans/healthcare/&#34;&gt;the VMWare healthcare benefits page&lt;/a&gt; (&lt;a href=&#34;https://web.archive.org/web/20211115105559/https://benefits.vmware.com/ie/health-plans/healthcare/&#34;&gt;archive.org backup&lt;/a&gt;)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that I came across the following blurb:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Waiting Periods &amp;amp; Pre-Existing Conditions&lt;/p&gt;
&lt;p&gt;Pre-existing conditions and waiting periods have been waived which provides you and your eligible dependents with coverage from your enrolment under the Company policy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I called Vhi’s group memberships team that deals with corporate accounts to find out how the two of us working on &lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;Small Tech&lt;/a&gt; for the common good at &lt;a href=&#34;https://small-tech.org&#34;&gt;our tiny two-person not-for-profit&lt;/a&gt; could get the same deal as folks working to increase the profits of a multi-billion dollar US Big Tech company.&lt;/p&gt;
&lt;p&gt;The answer, in short, was “you can’t.”&lt;/p&gt;
&lt;p&gt;With apologies to Orwell, I basically learned that we’re all equal but that some of us are more equal than others.&lt;/p&gt;
&lt;p&gt;What follows is a transcript of the relevant parts of that call&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, the most poignant moment of which was hearing the lovely person I was talking to express her resignation that nothing’s perfect and life’s not fair.&lt;/p&gt;
&lt;p&gt;I truly hope, from the bottom of my heart, that we do whatever is in our power to change that. Because it doesn’t have to be that way.&lt;/p&gt;
&lt;p&gt;We created this messed-up world.&lt;/p&gt;
&lt;p&gt;We can create a better one.&lt;/p&gt;
&lt;h2 id=&#34;transcript&#34;&gt;Transcript&lt;/h2&gt;
&lt;h3 id=&#34;me&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;VMware, for example, has waiting periods and pre-existing conditions waived on their plan so how would we add something like that to ours?&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ll be honest, Aral, it wouldn&amp;rsquo;t normally be a concession that we would give to, say, members in general. It may be a concession that we give but it depends really, to be honest, on when we&amp;rsquo;re negotiating with the group, per se, it&amp;rsquo;s not something that we would normally give on an individual basis, to be honest, so in terms of [inaudible] joining&lt;/p&gt;
&lt;h3 id=&#34;me-1&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Right, ’cos I thought you said that by law you&amp;rsquo;re required to give the same terms…&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-1&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;We are required to give you the same offer of cover, which is the element of cover, in terms of, say, whether we give concessions in joining and, even if we have corporates, you know what I mean, we might still have a corporate group scheme… it isn&amp;rsquo;t always a given that you would get concessions unfortunately, Aral.&lt;/p&gt;
&lt;h3 id=&#34;me-2&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Right, but I mean it&amp;rsquo;s not necessarily the same plan then, is it? If, for example, I have to wait – I don&amp;rsquo;t know how long, is it 26 weeks? – but someone at VMware doesn&amp;rsquo;t have to wait 26 weeks… that doesn&amp;rsquo;t really sound like the same plan. Does that make sense?&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-2&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;I hear what you&amp;rsquo;re saying and I do know what you&amp;rsquo;re looking for but unfortunately in terms of, say, our offer like absolutely… um… this is I suppose where we stand; that it&amp;rsquo;s not a concession that we would always give… it&amp;rsquo;s not a concession that is maybe given all the time… I do hear what you’re saying about particularly maybe VMware and maybe that&amp;rsquo;s been an experience, perhaps, I&amp;rsquo;m not sure but in general it&amp;rsquo;s not always a given.&lt;/p&gt;
&lt;h3 id=&#34;me-3&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Okay, so basically, like, if you do want to have no waiting periods you really do need to be working for like a multinational American company of some sort of like a Big Tech company, is that correct?&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-3&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;[pause] I can&amp;rsquo;t say when we might give that, you know what I mean, as I said…&lt;/p&gt;
&lt;h3 id=&#34;me-4&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;[interjecting] Right,so I can’t pay you any amount of money to…&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-4&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;[laughing] No… there&amp;rsquo;s a set price on the policy… we can&amp;rsquo;t, you know what I mean, we&amp;rsquo;re not gonna be changing it up to, you know, that is where you get into the realm of, say, I suppose… I suppose… it’s the opposite of community rating, you might say, you know what I mean. It&amp;rsquo;s risk rating, you know what I mean… and you&amp;rsquo;re paying extra to reduce the risk, you know what I mean, it&amp;rsquo;s not risk rating.&lt;/p&gt;
&lt;h3 id=&#34;me-5&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Right… so, I mean, it&amp;rsquo;s just not even an option for an everyday person?&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-5&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;It isn&amp;rsquo;t.&lt;/p&gt;
&lt;h3 id=&#34;me-6&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-6&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;It isn&amp;rsquo;t an option in terms [inaudible] cover, unfortunately.&lt;/p&gt;
&lt;h3 id=&#34;me-7&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Okay, all right, well that&amp;rsquo;s good to know… that&amp;rsquo;s good to know, alright… well I will… thank you for your time, I&amp;rsquo;ll definitely review the programmes that you have… the various options…&lt;/p&gt;
&lt;p&gt;It is interesting, though, I mean it does kind of create a two-tier system doesn&amp;rsquo;t it? I mean, I guess… are you are you Irish? Do you live in Ireland?&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-7&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;I do live in Ireland, yes [small laugh]&lt;/p&gt;
&lt;h3 id=&#34;me-8&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Yeah, how do you feel?…&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-8&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;And I am aware of the… I’m, well… it&amp;rsquo;s not so much how I feel, you know what I mean, because obviously I just have to respect that I work here. These are the rules that we go by…&lt;/p&gt;
&lt;h3 id=&#34;me-9&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Of course, of course, yeah… but I mean just as a human being, like, how do you feel about that living here? Like that, someone who works for a US corporation has almost more rights…?&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-9&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;[pause]&lt;/p&gt;
&lt;p&gt;I… you know, to be honest, in life there&amp;rsquo;s nothing perfect and there&amp;rsquo;s nothing fair…&lt;/p&gt;
&lt;h3 id=&#34;me-10&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;[laughs] …that’s true…&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-10&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;…and, you know what I mean, that&amp;rsquo;s going to be, you know, it’s “how long is a piece of string?” is the answer to that, you know what I mean… so where do we start and where do we stop?&lt;/p&gt;
&lt;h3 id=&#34;me-11&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Indeed, indeed… well thank you so much for your time… I will review the plans, I guess we&amp;rsquo;ll have to sign up with one of them after all… And, yeah, but thank you, it&amp;rsquo;s been enlightening.&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-11&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;Okay, Aral, nice to speak with you today and thank you for your call.&lt;/p&gt;
&lt;h3 id=&#34;me-12&#34;&gt;Me:&lt;/h3&gt;
&lt;p&gt;Likewise… yep, take care&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-12&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;Have a great day.&lt;/p&gt;
&lt;h3 id=&#34;me-13&#34;&gt;Me&lt;/h3&gt;
&lt;p&gt;You too.&lt;/p&gt;
&lt;h3 id=&#34;vhi-representative-13&#34;&gt;Vhi representative:&lt;/h3&gt;
&lt;p&gt;Bye-bye.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I also have a screenshot ;) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I, of course, informed the Vhi representative that I was taping the call for my records, just like they taping it for their purposes. (And I have the full ~10-minute recording with that in it.) &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>The magic one-line ImageMagick 7 AppImage installer</title>
      <link>https://ar.al/2021/11/24/the-magic-one-line-imagemagick-7-appimage-installer/</link>
      <pubDate>Wed, 24 Nov 2021 15:36:09 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/11/24/the-magic-one-line-imagemagick-7-appimage-installer/</guid>
      <description>&lt;p&gt;To install &lt;a href=&#34;https://imagemagick.org/&#34;&gt;ImageMagick 7&lt;/a&gt; on any distribution that supports &lt;a href=&#34;https://appimage.org/&#34;&gt;AppImage&lt;/a&gt;, copy and paste this one-line script into your favourite shell:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;bash -lic &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;wget -O /tmp/magick https://download.imagemagick.org/ImageMagick/download/binaries/magick &amp;amp;&amp;amp; chmod +x /tmp/magick &amp;amp;&amp;amp; test \$(wget -qO- https://download.imagemagick.org/ImageMagick/download/binaries/digest.rdf | grep &amp;#39;rdf:about=\&amp;#34;magick\&amp;#34;.*&amp;#39; -A6 | sed -rn &amp;#39;s/.*&amp;lt;digest:sha256&amp;gt;(.*?)&amp;lt;\/digest:sha256&amp;gt;/\1/p&amp;#39;) = \$(sha256sum /tmp/magick | sed -r &amp;#39;s/(.*)\s(.*)/\1/&amp;#39;) &amp;amp;&amp;amp; (sudo mv /tmp/magick /usr/local/bin/ &amp;amp;&amp;amp; echo &amp;#39;ImageMagick 7 successfully installed.&amp;#39;) || (rm /tmp/magick &amp;amp;&amp;amp; echo &amp;#39;Installation failed. Security error: message digest verification failed for ImageMagick 7 AppImage binary.&amp;#39;)&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;what-it-does&#34;&gt;What it does&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Runs itself in bash, regardless of what interactive shell its being run on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Downloads the &lt;a href=&#34;https://imagemagick.org/script/download.php#linux&#34;&gt;ImageMagick 7 AppImage binary&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sets the binary’s executable bit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Downloads the ImageMagick 7 &lt;a href=&#34;https://www.techopedia.com/definition/4024/message-digest&#34;&gt;message digests&lt;/a&gt; &lt;a href=&#34;https://download.imagemagick.org/ImageMagick/download/binaries/digest.rdf&#34;&gt;RDF&lt;/a&gt; with the SHA-256 hash of the binary.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“Parses” the fucking RDF&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; using regular expressions to extract the SHA-256 hash.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Uses the &lt;a href=&#34;https://help.ubuntu.com/community/HowToSHA256SUM&#34;&gt;sha256sum&lt;/a&gt; command to calculate the SHA-256 hash of the downloaded binary.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compares the two hashes. If they match, it installs the binary by moving it to the &lt;code&gt;/usr/local/bin&lt;/code&gt; folder. If they don’t match, it removes the downloaded binary. Either way, the script lets you know which action was taken.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;why-it-exists&#34;&gt;Why it exists&lt;/h2&gt;
&lt;p&gt;Because a lot of folks don’t actually verify the hashes of apps that they download to their machines and that’s because doing so is an overly-convoluted and manual process. If we can automate it with a script, we can guarantee that everyone will be verifying their downloads.&lt;/p&gt;
&lt;p&gt;This is not an issue when using app catalogues like the &lt;a href=&#34;https://developer.elementary.io/&#34;&gt;elementary OS AppCenter&lt;/a&gt;, but it is an important security hole for app binaries you download from the web.&lt;/p&gt;
&lt;p&gt;If you have thoughts or suggestions on how to improve the script, please feel free to &lt;a href=&#34;https://gist.github.com/aral/18a10a45e51981241d5d9759f67c8642&#34;&gt;share them on this gist&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;more-image-magic&#34;&gt;More image magic&lt;/h2&gt;
&lt;p&gt;See the post I wrote yesterday titled &lt;a href=&#34;https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/&#34;&gt;How to apply a chroma key using ImageMagick&lt;/a&gt;, which led me to create this script.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The only reason I can think of for using an RDF for this purpose is to make sure that as few people as possible actually verify the signature. It means, among other things, that tools like &lt;code&gt;xmllint&lt;/code&gt; fail. Please, folks, instead of intellectual purity and complexity maximalism, use the simplest thing that can possibly work. People downstream of you will thank you for it.&lt;/p&gt;
&lt;p&gt;What does that mean in practice? Consider having a single file for each hash that just contains the hash. That would be easiest to consume. If you must have a single file for all hashes, consider using JSON. If you can’t do without a certain quota of angular brackets in your life, use XML. RDF? Fucking really? &lt;em&gt;smh&lt;/em&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to apply a chroma key using ImageMagick</title>
      <link>https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/</link>
      <pubDate>Tue, 23 Nov 2021 17:03:16 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/</guid>
      <description>&lt;p&gt;Yesterday, I wrote a short post on the fediverse with &lt;a href=&#34;https://mastodon.ar.al/@aral/107322049470016693&#34;&gt;an overview of how to take a screenshot of an app with a context menu showing in elementary OS, while keeping its alpha channel and drop shadow&lt;/a&gt; using &lt;a href=&#34;https://krita.org/en/&#34;&gt;Krita&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Today, I decided that since I want localised screenshots for &lt;a href=&#34;https://github.com/small-tech/comet&#34;&gt;Comet&lt;/a&gt; in the &lt;a href=&#34;https://developer.elementary.io/&#34;&gt;elementary OS AppCenter&lt;/a&gt;, &lt;a href=&#34;https://github.com/small-tech/comet/issues/17&#34;&gt;I have to automate the screenshotting process&lt;/a&gt; (as I have 5 screenshots per language and, even with the three languages Comet will launch with – English, Turkish, and Dutch – that still means 15 screenshots).&lt;/p&gt;
&lt;p&gt;Automating regular screenshots is easy.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; What’s harder is to automate the screenshot with the context menu I mentioned earlier as that requires the use of a chroma key.&lt;/p&gt;
&lt;p&gt;To automate that, we need to use trusty old &lt;a href=&#34;https://imagemagick.org/&#34;&gt;ImageMagick&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;a-little-image-magic&#34;&gt;A little image magic&lt;/h2&gt;
&lt;p&gt;If you don’t already have it, you can easily install it in elementary OS using &lt;em&gt;apt&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;sudo apt install imagemagick
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, this will give you the legacy version of ImageMagick (version 6.x).&lt;/p&gt;
&lt;p&gt;The easiest way to install the latest version (7.x), is to use &lt;a href=&#34;https://imagemagick.org/script/download.php#linux&#34;&gt;the ImageMagick 7 AppImage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following one-line ImageMagick 7 AppImage installation script will do that for you while automatically verifying the message digest on any Linux distribution and on any shell:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;bash -lic &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;wget -O /tmp/magick https://download.imagemagick.org/ImageMagick/download/binaries/magick &amp;amp;&amp;amp; chmod +x /tmp/magick &amp;amp;&amp;amp; test \$(wget -qO- https://download.imagemagick.org/ImageMagick/download/binaries/digest.rdf | grep &amp;#39;rdf:about=\&amp;#34;magick\&amp;#34;.*&amp;#39; -A6 | sed -rn &amp;#39;s/.*&amp;lt;digest:sha256&amp;gt;(.*?)&amp;lt;\/digest:sha256&amp;gt;/\1/p&amp;#39;) = \$(sha256sum /tmp/magick | sed -r &amp;#39;s/(.*)\s(.*)/\1/&amp;#39;) &amp;amp;&amp;amp; (sudo mv /tmp/magick /usr/local/bin/ &amp;amp;&amp;amp; echo &amp;#39;ImageMagick 7 successfully installed.&amp;#39;) || (rm /tmp/magick &amp;amp;&amp;amp; echo &amp;#39;Installation failed. Security error: message digest verification failed for ImageMagick 7 AppImage binary.&amp;#39;)&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The commands in this post use ImageMagick 7 syntax but you can easily convert them to legacy ImageMagick 6 simply by removing the &lt;code&gt;magick&lt;/code&gt; command at the start. They will then function identically, using the older &lt;code&gt;convert&lt;/code&gt; command that’s part of ImageMagick 6.&lt;/p&gt;
&lt;h2 id=&#34;would-it-be-alright-by-you-if-i-degreenify-you&#34;&gt;Would it be alright by you if I degreenify you?&lt;/h2&gt;
&lt;p&gt;For the purposes of this post, let’s assume we already have the following screenshot of the app in a file called &lt;em&gt;original.png&lt;/em&gt; and we want to apply a chroma key to remove the green background while keeping the lovely soft drop shadow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/original.png&#34; alt=&#34;Screenshot of an app on a green background.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Unlike a green screen in live action video, our background is a single, uniform colour: pure green (&lt;code&gt;#00ff00&lt;/code&gt;). This will simplify things a lot.&lt;/p&gt;
&lt;p&gt;First off, there’s quite a bit of solid green around the area we want to remove, even before we get to the semi-transparent drop shadow that we want to preserve and, of course, to the body of the window itself. So let’s start by trimming off the excess green:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;magick convert original.png -trim +repage trimmed.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives us a smaller canvas to work with, which should speed up future processing also.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/trimmed.png&#34; alt=&#34;The same screenshot as before, with the excess green around the edges trimmed away.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Next, we run &lt;a href=&#34;https://legacy.imagemagick.org/Usage/photos/#green_screen&#34;&gt;a command to remove the background while keeping the transparency&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;magick convert trimmed.png -channel alpha -fx &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;1.0*b - 1.0*g + 1.0&amp;#34;&lt;/span&gt; image-with-alpha-and-colour-spill.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We now have a new image with alpha transparency but the semi-transparent areas have a green tint to them. This is known as colour spill.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/image-with-alpha-and-colour-spill.png&#34; alt=&#34;The same image as before but with the background removed.&#34;&gt;&lt;/p&gt;
&lt;p&gt;To remove the colour spill while maintaining the transparency, we have to do two things.&lt;/p&gt;
&lt;p&gt;First, we extract the alpha channel from this new image:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;magick convert image-with-alpha-and-colour-spill.png -alpha extract alpha-mask.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That gives us the following alpha mask:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/alpha-mask.png&#34; alt=&#34;A grayscale alpha mask showing the outline of the app. The opaque portions of the app are white, the drop shadow is greyscale, and the opaque background is pure white.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now that we have a clean alpha mask and, given we know the background colour, we can use &lt;a href=&#34;https://legacy.imagemagick.org/Usage/masking/#two_background&#34;&gt;a clever little calculation&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to remove the tint&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;magick convert original.png alpha-mask.png -alpha Off -fx &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;v==0 ? 0 : u/v - #00ff00/v + #00ff00&amp;#34;&lt;/span&gt; alpha-mask.png -compose Copy_Opacity -composite final.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that gives us our final image, with a beautiful alpha-channel drop-shadow without any colour spill.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/23/how-to-apply-a-chroma-key-using-imagemagick/final.png&#34; alt=&#34;Screenshot of the app with transparent background and drop shadow.&#34;&gt;&lt;/p&gt;
&lt;p&gt;I hope this helps you should you find yourself needing to do something similar.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I just launch Comet and the elementary OS Screenshot app from the command-line and ask the latter to take a screenshot of the active window with a one-second delay. Then, I use &lt;a href=&#34;https://manpages.ubuntu.com/manpages/trusty/man1/xdotool.1.html&#34;&gt;xdotool&lt;/a&gt; to ensure that Comet is active and has focus before it is captured and close the window after a longer delay.&lt;/p&gt;
&lt;p&gt;It looks something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; screenshot_and_close_comet &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
  flatpak run io.elementary.screenshot --window --delay&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &amp;amp;
  xdotool search --onlyvisible --class comet windowactivate windowfocus
  sleep &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;
  xdotool search --onlyvisible --class comet windowclose
&lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Ensure we have a current build.&lt;/span&gt;
task/build

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Welcome Screen&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# English&lt;/span&gt;
build/org.small_tech.comet &amp;amp;
screenshot_and_close_comet

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Turkish&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;LANGUAGE&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;tr_TR.utf8 build/org.small_tech.comet &amp;amp;
screenshot_and_close_comet

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Dutch&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;LANGUAGE&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;nl_NL.utf8 build/org.small_tech.comet &amp;amp;
screenshot_and_close_comet

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# And so on…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I didn’t have to go into the math this time around but I remember &lt;a href=&#34;https://ar.al/2985/&#34;&gt;diving pretty deep into alpha blending/compositing about a decade ago&lt;/a&gt; while working on one of my iPhone apps.&lt;/p&gt;
&lt;p&gt;If you want to understand the equation, start by reading up on the &lt;a href=&#34;https://legacy.imagemagick.org/script/fx.php&#34;&gt;FX Special Effects Image Operator&lt;/a&gt;. The equation is used for each pixel in the images; &lt;code&gt;u&lt;/code&gt; is the colour value from the first image and &lt;code&gt;v&lt;/code&gt; is the colour value from the second image. What the equation is doing is calculating how much green is present in each pixel and removing it. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Since I know that background colour is pure green, I hardcoded its hexadecimal value in the example to simplify the equation.&lt;/p&gt;
&lt;p&gt;If you don’t know the colour off-hand, you can grab it from the image itself – e.g., from its top-left corner – by substituting &lt;code&gt;u.p{0,0}&lt;/code&gt; in place of the hexadecimal colour value in the equation. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>My three-month-long elementary OS 6 upgrade adventure in three parts. (Part 1: Catts)</title>
      <link>https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/</link>
      <pubDate>Mon, 08 Nov 2021 15:20:41 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/https://github.com/small-tech/catts/raw/master//catts-screenshot.jpg&#34; alt=&#34;Screenshot of Catts in action. The switcher contains three icons: Tasks, AppCenter, and Calculator. AppCenter is selected. The switcher is in dark mode. The wallpaper behind the switcher is an illustration of cats on a roof. One (pink) looks out into the distance, a tiny blue one is about to paw its tail playfully, and a green one watches the scene.&#34;&gt;&lt;/p&gt;
&lt;p&gt;I just upgraded my operating system from &lt;a href=&#34;https://elementary.io&#34;&gt;elementary OS&lt;/a&gt; 5 to 6.&lt;/p&gt;
&lt;p&gt;It only took me three months.&lt;/p&gt;
&lt;h2 id=&#34;the-journey-begins&#34;&gt;The journey begins&lt;/h2&gt;
&lt;p&gt;elementary OS 6 (Odin) was &lt;a href=&#34;https://mastodon.ar.al/@aral/106732671022418813&#34;&gt;officially released on August 10th, 2021&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since there is no auto-update tool, you have to back everything up, wipe your computer, and install the new operating system from scratch. It took me a few days to build up the courage to do so. On August 14th, after publicly documenting &lt;a href=&#34;https://mastodon.ar.al/@aral/106754941938641144&#34;&gt;some&lt;/a&gt; &lt;a href=&#34;https://mastodon.ar.al/@aral/106754991222865001&#34;&gt;of&lt;/a&gt; &lt;a href=&#34;https://mastodon.ar.al/@aral/106755023714802672&#34;&gt;the&lt;/a&gt; &lt;a href=&#34;https://mastodon.ar.al/@aral/106767859217760177&#34;&gt;things&lt;/a&gt; I was backing up, I took the plunge.&lt;/p&gt;
&lt;h2 id=&#34;a-bumpy-start&#34;&gt;A bumpy start&lt;/h2&gt;
&lt;p&gt;I hit my first hurdle almost immediately when I realised that I’d been using a custom task switcher called &lt;a href=&#34;https://github.com/markstory/gala-alt-tab-plus&#34;&gt;gala-alt-tab-plus&lt;/a&gt; that hadn’t been updated for Odin. Speaking with Mark, the maintainer, it didn’t look like it was going to be a trivial update either.&lt;/p&gt;
&lt;p&gt;So I decided to bite the nail on the head and make an Odin-only version called &lt;a href=&#34;https://github.com/small-tech/catts&#34;&gt;Calmer Alt-Tab Task Switcher (Catts)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;mentary-songs-for-mentary-catts&#34;&gt;’Mentary songs for ’mentary Catts&lt;/h2&gt;
&lt;p&gt;I wanted Catts to eventually become the default task manager in elementary OS, not languish as an option for a handful of technically-savvy people &lt;a href=&#34;https://github.com/tom95/gala-alternate-alt-tab&#34;&gt;for over seven years&lt;/a&gt;, like its precursor.&lt;/p&gt;
&lt;p&gt;One way of achieving that might have been to take part in endless bikeshedding in GitHub issues, etc. That’s really not what I’m built for. So, instead, I knew what I needed to do was to &lt;em&gt;show not tell&lt;/em&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make it easy to install (and uninstall)&lt;/strong&gt; so anyone, including the elementary team, could try it out easily.&lt;/p&gt;
&lt;p&gt;This way, even if it wasn’t immediately accepted, people could still install and use it and, if enough people did, it might be included as the default later on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make the design case for why it is better&lt;/strong&gt; than the default task switcher and &lt;em&gt;do so visually&lt;/em&gt; in a manner that is difficult to ignore.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make it easy to adopt.&lt;/strong&gt; Don’t create obstacles to it being incorporated into the OS (so, for example, adhere to coding conventions).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fast forward a couple of days and I had &lt;a href=&#34;https://mastodon.ar.al/@aral/106788620044487865&#34;&gt;a rudimentary version working and ready to test on elementary OS 6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And this is how I announced it in the readme, to cover the points above:&lt;/p&gt;
&lt;blockquote&gt;
&lt;h2 id=&#34;why-catts&#34;&gt;Why Catts?&lt;/h2&gt;
&lt;p&gt;A quick visual demonstration should suffice (if it doesn’t, please read on for the various reasons outlined below.)&lt;/p&gt;
&lt;h3 id=&#34;elementary-os-stock-task-switcher&#34;&gt;Elementary OS stock task switcher:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/https://github.com/small-tech/catts/raw/master/demo/elementary-os-default-task-switcher.gif&#34; alt=&#34;Screen recording of the default elementary OS task switcher: windows animate in and out while switching between apps and the dock is repurposed as the task switcher view and morphs from the dock icons to the &gt;active app icons during the task switching process.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;catts&#34;&gt;Catts:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/11/08/my-three-month-long-elementary-os-6-upgrade-adventure-in-three-parts-part-1-catts/https://github.com/small-tech/catts/raw/master/demo/elementary-os-catts-task-switcher.gif&#34; alt=&#34;Screen recording of the Catts task switcher for elementary OS. It resembles the task switchers found in other operating systems like macOS, Windows, and many other Linux flavours. The active apps are shown as &gt;a row of icons and an indicator shows which app will be active when you release the alt key. The window title of the selected app is also shown. Nothing animates.&#34;&gt;&lt;/p&gt;
&lt;p&gt;In other words, because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt; is a hidden, shortcut gesture for quickly switching between the various windows you have open.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is already a graphically-heavy, slower alternative with the “Show Desktop” (&lt;kbd&gt;⌘&lt;/kbd&gt; + &lt;kbd&gt;↓&lt;/kbd&gt;) gesture that gives you an overview of the windows within a workspace with window previews that &amp;gt;can be used if differentiating windows based on their contents is important.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;In elementary OS, however, the task switcher:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Overloads the dock&lt;/strong&gt; (the dock is transformed to include icons of windows and the icons there used to indicate which window you’re switching to). This breaks the physicality of the dock and overloads its &amp;gt;meaning. That said, due to the amount of other animation going on, willing myself to concentrate on the dock is the only way I can use it at all.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Has excessive motion&lt;/strong&gt; (animates windows backwards or forwards while dimming them in/out every time you press &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt;). Imagine that happening with maximized or half-screen windows &amp;gt;on a 24&amp;quot; monitor. I don’t normally have issues with motion and it makes me feel seasick after a few uses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gets stuck.&lt;/strong&gt; Sometimes it will just get stuck in a state where no window is selected. Pressing &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt; again gets you out of it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is one-way&lt;/strong&gt; (&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt;) doesn’t do anything.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basically, the task switcher in elementary OS is unusable.&lt;/p&gt;
&lt;p&gt;This one, despite its &lt;a href=&#34;https://github.com/small-tech/catts/#limitations&#34;&gt;limitations&lt;/a&gt;, at least fixes the above issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Catts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is calm.&lt;/strong&gt; It does not animate my windows. I don’t want cognitive complexity when I’m fast switching between apps. I want to select the app I want to switch to and switch to it. That’s it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Uses icons.&lt;/strong&gt; There is very little cognitive load to recognising an icon. There’s a reason we use icons of applications in menus, etc., instead of tiny thumbnails of them. The same principles apply here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enables you to tell apart different windows of the same app&lt;/strong&gt; (simply, by displaying the window title in the switcher alongside the icon).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Uses the system colour scheme.&lt;/strong&gt; Love Dark Mode? Catts does too.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I feel elementary OS would be far more usable in general – not to mention more familiar for folks just coming over from macOS or Windows – if we were to &lt;a href=&#34;https://%3Egithub.com/elementary/gala/issues/1232&#34;&gt;replace the default task switcher with Catts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, I’ve opened &lt;a href=&#34;https://github.com/elementary/gala/issues/1232&#34;&gt;a feature request on the elementary OS Gala to do just that&lt;/a&gt;. If you agree, please &lt;a href=&#34;https://github.com/elementary/gala/%3Eissues/1232&#34;&gt;give it a thumbs up&lt;/a&gt; to show your support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;the-happy-ending&#34;&gt;The happy ending?&lt;/h2&gt;
&lt;p&gt;To cut a long story short, Catts was &lt;a href=&#34;https://github.com/elementary/gala/commit/52cbc3cc7b1e566d718fcfd527fb99176a1d829f&#34;&gt;brought into elementary OS&lt;/a&gt; &lt;a href=&#34;https://github.com/elementary/gala/blob/master/src/Widgets/WindowSwitcher.vala&#34;&gt;as the default Window Switcher&lt;/a&gt; &lt;a href=&#34;https://github.com/elementary/gala/commit/df1afe1d53fd4f1f624c5bd5b3db9d0cab868eda&#34;&gt;last week&lt;/a&gt; and should be in November’s operating system updates.&lt;/p&gt;
&lt;p&gt;I want to take this opportunity to thank &lt;a href=&#34;https://github.com/davidmhewitt&#34;&gt;David M. Hewitt&lt;/a&gt; for taking the initiative and his hard work in making this happen. I also want to thank &lt;a href=&#34;https://github.com/cassidyjames/&#34;&gt;Cassidy&lt;/a&gt; and &lt;a href=&#34;https://github.com/danrabbit/&#34;&gt;Dani&lt;/a&gt; for their support and input throughout the process.&lt;/p&gt;
&lt;h2 id=&#34;and-yet&#34;&gt;And yet…&lt;/h2&gt;
&lt;p&gt;So is it time to put Catts to rest?&lt;/p&gt;
&lt;p&gt;Not just yet.&lt;/p&gt;
&lt;p&gt;While I’m very happy to see a version of Catts incorporated into elementary OS, there is one major outstanding issue. The version that is being added to elementary OS &lt;a href=&#34;https://github.com/elementary/gala/pull/1234#issuecomment-915952824&#34;&gt;is inaccessible&lt;/a&gt;. I was able to cobble together &lt;a href=&#34;https://github.com/elementary/gala/pull/1234#issuecomment-916865467&#34;&gt;some basic accessibility for the widget using whatever scant documentation and code I could find&lt;/a&gt; in the GNOME/Gtk/Vala/elementary OS world and the current release of Catts does have that implemented.&lt;/p&gt;
&lt;p&gt;However, &lt;a href=&#34;https://github.com/elementary/gala/pull/1234#issuecomment-930397772&#34;&gt;the version being merged into the OS will be entirely inaccessible&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, if you need an (even if imperfectly) accessible task switcher, please still keep using Catts until the elementary folks manage to implement it properly in Gala.&lt;/p&gt;
&lt;p&gt;I’ve opened an issue to track this here: &lt;a href=&#34;https://github.com/elementary/gala/issues/1301&#34;&gt;https://github.com/elementary/gala/issues/1301&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’ve also opened a discussion in the elementary OS Human Interface Guidelines asking for accessibility/inclusivity to be adopted as a core tenet of the operating system and for both the operating system itself and the developer documentation to be updated accordingly. Please feel free to share your thoughts on that here: &lt;a href=&#34;https://github.com/elementary/hig/discussions/51&#34;&gt;https://github.com/elementary/hig/discussions/51&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once the task switcher in Vala is accessible, I will most likely retire Catts.&lt;/p&gt;
&lt;h2 id=&#34;but-thats-not-the-end-of-the-story&#34;&gt;But that’s not the end of the story&lt;/h2&gt;
&lt;p&gt;Three months? Surely, Catts didn’t take three months to build (it didn’t). &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I took about a month off after two years of the pandemic to go see our parents right after we got our vaccinations. It took me another few weeks&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; to build out two other projects as the task switcher wasn’t the only thing that had issues.&lt;/p&gt;
&lt;p&gt;You see, I had this little git commit message editor called &lt;a href=&#34;https://flathub.org/apps/details/org.small_tech.Gnomit&#34;&gt;Gnomit&lt;/a&gt; I wrote that I loved using everyday. With elementary OS 6, it stopped displaying correctly in dark mode. So, of course, this bugged me (because, hello, welcome to my life) so I decided to rewrite it specifically for elementary OS and to do so “properly” in &lt;a href=&#34;https://wiki.gnome.org/Projects/Vala&#34;&gt;Vala&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to get a feel for &lt;a href=&#34;https://docs.elementary.io/develop/&#34;&gt;the recommended development process&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I worked through the developer documentation, I saw there was a huge amount of boilerplate that needs to be implemented for every elementary OS app in order to adhere to the elementary OS &lt;a href=&#34;https://docs.elementary.io/develop/&#34;&gt;Human Interface Guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So I decided to create a best-practices application template for elementary OS called &lt;a href=&#34;https://github.com/small-tech/watson&#34;&gt;Watson&lt;/a&gt; and to use that to create my new git commit message editor, Comet.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I’ll cover Watson and Comet in Parts 2 and 3.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And we were in the process of finding a new place to live and moving to a new city throughout all this so it did all take longer than it otherwise could have. But that’s just life, I guess. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I wrote the original in &lt;a href=&#34;https://gjs.guide/&#34;&gt;GJS&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This, kids, is what we call “yak shaving.” &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to count Unicode glyphs in Vala using Gtk</title>
      <link>https://ar.al/2021/10/31/how-to-count-unicode-glyphs-in-vala-using-gtk/</link>
      <pubDate>Sun, 31 Oct 2021 21:26:17 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/10/31/how-to-count-unicode-glyphs-in-vala-using-gtk/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/10/31/how-to-count-unicode-glyphs-in-vala-using-gtk//2021/10/31/how-to-count-unicode-glyphs-in-vala-using-gtk/halloween.jpg&#34; alt=&#34;Photo of pumpkin and three ghost figurines.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Want to try something really scary this Halloween? Try counting the characters in a string in &lt;a href=&#34;https://wiki.gnome.org/Projects/Vala&#34;&gt;Vala&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, how many characters do you think there are in the following string?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;var zombie &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;🧟‍♀️️&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One?&lt;/p&gt;
&lt;p&gt;You’re right!&lt;/p&gt;
&lt;p&gt;So how would you go about getting this result in Vala? Use &lt;code&gt;zombie.length&lt;/code&gt;, did I hear you say?&lt;/p&gt;
&lt;p&gt;Oh, you poor, dear, naïve, sweet thing…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;🧟‍♀️️&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// 13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What’s that? What about &lt;code&gt;char_count ()&lt;/code&gt;?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;🧟‍♀️️&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;char_count&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// 4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What do you think this is? Some toy language and standard library like Swift where you can just do &lt;code&gt;zombie.characters.count&lt;/code&gt; and get the right answer (1) straight away?&lt;/p&gt;
&lt;p&gt;This is Vala and Gtk, motherfucker, get ready for some pain!&lt;/p&gt;
&lt;h2 id=&#34;so-which-is-correct-13-4-or-1&#34;&gt;So which is correct? 13, 4, or 1?&lt;/h2&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;p&gt;(They’re all “correct” based on what question you happen to be asking &lt;a href=&#34;https://manishearth.github.io/blog/2017/01/14/stop-ascribing-meaning-to-unicode-code-points/#indexing-by-code-point&#34;&gt;and what you need to do with the answer&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://apps.timwhitlock.info/unicode/inspect?s=%F0%9F%A7%9F%E2%80%8D%E2%99%80%EF%B8%8F%EF%B8%8F&#34;&gt;If we break it down&lt;/a&gt;, a female zombie (🧟‍♀️️) is, in &lt;a href=&#34;https://en.wikipedia.org/wiki/UTF-8&#34;&gt;UTF-8&lt;/a&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;glyph&lt;/td&gt;
&lt;td&gt;as a typographer would call it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;grapheme cluster&lt;/td&gt;
&lt;td&gt;as Unicode calls it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;code points&lt;/td&gt;
&lt;td&gt;🧟️️ (&lt;code&gt;F09FA79F&lt;/code&gt;) + Zero-width Joiner (&lt;code&gt;E2808D&lt;/code&gt;) + ♀ (&lt;code&gt;E29980&lt;/code&gt;) + Variation Selector 16 (&lt;code&gt;EFB88F&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;bytes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;F0 9F A7 9F E2 80 8D E2 99 80 EF B8 8F&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;how-to-count-ha-ha-ha-in-vala&#34;&gt;How to count (ha, ha, ha) in Vala&lt;/h2&gt;
&lt;p&gt;First off, let me tell you, I still don’t know what the equivalent of the Swift one-liner &lt;code&gt;zombie.characters.count&lt;/code&gt; is in Vala/GLib but, &lt;a href=&#34;https://mastodon.ar.al/@aral/107129888881292171&#34;&gt;after several days of banging my head against a wall&lt;/a&gt;, I figured out a way to count glyphs properly using a &lt;a href=&#34;https://valadoc.org/gtk+-3.0/Gtk.TextIter.html&#34;&gt;Gtk.TextIter&lt;/a&gt; with a &lt;a href=&#34;https://valadoc.org/gtk+-3.0/Gtk.TextBuffer.html&#34;&gt;Gtk.TextBuffer&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#902000&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;glyph_count&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;string text&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
    var text_buffer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextBuffer&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;set_text&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;text&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextIter&lt;/span&gt; glyph_count_iter&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_start_iter&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;out glyph_count_iter&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    var glyphs &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; 0&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(!&lt;/span&gt;glyph_count_iter&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;is_end&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;())&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
        glyphs&lt;span style=&#34;color:#666&#34;&gt;++;&lt;/span&gt;
        glyph_count_iter&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;forward_cursor_position&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;
    &lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;

    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; glyphs&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using this function, you can now do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;glyph_count &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;🧟‍♀️️&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And get the result approved by most human beings who think like human beings and not like computers.&lt;/p&gt;
&lt;h2 id=&#34;iterators-and-cursors-are-your-friend&#34;&gt;Iterators and cursors are your friend&lt;/h2&gt;
&lt;p&gt;When working with Unicode in Vala/Gtk, iterators and cursors are your friend so remember to always work with cursor positions and not characters.&lt;/p&gt;
&lt;p&gt;To add to the mindfuck, you don’t always get a cursor position when you ask for one. Sometimes you get a “character” offset instead.&lt;/p&gt;
&lt;p&gt;For example, the &lt;code&gt;cursor_position&lt;/code&gt; property on a &lt;code&gt;Gtk.TextBuffer&lt;/code&gt; instance isn’t a cursor position, it’s a “character” offset. (Because they hate you and your little dog too.) So you cannot compare the cursor position from a text buffer directly with the count you get from the &lt;code&gt;glyph_count ()&lt;/code&gt; function, above. The former uses Vala “characters” while the latter deals in glyphs (what humans call characters).&lt;/p&gt;
&lt;p&gt;Imagine the following is a string in a &lt;code&gt;Gtk.TextView&lt;/code&gt; and the cursor is at the end of the string:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;123🧟‍♀️️5│
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In that situation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;glyph_count &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// 5
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;cursor_position&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// 9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So we can’t use the &lt;code&gt;cursor_position&lt;/code&gt; property directly when we’re dealing with glyphs.&lt;/p&gt;
&lt;p&gt;What we can do is to create and use an iterator based on the “character” offset stored in the &lt;code&gt;cursor_position&lt;/code&gt; property:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;var cursor_position &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;cursor_position&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;

Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextIter&lt;/span&gt; cursor_position_iter&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
message_view_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_iter_at_offset&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;out cursor_position_iter&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; cursor_position&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, using its &lt;a href=&#34;https://valadoc.org/gtk+-3.0/Gtk.TextIter.compare.html&#34;&gt;compare ()&lt;/a&gt; method we can find out where in the string it is.&lt;/p&gt;
&lt;p&gt;For example, to check if the cursor is within the first line of text as a person types in a &lt;a href=&#34;https://valadoc.org/gtk+-3.0/Gtk.TextView.html&#34;&gt;Gtk.TextView&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;var text_view &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextView&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;
var text_buffer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextBuffer&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;set_text&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;First line\nSecond line&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
text_view&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;set_buffer&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;text_buffer&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

text_view&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;end_user_action&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;connect&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(()&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
  Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextIter&lt;/span&gt; end_of_first_line_iter&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
  text_view&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_iter_at_line&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;out end_of_first_line_iter&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; 0&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
  end_of_first_line_iter&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;forward_to_line_end&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;

  var cursor_position &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;cursor_position&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
  Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextIter&lt;/span&gt; cursor_position_iter&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
  message_view_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_iter_at_offset&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;out cursor_position_iter&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; cursor_position&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;cursor_position_iter&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;compare&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;end_of_first_line_iter&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;=&lt;/span&gt; 0&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
    print &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Cursor is on the first line!\n&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
  &lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, if you want to go to a specific position within a string, get the start iterator and use the &lt;a href=&#34;https://valadoc.org/gtk+-3.0/Gtk.TextIter.forward_cursor_positions.html&#34;&gt;forward_cursor_positions ()&lt;/a&gt; method to move to your desired offset.&lt;/p&gt;
&lt;p&gt;Hopefully, these tips will save you some time should you need to work with Unicode glyphs in Vala/Gtk&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Hope the dents in my head save you from ones in yours.&lt;/p&gt;
&lt;p&gt;Oh, and happy Halloween!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you know of an easier way, &lt;a href=&#34;https://mastodon.ar.al/@aral/107212353977277182&#34;&gt;please do let me know&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Perhaps while making apps for &lt;a href=&#34;https://elementary.io&#34;&gt;elementary OS&lt;/a&gt; &lt;em&gt;(hint, hint!)&lt;/em&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to change the colour of the underline in gspell</title>
      <link>https://ar.al/2021/10/17/how-to-change-the-colour-of-the-underline-in-gspell/</link>
      <pubDate>Tue, 19 Oct 2021 18:00:53 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/10/17/how-to-change-the-colour-of-the-underline-in-gspell/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://gitlab.gnome.org/GNOME/gspell&#34;&gt;gspell&lt;/a&gt; is GNOME’s spell-checking library.&lt;/p&gt;
&lt;p&gt;When it discovers &lt;span style=&#34;text-decoration: underline; text-decoration-style: wavy; text-decoration-color: red;&#34;&gt;misspelld&lt;/span&gt; &lt;span style=&#34;text-decoration: underline; text-decoration-style: wavy; text-decoration-color: red;&#34;&gt;wrds&lt;/span&gt;, it underlines them in dark red.&lt;/p&gt;
&lt;p&gt;(Only, it can’t do wavy underlines so it does solid ones.)&lt;/p&gt;
&lt;p&gt;To change the colour, say to Dracula pink for dark mode, you set &lt;code&gt;gspell_text_view.underline_color = &amp;quot;#ff79c6&amp;quot;;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Haha, just kidding, you can’t.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.gnome.org/GNOME/gspell/-/issues/24&#34;&gt;There’s a 5-year issue open to ask for an option to change the colour.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Being the Henry Ford of library makers, GNOME likely sees this lack of customisability as a feature not a bug (it’s available in any colour as long as it’s dark red).&lt;/p&gt;
&lt;p&gt;That said, it’s both a usability and accessibility issue, so here’s a workaround for wrestling control back from the library.&lt;/p&gt;
&lt;h2 id=&#34;workaround&#34;&gt;Workaround&lt;/h2&gt;
&lt;p&gt;The way I ended up managing to change the colour of the underline in gspell was to apply a tag to the entire text every time the text buffer changes or someone pastes into it.&lt;/p&gt;
&lt;p&gt;The tag sets the underline colour and I make sure the priority of the tag is set to the highest in the buffer’s tag table, thereby making it override the colour that gspell uses.&lt;/p&gt;
&lt;p&gt;Here are the relevant bits from my app, with a pink underline.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; string PINK_UNDERLINE &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;pink-underline&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextView&lt;/span&gt; text_view&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextBuffer&lt;/span&gt; text_buffer&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; Gspell&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextView&lt;/span&gt; g_spell_text_view&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextTag&lt;/span&gt; pink_underline_tag&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;

construct &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
    text_view &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextView&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;
    text_buffer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; text_view&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_buffer&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;

    g_spell_text_view &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;
        Gspell&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextView&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_from_gtk_text_view&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;text_view&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    g_spell_text_view&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;basic_setup&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;

    Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextTag&lt;/span&gt; pink_underline_tag &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextTag&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;PINK_UNDERLINE&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    var pink &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; Gdk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;RGBA&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;();&lt;/span&gt;
    pink&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;parse&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;#ff79c6&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    pink_underline_tag&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;underline_rgba&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; pink&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;tag_table&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;add&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;pink_underline_tag&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;changed&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;connect&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;override_underline_colour&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;paste_done&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;connect&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;override_underline_colour&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Workaround for the underline colour
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// not being configurable in gspell.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#902000&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;override_underline_colour&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
    Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextIter&lt;/span&gt; text_start&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;TextIter&lt;/span&gt; text_end&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_start_iter&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;out text_start&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_end_iter&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;out text_end&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;remove_tag_by_name&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;
        PINK_UNDERLINE&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;
        text_start&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;
        text_end
    &lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;apply_tag&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;
        pink_underline_tag&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;
        text_start&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;
        text_end
    &lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;

    pink_underline_tag&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;set_priority&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;
        text_buffer&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_tag_table&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;().&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_size&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt; 1
    &lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, as a little bonus, if you’re using gspell in your own elementary OS apps, don’t forget that you have to specify it as a dependency both for Meson/Ninja and for Flatpak separately.&lt;/p&gt;
&lt;p&gt;In your &lt;em&gt;meson.build&lt;/em&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;executable&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# …&lt;/span&gt;
    dependencies: &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;
      &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# …&lt;/span&gt;
      dependency&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;gspell-1&amp;#39;&lt;/span&gt;, version: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;gt;=1.8.0&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;,
    &lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;,
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# …&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For Flatpak declare it as a module in your Flatpak manifest. Below is the YAML version I’m using:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;modules&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;- &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;gspell-1&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;config-opts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;- &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;--disable-static&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;- &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;--disable-gtk-doc&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;sources&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;- &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;type&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;archive&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;url&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://download.gnome.org/sources/gspell/1.8/gspell-1.8.3.tar.xz&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;sha256&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;5ae514dd0216be069176accf6d0049d6a01cfa6a50df4bc06be85f7080b62de8&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;cleanup&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;- &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/bin&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>How to disable Gtk-Message warnings in your app</title>
      <link>https://ar.al/2021/10/15/how-to-disable-gtk-message-warnings-in-your-app/</link>
      <pubDate>Fri, 15 Oct 2021 21:11:56 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/10/15/how-to-disable-gtk-message-warnings-in-your-app/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/10/15/how-to-disable-gtk-message-warnings-in-your-app/./rube-goldberg-machine.jpg&#34; alt=&#34;A Rube-Goldberg machine: the tears of a woman peeling onions is funnelled into a pie that tips some scales that activate a blower that then lead to a number of other things (the end result is not shown in the detail).&#34;&gt;&lt;/p&gt;
&lt;p&gt;So every elementary OS app at the moment, if you start it from Terminal, welcomes you with a variation of the following salutation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Gtk-Message: … : Failed to load module &amp;#34;canberra-gtk-module&amp;#34;
Gtk-Message: … : Failed to load module &amp;#34;canberra-gtk-module&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Isn’t that friendly?&lt;/p&gt;
&lt;h2 id=&#34;the-journey-begins&#34;&gt;The journey begins…&lt;/h2&gt;
&lt;p&gt;I initially thought this was &lt;a href=&#34;https://github.com/flatpak/flatpak/issues/3521&#34;&gt;a long-standing issue with Flatpak&lt;/a&gt; (&lt;a href=&#34;https://github.com/flatpak/flatpak/issues/4491&#34;&gt;and I was a shady bitch about it too&lt;/a&gt;) but, thanks to &lt;a href=&#34;https://github.com/flatpak/flatpak/issues/3521#issuecomment-944338059&#34;&gt;a detailed write-up by Simon McVittie&lt;/a&gt;, we now know that it is more likely due to a configuration mismatch between the host OS (in this case elementary OS 6) and the runtime in your Flatpak.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;So just turn off those warnings in your app if they bother you so much, I hear you say.&lt;/p&gt;
&lt;p&gt;Well, reader, &lt;a href=&#34;https://mail.gnome.org/archives/gtk-list/2017-April/msg00006.html&#34;&gt;it’s not that simple&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After hitting my head on a brick wall (also known as GNOME/GTK documentation) for a few hours to no avail, &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gtk/-/issues/4344&#34;&gt;I opened an issue on the GTK tracker&lt;/a&gt;, using it like Discourse as if I’d been raised in a barn like some sort of animal.&lt;/p&gt;
&lt;p&gt;Thanks to Emmanuele Bassi’s hints &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gtk/-/issues/4344#note_1290574&#34;&gt;in his response&lt;/a&gt;, I was able to finally find a way to disable the Gtk-Message warnings.&lt;/p&gt;
&lt;h2 id=&#34;a-happy-ending&#34;&gt;A happy ending&lt;/h2&gt;
&lt;p&gt;All you have to do&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, see, is create a &lt;a href=&#34;https://docs.gtk.org/glib/func.log_set_writer_func.html&#34;&gt;custom log writer function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here’s an excerpt of the relevant bits from the app I’m building in case they help you too:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;Application&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; Gtk&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;Application&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; string flatpak_id&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; bool is_running_as_flatpak&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;

    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;Application&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;

    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#902000&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#06287e&#34;&gt;main&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;string&lt;span style=&#34;color:#666&#34;&gt;[]&lt;/span&gt; commandline_arguments&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
        flatpak_id &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; Environment&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;get_variable&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;FLATPAK_ID&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
        is_running_as_flatpak &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; flatpak_id &lt;span style=&#34;color:#666&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;

        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;is_running_as_flatpak&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
            Log&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;set_writer_func&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;log_writer&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
        &lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;

        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Application &lt;span style=&#34;color:#666&#34;&gt;().&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;run&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;commandline_arguments&lt;span style=&#34;color:#666&#34;&gt;);&lt;/span&gt;
    &lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;

    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; LogWriterOutput &lt;span style=&#34;color:#06287e&#34;&gt;log_writer&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;LogLevelFlags log_level&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;CCode &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;array_length_type &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;gsize&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;)]&lt;/span&gt; LogField&lt;span style=&#34;color:#666&#34;&gt;[]&lt;/span&gt; fields&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; log_level &lt;span style=&#34;color:#666&#34;&gt;==&lt;/span&gt; LogLevelFlags&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;LEVEL_MESSAGE&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; LogWriterOutput&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;HANDLED&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; LogWriterOutput&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;UNHANDLED&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;;&lt;/span&gt;
    &lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;epilogue&#34;&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;Hopefully, knowing the folks at elementary OS, they’ll get to the bottom of the issue so we won’t need this workaround in the near future.&lt;/p&gt;
&lt;p&gt;In the meanwhile, I plan on adding this to &lt;a href=&#34;https://github.com/small-tech/watson&#34;&gt;Watson&lt;/a&gt; – the best-practices application template for elementary OS 6 (Odin) that I’m working on, so that any apps created based on it will silence these warnings by default.&lt;/p&gt;
&lt;p&gt;There is a lot to admire about Gtk+, really there is&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, but sometimes it does feel like you have to construct &lt;a href=&#34;https://en.wikipedia.org/wiki/Rube_Goldberg_machine&#34;&gt;a Rube Goldberg machine&lt;/a&gt; just to do the simplest things.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Also, according to Simon, as apps are ported to GTK4, the problem should go away as “GTK 4 &lt;a href=&#34;https://blog.gtk.org/2018/03/06/input-methods-in-gtk-4/&#34;&gt;no longer supports loading arbitrary modules specified in GTK_MODULES&lt;/a&gt;”. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Sarcasm. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Not sarcasm. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Implementing dark mode in a handful of lines of CSS with CSS filters</title>
      <link>https://ar.al/2021/08/24/implementing-dark-mode-in-a-handful-of-lines-of-css-with-css-filters/</link>
      <pubDate>Tue, 24 Aug 2021 20:37:50 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/08/24/implementing-dark-mode-in-a-handful-of-lines-of-css-with-css-filters/</guid>
      <description>&lt;p&gt;I &lt;em&gt;finally&lt;/em&gt; got round to implementing dark mode for this site (the cobbler’s children have no shoes and all that…)&lt;/p&gt;
&lt;p&gt;Here’s all the CSS I had to add:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;@&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;media&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;prefers-color-scheme&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;dark&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Invert all elements on the body while attempting to not alter the hue substantially. */&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;) &lt;span style=&#34;color:#007020&#34;&gt;hue-rotate&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;deg&lt;/span&gt;);
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Workarounds and optical adjustments. */&lt;/span&gt;

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Firefox workaround: Set the background colour for the html
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;     element separately because, unlike other browsers, Firefox
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;     doesn’t apply the filter to the root element’s background. */&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#111&lt;/span&gt;;
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Do not invert media (revert the invert). */&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;video&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;iframe&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;) &lt;span style=&#34;color:#007020&#34;&gt;hue-rotate&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;deg&lt;/span&gt;);
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Improve contrast on icons. */&lt;/span&gt;
  .&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;icon&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;15&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;) &lt;span style=&#34;color:#007020&#34;&gt;hue-rotate&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;deg&lt;/span&gt;);
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Re-enable code block backgrounds. */&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;pre&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;);
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Improve contrast on list item markers. */&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;::&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;marker&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#666&lt;/span&gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;The real magic happens in the first rule:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;) &lt;span style=&#34;color:#007020&#34;&gt;hue-rotate&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;deg&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I run an &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/filter#invert&#34;&gt;invert filter&lt;/a&gt; alongside a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/filter#hue-rotate&#34;&gt;hue rotate filter&lt;/a&gt; on the body element. That inverts the colours on the page while attempting to keep the hues as similar as possible.&lt;/p&gt;
&lt;p&gt;You might be wondering why I didn’t apply this rule to the root &lt;code&gt;html&lt;/code&gt; element. Thing is, originally, I did&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. However, unlike other browsers, &lt;a href=&#34;https://stackoverflow.com/questions/61246462/css-filterinvert-not-working-with-background-color/61265706#61265706&#34;&gt;Firefox does not apply the invert filter to the background of the root element&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, instead, I have to set the HTML element’s background manually, in the section on workarounds and optical adjustments:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#111&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(Since the background colour of the page is &lt;code&gt;#eee&lt;/code&gt;, I set the dark mode background to its inverse, &lt;code&gt;#111&lt;/code&gt;, which matches what the invert filter does for the body.)&lt;/p&gt;
&lt;p&gt;Next, I re-inverting media like images, videos, and iframes so they appear as originally intended:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;video&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;iframe&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;) &lt;span style=&#34;color:#007020&#34;&gt;hue-rotate&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;deg&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(As you can see, I’m simply running the same rule again to revert the invert.)&lt;/p&gt;
&lt;p&gt;Finally, the rest of the rules are little opticals tweaks, to improve contrast on a few elements like the icons in the header and the backgrounds of code block that don’t fare well with the inversion filter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Improve contrast on icons. */&lt;/span&gt;
.&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;icon&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;15&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;) &lt;span style=&#34;color:#007020&#34;&gt;hue-rotate&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;deg&lt;/span&gt;);
}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Re-enable code block backgrounds. */&lt;/span&gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;pre&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;filter&lt;/span&gt;: &lt;span style=&#34;color:#007020&#34;&gt;invert&lt;/span&gt;(&lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;%&lt;/span&gt;);
}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Improve contrast on list item markers. */&lt;/span&gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;::&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;marker&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#666&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you’re looking to implement dark mode on your web site, &lt;a href=&#34;https://twitter.com/CassidyJames/status/1430263240169193473&#34;&gt;you don’t use shadows/shades to denote hierarchy&lt;/a&gt;, and you don’t have a huge amount of time on your hands to craft a separate stylesheet, CSS filters are your friend.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://mastodon.online/@kieranbarker/106815832025577696&#34;&gt;Kieran Barker&lt;/a&gt;, &lt;a href=&#34;https://infosec.exchange/@varx/106814202424878948&#34;&gt;varx&lt;/a&gt;, &lt;a href=&#34;https://floss.social/@silmathoron/106815596344785819&#34;&gt;Silmathoron&lt;/a&gt;, and others on the fediverse for pointing out the issue with Firefox and suggesting potential fixes. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Omnibus GitLab Let’s Encrypt renewal error and fix</title>
      <link>https://ar.al/2021/08/24/omnibus-gitlab-lets-encrypt-renewal-error-and-fix/</link>
      <pubDate>Tue, 24 Aug 2021 19:39:19 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/08/24/omnibus-gitlab-lets-encrypt-renewal-error-and-fix/</guid>
      <description>&lt;p&gt;I moved &lt;a href=&#34;https://source.small-tech.org&#34;&gt;source.small-tech.org&lt;/a&gt;, our self-hosted GitLab instance, to &lt;a href=&#34;https://eclips.is&#34;&gt;Eclips.is&lt;/a&gt; a few months ago and noticed today that the Let’s Encrypt certificate had failed to renew.&lt;/p&gt;
&lt;p&gt;When I looked on the server, the outdated Let’s Encrypt certificates were in &lt;code&gt;/etc/gitlab/ssl&lt;/code&gt; as expected but, when I looked in the GitLab configuration file (&lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt;), the Let’s Encrypt integration section was entirely commented out.&lt;/p&gt;
&lt;p&gt;Having absolutely no memory of how I created the original certificates, I tried enabling those settings and reconfiguring GitLab (&lt;code&gt;sudo gitlab-ctl reconfigure&lt;/code&gt;) and got the following errors:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;Error executing action &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;run&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt; on resource &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;ruby_block[create certificate for source.small-tech.org]&amp;#39;&lt;/span&gt;

  RuntimeError
  ------------
  &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;source.small-tech.org&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; Validation failed, unable to request certificate

  Cookbook Trace:
  ---------------
  /opt/gitlab/embedded/cookbooks/cache/cookbooks/acme/resources/certificate.rb:111:in &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;block &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt; levels&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; in class_from_file&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;Error executing action `create` on resource &amp;#39;&lt;/span&gt;acme_certificate&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;staging&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;  RuntimeError
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;  ------------
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;  ruby_block[create certificate for source.small-tech.org] (/opt/gitlab/embedded/cookbooks/cache/cookbooks/acme/resources/certificate.rb line 108) had an error: RuntimeError: [source.small-tech.org] Validation failed, unable to request certificate
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;  Cookbook Trace:
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;  ---------------
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;  /opt/gitlab/embedded/cookbooks/cache/cookbooks/acme/resources/certificate.rb:111:in `block (3 levels) in class_from_file&amp;#39;&lt;/span&gt;

Error executing action &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;create&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt; on resource &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;letsencrypt_certificate[source.small-tech.org]&amp;#39;&lt;/span&gt;

    RuntimeError
    ------------
    acme_certificate&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;staging&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;/opt/gitlab/embedded/cookbooks/cache/cookbooks/letsencrypt/resources/certificate.rb line 26&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; had an error: RuntimeError: ruby_block&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;create certificate &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; source.small-tech.org&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;/opt/gitlab/embedded/cookbooks/cache/cookbooks/acme/resources/certificate.rb line 108&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; had an error: RuntimeError: &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;source.small-tech.org&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; Validation failed, unable to request certificate

    Cookbook Trace:
    ---------------
    /opt/gitlab/embedded/cookbooks/cache/cookbooks/acme/resources/certificate.rb:111:in &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;block &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;3&lt;/span&gt; levels&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; in class_from_file&lt;span style=&#34;&#34;&gt;&amp;#39;&lt;/span&gt;

There was an error running gitlab-ctl reconfigure:

letsencrypt_certificate&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;source.small-tech.org&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;letsencrypt::http_authorization line 6&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; had an error: RuntimeError: acme_certificate&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;staging&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;/opt/gitlab/embedded/cookbooks/cache/cookbooks/letsencrypt/resources/certificate.rb line 26&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; had an error: RuntimeError: ruby_block&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;create certificate &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; source.small-tech.org&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;/opt/gitlab/embedded/cookbooks/cache/cookbooks/acme/resources/certificate.rb line 108&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; had an error: RuntimeError: &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;source.small-tech.org&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; Validation failed, unable to request certificate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;the-fix&#34;&gt;The fix&lt;/h2&gt;
&lt;p&gt;Going by &lt;a href=&#34;https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3843&#34;&gt;what worked for someone else&lt;/a&gt;, I did the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Deleted the &lt;code&gt;/etc/gitlab/ssl&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enabled the following two properties in &lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt; under the Let’s Encrypt integration section:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;letsencrypt&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;enable&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;true&lt;/span&gt;
letsencrypt&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;auto_renew&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reconfigured GitLab:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;sudo gitlab-ctl reconfigure
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that seemed to fix things.&lt;/p&gt;
&lt;p&gt;Documenting it here in hopes it might help somebody else (e.g., future me) ;)&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Key Mapper: a visual tool for remapping keys (and more) on Linux</title>
      <link>https://ar.al/2021/08/16/key-mapper-a-visual-tool-for-remapping-keys-and-more-on-linux/</link>
      <pubDate>Mon, 16 Aug 2021 11:07:04 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/08/16/key-mapper-a-visual-tool-for-remapping-keys-and-more-on-linux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/08/16/key-mapper-a-visual-tool-for-remapping-keys-and-more-on-linux/./key-mapper.png&#34;
         alt=&#34;Screenshot of the Key Mapper application running in dark mode. The Device selection box reads Topre Corporation Realforce 87. The interface has buttons for Restore Defaults, Apply, Copy, New, Delete. On the left-hand side form, a selection box labelled Preset has Typography selected. Under that, a rename text box is empty (with an action button next to it). Finally, there’s an Autoload switch that’s on. On the right-hand side, there is a two column table, headings: key, mapping. There are four mappings: (key) ISO Level3 Shift &amp;#43; bracketLeft → (mapping) ISO_Level3_Shift &amp;#43; Shift_R &amp;#43; v, ISO Level3 Shift &amp;#43; bracketRight → (mapping) ISO_Level3_Shift &amp;#43; Shift_R &amp;#43; b, (key) ISO Level Shift &amp;#43; semicolon → (mapping) ISO_Level3_Shift &amp;#43; v, (key) ISO Level Shift &amp;#43; apostrophe → (mapping) ISO_Level3_Shift &amp;#43; b&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Mapping keys has never been easier in Linux.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’m a bit particular when it comes to typing.&lt;/p&gt;
&lt;p&gt;Specifically, I like to use &lt;a href=&#34;https://ar.al/2018/07/18/typographical-typing-habits-for-linux/&#34;&gt;good typographical habits&lt;/a&gt; whenever possible.&lt;/p&gt;
&lt;p&gt;And, I use an external keyboard with a physical ANSI US Keyboard layout set to UK Macintosh layout on my Linux laptop&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. So, I need to &lt;a href=&#34;https://ar.al/2019/03/12/reclaiming-your-tilde-and-backtick-with-mac-uk-layout-on-an-ansi-us-keyboard/&#34;&gt;reclaim my backtick/tilde key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Previously, I used a combination of &lt;code&gt;xmodmap&lt;/code&gt;, &lt;code&gt;xev&lt;/code&gt;, and a &lt;em&gt;.desktop&lt;/em&gt; in &lt;em&gt;autostart&lt;/em&gt; to handle the backtick/tilde issue – which was about as fun as it sounds to figure out and implement – and the standard &lt;a href=&#34;https://en.wikipedia.org/wiki/AltGr_key&#34;&gt;Alt Graph&lt;/a&gt; modifiers for things like typographical quotes. And while the &lt;kbd&gt;AltGr&lt;/kbd&gt; modifiers are great and all, they weren’t entirely ergonomic.&lt;/p&gt;
&lt;p&gt;When I was on a Mac, I used an awesome tool called &lt;a href=&#34;https://karabiner-elements.pqrs.org/&#34;&gt;Karabiner-Elements&lt;/a&gt; to handle all this with a minimum of fuss.&lt;/p&gt;
&lt;p&gt;Surely it shouldn’t be this hard to remap your keys in Linux in 2021! Well, thanks to an app I just discovered called (wait for it) &lt;a href=&#34;https://github.com/sezanzeb/key-mapper&#34;&gt;Key Mapper&lt;/a&gt;, it’s not.&lt;/p&gt;
&lt;h2 id=&#34;mapping-keys-for-fun-and-pedantry&#34;&gt;Mapping keys for fun and pedantry&lt;/h2&gt;
&lt;p&gt;Using Key Mapper is fairly straightforward.&lt;/p&gt;
&lt;p&gt;Once you’ve installed&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and launched it, you’re greeted with a simple interface where you can select the device&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; you want to create mappings for.&lt;/p&gt;
&lt;p&gt;From there, you can create a new set of mappings, give them a name (confusingly in a separate text box called rename) and choose whether they should auto load.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Finally, you create the mappings themselves by first pressing the key or key combination you want to map and then entering the codes of the key(s) you want them to map to.&lt;/p&gt;
&lt;p&gt;Note that you are mapping keys to keys, not keys to characters. The distinction is important. So, for example, to map you’re mapping &lt;kbd&gt;AltGr&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt; to &lt;kbd&gt;AltGr&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;v&lt;/kbd&gt;, not &lt;kbd&gt;AltGr&lt;/kbd&gt; + &lt;kbd&gt;[&lt;/kbd&gt; to &lt;code&gt;‘&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Or, more precisely, using the key names used by Key Mapper, you’re mapping &lt;code&gt;ISO Level3 Shift + bracketleft&lt;/code&gt; to &lt;code&gt;ISO_Level_3_Shift + Shift_R + v&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;niggles&#34;&gt;Niggles&lt;/h2&gt;
&lt;p&gt;Key Mapper is great but it’s not perfect. Here are a few niggles I’ve noticed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There is a noticable delay of about a second when a mapping fires. This could get annoying fast (and wasn’t an issue with the manual way I was doing mappings earlier).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The mappings didn’t survive suspend. It remains to be seen if that was a one-off or if it keeps happening.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There doesn’t seem to be a way to use a preset from one device on another. Case in point: I wanted to use the same mappings on my external keyboard and on the laptop keyboard so I had to create them twice. Not a huge problem for a handful of mappings but it could be more of a headache for larger lists.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;a-great-start&#34;&gt;A great start&lt;/h2&gt;
&lt;p&gt;Having a graphical key mapper in Linux is great. Here’s hoping that some of the niggles are not facts of life and can be improved upon with time.&lt;/p&gt;
&lt;p&gt;Worst case scenario, I’ll have to hunt down how I was doing things in the past and reimplement that on my new elementary OS 6 installation.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Hey, don’t judge me! &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I just grabbed the &lt;code&gt;.deb&lt;/code&gt; file &lt;a href=&#34;https://github.com/sezanzeb/key-mapper/releases&#34;&gt;from their releases page&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You’re not limited to keyboards; you can remap keys on mice and gamepads. Key Mapper also states that it works on X11 and Wayland, although I’ve only tested it under X11. And, though I haven’t used them yet, you can also use macros. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In my limited testing so far, auto-loading seems to survive sleep for me but it did fail once after a suspend. Getting mappings to stick was also an issue with my homebrew system previously and I don’t remember how I finally managed to make it work reliably at both sign in and after sleep/suspend. I may have to hunt down my backup configuration files or re-read my blog posts that I linked above if this becomes a headache. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I plan on writing a comprehensive post on elementary OS once I’ve got my laptop back to running the way I want it to under version 6/Odin. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Apple is trying to redefine what it means to violate your privacy. We must not let it.</title>
      <link>https://ar.al/2021/08/08/apple-is-trying-to-redefine-what-it-means-to-violate-your-privacy-we-must-not-let-it/</link>
      <pubDate>Sun, 08 Aug 2021 16:36:02 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/08/08/apple-is-trying-to-redefine-what-it-means-to-violate-your-privacy-we-must-not-let-it/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/08/08/apple-is-trying-to-redefine-what-it-means-to-violate-your-privacy-we-must-not-let-it/screeching-voice-of-the-minority.png&#34;
         alt=&#34;Illustration of white man wearing black t-shirt that has a frowning face iPhone and the words “the screeching voice of the minority.”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screeching for freedom. Illustration by Jérémie Fontana.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://ar.al/831/&#34;&gt;I was there when Steve Jobs unveiled the original iPhone&lt;/a&gt; (ironically, I gave a talk on Flash there right after his keynote). I &lt;a href=&#34;https://ar.al/2401/&#34;&gt;started&lt;/a&gt; &lt;a href=&#34;https://ar.al/3074/&#34;&gt;making&lt;/a&gt; &lt;a href=&#34;https://better.fyi&#34;&gt;apps&lt;/a&gt; for iOS from day one, learned Swift while it was still in alpha and updated my code with every new version. My work has been featured by Apple on its App Store and our app, &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;, has maintained glowing reviews and &lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1080964978&#34;&gt;a 4.7 star rating&lt;/a&gt; there for years.&lt;/p&gt;
&lt;p&gt;If Apple goes ahead with &lt;a href=&#34;https://www.apple.com/child-safety/&#34;&gt;its plans&lt;/a&gt; to have your devices violate your trust and work against your interests, I will not write another line of code for their platforms ever again.&lt;/p&gt;
&lt;h2 id=&#34;redefining-what-privacy-means&#34;&gt;Redefining what privacy means&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Privacy is the fundamental human right to choose what you share with others and what you keep to yourself.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What Apple is doing with its plans to scan content on your own devices and notify third parties is an attempt to redefine what it means to violate your privacy.&lt;/p&gt;
&lt;p&gt;It doesn’t matter where your privacy gets violated (on your own device or in the cloud). It matters that your privacy is being violated. &lt;strong&gt;The violation of privacy isn’t in the scanning of the content, it is in the notification of a third-party based on the scan.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if a third party isn’t notified, if the insight gleaned from the scan is used in the interests of a third party instead of in your interests and according to your wishes, this is also a violation of your privacy.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Violation of privacy by proxy is still violation of privacy.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;apple-are-we-the-baddies&#34;&gt;Apple: are we the baddies?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Without privacy we don&amp;rsquo;t have civil liberties. If we make public the default, then anything we want to keep private has an association of guilt attached.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you were to ask me who is a bigger threat to our human rights today, Google or Apple with these new plans, it’s Apple, without a doubt.&lt;/p&gt;
&lt;p&gt;If you know anything about me or &lt;a href=&#34;https://small-tech.org&#34;&gt;my work in the past decade&lt;/a&gt; you will understand exactly what that means.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/notes/apple-vs-google-on-privacy-a-tale-of-absolute-competitive-advantage/&#34;&gt;I’m as shocked as you are.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I do not say this lightly or take any pleasure whatsoever in saying it.&lt;/p&gt;
&lt;p&gt;I cannot stress enough how important it is that this line does not get crossed.&lt;/p&gt;
&lt;h2 id=&#34;slippery-slope-try-cliff-edge&#34;&gt;Slippery slope? Try cliff edge.&lt;/h2&gt;
&lt;p&gt;If Apple crosses this line, there’s no going back. It will set a dire precedent and shift the borders of personhood irrevocably.&lt;/p&gt;
&lt;p&gt;Google will follow suit.&lt;/p&gt;
&lt;p&gt;Soon any system that doesn’t implement on-device scanning/reporting will be deemed illegal (or carry an association of guilt).&lt;/p&gt;
&lt;p&gt;It won’t be limited to phones and tablets. Your Mac is next. General computing as we know it could cease to exist.&lt;/p&gt;
&lt;p&gt;It won’t be limited to the initial content either. Erdoğan, Putin, Bolsanaro and their brethren will decide the scope.&lt;/p&gt;
&lt;p&gt;Just last week &lt;a href=&#34;https://www.inkl.com/news/hungary-orders-shops-to-cover-up-lgbt-themed-children-s-books?share=WobmByfygzy&#34;&gt;Hungary ordered shops to cover up LGBT-themed children’s books&lt;/a&gt;. What’s betting Orban adores Apple’s new plans? What happens when Márk receives a racy photo from Mátyás? (Oh, and did I mention, in Hungary they will eventually make Apple set the reporting age on iMessage photos so it applies to all under 18s.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mark my words: Apple’s plans to have teenagers’ phones snitch to their parents will result in honour killings in places like Turkey. Women will get murdered because of this. Tim Cook, you will have the blood of queer people on your hands. I truly hope you reconsider starting down this dangerous path.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is not a slippery slope. &lt;a href=&#34;https://twitter.com/SarahJamieLewis/status/1423403656733290496&#34;&gt;It’s a cliff edge.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;beyond-sorry&#34;&gt;Beyond sorry&lt;/h2&gt;
&lt;p&gt;Apple backing down on this won’t be enough.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; To maintain trust, it must make a contractual commitment that the data on your device is yours and yours alone and that Apple’s algorithms can only analyse it as permitted by you and only in your interests.&lt;/p&gt;
&lt;p&gt;This is a greater struggle &lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;to protect personhood in the digital network age.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;Today, we extend ourselves with technology.&lt;/a&gt; Not owning and controlling these aspects of ourselves is a violation of our personhood.&lt;/p&gt;
&lt;h2 id=&#34;protecting-personhood-in-the-digital-network-age&#34;&gt;Protecting personhood in the digital network age&lt;/h2&gt;
&lt;p&gt;When I wrote &lt;a href=&#34;https://cyborgrights.eu/&#34;&gt;The Universal Declaration of Cyborg Rights&lt;/a&gt;, I wanted to get people thinking about the kind of constitutional protections we would need to protect personhood in the digital network age.&lt;/p&gt;
&lt;p&gt;I didn’t think we’d need them so soon. I thought we had more time.&lt;/p&gt;
&lt;p&gt;But where do you even begin to get legislators to understand and act on such a thing in the here and now?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/11/29/the-future-of-internet-regulation-at-the-european-parliament/&#34;&gt;It’s not like I haven’t tried.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-screeching-voices-of-the-minority&#34;&gt;The screeching voices of the minority&lt;/h2&gt;
&lt;p&gt;Almost 5,000 people – security and privacy experts, cryptographers, researchers, professors, legal experts and Apple customers – have signed &lt;a href=&#34;https://appleprivacyletter.com/&#34;&gt;an open letter asking Apple to halt deployment of its content monitoring technology immediately and to issue a statement reaffirming their commitment to end-to-end encryption and privacy as a fundamental human right.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Although Apple hasn’t responded to us yet, it did circulate a memo internally calling us &lt;a href=&#34;https://9to5mac.com/2021/08/06/apple-internal-memo-icloud-photo-scanning-concerns/&#34;&gt;“the screeching voices of the minority.”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I urge you to &lt;a href=&#34;https://appleprivacyletter.com/&#34;&gt;join us.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We have a lot to screech about.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is &lt;a href=&#34;https://ar.al/2020/03/25/apple-just-killed-offline-web-apps-while-purporting-to-protect-your-privacy-why-thats-a-bad-thing-and-why-you-should-care/#but-apple-cares-about-your-privacy&#34;&gt;what Apple News has been doing for some time&lt;/a&gt;). Apple profiles you on your own device and enables advertisers to target you and attempt to manipulate your behaviour and emotional state for profit. Apple says that this does not violate your privacy. It absolutely violates your privacy by any meaningful definition of what privacy is.&lt;/p&gt;
&lt;p&gt;Note that this is &lt;a href=&#34;https://basicattentiontoken.org/&#34;&gt;also what the Brave browser does&lt;/a&gt;. Does Brave violate your privacy? 100%! &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Whether or not Apple bows down to pressure on this and reverses course, it offers you precious insight into how they think.&lt;/p&gt;
&lt;p&gt;It’s their phone, not yours.&lt;/p&gt;
&lt;p&gt;Tim Apple is your daddy and as long as you live under his roof, you live under his rules. And he’s just made it clear he can enter your room whenever he likes and search your drawers.&lt;/p&gt;
&lt;p&gt;It might be time to think about moving out.&lt;/p&gt;
&lt;p&gt;Problem is, where do you go? Do you move in with creepy uncle Google next door? You’re screwed either way.&lt;/p&gt;
&lt;p&gt;Oh and remember that your banking app only works on iOS and Android…&lt;/p&gt;
&lt;p&gt;I’m seeing people say “just don’t use an iPhone.” It’s not that simple when everyday things like financial apps with two-factor authentication are locked into the two main platforms.&lt;/p&gt;
&lt;p&gt;We need legislation to ensure critical services use open standards so you can use your &lt;a href=&#34;https://www.pine64.org/pinephone/&#34;&gt;PinePhone&lt;/a&gt; to buy lunch in the future.&lt;/p&gt;
&lt;p&gt;It’s shocking how easily some folks jump to the technological equivalent of “just go live in a cave” as the solution. No, that’s not an acceptable alternative. We deserve to partake in modern life without sacrificing our human rights.&lt;/p&gt;
&lt;p&gt;It’s victim blaming to tell everyday people they’re at fault for using one of the two main tech platforms instead of an (as of yet inaccessible) alternative. I have two PinePhones and my room overflows with open hardware. No, I don’t blame you for using an iPhone or an Android device. You’re the victim here.&lt;/p&gt;
&lt;p&gt;Blame the actual culprits: clueless legislators/policymakers who allow these monopolies to continue and fail to protect our human rights. Blame Big Tech and those who enable it. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Fish shell</title>
      <link>https://ar.al/2021/07/25/fish-shell/</link>
      <pubDate>Sun, 25 Jul 2021 13:27:53 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/07/25/fish-shell/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/07/25/fish-shell/fish-shell-logo-ascii.svg&#34;
         alt=&#34;An ASCII drawing of a fish&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Fishing for a new shell? (Sorry.)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;beautiful-defaults&#34;&gt;Beautiful defaults&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/videos/ux-talk/&#34;&gt;Good design&lt;/a&gt; isn’t about removing the seams altogether&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;; it’s about having beautiful defaults and layering the seams. And this is something that &lt;a href=&#34;https://fishshell.com/&#34;&gt;Fish shell&lt;/a&gt; absolutely nails.&lt;/p&gt;
&lt;p&gt;To be fair, I have no idea why I didn’t give it a proper shot until yesterday. I guess because Zsh, or more precisely &lt;a href=&#34;https://ohmyz.sh/&#34;&gt;Oh My Zsh&lt;/a&gt;, has been perfectly adequate.&lt;/p&gt;
&lt;p&gt;It’s not until you experience the intelligent auto-suggestions and completions in Fish, however, that you truly realise what you’ve been missing. And that’s before you learn just how easy it is to extend as nearly everything is a function.&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;First off, &lt;a href=&#34;https://fishshell.com/&#34;&gt;download and install Fish shell&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’m running &lt;a href=&#34;https://elementary.io/&#34;&gt;elementary OS&lt;/a&gt; on my &lt;a href=&#34;https://starlabs.systems/&#34;&gt;Star Labs&lt;/a&gt; notebook so I followed &lt;a href=&#34;https://launchpad.net/~fish-shell/+archive/ubuntu/release-3&#34;&gt;the Ubuntu instructions&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;sudo apt-add-repository ppa:fish-shell/release-3
sudo apt update
sudo apt install fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, you can simply type &lt;code&gt;fish&lt;/code&gt; in your current shell to open a Fish shell instance.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/07/25/fish-shell/auto-suggest.png&#34;
         alt=&#34;Screenshot of auto-suggest for &amp;#39;git remote&amp;#39; &amp;#43; tab, with the add subcommand suggestion selected. From the history, the prompt is suggesting completion to &amp;#39;origin git@github.com:small-tech/gills.git&amp;#39; and, underneath, it shows a list of other subcommand suggestions along with their descriptions: add (Adds a new remote), set-branches (Changes the list of branches tracked by a remote), get-url (Retrieves URLs for a remote), set-head (Sets the default branch for a remote), prune (Deletes all stale tracking branches), set-url (Changes URLs for a remote), remove (Removes a remote), show (Shows a remote) …and 2 more rows&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Out of the box, you get the excellent auto-suggest/completion.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I recommend you take a moment to at least skim through the comprehensive &lt;a href=&#34;https://fishshell.com/docs/current/tutorial.html&#34;&gt;tutorial&lt;/a&gt;, &lt;a href=&#34;https://fishshell.com/docs/current/index.html&#34;&gt;documentation&lt;/a&gt;, and &lt;a href=&#34;https://fishshell.com/docs/current/faq.html&#34;&gt;frequently-asked questions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;sprucing-it-up&#34;&gt;Sprucing it up&lt;/h2&gt;
&lt;p&gt;While Fish shell is perfectly usable without customisations, there are a few things I’ve gotten used to having in my shells that I wouldn’t want to do without. These range from the practical to the merely cosmetic.&lt;/p&gt;
&lt;p&gt;The first thing I’d highly recommend you do is install &lt;a href=&#34;https://github.com/jorgebucaran/fisher&#34;&gt;Fisher&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;fisher&#34;&gt;Fisher&lt;/h2&gt;
&lt;p&gt;Fisher is a plugin manager for Fish. It does one thing and does it well.&lt;/p&gt;
&lt;p&gt;You can install it by running the following one-liner (&lt;a href=&#34;https://git.io/fisher&#34;&gt;this is the script that it will run on your machine&lt;/a&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;curl -sL https://git.io/fisher | &lt;span style=&#34;color:#007020&#34;&gt;source&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; fisher install jorgebucaran/fisher
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From there, you can browse the &lt;a href=&#34;https://github.com/jorgebucaran/awsm.fish&#34;&gt;list of Fisher plugins&lt;/a&gt; on &lt;a href=&#34;https://github.com/jorgebucaran/awsm.fish&#34;&gt;awsm.fish&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Following is a brief list of the ones I installed.&lt;/p&gt;
&lt;h2 id=&#34;tide&#34;&gt;Tide&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/IlanCosman/tide&#34;&gt;Tide&lt;/a&gt; is a modern prompt manager for Fish.&lt;/p&gt;
&lt;p&gt;After playing with it a little, &lt;a href=&#34;https://github.com/aral/tide&#34;&gt;I forked it&lt;/a&gt; to customise the Git prompt (I like to have my Git prompt tell me what the status is in words instead of cryptic symbols.)&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/07/25/fish-shell/./custom-tide-git-prompt.png&#34;
         alt=&#34;Screenshot of my prompt. It reads: ~/ar.al/site ❨git❩  master 1 untracked&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A word is worth a thousand symbols. Or something.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Also, note that in the screenshot you can see a count of the files that fall into different git statuses. Normally, this would slow a prompt down but Tide is about as snappy as can be because it is asynchronous so you are never blocked from entering a command while waiting for your prompt to render.&lt;/p&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fisher install IlanCosman/tide
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;gills&#34;&gt;Gills&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/07/25/fish-shell/./gills.png&#34;
         alt=&#34;Screenshot of Gills showing the empty line after the command and the command output&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Fish shell gives you gills (I need help, I know.)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/jorgebucaran/fisher#creating-a-plugin&#34;&gt;Creating a plugin&lt;/a&gt; is so simple in Fish that I’ve already made a very simple one even though I only just started using the shell yesterday.&lt;/p&gt;
&lt;p&gt;The plugin, which I call &lt;a href=&#34;https://github.com/small-tech/gills&#34;&gt;Gills&lt;/a&gt;, is about as simple as it gets: it just adds an empty line after your prompt and before the output of your command to balance the whitespace around them so you can more easily separate your prompts from your command output while skimming your terminal’s scrollback buffer.&lt;/p&gt;
&lt;p&gt;It’s called Gills because it gives your output room to breathe. Get it? (Did I mention I was sorry?)&lt;/p&gt;
&lt;p&gt;Technically, like basically everything else in Fish shell, &lt;a href=&#34;https://zerokspot.com/weblog/2016/01/16/fishy-functions/&#34;&gt;it’s just a function&lt;/a&gt;. In this case, one that handles Fish shell’s &lt;code&gt;fish_postexec&lt;/code&gt; event.&lt;/p&gt;
&lt;p&gt;(It also handles a couple of special cases like &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;pushd&lt;/code&gt;, and &lt;code&gt;popd&lt;/code&gt; that do not produce any output. In those cases, it doesn’t add the additional empty line. If you find any other edge cases, please feel free to &lt;a href=&#34;https://github.com/small-tech/gills/issues&#34;&gt;open an issue&lt;/a&gt; or &lt;a href=&#34;https://github.com/small-tech/gills/pulls&#34;&gt;a pull request&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fisher install small-tech/gills
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;node-vesion-manager-nvm&#34;&gt;Node Vesion Manager (nvm)&lt;/h2&gt;
&lt;p&gt;I work with &lt;a href=&#34;https://nodejs.org&#34;&gt;Node.js&lt;/a&gt; everyday so, prior to Fish shell, I was using &lt;a href=&#34;https://github.com/nvm-sh/nvm&#34;&gt;nvm-sh/nvm&lt;/a&gt; to manage my node versions.&lt;/p&gt;
&lt;p&gt;While you can still use that script in Fish, there is a better option that’s called &lt;a href=&#34;https://github.com/jorgebucaran/nvm.fish&#34;&gt;nvm.fish&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Designed for Fish, this tool helps you manage multiple active versions of Node on a single local environment. Quickly install and switch between runtimes without cluttering your home directory or breaking system-wide scripts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fisher install jorgebucaran/nvm.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From there on, you can use commands like &lt;code&gt;nvm install lts&lt;/code&gt; to quickly install and use different versions of Node.&lt;/p&gt;
&lt;p&gt;nvm.fish also supports &lt;a href=&#34;https://dev.to/kovah/nvmrc-or-node-version-which-one-do-you-prefer-300n&#34;&gt;&lt;code&gt;.node-version&lt;/code&gt; and &lt;code&gt;.nvmrc&lt;/code&gt; files&lt;/a&gt; so pop one of those bad boys into your project directory to automatically have the Node version specified within them available when you switch to your project’s directory in your shell.&lt;/p&gt;
&lt;h2 id=&#34;puffer-fish&#34;&gt;Puffer Fish&lt;/h2&gt;
&lt;p&gt;One feature of Zsh that I use all the time is to navigate back multiple directories simply by adding more dots to my command. So, for example, if I’m in the &lt;code&gt;/a/b/c/d/e/&lt;/code&gt; directory and I want to change to the &lt;code&gt;b&lt;/code&gt; folder, I can simply write &lt;code&gt;cd ....&lt;/code&gt; and Zsh will interpret that as &lt;code&gt;cd ../../../&lt;/code&gt; and save me some typing.&lt;/p&gt;
&lt;p&gt;(Hey, I’m a developer, being lazy is part of the job description.)&lt;/p&gt;
&lt;p&gt;So, anyway &lt;a href=&#34;https://github.com/nickeb96/puffer-fish&#34;&gt;Puffer Fish&lt;/a&gt; enables Fish shell do the same thing. Actually, it’s even better because you get in-place expansion.&lt;/p&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;https://github.com/nickeb96/puffer-fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;z&#34;&gt;z&lt;/h2&gt;
&lt;p&gt;While Puffer Fish makes it easy traverse backwards through a directory stack, &lt;a href=&#34;https://github.com/jethrokuan/z&#34;&gt;z&lt;/a&gt; makes it easy to jump to any (previously visited) directory at any time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;z tracks the directories you visit. With a combination of frequency and recency, it enables you to jump to the directory in mind.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, for example, if I visit &lt;code&gt;~/small-tech/small-web/domain&lt;/code&gt; and then, later, I want to get back there, I can simply type the following from anywhere:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;z domain
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And it’ll pop me back into my project folder.&lt;/p&gt;
&lt;p&gt;I wasn’t sure if I’d end up using it when I first installed it but I find myself using it quite often to quickly switch to the directories of projects I work on often.&lt;/p&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fisher install jethrokuan/z
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;autopair&#34;&gt;Autopair&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jorgebucaran/autopair.fish&#34;&gt;Autopair&lt;/a&gt; automatically matches opening/closing pairs of common punctuation like quotation marks and parentheses.&lt;/p&gt;
&lt;p&gt;It might seem like a small thing, but it’s one of those things that gives you a little jolt of dopamine every time it &lt;em&gt;Just Works™&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fisher install jorgebucaran/autopair.fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;dracula-theme&#34;&gt;Dracula theme&lt;/h2&gt;
&lt;p&gt;I like and use the Dracula theme pretty much everywhere and, of course, &lt;a href=&#34;https://draculatheme.com/fish&#34;&gt;there’s a version for Fish&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Install it using Fisher:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fisher install dracula/fish
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/07/25/fish-shell/fish_config.png&#34;
         alt=&#34;Screenshot of the web interface for fish_config, showing the  colors, prompt, functions, variables, history, bindings, and abbreviations tabs.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;How do you like your Fish?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you’re coming from Bash or, like me, from Zsh, you might be wondering where to put your aliases, etc. The equivalent of &lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt; in Fish is &lt;code&gt;~/.config/fish/config.fish&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can also configure your shell visually in your web browser by running the following command in your terminal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;fish_config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, while Fish does have aliases, it also has a much more powerful alternative called &lt;a href=&#34;https://fishshell.com/docs/current/interactive.html#abbreviations&#34;&gt;abbreviations&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;abbreviations&#34;&gt;Abbreviations&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/07/25/fish-shell/abbreviations.png&#34;
         alt=&#34;Screenshot of Fish shell suggesting abbreviation completions for the command get-lo: git-log (Abbreviation: git log --graph --decorate --pretty=oneline --abbrev-commit) and git-log-dates  (Abbreviation: git log --graph --decorate --pretty=format:&amp;#34;%h [%cr] %s)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Abbreviations? DMIID (Abbreviation: don’t mind if I do)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Abbreviations are just regular Fish functions but you get a handy shortcut (&lt;a href=&#34;https://fishshell.com/docs/current/cmds/abbr.html#cmd-abbr&#34;&gt;abbr&lt;/a&gt;) for defining them. Unlike aliases, you get auto-suggestions for abbreviations and they are expanded in place so you can see the full command.&lt;/p&gt;
&lt;p&gt;While I’ve converted most of my Zsh aliases to abbreviations, I’ve kept some (like &lt;code&gt;code&lt;/code&gt; for &lt;code&gt;codium&lt;/code&gt;) as aliases.&lt;/p&gt;
&lt;p&gt;My advice is to convert all your aliases to abbreviations and then you will know right away whether you need to convert any back to aliases when you use them. (e.g., I didn’t want to keep seeing &lt;code&gt;/usr/local/codium&lt;/code&gt; on my history so I decided to keep &lt;code&gt;code&lt;/code&gt; as an alias.)&lt;/p&gt;
&lt;p&gt;Here is my current &lt;code&gt;config.fish&lt;/code&gt; file, in case it gives you some ideas. I’ve also listed links to the custom commands used to make it easier for you to find and install them.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;status &lt;/span&gt;is-interactive
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Aliases
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# VSCodium
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020&#34;&gt;alias &lt;/span&gt;code &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/usr/bin/codium&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Abbreviations
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Copy and paste to the clipboard by piping to these commands.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (Inspired by the default behaviour in macOS.)
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global pbcopy &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;xsel --clipboard --input&amp;#39;&lt;/span&gt;
    abbr --add --global pbpaste &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;xsel --clipboard --output&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Open files in their associated apps from Terminal.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global &lt;span style=&#34;color:#007020&#34;&gt;open&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;xdg-open &amp;amp;&amp;gt;/dev/null &amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Git aliases to make git a bit more humane for everyday use.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global git-log &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;git log --graph --decorate --pretty=oneline --abbrev-commit&amp;#39;&lt;/span&gt;
    abbr --add --global git-log-dates &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;git log --graph --decorate --pretty=format:&amp;#34;%h [%cr] %s&amp;#39;&lt;/span&gt;
    abbr --add --global git-tag &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;git tag -n&amp;#39;&lt;/span&gt;
    abbr --add --global git-undo-last-commit &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;git reset HEAD~&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Aliases for getting system and app information.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global system-information &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;neofetch&amp;#39;&lt;/span&gt;
    abbr --add --global disk-usage &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;dust&amp;#39;&lt;/span&gt;
    abbr --add --global which-kernel &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;apt-cache policy linux-generic&amp;#39;&lt;/span&gt;
    abbr --add --global node-v8-version &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;node -p process.versions.v8&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Make rm a little safer (have it prompt once when deleting
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# more than three files or when deleting recursively).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global rm &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;rm -I&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# A nicer ls that also shows the git status of files
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global l &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;exa -lh --git --all&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# A nicer ls that also shows the git status of files
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (but not hidden files)
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global ll &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;exa -lh --git&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Same nicer ls but in tree view.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global lt &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;exa -lh --git --all --tree&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# lc = line count
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global lc &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wc -l&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Find out what’s running on port X
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global port &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;lsof -i&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Better find
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global find &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;fd&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Better ps
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global ps &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;procs&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Package.json validator
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global validate-package.json &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;pjv package.json&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Use ripgrep instead of grep
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    abbr --add --global grep &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;rg&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;tools-mentioned-in-the-configuration&#34;&gt;Tools mentioned in the configuration&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;http://www.kfish.org/software/xsel/&#34;&gt;XSel&lt;/a&gt; is a command-line program for getting and setting the contents of the X selection. Normally this is only accessible by manually highlighting information and pasting it with the middle mouse button.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;http://manpages.ubuntu.com/manpages/focal/man1/xdg-open.1.html&#34;&gt;xdg-open&lt;/a&gt; opens a file or URL in the person&amp;rsquo;s preferred application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/dylanaraps/neofetch&#34;&gt;neofetch&lt;/a&gt; displays information about your operating system, software and hardware in an aesthetic and visually pleasing way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/bootandy/dust&#34;&gt;dust&lt;/a&gt; is a more intuitive &lt;code&gt;du&lt;/code&gt; for visualising disk usage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://the.exa.website/&#34;&gt;exa&lt;/a&gt; is a modern replacement for &lt;code&gt;ls&lt;/code&gt; that uses colours to distinguish file types and metadata and knows about symlinks, extended attributes, and Git.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/sharkdp/fd&#34;&gt;fd&lt;/a&gt; is a simple, fast and user-friendly alternative to the &lt;code&gt;find&lt;/code&gt; command for finding entries in your filesystem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/dalance/procs&#34;&gt;procs&lt;/a&gt; is a replacement for &lt;code&gt;ps&lt;/code&gt; in a human-readable format and with some other additional features.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/BurntSushi/ripgrep&#34;&gt;ripgrep (rg)&lt;/a&gt; is a line-oriented search tool that recursively searches the current directory for a regex pattern. It is similar to other popular search tools like The Silver Searcher, &lt;code&gt;ack&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/gorillamania/package.json-validator&#34;&gt;package.json validator (pjv)&lt;/a&gt; verifies that your package.json files are correct.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;This introductory post doesn’t even begin to scratch the surface of what you can do with Fish but I hope that it sparks your interest and whets your appetite.&lt;/p&gt;
&lt;p&gt;To learn more, view the documentation on &lt;a href=&#34;https://fishshell.com&#34;&gt;the Fish shell website&lt;/a&gt;. You can also launch the documentation in a browser locally from Fish shell itself using the &lt;code&gt;help&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;For example, to learn about abbreviations, run the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;help&lt;/span&gt; abbreviations
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, what are you waiting for?&lt;/p&gt;
&lt;p&gt;Go Fish!&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you remove the seams altogether, you end up with the shiny locked boxes that Apple ships. Sure, they’re lovely to look at and use but you do so on the ever-evolving unilateral terms imposed by the corporation that makes them. This has considerable ramifications when it comes to protecting your freedom to own, control, and use your tools as you want to and impacts on your human rights (e.g., privacy), right to tinker, right to repair, etc. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Remote: a little module for more elegant remoting with WebSockets</title>
      <link>https://ar.al/2021/06/25/remote-a-little-module-for-more-elegant-remoting-with-websockets/</link>
      <pubDate>Fri, 25 Jun 2021 13:59:29 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/06/25/remote-a-little-module-for-more-elegant-remoting-with-websockets/</guid>
      <description>&lt;p&gt;Remote&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; is a tiny (&lt;a href=&#34;https://github.com/small-tech/remote/blob/main/index.js#L12&#34;&gt;&amp;lt; 50 lines of code&lt;/a&gt;) module that creates a very lightweight façade over a socket connection, using convention over configuration to give you an expressive interface with which to send outgoing messages and handle incoming ones.&lt;/p&gt;
&lt;p&gt;You can use it both on the server and on the client.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Here’s a simple &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; example that performs some basic arithmetic on the server and keeps a count.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://sitejs.org/#install&#34;&gt;Site.js&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a folder to hold the example and the following directory structure inside it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;├── .dynamic
│  └── .wss
│     └── index.cjs
└── index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;The &lt;code&gt;index.cjs&lt;/code&gt; file is where you will put your server-side WebSocket route and &lt;code&gt;index.html&lt;/code&gt; is where you will have your client.&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initialise an npm project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the Remote module.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm install @small-tech/remote
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the client (in &lt;em&gt;index.html&lt;/em&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module&amp;#39;&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Only load from Skypack for simple demonstration purposes. Don’t
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// use in production as it doesn’t support sub-resource integrity.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// (See https://ar.al/2020/12/30/skypack-backdoor-as-a-service/)
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; Remote from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://cdn.skypack.dev/@small-tech/remote&amp;#39;&lt;/span&gt;

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;`wss://&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;window&lt;/span&gt;.location.hostname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/`&lt;/span&gt;)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; remote &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Remote(socket)

  socket.addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;open&amp;#39;&lt;/span&gt;, () =&amp;gt; {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Send remote events.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    remote.counter.increment.send()
    remote.addNumbers.request.send({firstNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;40&lt;/span&gt;, secondNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;})
    remote.subtractNumbers.request.send({firstNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;44&lt;/span&gt;, secondNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;})
  })

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Handle remote events.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
  remote.counter.update.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    log(&lt;span style=&#34;color:#4070a0&#34;&gt;`Counter is now &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.count&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
  }

  remote.addNumbers.response.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    log (&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.firstNumber&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; + &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.secondNumber&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; = &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.result&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
  }

  remote.subtractNumbers.response.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.firstNumber&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; - &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.secondNumber&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; = &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.result&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// DOM manipulation.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; output &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;.getElementById(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;output&amp;#39;&lt;/span&gt;)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; log &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; li &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;.createElement(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;li&amp;#39;&lt;/span&gt;)
    li.innerHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message
    output.appendChild(li)
  }
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;Simple remote arithmetic&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;output&amp;#39;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the server (in &lt;em&gt;.dynamic/.wss/index.cjs&lt;/em&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; Remote &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/remote&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; count &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;

module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (client, request) {

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; remote &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Remote(client)

  remote.addNumbers.request.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    remote.addNumbers.response.send({
      firstNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; message.firstNumber,
      secondNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; message.secondNumber,
      result&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; message.firstNumber &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; message.secondNumber
    })
  }

  remote.subtractNumbers.request.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    remote.subtractNumbers.response.send({
      firstNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; message.firstNumber,
      secondNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; message.secondNumber,
      result&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; message.firstNumber &lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt; message.secondNumber
    })
  }

  remote.counter.increment.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    count&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;
    remote.counter.update.send({ count })
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run Site.js (in &lt;em&gt;the root of your example folder&lt;/em&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;site
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now hit &lt;code&gt;https://localhost&lt;/code&gt; and you should see the following output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Simple remote arithmetic

    • Counter is now 1
    • 40 + 2 = 42
    • 44 - 2 = 42
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you hit the page from other browser tabs, you should see the counter increase (it will reset if you restart Site.js as we are not persisting the value anywhere.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Messages are sent as JSON strings and received messages are parsed from JSON strings.&lt;/p&gt;
&lt;p&gt;There are two special keywords: &lt;code&gt;send&lt;/code&gt; and &lt;code&gt;handler&lt;/code&gt;. You use the former when sending a message and you define the latter as a function to handle received messages.&lt;/p&gt;
&lt;p&gt;Otherwise, there is nothing special about the path segments that make up the rest of the statements you see. Anything between the &lt;code&gt;remote&lt;/code&gt; instance reference and either &lt;code&gt;send&lt;/code&gt; or &lt;code&gt;handler&lt;/code&gt; is used as the message type.&lt;/p&gt;
&lt;p&gt;So, for example, &lt;code&gt;addNumbers.request&lt;/code&gt; and &lt;code&gt;addNumbers.response&lt;/code&gt; are message types, as is &lt;code&gt;counter.update&lt;/code&gt;. Remote automatically creates the defined object hierarchies using some &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&#34;&gt;Proxy&lt;/a&gt; magic.&lt;/p&gt;
&lt;p&gt;So far, Remote is working well for me as I build &lt;a href=&#34;https://github.com/small-tech/domain&#34;&gt;Domain&lt;/a&gt;. Give it a shot and see if it works for you too.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In building &lt;a href=&#34;https://github.com/small-tech/domain&#34;&gt;Domain&lt;/a&gt; (see a demo of it from &lt;a href=&#34;https://small-tech.org/videos/small-is-beautiful-11/&#34;&gt;last week’s episode of Small is Beautiful&lt;/a&gt;) using &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; and &lt;a href=&#34;https://kit.svelte.dev/&#34;&gt;SvelteKit&lt;/a&gt;, I use WebSockets&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; for calls between the client and server. This works really well and I find it much easier (less verbose and easier to maintain) than HTTP calls.&lt;/p&gt;
&lt;p&gt;Even though the majority of the communication in Domain is request/response, I didn’t want to implement &lt;a href=&#34;https://github.com/small-tech/site.js-websocket-rpc-example&#34;&gt;traditional RPC over WebSocket&lt;/a&gt; (which is quite restrictive). Instead, I wanted to retain the feel of a message-based API but give it some structure and an elegant, expressive interface.&lt;/p&gt;
&lt;p&gt;Starting out, I didn’t want to use a heavyweight framework of any kind so I was simply passing messages back and forth, using plain old strings (POS; not a terrible acronym to describe the practice, if you think about it) for the message names. This works well enough as a quick and dirty way to get started but, as your app grows, you’ll likely start to feel the need to refactor.&lt;/p&gt;
&lt;p&gt;During the first pass of refactoring, I replaced the POS with constants. This is fine, and gives you additional type safety at compile-time but it is also more verbose. But I just wasn’t happy with how the code read. So I decided to create a tiny class to standardise and abstract out the socket communication between client and server. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s ‘isomorphic’, if you want to get fancy about it. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you want to persist the counter so it survives serve restarts, it is trivial to do so using Site.js.&lt;/p&gt;
&lt;p&gt;Just update the server like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;db.counter) {
  db.counter &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { count&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt; }
}

module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (client, request) {

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
  remote.counter.increment.handler &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
    db.counter.count&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;
    remote.counter.update.send({ count&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; db.counter.count })
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(&lt;code&gt;db&lt;/code&gt; is a global reference to the &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JavaScript Database (JSDB)&lt;/a&gt; instance that’s available for you to use in any HTTP or WebSocket route in Site.js.) &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Make anything a JavaScript module using Node.js ESM Module Loaders</title>
      <link>https://ar.al/2021/05/27/make-anything-a-javascript-module-using-node.js-esm-module-loaders/</link>
      <pubDate>Thu, 27 May 2021 13:05:31 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/27/make-anything-a-javascript-module-using-node.js-esm-module-loaders/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/05/27/make-anything-a-javascript-module-using-node.js-esm-module-loaders/message-in-a-bottle.jpg&#34;
         alt=&#34;A message in a bottle on an empty beach with a beautiful blue sea behind it.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Disclaimer: you can’t load an actual bottle into Node.js (yet).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I have a message in a text file called &lt;code&gt;bottle.txt&lt;/code&gt;. Here’s how I load it into Node.js and output it to the console:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; message from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;bottle.txt&amp;#39;&lt;/span&gt;
message()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wait, what? How?&lt;/p&gt;
&lt;p&gt;I’m glad you asked.&lt;/p&gt;
&lt;p&gt;It’s thanks to Node.js’s experimental &lt;a href=&#34;https://nodejs.org/api/esm.html#esm_loaders&#34;&gt;ESM Module Loaders feature&lt;/a&gt;. Here’s how you can implement this using the excellent &lt;a href=&#34;https://www.npmjs.com/package/node-esm-loader&#34;&gt;node-esm-loader&lt;/a&gt; package which makes the Node ESM Loader API even easier to work with:&lt;/p&gt;
&lt;h2 id=&#34;message-in-a-bottletxt-file&#34;&gt;Message in a bottle(.txt file)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new project directory, switch to it, and initialise a new Node project there.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;mkdir message-in-a-bottle
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; message-in-a-bottle
npm init -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once you’re done, edit the &lt;em&gt;package.json&lt;/em&gt; file to add the following key/value pair so Node knows to interpret &lt;em&gt;.js&lt;/em&gt; files as ESM and not CommonJS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style=&#34;&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a text file called &lt;em&gt;bottle.txt&lt;/em&gt; and add the following text to it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;This is a message in a bottle(.txt file)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, create the &lt;em&gt;index.js&lt;/em&gt; file you saw earlier:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; message from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;bottle.txt&amp;#39;&lt;/span&gt;
message()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now for the magic! Let’s make Node understand how to compile text files. First, install the &lt;em&gt;node-esm-loader&lt;/em&gt; module to make it easier for us to work with the ESM Loaders feature:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm i node-esm-loader
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, let’s configure our loader to tell Node how to interpret text files. Create a file called &lt;em&gt;.loaderrc.js&lt;/em&gt; with the following content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { URL } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; {
  loaders&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; [
    {
      resolve(specifier, opts) {
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (specifier.endsWith(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.txt&amp;#39;&lt;/span&gt;)) {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; { parentURL } &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; opts
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; url &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; URL(specifier, parentURL).href
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; { url }
        }
      },
      format(url, opts) {
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (url.endsWith(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.txt&amp;#39;&lt;/span&gt;)) {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; { format&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module&amp;#39;&lt;/span&gt; }
        }
      },
      transform(source, opts) {
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (opts.url.endsWith(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.txt&amp;#39;&lt;/span&gt;)) {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; {
            source&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`export default function () {
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;              console.log(\`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;String&lt;/span&gt;(source)&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;\`)
&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;            }`&lt;/span&gt;
          }
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, run your project, telling Node to use your new loader:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;node --experimental-loader&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;node-esm-loader .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;p&gt;Alongside a warning that the ESM Loaders feature is experimental, you should see the following output in your terminal window:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;This is a message in a bottle(.txt file)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;In your loader, the &lt;code&gt;resolve()&lt;/code&gt; and &lt;code&gt;format()&lt;/code&gt; methods are where you tell Node that it should handle text files and treat them as JavaScript modules. And the &lt;code&gt;transform()&lt;/code&gt; method is where you return an actual JavaScript module by compiling the text file (in this case, by wrapping its contents in a function that outputs them to the console).&lt;/p&gt;
&lt;h2 id=&#34;beyond-the-basics&#34;&gt;Beyond the basics&lt;/h2&gt;
&lt;p&gt;So you’re most likely not going to use this for text files.&lt;/p&gt;
&lt;p&gt;Instead, you can use it, as I am for example, to make a custom Express server that &lt;a href=&#34;https://vitejs.dev/guide/ssr.html&#34;&gt;server-side renders&lt;/a&gt; &lt;a href=&#34;https://svelte.dev&#34;&gt;Svelte&lt;/a&gt; files by using &lt;a href=&#34;https://vitejs.dev&#34;&gt;Vite&lt;/a&gt; as middleware.&lt;/p&gt;
&lt;p&gt;I look forward to seeing all the creative uses you put this awesome feature to.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Archival cascades: a practical way to not break URLs</title>
      <link>https://ar.al/2021/05/26/archival-cascades-a-practical-way-to-not-break-urls/</link>
      <pubDate>Wed, 26 May 2021 11:05:56 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/26/archival-cascades-a-practical-way-to-not-break-urls/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/05/26/archival-cascades-a-practical-way-to-not-break-urls/cascading-waterfalls.jpg&#34;
         alt=&#34;Photo of cascading waterfalls&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Cascades are beautiful things.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This week, I reduced our DigitalOcean hosting costs for &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt; from ~$90/month to $5/month by moving &lt;a href=&#34;https://source.small-tech.org&#34;&gt;our canonical source code repositories&lt;/a&gt; as well as a few other servers to the complimentary hosting provided to our not-for-profit by the &lt;a href=&#34;https://eclips.is&#34;&gt;Eclips.is&lt;/a&gt; initiative by &lt;a href=&#34;https://greenhost.net&#34;&gt;Greenhost&lt;/a&gt; and &lt;a href=&#34;https://www.opentech.fund/&#34;&gt;Open Technology Fund&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As part of the move, I had to decide what to do with the three servers that were still running various parts of the &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; web site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The latest version of the Ind.ie web site, with a notice that we were now Small Technology Foundation (this was a &lt;a href=&#34;https://hugo.io&#34;&gt;Hugo&lt;/a&gt; site).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The site we had from 2013-2017 (this was server-side-generated site using a custom engine I’d written in Node.js).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&#34;https://ind.ie/labs&#34;&gt;labs&lt;/a&gt; site, which linked to a number of projects and held a few technical blog posts (this was a server-side-rendered site with a custom Express server I’d written in Node.js and which was deployed using &lt;a href=&#34;https://dokku.com/&#34;&gt;dokku&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;memories&#34;&gt;Memories&lt;/h2&gt;
&lt;p&gt;We haven’t been known as Ind.ie for the past three years now but that doesn’t mean we can just switch those servers off and be done with it. I’m hugely proud of our journey over the past eight years. Most of all in the fact that, as hard as it has been, we are still going. And that we will keep going for the foreseeable future. The Ind.ie site is an archive of the learning, iterating, and growing (in knowledge, understanding, and tools; not in size) that we’ve done through five of those years. Including…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://ind.ie/ethical-design/&#34;&gt;The ethical design manifesto&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://ind.ie/archive/summit/&#34;&gt;The Ind.ie Summit&lt;/a&gt; and all of &lt;a href=&#34;https://ind.ie/archive/summit/videos/&#34;&gt;the videos from it&lt;/a&gt; (which I went through one-by-one to update and fix the video links on… my goodness we all look so young!)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://ind.ie/labs/blog/&#34;&gt;Technical blog posts from the labs section&lt;/a&gt;, not to mention &lt;a href=&#34;https://ind.ie/labs/&#34;&gt;a photo of Oskar in a lab coat and red bow tie&lt;/a&gt;.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-plan&#34;&gt;The plan&lt;/h2&gt;
&lt;p&gt;I knew I wanted to collate the three servers into one and use &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; to host static versions of them.&lt;/p&gt;
&lt;p&gt;The latest version of the Ind.ie web site was the easiest to deal with. Site.js has &lt;a href=&#34;https://sitejs.org/#static-sites&#34;&gt;native support for Hugo&lt;/a&gt; so all I had to do was to create a &lt;code&gt;.hugo&lt;/code&gt; directory in my new site and copy it there.&lt;/p&gt;
&lt;p&gt;So my archived Ind.ie site looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ind.ie/
  ╰ .hugo
       ╰ (contents of the latest ind.ie site)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, I needed to serve the statically-generated contents of the site from 2013-2017. Now you’re probably thinking: “but Site.js is already serving the Hugo site and you need to serve that static content from the same namespace. How do you do that?”&lt;/p&gt;
&lt;p&gt;Well, there are two ways I could have done it. Since Site.js simply serves any content in the root of your site as static content, I could have just copied the content there. But Site.js also has a feature specifically for this use case called &lt;a href=&#34;https://github.com/small-tech/site.js/blob/master/README.md#the-archival-cascade&#34;&gt;archival cascades&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;archival-cascades&#34;&gt;Archival cascades&lt;/h2&gt;
&lt;p&gt;If you have static archives of previous versions of your site, you can have Site.js automatically serve them for you.&lt;/p&gt;
&lt;p&gt;Just put them into folder named &lt;code&gt;.archive-1&lt;/code&gt;, &lt;code&gt;.archive-2&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;If a path cannot be found in your current site, Site.js will search for it first in &lt;code&gt;.archive-2&lt;/code&gt; and, if it cannot find it there either, in &lt;code&gt;.archive-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Paths in your current site will override those in &lt;code&gt;.archive-2&lt;/code&gt; and those in &lt;code&gt;.archive-2&lt;/code&gt; will, similarly, override those in &lt;code&gt;.archive-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This was you create a cascade of archives where you can serve static snapshots of older content.&lt;/p&gt;
&lt;p&gt;Using the archival cascade, old links will never die but if you do replace them with newer content in newer versions, those will take precedence.&lt;/p&gt;
&lt;p&gt;So, after this step, my archive site structure looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ind.ie/
  ├ .hugo
  │    ╰ (contents of the latest ind.ie site)
  ╰ .archive-1
       ╰ (contents of the ind.ie site from 2013-2018)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;taking-a-static-snapshot-using-wget&#34;&gt;Taking a static snapshot using wget&lt;/h2&gt;
&lt;p&gt;Since the labs site was server-side rendered, I needed to get a static snapshot of it.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; The easiest way I could find of doing that was to use the handy &lt;code&gt;wget&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;I simply ran the server on my local development machine (on port 3000) and ran the following command to save a static snapshot of it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;wget --recursive --domains localhost --no-parent --page-requisites http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once I had the static snapshot, I just added it to the archival cascade. So my final site structure looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ind.ie/
  ├ .hugo
  │    ╰ (contents of the latest ind.ie site)
  ├ .archive-1
  │    ╰ (contents of the ind.ie site from 2013-2018)
  ╰ .archive-2
       ╰ /labs
           ╰ (contents of the ind.ie labs site)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, I set up a server running Site.js, pointed the ind.ie domain to it, and &lt;a href=&#34;https://github.com/small-tech/site.js/blob/master/README.md#sync&#34;&gt;synced&lt;/a&gt; the site over.&lt;/p&gt;
&lt;h2 id=&#34;dont-break-urls&#34;&gt;Don’t break URLs&lt;/h2&gt;
&lt;p&gt;It’s all too simple to just turn a server off and forget about it but the web doesn’t forget. What you leave behind will be a bunch of dead links. It’s not always practical to keep everything running forever but if you want to try and not break links, &lt;a href=&#34;https://github.com/small-tech/site.js/blob/master/README.md#the-archival-cascade&#34;&gt;archival cascades&lt;/a&gt; and &lt;a href=&#34;https://github.com/small-tech/site.js/blob/master/README.md#native-404--302-support&#34;&gt;404 to 302 support&lt;/a&gt; in Site.js should help.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Some of the links to articles on Labs might be broken as they refer to a forum that no longer exists. This was hosted by a commerical third-party and, sadly, we weren’t able to get a usable static export of our data at the time. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;As an alternative, if I had wanted to, I could have kept the server running somewhere and used the &lt;a href=&#34;https://github.com/small-tech/site.js/blob/master/README.md#native-404--302-support&#34;&gt;native 404 to 302&lt;/a&gt; in Site.js to keep serving the old site. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to get clean analog audio from a Blue Yeti microphone into a Sony a6400 camera using a Raspberry Pi Zero</title>
      <link>https://ar.al/2021/05/23/how-to-get-clean-analog-audio-from-a-blue-yeti-microphone-into-a-sony-a6400-camera-using-a-raspberry-pi-zero/</link>
      <pubDate>Sun, 23 May 2021 20:36:13 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/23/how-to-get-clean-analog-audio-from-a-blue-yeti-microphone-into-a-sony-a6400-camera-using-a-raspberry-pi-zero/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/1144931351.jpg?mw=2500&amp;mh=1406&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/543975149.m3u8?s=844e2d3bfa9d550a125b7f617dbe2dc4a9cbfe00#t=27&#39; type=&#39;application/x-mpegURL&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/543975149.hd.mp4?s=b5b5f7a9c984977270aa7c542bef149a1c205a9f&amp;profile_id=175#t=27&#39; type=&#39;video/mp4&#39;&gt;
    &lt;track type=&#39;captions&#39; src=&#39;./captions.vtt&#39; srclang=&#39;en&#39; label=&#39;English&#39; default&gt;
    &lt;p&gt;&lt;a href=&#39;https://player.vimeo.com/external/543975149.hd.mp4?s=b5b5f7a9c984977270aa7c542bef149a1c205a9f&amp;profile_id=175&amp;download=1&#39;&gt;Download the video.&lt;/a&gt;&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;A live demonstration from today’s S’update. &lt;a href=&#39;#transcript&#39;&gt;Transcript&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;clean-audio--clean-usb-power&#34;&gt;Clean audio = clean USB power&lt;/h2&gt;
&lt;p&gt;You can connect the headphone out of a Blue Yeti microphone into the mic in of a Sony a6400 camera but the quality of the audio you get will depend on how clean the USB source powering your Blue Yeti is.&lt;/p&gt;
&lt;p&gt;In order to get analog audio into your camera from a Blue Yeti, you need to plug the Blue Yeti into a computer&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; via USB and into your camera via an auxiliary cable. When I did this with the Blue Yeti plugged into a USB hub connected to a MacBook, I got quite a bit of noise, even after turning the input volume down to 1 on the Sony a6400 (which you should do).&lt;/p&gt;
&lt;p&gt;When I tried plugging it into my daily driver development machine (a StarLabs LabTop), the noise was unbearably loud. Then, however, when I plugged it into an old MacBook Pro, the noise was almost gone. So I thought I’d give it a try with a Raspberry Pi Zero and ­­— lo and behold – silence. It’s the cleanest USB power for a Blue Yeti that I’ve been able to find yet and it doesn’t hurt that it’s a $5 device.&lt;/p&gt;
&lt;h2 id=&#34;step-by-step&#34;&gt;Step-by-step&lt;/h2&gt;
&lt;p&gt;So, in case you have the same problem getting clean USB power from your existing devices to power a Blue Yeti microphone with a camera like the Sony a6400:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get a Raspberry Pi Zero (with the official power adapter&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and a good quality Micro USB male to USB A female adapter) and plug it in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Turn the volume in your Sony camera down to 1 (Menu → Movie 2 → Audio Rec Level&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Turn the gain on your Blue Yeti down and turn the headphone volume to somewhere half-way (you can tweak it using your level indicators but better to start lower and go higher rather than give yourself a loud shock).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Plug the Blue Yeti headphone out to the microphone in of your camera.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Plug the Blue Yeti USB out into the USB in on your Raspberry Pi Zero.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that’s it. You should enjoy beautiful, clean audio with your Blue Yeti and Sony a6400&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;That should do it!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;To see a live demonstration, watch today’s update, embedded above. A &lt;a href=&#34;#transcript&#34;&gt;transcript&lt;/a&gt; of the stream is below.&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;h3 id=&#34;transcript&#34;&gt;Transcript&lt;/h3&gt;
&lt;p&gt;Hello and welcome to a special update you might have noticed if you watched the last few S’updates or the last Small is Beautiful that we did this week, this past week, that my audio was out of sync. In fact, if you&amp;rsquo;re paying attention right now, my audio should be out of sync. And I want to show you how I fixed that, how I found out what the problem was and how I fixed it. So let me show you, first of all, I&amp;rsquo;m also going to put on these headphones so I can hear what you&amp;rsquo;re hearing from my monitor over here.&lt;/p&gt;
&lt;p&gt;So, all right. Now I can hear myself. The audio is fine. This is in terms of levels, et cetera. And let me show you my setup just slowly over here. Wait a minute. Actually, let me switch it up first, because I had it really nicely placed there. There we go. Let me pick up the camera now. So this is my setup and lots of screens. I know.&lt;/p&gt;
&lt;p&gt;And in terms of the audio, so I have a Blue Yeti microphone up here and that Blue Yeti microphone, I&amp;rsquo;m going to stand up here and try not to snag my headphone cable off, has a USB connection here and a mic connection. And you can see apart from also it&amp;rsquo;s quite dusty. You can see that they&amp;rsquo;re both connected. What&amp;rsquo;s happening is I&amp;rsquo;m using the USB for power and I&amp;rsquo;m actually getting an analog signal out of the the headphone jack over there and where those are going to…&lt;/p&gt;
&lt;p&gt;So if you look over here, I have a USB hub and this is the power coming in. So that&amp;rsquo;s just providing power to the Blue Yeti. Now, you can&amp;rsquo;t connect this to any power source. It has to be a computer. The Blue Yeti is very peculiar about that. And the headphone jack, if you follow that… is connected to the back of my Atem Mini Pro into the microphone input over here.&lt;/p&gt;
&lt;p&gt;And now if you look at my Atem controller here, you&amp;rsquo;ll see in the settings that it&amp;rsquo;s the mic is set to line. OK, that&amp;rsquo;s important. I&amp;rsquo;m not going to put it on microphone right now because it would deafen you, but the headphone jack provides a signal that&amp;rsquo;s a little lower than line usually, but it&amp;rsquo;s still much higher than microphone powered like a microphone with power would be providing.&lt;/p&gt;
&lt;p&gt;So that would be very, very loud if I switch to that. Now, the problem is and you can see that the sync is off, I&amp;rsquo;m assuming I can&amp;rsquo;t see that right now. But I&amp;rsquo;m assuming if you&amp;rsquo;re watching this, my lip sync is off. And that&amp;rsquo;s because let me show you my camera over here. So the video is coming out of this Sony Alpha 6400 and it&amp;rsquo;s coming out via HDMI that you can see over there.&lt;/p&gt;
&lt;p&gt;So the problem is that HDMI has latency, all HDMI has latency. So the video is coming in slower than the audio. And that&amp;rsquo;s why there&amp;rsquo;s no lip sync. Now, with the Atem Mini Pro – with the app –  which I&amp;rsquo;m running on a MacBook over here,&lt;/p&gt;
&lt;p&gt;you can actually set the latency. So I can… if I go over here, over here, I can actually add a delay so I can add, I don&amp;rsquo;t know, let’s say &lt;strong&gt;[stuttering audio]&lt;/strong&gt; four frames of delay, which is now making it very difficult for me to speak when I hear myself, because it&amp;rsquo;s coming in four frames delayed. So I&amp;rsquo;m just taking the headphones off. Now, this may or may not fix the problem. I am going directly from my microphone source in the mixer and previously four frames of delay is what I arrived at experimentally.&lt;/p&gt;
&lt;p&gt;But recently something happened. I don&amp;rsquo;t know what it was. And I&amp;rsquo;ve done some tests and it seems like maybe when chroma key is on, the delay is different. Maybe this latest version of the the software has actually fixed it. I don&amp;rsquo;t know, because I only just updated. But the problem really is that this is not the way you should be doing it. Right? And so why was I splitting this up? Why was I taking… or why did I decide that I should put, like, the audio in directly to the mixer instead of into the mic ­– into the, sorry – into the camera, which is how I could have done it.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s because when I tried it, I just got horrible static and horrible noise and I tried to narrow it down back then when I first set it up and I didn&amp;rsquo;t have time, so I was like, OK, I&amp;rsquo;ll do it like this. And then Atem came up with the delay and it was OK. So I&amp;rsquo;m going to turn that delay off again. So that will…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[stuttering audio]&lt;/strong&gt; and there we go, the delay is zero now on my Atem controller… you can see over here the delay is zero. And let me show you what happens if I just plug the… instead of the line in… So instead of plugging it into the Atem, if I plugged it into the into the camera directly, let&amp;rsquo;s see what happens now.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also going to… over here… momentarily… Let&amp;rsquo;s see. Let me put my headphones on as well just so I can hear what&amp;rsquo;s happening. OK. All right. So for one thing, I do have the audio levels on the camera. Let me try and get that here. The audio record level is set all the way down to one. OK, so when I do the switch, hopefully it won&amp;rsquo;t it won&amp;rsquo;t deafen your ears.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m taking out the audio. So are you going to lose my audio momentarily… &lt;strong&gt;[silence]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OK, and I just plug it in to&lt;/p&gt;
&lt;p&gt;to the camera and you can hear it. I mean, it&amp;rsquo;s I&amp;rsquo;ve turned it down the doors. You can see on the on the camera, if I like to show you this, if I go to the menu, your record level, you see it&amp;rsquo;s that one. And if I&amp;rsquo;m quiet for a second…&lt;/p&gt;
&lt;p&gt;You can hear that sound, right? If I actually turn the audio level up…&lt;/p&gt;
&lt;p&gt;OK, so that&amp;rsquo;s the that&amp;rsquo;s the buzz that we&amp;rsquo;re getting and this is terrible, terrible audio. Of course, it&amp;rsquo;s terrible audio, but if you are watching now my lips, that should be in sync because, of course, the audio is now coming through the HDMI and it&amp;rsquo;s synched in the camera and it&amp;rsquo;s coming into the mixer. And at that point, it&amp;rsquo;s it&amp;rsquo;s mixed. So there&amp;rsquo;s not going to be any lip sync issue.&lt;/p&gt;
&lt;p&gt;This is how you should be doing it. Now, this is not the horrible audio you want now. I mean, we can try and make it a bit better. So it&amp;rsquo;s much louder, of course, because remember, it was set to line on the other one. So you can… what you can do is… let me show you you can try turning down the headphone jack over here. And now that should be a bit better if you can still hear me. And also, there is, of course, a gain at the back of the… of the &lt;strong&gt;[Atem]&lt;/strong&gt; let&amp;rsquo;s see…&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s try to get that in there. There&amp;rsquo;s a gain and you can turn that down as well. All right. So but this is not great audio by any means. You can hear that terrible humming. So I was like, OK, why why is this humming happening? And so today, what I did and I&amp;rsquo;ll do it now, it&amp;rsquo;s got to do with the USB connection with the power connection over here. So it&amp;rsquo;s got to do with this.&lt;/p&gt;
&lt;p&gt;So what I tried was, OK, oh, it&amp;rsquo;s gone to sleep again. So let me turn this… or is it just… [wakes Mac from sleep] ok, there we go what I thought was OK, so what would happen if I changed… If I unplugged…the  I wasn&amp;rsquo;t using this USB hub, but I plugged it into this old MacBook Pro, Laura&amp;rsquo;s old MacBook Pro that&amp;rsquo;s just lying around here. So look at what happens when I do that. You&amp;rsquo;re going to lose my audio for a second again and then it will come back. &lt;strong&gt;[silence]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All right, so now it&amp;rsquo;s being powered by the MacBook Pro. And can you hear that? That&amp;rsquo;s actually way better! There is it&amp;rsquo;s not the same sort of&lt;/p&gt;
&lt;p&gt;the same sort of noise that we have. So if I go over here onto the camera and I just turn up. The audio level…&lt;/p&gt;
&lt;p&gt;You can see you can hear when I turn it up…&lt;/p&gt;
&lt;p&gt;There is some, but… It&amp;rsquo;s not… and of course, it&amp;rsquo;s going to be clipping.&lt;/p&gt;
&lt;p&gt;But that is so much better, so, OK, so that was the “aha!” moment. All right, so this has to do with how much noise there is, how clean the USB connection is. So it&amp;rsquo;s it&amp;rsquo;s electro&lt;/p&gt;
&lt;p&gt;electromagnetic interference, basically. So I was like, OK, I&amp;rsquo;m not going to use a MacBook Pro for this, not least of all, because that MacBook Pro, even if it&amp;rsquo;s just on at some time, sometimes the fan comes on and that&amp;rsquo;s really loud. So I was like, OK, what is? And again, you can&amp;rsquo;t connect it directly to just a USB power source. It needs to be a computer or the blue yeti doesn&amp;rsquo;t work. So it&amp;rsquo;s like, OK, what&amp;rsquo;s the simplest computer that I could have that&amp;rsquo;s also silent? That&amp;rsquo;s also really well built and shielded.&lt;/p&gt;
&lt;p&gt;And that is what brought me to try…&lt;/p&gt;
&lt;p&gt;the. Raspberry Pi Zero. So this is a Raspberry Pi Zero, one of a couple that I have hanging around that I was playing with and I was like, let&amp;rsquo;s try it with that. Now, every component matters in terms of the noise. And this is connected with the official cable, to… that&amp;rsquo;s actually an iPhone (or an iPad) power adapter there. I tried it originally with with a Raspberry Pi 4 with the original Raspberry Pi power adapter.&lt;/p&gt;
&lt;p&gt;And that was very, very low noise as well. Much lower noise, actually, than the MacBook Pro. So I was like, OK, I hope it works with the with the Raspberry Pi zero as well. And spoiler: it did! So let me just show you what it&amp;rsquo;s like with that. But also remember, every component counts. So this cable here, for example, if I was to use so this cable here is where I&amp;rsquo;m going to connect the USB from the Yeti.&lt;/p&gt;
&lt;p&gt;But I also tried it with this sort of cheap adapter that I had and there was a lot more noise with this. So every component really matters. So again, we&amp;rsquo;re going to lose audio for a second, but hopefully for the last time and I&amp;rsquo;m going to unplug the USB from the MacBook, oops that&amp;rsquo;s the power… [silence … struggles to plug in USB with one hand]&lt;/p&gt;
&lt;p&gt;Right. OK, so I&amp;rsquo;ve just plug it in, that is that is harder to do than it looks with one hand to push you, apparently beyond my capabilities. All right. So…&lt;/p&gt;
&lt;p&gt;Now, that is connected to a Raspberry Pi Zero and that is connected to power, so the Blue Yeti is on, and I have my levels. What&amp;rsquo;s interesting is I found that the levels are lower. So I&amp;rsquo;m just going to say, OK, here we go. Actually, there we go. Let me let me just adjust this… Oh, of course, the gain… is up so the levels are lower. So what I can do… is… I can turn the gain…&lt;/p&gt;
&lt;p&gt;All right, wait a minute, sorry, let me switch to the camera so you know what I&amp;rsquo;m doing, I can turn the gain almost all the way down over here. Now, you&amp;rsquo;re not going to hear me very well. I&amp;rsquo;m just I&amp;rsquo;m very close to the microphone right now. And what I can do is I can then use… here… Let&amp;rsquo;s switch to this… I can then adjust the volume over here that I&amp;rsquo;m getting.&lt;/p&gt;
&lt;p&gt;And and that&amp;rsquo;s pretty good. OK, I&amp;rsquo;m in the reds a little bit. So if you look at my monitor, you can see my levels. But I&amp;rsquo;m going to stop talking for a second and I just want you to take a look at those levels there, OK? &lt;strong&gt;[silence]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s how little noise there is with the Raspberry Pi Zero with the Blue Yeti connected to the Raspberry Pi Zero. So again, I&amp;rsquo;m just doing this all live. So these are… not I&amp;rsquo;m going to get the levels just right. But that, in a nutshell, is how I&amp;rsquo;m going to be doing things from now on. And that should solve the lip-sync problem completely. And, yeah…&lt;/p&gt;
&lt;p&gt;I can turn the mic off from the Atem Mini Pro and. Yeah. So it should be going in, synchronized into the Atem and this… So the real thing is that this little… If you&amp;rsquo;re having the same issue, for example, get a Raspberry Pi Zero. Get the official connector and everything, it might be a few bucks more, but just get the good stuff and I think this cable was from Pimoroni, I&amp;rsquo;m not sure in of their sets or I don&amp;rsquo;t know if it&amp;rsquo;s just a Raspberry Pi official cable, I don&amp;rsquo;t know…&lt;/p&gt;
&lt;p&gt;But just get all good components for it, and, uh…&lt;/p&gt;
&lt;p&gt;That is how you can get very, very clean audio out of the headphone jack and into the microphone, jack of the computer, and that way you you won&amp;rsquo;t have any audio sync issues whatsoever. So I hope you found this useful. And this has been a special little S’update, today, Sunday, May 23rd, 2021. Take care.&lt;/p&gt;
&lt;p&gt;Be well. Bye bye!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;A USB power supply won’t do, it has to be a computer. Otherwise, the Blue Yeti will power on but you will not get any sound from it. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I used an iPhone/iPad power adapter with a good quality USB cable in the demo. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And, of course, make sure Audio Recording is set to on in Menu → Movie 2. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;These instructions should work for the Blue Yeti and any other similar camera but I’ve only tested them with the Sony a6400. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>JSDB Migrations</title>
      <link>https://ar.al/2021/05/16/jsdb-migrations/</link>
      <pubDate>Sun, 16 May 2021 17:57:41 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/16/jsdb-migrations/</guid>
      <description>&lt;p&gt;I’m busy working on &lt;a href=&#34;https://github.com/small-tech/basil&#34;&gt;Basil&lt;/a&gt;, the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; host, and while it’s nowhere near ready to use yet, I thought I’d try my hand at writing a database migration as they will be necessary once other people start using it.&lt;/p&gt;
&lt;p&gt;Basil runs on &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; and Site.js uses &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JSDB&lt;/a&gt; – JavaScript Database — as its database.&lt;/p&gt;
&lt;p&gt;True to its name, JSDB is 100% JavaScript, so, of course, you write your migrations in JavaScript also.&lt;/p&gt;
&lt;p&gt;Here’s what the migration I wrote ended up looking like:&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Migration of Settings table from version 1 to version 2.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Payment providers is now an array instead of an object
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is pre-populated with the defaults for the None and Token
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// provider types.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Also, adds the version attribute for the first time, thereby
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// enabling future migrations.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; JSDB &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/jsdb&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; db &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSDB.open(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.db&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; settings &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; db.settings

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is migration necessary?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (db.settings.version &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; db.settings.version &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;) {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Already migrated, exiting.&amp;#39;&lt;/span&gt;)
  process.exit()
}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Add version to settings table.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;settings.version &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Migrate the payment object to an array, copying over
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// the Stripe-related details in version 1.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; payment &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; settings.payment

settings.payment &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Set the payment provider to the new index of the only one
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// that existed previously (Stripe).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  provider&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;,

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create the providers array.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  providers&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; [
    {
      name&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;None&amp;#39;&lt;/span&gt;,
      modes&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;
    },
    {
      name&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Tokens&amp;#39;&lt;/span&gt;,
      modes&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;,
      codes&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; []
    },
    {
      name&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Stripe&amp;#39;&lt;/span&gt;,
      modes&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; [ &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;test&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;live&amp;#39;&lt;/span&gt; ],
      mode&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; payment.mode,
      modeDetails&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; payment.modeDetails,
      currency&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; payment.currency,
      price&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; payment.price,
      amount&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; payment.amount
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Neither Site.js nor JSDB has any built-in support for migrations yet so this is something you would write into the logic of your own applications at the moment.&lt;/p&gt;
&lt;p&gt;As Basil is still under heavy initial development, I just made it a script and ran it directly.&lt;/p&gt;
&lt;p&gt;I’m going to keep experimenting with migrations as I work on Basil and I wouldn’t be surprised if they work their way into JSDB and/or Site.js in time.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/small-tech/basil/blob/main/.migrations/settings-v1-to-v2.cjs&#34;&gt;The actual migration script&lt;/a&gt; has some more code to back up the table and handle errors, etc., but this is the gist of it. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Scraping the latest EU VAT rates for e-services from the European Commission’s web site with Node.js</title>
      <link>https://ar.al/2021/05/14/scraping-the-latest-eu-vat-rates-for-e-services-from-the-european-commissions-web-site-with-node.js/</link>
      <pubDate>Fri, 14 May 2021 15:14:36 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/14/scraping-the-latest-eu-vat-rates-for-e-services-from-the-european-commissions-web-site-with-node.js/</guid>
      <description>&lt;p&gt;So, you now know &lt;a href=&#34;https://ar.al/2021/05/14/using-the-european-commission-eu-vat-number-validation-api-with-node.js/&#34;&gt;how to verify an EU VAT number with Node.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lucky you! (Don’t say I don’t spoil you.)&lt;/p&gt;
&lt;p&gt;But do you know what the latest VAT rates are every EU country?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Do you? Huh, do you, punk?)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;OK, so &lt;a href=&#34;https://ec.europa.eu/taxation_customs/business/vat/telecommunications-broadcasting-electronic-services/vat-rates_en&#34;&gt;you can find them here&lt;/a&gt;. Yes, that’s an HTML page. No, I haven’t been able to find a simple JSON feed or anything. Yes, it is 2021.&lt;/p&gt;
&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;p&gt;So anyway, here’s one way you can scrape it using Node.js:&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3 id=&#34;install-the-dependencies&#34;&gt;Install the dependencies.&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm install cheerio node-fetch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;scrape-it-like-you-mean-it&#34;&gt;Scrape it like you mean it.&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt; as cheerio from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;cheerio&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; fetch from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;node-fetch&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; htmlLikeIts1999 &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; fetch(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://ec.europa.eu/taxation_customs/business/vat/telecommunications-broadcasting-electronic-services/vat-rates_en&amp;#39;&lt;/span&gt;)).text()

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; $ &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; cheerio.load(htmlLikeIts1999)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; taxRates &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {}

$(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;tr.table-vat-rates&amp;#39;&lt;/span&gt;).each((index, row) =&amp;gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; cells &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; $(row).find(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;td&amp;#39;&lt;/span&gt;)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; countryName &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; cells.first().html()
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; taxRate &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;parseInt&lt;/span&gt;(cells.last().html().replace(&lt;span style=&#34;color:#235388&#34;&gt;/\&amp;lt;br.*?$/&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;))
  taxRates[countryName] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; taxRate
})

console.log(taxRates)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you run it, you should see a nice hash table of the latest VAT rates resembling the one below appear in your console.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;{
  Austria&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;20&lt;/span&gt;,
  Belgium&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;21&lt;/span&gt;,
  Bulgaria&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;20&lt;/span&gt;,
  Cyprus&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;19&lt;/span&gt;,
  &lt;span style=&#34;&#34;&gt;…&lt;/span&gt;
  Sweden&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;25&lt;/span&gt;,
  Slovenia&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;22&lt;/span&gt;,
  Slovakia&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;20&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, there you go.&lt;/p&gt;
&lt;p&gt;The JSON endpoint for EU VAT rates that you never knew you wanted is now yours to cherish.&lt;/p&gt;
&lt;p&gt;(Until they change the HTML structure, that is.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;w00t, etc.&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You feeling lucky? If you’re a real daredevil, why not use regular expressions instead? Go on… I double dare you…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; fetch from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;node-fetch&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; htmlLikeIts1999 &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; fetch(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://ec.europa.eu/taxation_customs/business/vat/telecommunications-broadcasting-electronic-services/vat-rates_en&amp;#39;&lt;/span&gt;)).text()

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; taxRates &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {}

htmlLikeIts1999.split(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;).filter(line =&amp;gt; line.includes(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;tr class=&amp;#39;&lt;/span&gt;)).map(row =&amp;gt; row.trim().replace(&lt;span style=&#34;color:#235388&#34;&gt;/^\&amp;lt;tr.*?\&amp;gt;/&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;).split(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;td&amp;#39;&lt;/span&gt;)).forEach(row =&amp;gt; taxRates[row[&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;].replace(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;gt;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;).replace(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;/&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;parseInt&lt;/span&gt;(row[&lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;].replace(&lt;span style=&#34;color:#235388&#34;&gt;/&amp;lt;br.*?$/&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;).replace(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;gt;&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)))

console.log(taxRates)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(It’s ok, you can cry a little now.) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Using the European Commission EU VAT Number validation API with Node.js</title>
      <link>https://ar.al/2021/05/14/using-the-european-commission-eu-vat-number-validation-api-with-node.js/</link>
      <pubDate>Fri, 14 May 2021 13:46:20 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/14/using-the-european-commission-eu-vat-number-validation-api-with-node.js/</guid>
      <description>&lt;p&gt;You may know of the &lt;a href=&#34;https://ec.europa.eu/taxation_customs/vies/&#34;&gt;VIES&lt;/a&gt; site where you can manually validate EU VAT numbers but did you know that the European Commission also has an API for programmatically doing this?&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;So that’s the good news.&lt;/p&gt;
&lt;p&gt;The bad news is that &lt;a href=&#34;https://en.wikipedia.org/wiki/SOAP&#34;&gt;it’s SOAP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ll wait until you’re done retching… ok, feeling better? Let’s move on.&lt;/p&gt;
&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;p&gt;Here’s how you consume it in Node.js:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3 id=&#34;install-the-soaphttpswwwnpmjscompackagesoap-package&#34;&gt;Install the &lt;a href=&#34;https://www.npmjs.com/package/soap&#34;&gt;soap&lt;/a&gt; package:&lt;/h3&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm install soap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;
&lt;h3 id=&#34;create-the-web-service-by-consuming-the-wsdl-filehttpseceuropaeutaxation_customsviescheckvatservicewsdl-and-calling-the-right-method2&#34;&gt;Create the web service by consuming &lt;a href=&#34;https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl&#34;&gt;the WSDL file&lt;/a&gt; and calling the right method:&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/h3&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; soap from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;soap&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; wsdl &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; client &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; soap.createClientAsync(wsdl)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; result &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; client.checkVatAsync({
  countryCode&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;EE&amp;#39;&lt;/span&gt;,
  vatNumber&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;100244258&amp;#39;&lt;/span&gt;
})

console.log(result)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that’s all there is to it.&lt;/p&gt;
&lt;p&gt;Enjoy! &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I didn’t until I stumbled onto it today while doing research into &lt;a href=&#34;https://ec.europa.eu/taxation_customs/business/vat/oss_en&#34;&gt;VAT OSS&lt;/a&gt; for &lt;a href=&#34;https://github.com/small-tech/basil&#34;&gt;Basil&lt;/a&gt;, which is part of our &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; initiative at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;VAT Number in the example retrieved following a web search from &lt;a href=&#34;https://www.seb.ee/eng/contact/contact&#34;&gt;SEB’s contact page&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;As much as that word can be used for anything tax-related. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Hell site</title>
      <link>https://ar.al/2021/05/10/hell-site/</link>
      <pubDate>Mon, 10 May 2021 16:20:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/10/hell-site/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/05/10/hell-site/./pigs-talking-about-the-free-model.png&#34; alt=&#34;Pigs talking about the “free” model: Pig 1: Isn’t it great? We have to pay nothing for the barn. Pig 2: Yeah and even the food is free.&#34;&gt;&lt;/p&gt;
&lt;p&gt;They have a word for Twitter on the fediverse.&lt;/p&gt;
&lt;p&gt;They call it “hell site”.&lt;/p&gt;
&lt;p&gt;It’s very apt.&lt;/p&gt;
&lt;p&gt;When I first joined about 15 years ago, at the end of 2006, it was a very different place. A small, non-algorithmically curated space where you could have group chats with your friends.&lt;/p&gt;
&lt;p&gt;What I didn’t know back then was that Twitter, Inc., was a venture-capital-funded startup.&lt;/p&gt;
&lt;p&gt;It wouldn’t have mattered if I’d known, either, as I was clueless about funding or business models. I thought everyone in tech was just trying to make the new everyday things that improved people’s lives.&lt;/p&gt;
&lt;p&gt;Even six years later, in 2012, I was still fixated on improving people’s experiences within the current system:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Objects have value not because of what they are but because of what they enable us to do.&lt;/p&gt;
&lt;p&gt;And, as the people who make objects, we have a profound responsibility. A responsibility to not take for granted the limited time that each of us has in this world. A responsibility to make that time as beautiful, as comfortable, as painless, as empowering, and as delightful as possible though the experiences that we craft.&lt;/p&gt;
&lt;p&gt;Because this is all there is.&lt;/p&gt;
&lt;p&gt;And it’s up to us to make it better.”&lt;/p&gt;
&lt;p&gt;– &lt;a href=&#34;http://www.breakingthin.gs/this-is-all-there-is.html&#34;&gt;This is all there is&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What a fool, right?&lt;/p&gt;
&lt;p&gt;Please take as much time as you need to point and laugh.&lt;/p&gt;
&lt;p&gt;OK, done? Let’s move on…&lt;/p&gt;
&lt;h2 id=&#34;privilege-is-just-another-word-for-not-having-to-care&#34;&gt;Privilege is just another word for not having to care&lt;/h2&gt;
&lt;p&gt;Back then, I took for granted that the system in general was mostly good. At least I didn’t think it was actively evil.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Sure, I was on the streets of London, along with hundreds of thousands of others, protesting the looming war in Iraq. And sure, I understood that we lived in an unequal, unjust, racist, sexist, and classist society (heck, I studied critical media theory for four years, so I had Chomsky coming out of my ears), but I somehow thought tech existed outside of this sphere. That is, if I thought about it at all.&lt;/p&gt;
&lt;p&gt;Which clearly just meant that things weren’t bad enough to be affecting me personally to a degree where I felt I should even educate myself about it. And that, folks, is what we call privilege.&lt;/p&gt;
&lt;p&gt;Sure, it felt odd whenever one of these startups did something that wasn’t in our best interests. But they told us they made mistakes and they apologised so we believed them. For a while. Until it became impossible to. And hey, I was just making “cool stuff” that “improved people’s lives” –  first in Flash and then for the iPhone and iPad…&lt;/p&gt;
&lt;p&gt;But I’m rushing ahead.&lt;/p&gt;
&lt;p&gt;Back to me being entirely ignorant of business models and venture capital. Hey, you might find yourself at this point today. There’s no shame in that. So listen closely: here’s the problem with venture capital.&lt;/p&gt;
&lt;h2 id=&#34;what-happens-in-venture-capital-stays-in-venture-capital&#34;&gt;What happens in Venture Capital stays in Venture Capital&lt;/h2&gt;
&lt;p&gt;Venture capital is a high-stakes game of roulette and Silicon Valley is the casino.&lt;/p&gt;
&lt;p&gt;A venture capitalist will invest, say, $5M in ten “startups” in full knowledge that nine of them will fail. What this gentleman needs (it’s almost always a “gentleman”) is for the remaining one to be a billion-dollar unicorn. And he (it’s almost always a he) doesn’t invest his own money either. He invests other people’s money. And they want 5×–10× their money back because this game of roulette is very risky.&lt;/p&gt;
&lt;p&gt;So how does a startup become a unicorn? Well, there is one battle-tested business model that is known to work: &lt;strong&gt;people farming.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3 id=&#34;addict-people&#34;&gt;Addict people.&lt;/h3&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Offer your service for free to your “users” and try to get as many people as possible hooked on your product.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because you need to &lt;a href=&#34;https://2017.ind.ie/excuse-me/&#34;&gt;scale exponentially&lt;/a&gt; to get the &lt;a href=&#34;https://en.wikipedia.org/wiki/Network_effect&#34;&gt;network effects&lt;/a&gt; and you the need network effects to lock in the people that you originally attracted.&lt;/p&gt;
&lt;p&gt;Heck, highly-celebrated folks have even written best-selling how-to guides about this step like &lt;a href=&#34;https://www.goodreads.com/book/show/22668729-hooked&#34;&gt;Hooked: How to Build Habit-Forming Products &lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That’s Silicon Valley for you.&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;
&lt;h3 id=&#34;farm-them&#34;&gt;Farm them.&lt;/h3&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Collect as much data about people as you can.&lt;/p&gt;
&lt;p&gt;Track them on your app, across the web, and even in physical space to create detailed profiles of their behaviour. Use this intimate insight into their lives to attempt to understand, predict, and manipulate them. Monetise this with your actual customers, who pay you for this service.&lt;/p&gt;
&lt;p&gt;This is what some folks call Big Data and what others call &lt;a href=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/&#34;&gt;surveillance capitalism&lt;/a&gt;.&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;
&lt;h3 id=&#34;exit-sell&#34;&gt;Exit (sell).&lt;/h3&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A startup is a temporary business and your endgame is to sell it to a wealthier startup or to an existing Big Tech company or to the public via an initial public offering (IPO).&lt;/p&gt;
&lt;p&gt;If you made it here, congratulations. You might just become Silicon Valley’s next douchebag billionaire and Bitcoin philanthropist.&lt;/p&gt;
&lt;p&gt;Many startups fail at the first step but as long as the VC gets their one unicorn, they’re happy.&lt;/p&gt;
&lt;h2 id=&#34;calling-bullshit-part-1&#34;&gt;Calling bullshit (part 1)&lt;/h2&gt;
&lt;p&gt;So I didn’t know that having venture capital meant Twitter had to grow exponentially and become a billion-dollar unicorn. I didn’t understand that those of us using it – and helping it improve – in those early days were ultimately responsible for its success. We were hoodwinked. (At least I was and I’m sure I’m not the only one who feels that way.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All this to say that Twitter was destined to become the Twitter it is today the moment it took its first bit of “angel investment” at the very beginning.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s just how the Silicon Valley game of venture capital and unicorns goes. It is what it is. &lt;strong&gt;And what it is everything I’ve been spending the last eight years to &lt;a href=&#34;https://small-tech.org/videos&#34;&gt;raise awareness about&lt;/a&gt;, &lt;a href=&#34;https://better.fyi&#34;&gt;protect people from&lt;/a&gt;, and &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;build alternatives to&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here are some recordings of my talks that you can watch from this period:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://2017.ind.ie/the-camera-panopticon/&#34;&gt;The Camera Panoption&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://2017.ind.ie/decentralise-everything/&#34;&gt;Decentralise Everything&lt;/a&gt; (2015)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://2017.ind.ie/excuse-me/&#34;&gt;Excuse Me, Your Unicorn Keeps Shitting In My Back Yard, Can He Please Not?&lt;/a&gt; (2016)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://small-tech.org/videos/ethical-design-and-democracy/&#34;&gt;Ethical Design and Democracy&lt;/a&gt; (2016)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://small-tech.org/videos/small-technology/&#34;&gt;Small Technology (with Laura Kalbag)&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://small-tech.org/videos/eastern-partnership-civil-society-online-hackathon-2020/&#34;&gt;Small Tech &amp;gt; Big Tech (with Laura Kalbag) – online&lt;/a&gt; (2020)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As part the “raising awareness” part, I was also trying to use platforms like Twitter and Facebook &lt;a href=&#34;https://ar.al/notes/spyware-vs-spyware/&#34;&gt;against the grain&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I wrote in &lt;a href=&#34;https://ar.al/notes/spyware-vs-spyware/&#34;&gt;Spyware vs Spyware&lt;/a&gt; in 2014: “We must use existing systems to promote our alternatives if our alternatives are to exist at all.” Even back then, that was perhaps rather optimistic but one crucial difference was that Twitter, at least, didn’t have an algorithmic timeline.&lt;/p&gt;
&lt;h2 id=&#34;algorithmic-timelines-or-gaslighting-20&#34;&gt;Algorithmic timelines (or gaslighting 2.0)&lt;/h2&gt;
&lt;p&gt;What is an algorithmic timeline? Let me try and explain.&lt;/p&gt;
&lt;p&gt;What you think happens when you tweet: “I have 44,000 people following me. When I write something, 44,000 people will see it.”&lt;/p&gt;
&lt;p&gt;What actually happens when you tweet: Your tweet might reach zero, fifteen, a few hundred, or a few thousand people.&lt;/p&gt;
&lt;p&gt;Based on what?&lt;/p&gt;
&lt;p&gt;Fuck knows.&lt;/p&gt;
&lt;p&gt;(Or, more precisely, only Twitter, Inc., knows.)&lt;/p&gt;
&lt;p&gt;So an algorithmic timeline is a black box that filters reality and decides who gets to see what and when based on an entirely arbitrary set of criteria determined by the corporate entity it belongs to.&lt;/p&gt;
&lt;p&gt;In other words, &lt;strong&gt;an algorithmic timeline is just a euphemism for mass socially-acceptable gaslighting. It is gaslighting 2.0.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-algorithm-is-an-asshole&#34;&gt;The algorithm is an asshole&lt;/h2&gt;
&lt;p&gt;The nature of the algorithm mirrors the nature of the corporation that owns and authors it.&lt;/p&gt;
&lt;p&gt;Given that &lt;a href=&#34;https://www.theguardian.com/commentisfree/2021/may/08/the-meltdown-at-basecamp-shows-even-small-tech-firms-are-sociopathic&#34;&gt;corporations are sociopathic by nature&lt;/a&gt;, it should come as no surprise that their algorithms are too. In short, &lt;strong&gt;the algorithms of people farmers like Twitter and Facebook are shit-stirring assholes that get off on causing as much conflict and controversy as possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hey, what did you expect exactly from &lt;a href=&#34;https://twitter.com/jack&#34;&gt;one billionaire who has “#Bitcoin” as their bio&lt;/a&gt; and &lt;a href=&#34;https://www.newyorker.com/magazine/2010/09/20/the-face-of-facebook&#34;&gt;another that calls the people who &lt;strike&gt;use&lt;/strike&gt; &lt;em&gt;are used by&lt;/em&gt; his service “dumb fucks?”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These bastards delight in showing you things they know will upset you in hopes that you will retaliate. They revel in the resulting fallout. Why? Because the more “engagement” there is on the platform – the more clicks, the more time their addicts (“users”) spend on it – the more money their corporations make.&lt;/p&gt;
&lt;p&gt;Well, enough of that, thank you very much.&lt;/p&gt;
&lt;h2 id=&#34;calling-bullshit-part-2&#34;&gt;Calling bullshit (part 2)&lt;/h2&gt;
&lt;p&gt;While I feel it’s important to raise awareness of the harms of Big Tech, I have probably said and written everything there is to say on the matter over the past eight years. I’ve given over a hundred &lt;a href=&#34;https://small-tech.org/videos&#34;&gt;talks&lt;/a&gt; at various conferences alone over that time, not to mention media interviews in print, and on radio, and television.&lt;/p&gt;
&lt;p&gt;Here are some links to a relative handful of the things I’ve written on the subject during this period:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/schnail-mail-free-real-mail-for-life/&#34;&gt;Schnail Mail&lt;/a&gt; (2013)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/beware-of-geeks-bearing-gifts/&#34;&gt;Beware of geeks bearing gifts&lt;/a&gt; (2013)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/trickle-down-technology/&#34;&gt;Trickle‐down technology and why it doesn’t work.&lt;/a&gt; (2013)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/towards-an-indie-tech-manifesto/&#34;&gt;Towards an Indie Tech Manifesto&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/how-web-2-0-killed-the-internet/&#34;&gt;How Web 2.0 Killed the Internet&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/spyware-2.0/&#34;&gt;Spyware 2.0&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/privacy-as-innovation/&#34;&gt;Privacy as Innovation&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/what-is-the-ind.ie-manifesto/&#34;&gt;What is the ind.ie manifesto?&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/why/&#34;&gt;Why?&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/alternatives/&#34;&gt;Alternatives&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/the-camera-panopticon/&#34;&gt;The Camera Panopticon&lt;/a&gt; (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/europe-we-need-to-talk-about-institutional-corruption/&#34;&gt;Europe, we need to talk about institutional corruption&lt;/a&gt; (2015)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/why-im-not-speaking-at-cpdp/&#34;&gt;Why I’m not speaking at CPDP (Hint: it’s the privacy-washing, stupid!)&lt;/a&gt; (2016)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;The nature of the self in the digital age&lt;/a&gt; (2016)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;Encouraging individual sovereignty and a healthy commons&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/we-didnt-lose-control-it-was-stolen/&#34;&gt;We didn’t lose control, it was stolen&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/decrypting-amber-rudd/&#34;&gt;Decrypting Amber Rudd&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/towards-an-internet-of-people-with-diem25&#34;&gt;Introducing the 7th pillar of DiEM25: An Internet of People – a progressive tech policy for a democratic Europe.&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/constructive-disobedience/&#34;&gt;Constructive disobedience&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/notes/farewell-not-goodbye&#34;&gt;Farewell, not goodbye: leaving DiEM25 (or “We need to talk about democracy, transparency, feminism, and Assange.”)&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;Web+&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/06/27/kyriarchy/&#34;&gt;Kyriarchy&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/06/29/reclaiming-rss/&#34;&gt;Reclaiming RSS&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/07/30/out-of-the-frying-pan-and-into-the-fire/&#34;&gt;Out of the frying pan and into the fire&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/08/04/multiwriter-dat-could-power-the-next-web/&#34;&gt;Multi-writer Dat could power the next Web&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/&#34;&gt;Better Blocker: two year review and thoughts on the future&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/08/29/extended-codice-interview-with-rai-1/&#34;&gt;Extended Codice Interview With Rai 1&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/&#34;&gt;Better, simpler, and more affordable&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/&#34;&gt;GDMR: this one simple regulation could end surveillance capitalism in the EU&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/12/07/baby-steps/&#34;&gt;Baby steps&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/&#34;&gt;Surveillance capitalism at the BBC&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2018/12/23/what-does-a-private-communicator-look-like/&#34;&gt;What does a private communicator look like?&lt;/a&gt; (2018)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/deployment-first-development/&#34;&gt;Deployment-first development&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/&#34;&gt;Success criteria for the PC 2.0 era&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/the-post-web-is-single-tenant/&#34;&gt;The post-Web is single tenant&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/&#34;&gt;I was wrong about Google and Facebook: there’s nothing wrong with them (so say we all)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;On the General Architecture of the Peer Web (and the placement of the PC 2.0 era within the timeline of general computing and the greater socioeconomic context)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/02/14/privacy-is-not-a-science-it-is-a-human-right/&#34;&gt;Privacy is not a science, it is a human right&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Technology&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;Slavery 2.0 and how to avoid it: a practical guide for cyborgs&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/05/11/the-dos-and-donts-of-tech-regulation/&#34;&gt;The Do’s and Don’ts of Tech Regulation&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/06/22/have-you-heard-about-silicon-valleys-unpaid-research-and-development-department-its-called-the-eu/&#34;&gt;Have you heard about Silicon Valley’s unpaid research and development department? It’s called the EU.&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/11/29/the-future-of-internet-regulation-at-the-european-parliament/&#34;&gt;The Future of Internet Regulation at the European Parliament&lt;/a&gt; (2019)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;In 2020 and beyond, the battle to save personhood and democracy requires a radical overhaul of mainstream technology&lt;/a&gt; (2020)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2020/03/25/apple-just-killed-offline-web-apps-while-purporting-to-protect-your-privacy-why-thats-a-bad-thing-and-why-you-should-care/&#34;&gt;Apple just killed Offline Web Apps while purporting to protect your privacy: why that’s A Bad Thing and why you should care&lt;/a&gt; (2020)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;What is the Small Web?&lt;/a&gt; (2020)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2021/04/16/clean-up-the-web/&#34;&gt;Clean up the Web&lt;/a&gt; (2021)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2021/05/08/ethics-as-pr-or-the-some-very-good-people-work-there-fallacy/&#34;&gt;Ethics as PR (or the ‘Some Very Good People Work There!’ Fallacy)&lt;/a&gt; (2021)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Has any of it done any good?&lt;/p&gt;
&lt;p&gt;I don’t know.&lt;/p&gt;
&lt;p&gt;I hope so.&lt;/p&gt;
&lt;p&gt;I also challenged countless folks at surveillance capitalists like Google and Facebook on Twitter and – before I left a few years ago – on Facebook, and elsewhere. (Anyone remember &lt;a href=&#34;http://hasericrepliedyet.com/&#34;&gt;the time I got Samuel L. Jackson to call out Eric Schmidt for Google mining people’s email?&lt;/a&gt;) That was fun. But I digress…&lt;/p&gt;
&lt;p&gt;Did any of that do any good?&lt;/p&gt;
&lt;p&gt;I don’t know.&lt;/p&gt;
&lt;p&gt;I hope so.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But here is what I do know:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does calling people out make me miserable? Yes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it nice? No.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I like conflict? No.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So enough is enough.&lt;/p&gt;
&lt;p&gt;People come up to me sometimes to thank me for “speaking out.” Well, that “speaking out” comes at a very high price. So maybe some of those folks can pick up where I left off. Or not. Either way, I’m done with it.&lt;/p&gt;
&lt;h2 id=&#34;in-your-face&#34;&gt;In your face&lt;/h2&gt;
&lt;p&gt;One thing you have to understand about surveillance capitalism is that it is the mainstream. It is the dominant model. Every Big Tech company and startup is part of it&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. And being exposed to their latest bullshit and to hypocritical posts by people who proudly affiliate with them while purporting to work for social justice is not good for anyone’s mental health.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s like living in a factory farm owned by wolves where the loudest proponents of the system are the chickens that have been hired as line managers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ve spent the last eight years, at least, responding to such things and trying to point out how Big Tech and surveillance capitalism cannot be reformed.&lt;/p&gt;
&lt;p&gt;And it makes me miserable.&lt;/p&gt;
&lt;p&gt;So I’m through doing so on platforms with shit-stirring asshole algorithms that get off on inflicting as much misery on me as they can in hopes of getting a rise out of me because that “makes the number go up.”&lt;/p&gt;
&lt;p&gt;Fuck you, Twitter!&lt;/p&gt;
&lt;p&gt;I’m done with your bullshit.&lt;/p&gt;
&lt;h2 id=&#34;what-next&#34;&gt;What next?&lt;/h2&gt;
&lt;p&gt;In a lot of ways, this decision has been a long-time coming. I set up &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;my own place on the fediverse&lt;/a&gt; using &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt; several years ago and have been using it ever since. If you haven’t heard of the fediverse, think of it like this:&lt;/p&gt;
&lt;p&gt;Imagine that you (or your group of friends) own your own copy of &lt;code&gt;twitter.com&lt;/code&gt;. But instead of &lt;code&gt;twitter.com&lt;/code&gt;, yours is at &lt;code&gt;your-place.org&lt;/code&gt;. And instead of Jack Dorsey, you make the rules.&lt;/p&gt;
&lt;p&gt;You’re not limited to just talking to people on &lt;code&gt;your-place.org&lt;/code&gt; either.&lt;/p&gt;
&lt;p&gt;I also own my own place at &lt;code&gt;my-place.org&lt;/code&gt; (let’s say I’m &lt;code&gt;@me@my-place.org&lt;/code&gt;). I can follow &lt;code&gt;@you@your-place.org&lt;/code&gt; as well as &lt;code&gt;@them@their.site&lt;/code&gt; and &lt;code&gt;@someone-else@some-other.place&lt;/code&gt;. It works because we all speak a common language called &lt;a href=&#34;https://en.wikipedia.org/wiki/ActivityPub&#34;&gt;ActivityPub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So imagine a world where there are thousands of &lt;code&gt;twitter.coms&lt;/code&gt; and they can all talk to one another and Jack has fuck-all to do with it.&lt;/p&gt;
&lt;p&gt;Well, that’s &lt;a href=&#34;https://en.wikipedia.org/wiki/Fediverse&#34;&gt;the fediverse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And while Mastodon is just one way to get your own place on the fediverse, &lt;a href=&#34;https://joinmastodon.org/&#34;&gt;joinmastodon.org&lt;/a&gt; is a good place to start learning about it and get started in a way that is friendly and doesn’t require any technical knowledge.&lt;/p&gt;
&lt;p&gt;As I already mentioned, I’ve been on the fediverse since the earliest days of Mastodon and I was already manually forwarding posts from there to Twitter.&lt;/p&gt;
&lt;p&gt;I’ve now automated that process using &lt;a href=&#34;https://moa.party/&#34;&gt;moa.party&lt;/a&gt; and, &lt;strong&gt;going forward, I will no longer be checking Twitter or responding on it.&lt;/strong&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;As my posts on Mastodon are now automatically forwarded there, you can still use it to keep up with what I’m doing, if you like. Or, why not take the opportunity to &lt;a href=&#34;https://joinmastodon.org/&#34;&gt;join the fediverse&lt;/a&gt; and have a play?&lt;/p&gt;
&lt;h2 id=&#34;small-is-beautiful&#34;&gt;Small is Beautiful&lt;/h2&gt;
&lt;p&gt;While I still believe that having good critiques of Big Tech is essential to influencing effective regulation, I don’t know if effective regulation is even possible given the levels of institutional corruption we have today (lobbying, revolving doors, public-private-partnerships, corporate capture, etc.)&lt;/p&gt;
&lt;p&gt;What I do know is that the antidote to Big Tech is &lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;Small Tech&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We must build alternative technological infrastructure that is owned and controlled by individuals – not corporations or governments. This is a prerequisite for a future with personhood, human rights, democracy, and social justice.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Otherwise, we face a bleak tomorrow where our only agency is to beg some Silicon Valley king or other to “please sir, be kind”.&lt;/p&gt;
&lt;p&gt;I also know that working on building such alternatives makes me happy while despairing at the state of the world just makes me utterly miserable. I know it’s privilege to have the skills and experience I have that enable me to work on such things. And I intend on making the most of it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Going forward, I plan to concentrate as much of my time and energy as possible on building the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’d like to talk about that (or anything else), you can &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;find me on the fediverse&lt;/a&gt;. You can also chat to me on my &lt;a href=&#34;https://ar.al&#34;&gt;S’update&lt;/a&gt; live streams here and during our &lt;a href=&#34;https://small-tech.org/small-is-beautiful&#34;&gt;Small is Beautiful&lt;/a&gt; live streams with &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here’s to brighter days ahead…&lt;/p&gt;
&lt;p&gt;Stay safe.&lt;/p&gt;
&lt;p&gt;Be well.&lt;/p&gt;
&lt;p&gt;Love one another.&lt;/p&gt;
&lt;p&gt;💕️&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Not that good and evil are the best ways to talk about all this. True evil is much more mundane and often far less intentioned that what you see in the movies. Usually, it simply involves remaining neutral in situations of injustice. In the words of Desmond Tutu, who I must have quoted a hundred times in the past eight years or so, “If you are neutral in situations of injustice, you have chosen the side of the oppressor. If an elephant has its foot on the tail of a mouse and you say that you are neutral, the mouse will not appreciate your neutrality.” &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you’re not a venture-capital-funded new tech company, don’t call yourself a startup, that’s their brand and, if they’re in the same area as you, they have $5M in the bank to undercut and destroy your sustainable small tech business with. So don’t legitimise them by calling yourself that. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m also planning on unfollowing everyone on Twitter so folks are not mislead into thinking I will be seeing their posts (which, given what we know ­– or, more precisely, what we &lt;em&gt;don’t know&lt;/em&gt; – about Twitter’s algorithm, was never a given anyway). It might take some time for me to fully realise that as it’s not a priority. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Ethics as PR (or the ‘Some Very Good People Work There!’ Fallacy)</title>
      <link>https://ar.al/2021/05/08/ethics-as-pr-or-the-some-very-good-people-work-there-fallacy/</link>
      <pubDate>Sat, 08 May 2021 11:03:20 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/05/08/ethics-as-pr-or-the-some-very-good-people-work-there-fallacy/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/05/08/ethics-as-pr-or-the-some-very-good-people-work-there-fallacy/doctors-in-cigarette-ads.webp&#34; alt=&#34;Cigarette ad from the United States circa 1930s. Photo of a male doctor smoking. “More Doctors Smoke Camels Than Any Other Cigarette”&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;ethics-in-ai&#34;&gt;“Ethics in AI”&lt;/h3&gt;
&lt;p&gt;There’s been a lot of research into “Ethics in AI” recently and it’s being sponsored and led by… &lt;em&gt;&lt;strong&gt;*checks notes*&lt;/strong&gt;&lt;/em&gt;… &lt;a href=&#34;https://www.newstatesman.com/science-tech/technology/2019/06/how-big-tech-funds-debate-ai-ethics&#34;&gt;surveillance capitalists like DeepMind&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Similarly, lots of people are working on “Ethics in Health” at Philip Morris and “Ethics in Environmentalism” at ExxonMobil.&lt;/p&gt;
&lt;p&gt;OK, I made the last two up.&lt;/p&gt;
&lt;p&gt;Did you notice?&lt;/p&gt;
&lt;p&gt;(I guess it was pretty blatant.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Because, clearly, that would be &lt;em&gt;ethicswashing&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So how come we accept the first?&lt;/p&gt;
&lt;p&gt;If someone told you they were working on environmental ethics at ExxonMobil, they would get laughed out of the room. Yet folks who work on “Ethics in AI” at surveillance capitalists like Google and Facebook are invited to talk to policymakers and sit at the table when crafting privacy and other &lt;a href=&#34;https://ar.al/2019/05/11/the-dos-and-donts-of-tech-regulation/&#34;&gt;technology regulation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;environmental-ethics-at-exxonmobil&#34;&gt;Environmental ethics at ExxonMobil&lt;/h3&gt;
&lt;p&gt;The only way I can make sense of all this is that we don’t see a Google or a Facebook as being as bad as a Philip Morris or an ExxonMobil.&lt;/p&gt;
&lt;p&gt;I’ve been trying to explain why they are just as bad &lt;a href=&#34;https://small-tech.org/videos&#34;&gt;for the past seven years&lt;/a&gt; and I don’t know what more there is I can say.&lt;/p&gt;
&lt;p&gt;My goal here isn’t to point fingers at the people who take these positions (although, goodness knows, if you have an alternative, please take it), but at the corporations themselves who are creating the positions to begin with as a cynical part of their lobbying and public relations initiatives.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://venturebeat.com/2020/12/11/what-big-tech-and-big-tobacco-research-funding-have-in-common/&#34;&gt;Big Tech is following Big Tobacco’s rule book&lt;/a&gt; to the letter and it seems to be working splendidly so far.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/11/29/the-future-of-internet-regulation-at-the-european-parliament/&#34;&gt;This is why we can’t regulate effectively.&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The truly &lt;a href=&#34;https://www.mayoclinicproceedings.org/article/S0025-6196(11)60563-6/fulltext&#34;&gt;damning evidence of Big Tobacco’s behavior&lt;/a&gt; only came to light after years of litigation. However, the parallels between the public facing history of Big Tobacco’s behavior and the current behavior of Big Tech should be a cause for concern … We believe that it is vital, particularly for universities and other institutions of higher learning, to discuss the appropriateness and the tradeoffs of accepting funding from Big Tech, and what limitations or conditions should be put in place.”&lt;/p&gt;
&lt;p&gt;– Mohamed Abdalla (PhD student, University of Toronto Center for Ethics) and Moustafa Abdalla (student, Harvard Medical School) (&lt;a href=&#34;https://venturebeat.com/2020/12/11/what-big-tech-and-big-tobacco-research-funding-have-in-common/&#34;&gt;What Big Tech and Big Tobacco research funding have in common&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;institutional-corruption&#34;&gt;Institutional corruption&lt;/h3&gt;
&lt;p&gt;We are &lt;a href=&#34;https://ar.al/notes/europe-we-need-to-talk-about-institutional-corruption/&#34;&gt;institutionally corrupt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition to the massive influence of &lt;a href=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/&#34;&gt;lobbying, revolving doors, and sponsorships&lt;/a&gt;, we also have &lt;a href=&#34;https://www.escr-net.org/corporateaccountability/corporatecapture&#34;&gt;corporate capture&lt;/a&gt; of academia.&lt;/p&gt;
&lt;p&gt;Thanks to this neoliberal success story, university departments today are left at the mercy of funding by industry. If the only way an academic in the field can progress is to work with or secure funding from a Big Tech company that is not necessarily the academic’s fault, per se, but a systemic failure.&lt;/p&gt;
&lt;p&gt;This is not to say that we should disregard those who make sacrifices to go against the grain and vocally oppose and refuse to take part in this system. On the contrary, we should be celebrating and supporting them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“During a panel conversation, Black in AI cofounder Rediet Abebe said she will refuse to take funding from Google, &lt;strong&gt;and that more senior faculty in academia need to speak up.&lt;/strong&gt; Next year, Abebe will become the first Black woman assistant professor ever in the Electrical Engineering and Computer Science (EECS) department at UC Berkeley.&lt;/p&gt;
&lt;p&gt;‘Maybe a single person can do a good job separating out funding sources from what they’re doing, but you have to admit that in aggregate there’s going to be an influence. If a bunch of us are taking money from the same source, there’s going to be a communal shift towards work that is serving that funding institution,’ she said.”&lt;/p&gt;
&lt;p&gt;– &lt;a href=&#34;https://venturebeat.com/2020/12/11/what-big-tech-and-big-tobacco-research-funding-have-in-common/&#34;&gt;What Big Tech and Big Tobacco research funding have in common&lt;/a&gt; (emphasis mine)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;when-pr-backfires&#34;&gt;When PR backfires&lt;/h3&gt;
&lt;p&gt;Big Tech is not infallible.&lt;/p&gt;
&lt;p&gt;Far from it.&lt;/p&gt;
&lt;p&gt;When &lt;em&gt;ethicswashing&lt;/em&gt; goes wrong, it can backfire spectacularly and become a PR nightmare as we saw with the example of the ethicists hired by Google &lt;a href=&#34;https://www.theverge.com/2020/12/3/22150355/google-fires-timnit-gebru-facial-recognition-ai-ethicist&#34;&gt;who got fired when they didn’t reach the conclusions the corporation wanted them to&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a relationship that is destined to fail as long as an ethicist does what their job title says instead of what the corporation wants them to do. Because surveillance capitalists like Google are fundamentally unethical.&lt;/p&gt;
&lt;p&gt;And while &lt;a href=&#34;https://www.theverge.com/2021/4/13/22370158/google-ai-ethics-timnit-gebru-margaret-mitchell-firing-reputation&#34;&gt;fallout like this can undo the positive PR that Google is trying to cultivate&lt;/a&gt;, the presence of respected people in these positions is often all that’s necessary for the corporation to benefit from their legitimacy.&lt;/p&gt;
&lt;p&gt;Maybe what we need is Big Tech’s own &lt;a href=&#34;https://academic.oup.com/ntr/article/20/8/1033/4774596&#34;&gt;Surgeon General’s Reports&lt;/a&gt; moment. But that’s not going to be likely if the Surgeon General works for Philip Morris.&lt;/p&gt;
&lt;h3 id=&#34;greta-thunberg-would-never-work-at-exxonmobil&#34;&gt;Greta Thunberg would never work at ExxonMobil&lt;/h3&gt;
&lt;p&gt;Being a privacy or AI ethicist at a Silicon Valley Big Tech company is the same as being an environmental ethicist at ExxonMobil: your very presence there legitimises the corporation.&lt;/p&gt;
&lt;p&gt;There is a reason Greta Thunberg would never work at ExxonMobil.&lt;/p&gt;
&lt;p&gt;There is a reason doctors would not work at Philip Morris.&lt;/p&gt;
&lt;p&gt;There is a reason pacifists would never join the military.&lt;/p&gt;
&lt;p&gt;Because no matter how well-intentioned you are, you are not going to change the fundamental nature of these institutions.&lt;/p&gt;
&lt;p&gt;For corporations, their fundamental nature is dictated by their business model. For surveillance capitalists, the business model is based on mass violations of your privacy that are used to profile you with the goal of understanding, predicting and affecting your behaviour for profit. This is a fundamentally unethical business model within a fundamentally unethical socioeconomic system.&lt;/p&gt;
&lt;p&gt;These corporations will employ you for as long as they can launder their reputations by using your legitimacy. And when you become too problematic, they will spit you right out again.&lt;/p&gt;
&lt;p&gt;The sooner we all realise this, the sooner we tell them “we see what you’re doing”, the sooner no one else has to go through this. Because this isn’t good for anyone involved.&lt;/p&gt;
&lt;p&gt;‘Ethics’, as far as a surveillance capitalist is concerned, is public relations.&lt;/p&gt;
&lt;p&gt;And I know that’s not what anyone signed up for.&lt;/p&gt;
&lt;h3 id=&#34;lots-of-very-good-people&#34;&gt;Lots of very good people…&lt;/h3&gt;
&lt;p&gt;Whenever there’s criticism of a surveillance capitalist like Google or Facebook, someone always says “but I know lots of very good people who work there.”&lt;/p&gt;
&lt;p&gt;I don’t doubt it for a minute.&lt;/p&gt;
&lt;p&gt;Heck, &lt;em&gt;I know lots of very good people who work there!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In fact, the more wonderful they are, the more legitimacy they add to the unethical corporation they work at. It’s time we at least acknowledged that this is a problem even if we don’t have all the solutions yet.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Acknowledging the problem is the first step in making these corporations socially unacceptable so we can effectively regulate them and secure funding for alternatives. Alternatives not just in technology but also in academia where these good folks can carry out truly independent research for the common good.&lt;/p&gt;
&lt;p&gt;Eric Schmidt once told me “if we ever get too evil, we won’t be able to find anyone to work for us.”&lt;/p&gt;
&lt;p&gt;I guess either he was wrong or we just don’t think they’re evil enough yet.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Owned by &lt;a href=&#34;https://en.wikipedia.org/wiki/Alphabet_Inc.&#34;&gt;Alphabet, Inc.&lt;/a&gt;, the same multi-trillion-dollar corporation that owns Google, YouTube, etc. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And realise, I’m not talking about the folks who work in the cafeteria at Google but the ones with PhDs that sit on policymaking committees and the engineers on six-figure salaries with stock options that keep the machine ticking; the latter being the ones who might have other options even within this capitalist hellscape that we find ourselves mired in. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Using Subresource Integrity (SRI) in Vite with @small-tech/vite-plugin-sri</title>
      <link>https://ar.al/2021/04/19/subresource-integrity-in-vite/</link>
      <pubDate>Mon, 19 Apr 2021 12:24:02 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/04/19/subresource-integrity-in-vite/</guid>
      <description>&lt;p&gt;A few weeks ago I created a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity&#34;&gt;Subresource Integrity&lt;/a&gt; (SRI) plugin called &lt;a href=&#34;https://github.com/small-tech/vite-plugin-sri#readme&#34;&gt;@small-tech/vite-plugin-sri&lt;/a&gt; for &lt;a href=&#34;https://vitejs.dev&#34;&gt;Vite&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-is-sri-and-why-should-i-care&#34;&gt;What is SRI and why should I care?&lt;/h2&gt;
&lt;p&gt;SRI is important if you don’t trust the place you’re loading resources (scripts and styles) from.&lt;/p&gt;
&lt;p&gt;In your HTML file, you provide the hash of each external resource you’re loading. The browser then guarantees that the resource that’s loaded is the one you expected (or it doesn’t load it).&lt;/p&gt;
&lt;p&gt;SRI was introduced on the Big Web because site owners don’t trust Content Delivery Networks (CDNs)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. On the &lt;a href=&#34;https://small-tech.org/research-and-development/&#34;&gt;Small Web&lt;/a&gt; (see &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;What is the Small Web&lt;/a&gt;), we don’t trust any server, not even the one our app is being loaded from. So we use SRI for all external resources loaded by our apps.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;how-do-i-use-it&#34;&gt;How do I use it?&lt;/h2&gt;
&lt;p&gt;Just add the plugin to your &lt;code&gt;vite.config.js&lt;/code&gt; file, like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { defineConfig } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;vite&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; sri from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/vite-plugin-sri&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; defineConfig({
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  plugins&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; [sri()]
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And run your build:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npx vite build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;is-this-for-vite-only&#34;&gt;Is this for Vite only?&lt;/h2&gt;
&lt;p&gt;This plugin is specifically for Vite but &lt;a href=&#34;https://github.com/JonasKruckenberg&#34;&gt;Jonas Kruckenberg&lt;/a&gt; has a generic plugin for Rollup called &lt;a href=&#34;https://github.com/JonasKruckenberg/rollup-plugin-sri&#34;&gt;rollup-plugin-sri&lt;/a&gt; that does the same thing, and which mine was inspired by.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I talk more about what could happen if a CDN does not support SRI in &lt;a href=&#34;https://ar.al/2020/12/30/skypack-backdoor-as-a-service/&#34;&gt;Skypack: Backdoor as a Service?&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;So what about the initial index HTML file that’s served? Surely a malicious server could just serve anything it wants there and all our SRI stuff becomes security/privacy theatre, as the root of our trust model is broken, no?&lt;/p&gt;
&lt;p&gt;If we limit ourselves to the context of the browser, yes.&lt;/p&gt;
&lt;p&gt;That’s why, for the Small Web, we need an out-of-band means (e.g., a browser extension) of verifying that the source of the initial index file is also what we expect it to be (e.g., by verifying the hash and signature embedded in the source).&lt;/p&gt;
&lt;p&gt;Whether or not you need to do any of this, of course, depends on your threat model. For most people, I would consider it overkill as compromising a web host to such a degree to carry out a targetted phishing attack on someone is something I’d consider a state-level attack.&lt;/p&gt;
&lt;p&gt;And yet this is the only way with a web app to truly ensure that the place you’re going to enter your all-important passphrase into is what you expect it to be. So, of course, we are going to support it. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Clean up the web</title>
      <link>https://ar.al/2021/04/16/clean-up-the-web/</link>
      <pubDate>Fri, 16 Apr 2021 19:45:42 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/04/16/clean-up-the-web/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Developers, it’s time for you to choose a side:&lt;/strong&gt; will you help rid the web of privacy-invading tracking or be complicit in it?&lt;/p&gt;
&lt;h3 id=&#34;-httpscleanuptheweborg&#34;&gt;🚮️ &lt;a href=&#34;https://CleanUpTheWeb.org&#34;&gt;https://CleanUpTheWeb.org&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Spread the word using the #CleanUpTheWeb and #FlocOffGoogle hashtags.&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Passing data from layouts to pages in SvelteKit</title>
      <link>https://ar.al/2021/04/03/passing-data-from-layouts-to-pages-in-sveltekit/</link>
      <pubDate>Sat, 03 Apr 2021 13:26:13 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/04/03/passing-data-from-layouts-to-pages-in-sveltekit/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/04/03/passing-data-from-layouts-to-pages-in-sveltekit/sveltekit-data-flow.svg&#34;
         alt=&#34;Sequence diagram representing the data flow detailed in the text of this post.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Data flow from layout to page (slot) in SvelteKit&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’m prototyping Basil, the free and open hosting client that’s going to power &lt;a href=&#34;https://small-web.org&#34;&gt;small-web.org&lt;/a&gt;, in &lt;a href=&#34;https://kit.svelte.dev/&#34;&gt;SvelteKit&lt;/a&gt; and one thing I want to ensure from the outset is that the app is not hardcoded for our use so that anyone can easily set up a &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; host simply by installing and configuring it.&lt;/p&gt;
&lt;p&gt;Now, ideally, I want that configuration data to be loaded and rendered on the server (SSR/SSG) once.&lt;/p&gt;
&lt;p&gt;Given that you cannot use Node APIs in &lt;a href=&#34;https://kit.svelte.dev/docs#layouts&#34;&gt;layouts&lt;/a&gt;/&lt;a href=&#34;https://kit.svelte.dev/docs#routing-pages&#34;&gt;pages&lt;/a&gt; even when they’re rendered on the server, the way to do this is to use an &lt;a href=&#34;https://kit.svelte.dev/docs#routing-endpoints&#34;&gt;endpoint&lt;/a&gt; and consume it on the server in your main layout and then pass the data to any pages the layout has slotted into it using &lt;a href=&#34;https://kit.svelte.dev/docs#loading-output-context&#34;&gt;the context&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To cut a long story short, I struggled a bit to get this running initially as there are two separate “context” concepts in SvelteKit&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; and I couldn’t find any code samples when I was starting out with it. So here’s the code sample I wish I had.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Remember that SvelteKit is in beta and this might all change. There’s also the chance that, while this works, I’m still “holding it wrong” and there might be an even more straightforward way to achieve this that I do not know about.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;the-endpoint&#34;&gt;The endpoint&lt;/h2&gt;
&lt;p&gt;The rest of this post assumes that you’ll be working with the following endpoint, defined in &lt;code&gt;src/routes/config.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; os from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;os&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; fs from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; path from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Load the configuration from ~/.small-tech.org/basil/config.json
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; config &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSON.parse(fs.readFileSync(path.join(os.homedir(), &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.small-tech.org&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;basil&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;config.json&amp;#39;&lt;/span&gt;), &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Return the configuration in response to a GET request on /config
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; get() {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; {
    body&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; config
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;layout-load-the-data&#34;&gt;Layout: load the data&lt;/h2&gt;
&lt;p&gt;You load the data in the &lt;a href=&#34;https://kit.svelte.dev/docs#loading&#34;&gt;load()&lt;/a&gt; handler.&lt;/p&gt;
&lt;p&gt;For example, your layout in &lt;code&gt;src/routes/$layout.svelte&lt;/code&gt; might resemble the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;context&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module&amp;#39;&lt;/span&gt;&amp;gt;
	&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;../app.css&amp;#39;&lt;/span&gt;
	&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; Nav from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;../lib/Nav.svelte&amp;#39;&lt;/span&gt;

	&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; load({ page, fetch, session, context }) {
		&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; url &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/config&amp;#39;&lt;/span&gt;
		&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; res &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; fetch(url)

		&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (res.ok) {
			&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; config &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; res.json()
			&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; { context&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; { config } }
		}

		&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; {
			status&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; res.status,
			error&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;`Could not load &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;url&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
		};
	}
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;Nav&lt;/span&gt;/&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;slot&lt;/span&gt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two key things to note here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;This is a &lt;a href=&#34;https://kit.svelte.dev/docs#ssr-and-javascript&#34;&gt;context=&amp;lsquo;module&amp;rsquo;&lt;/a&gt; script, not a regular script tag.&lt;/li&gt;
&lt;li&gt;We’re returning an object with a &lt;code&gt;context&lt;/code&gt; property from the &lt;code&gt;load()&lt;/code&gt; handler.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So what’s this context property?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://kit.svelte.dev/docs#loading-output-context&#34;&gt;According to the docs&lt;/a&gt;, it is exactly what we need:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This will be merged with any existing context and passed to the load functions of subsequent layout and page components.&lt;/p&gt;
&lt;p&gt;This only applies to layout components, not page components.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;The important thing to note is that it is not the same as the context you manipulate using the &lt;code&gt;setContext()&lt;/code&gt; and &lt;code&gt;getContext()&lt;/code&gt; methods. In fact, if you try to access those methods from &lt;code&gt;context=&#39;module&#39;&lt;/code&gt; scripts, you will get an error.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;page-load-the-data-from-the-context&#34;&gt;Page: load the data from the context&lt;/h2&gt;
&lt;p&gt;Now, let’s say you have a page at &lt;code&gt;src/routes/index.svelte&lt;/code&gt; that gets slotted into your layout.&lt;/p&gt;
&lt;p&gt;To use the configuration data, you just need to get it from the context you returned in your layout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;context&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module&amp;#39;&lt;/span&gt;&amp;gt;
	&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; config
	&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; load({ page, fetch, session, context }) {
		config &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; context.config
		&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;
	}
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;main&lt;/span&gt;&amp;gt;
	&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;{config.name}&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
	&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;About this Small Web host&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;
	&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{config.description}&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;main&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
	&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* etc. */&lt;/span&gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it!&lt;/p&gt;
&lt;p&gt;Hope it helps in case you were stuck trying to do the same thing :)&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can actually also use the other type of context in Svelte/SvelteKit for this also but it is a far more convoluted process that involves &lt;a href=&#34;https://kit.svelte.dev/docs#loading-output-props&#34;&gt;returning a prop&lt;/a&gt; from the &lt;code&gt;load()&lt;/code&gt; handler in your layout and storing it in the context using &lt;code&gt;setContext()&lt;/code&gt; from a separate script tag (not your &lt;code&gt;context=&#39;module&#39;&lt;/code&gt; script tag) in the layout, and, finally, using &lt;code&gt;getContext()&lt;/code&gt; to retrieve it from the script tag (not your &lt;code&gt;context=&#39;module&#39;&lt;/code&gt; script tag) of your page. &lt;em&gt;Whew!&lt;/em&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Site.js starter template for vite &#43; svelte</title>
      <link>https://ar.al/2021/04/01/site.js-starter-template-for-vite-plus-svelte/</link>
      <pubDate>Thu, 01 Apr 2021 16:35:45 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/04/01/site.js-starter-template-for-vite-plus-svelte/</guid>
      <description>&lt;p&gt;Over the past few weeks, I’ve been experimenting with some initial clients for the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; clients for &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; and &lt;a href=&#34;https://github.com/small-tech/place&#34;&gt;Place&lt;/a&gt; using &lt;a href=&#34;https://snowpack.dev&#34;&gt;Snowpack&lt;/a&gt; and &lt;a href=&#34;https://svelte.dev&#34;&gt;Svelte&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I finally got everything working after some trouble (and a few discussions/pull requests, etc.), some recent regressions in Snowpack broke what I had again. So I thought that I’d give &lt;a href=&#34;https://vitejs.dev&#34;&gt;Vite&lt;/a&gt; a shot, both out of curiosity and to see if, possibly, things could be simpler.&lt;/p&gt;
&lt;p&gt;Fast forward a couple of days and I’m ahead of where I was previously. This is not to poop on the Snowpack folks, they’ve been rather responsive and are a small team trying first and foremost to build a CDN (which I do have issues with, &lt;a href=&#34;https://ar.al/2020/12/30/skypack-backdoor-as-a-service/&#34;&gt;due to the lack of subresource integrity&lt;/a&gt;, but that’s a different conversation)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;site-vite-svelte&#34;&gt;site-vite-svelte&lt;/h2&gt;
&lt;p&gt;One of the things I wanted to do was to create a starter template for kickstarting projects that use this combination of client-side tech. So today I released the site-vite-svelte template for Site.js.&lt;/p&gt;
&lt;h3 id=&#34;installation&#34;&gt;Installation&lt;/h3&gt;
&lt;p&gt;To install it, just do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm init using small-tech/site-vite-svelte my-project
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That will use &lt;a href=&#34;https://ar.al/2021/04/01/npm-init-using/&#34;&gt;npm init using&lt;/a&gt; to download the contents of the &lt;a href=&#34;https://github.com/small-tech/site-vite-svelte&#34;&gt;site-vite-svelte repository&lt;/a&gt; to your &lt;code&gt;my-project&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Then, just do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; my-project
npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And you’re ready to go!&lt;/p&gt;
&lt;h3 id=&#34;usage&#34;&gt;Usage&lt;/h3&gt;
&lt;p&gt;You can choose to either run just the Vite + Svelte client using &lt;code&gt;npm run vite&lt;/code&gt; or you can run Site.js + the Vite + Svelte client using &lt;code&gt;npm run dev&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For full documentation, &lt;a href=&#34;https://github.com/small-tech/site-vite-svelte#readme&#34;&gt;please see the readme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I know this isn’t &lt;a href=&#34;https://sitejs.org/#static-sites&#34;&gt;as smoothly integrated as Hugo is&lt;/a&gt; into Site.js but that’s on purpose. When it comes to Place, having the client decoupled from the Small Web protocol server is a feature, not a bug. As for Site.js, I want to see how I get on with these particular modules in a decoupled manner before deciding whether it’s worth integrating them more closely&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So feel free to have a play and let me know how you get on if you do and I hope you find it useful.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On the topic of subresource integrity, I also released &lt;a href=&#34;https://github.com/small-tech/vite-plugin-sri&#34;&gt;a vite plugin for subresource integrity&lt;/a&gt; yesterday. Enjoy! &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Also, I’m aware that while Place is lean (and will hopefully only get leaner as it shapes up), Site.js is quite bloated right now and I don’t want to add to that. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>npm init using</title>
      <link>https://ar.al/2021/04/01/npm-init-using/</link>
      <pubDate>Thu, 01 Apr 2021 16:04:54 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/04/01/npm-init-using/</guid>
      <description>&lt;p&gt;Want to download a git repository to use as a starting point for your own Node.js app but you don’t want to clone the repository?&lt;/p&gt;
&lt;p&gt;Try this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm init using &amp;lt;github-account&amp;gt;/&amp;lt;starter-project-name&amp;gt; &amp;lt;my-project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What that will do is download the specified repository into the &lt;code&gt;my-project&lt;/code&gt; directory on your local machine.&lt;/p&gt;
&lt;p&gt;Then, you can just:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; my-project
npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And you’re off!&lt;/p&gt;
&lt;p&gt;For example, to start using the &lt;a href=&#34;https://github.com/small-tech/site-vite-svelte&#34;&gt;Site.js starter template for Vite + Svelte&lt;/a&gt;, do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm init using small-tech/site-vite-svelte my-site
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;how-it-works&#34;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Under the hood, this uses a &lt;a href=&#34;https://github.com/tiged/tiged&#34;&gt;tiged&lt;/a&gt;, a community-maintained forked of &lt;a href=&#34;https://github.com/Rich-Harris/degit&#34;&gt;degit&lt;/a&gt; (see what they did there?) that I then subsequently forked and renamed to &lt;a href=&#34;https://github.com/aral/create-using&#34;&gt;create-using&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The way &lt;code&gt;npm init&lt;/code&gt; works, &lt;code&gt;npm init something&lt;/code&gt; gets translated to &lt;code&gt;npx create-something&lt;/code&gt;, which in turn downloads and runs the &lt;code&gt;create-something&lt;/code&gt; module from npm, which is supposed to, well, create &lt;em&gt;something&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In this case, &lt;code&gt;create-using&lt;/code&gt; (being &lt;code&gt;tiged&lt;/code&gt;) downloads and extracts the tarball of the referenced git repository on GitHub (other hosts are also supported) to the local directory you specify.&lt;/p&gt;
&lt;p&gt;So, basically it’s a little hack so we can have more expressive syntax.&lt;/p&gt;
&lt;p&gt;If you don’t care for such things, you can do exactly the same thing using &lt;code&gt;tiged&lt;/code&gt; directly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npx tiged small-tech/site-vite-svelte my-site
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Personally, I’d rather remember &lt;code&gt;npm init using&lt;/code&gt; but you do whatever floats your boat. Heck, you can even go old-skool and just clone the repository using git like some sort of cavaman! 😛️&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>References to methods are a JavaScript minefield</title>
      <link>https://ar.al/2021/03/09/references-to-methods-are-a-javascript-minefield/</link>
      <pubDate>Tue, 09 Mar 2021 14:22:56 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/03/09/references-to-methods-are-a-javascript-minefield/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2021/03/09/references-to-methods-are-a-javascript-minefield/wile-e-coyote.webp&#34;
         alt=&#34;Wile E. Coyote blowing himself up with some TNT as the roadrunner watches from afar.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Your code, doing exactly what you told it to (as always).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So, tell me what’s wrong with the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//               ms  s  m  h
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; ONE_DAY &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;60&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;60&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;24&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; ONCE_A_DAY &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; ONE_DAY

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; Certificate {
  &lt;span style=&#34;&#34;&gt;#&lt;/span&gt;renewalDate &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;

  constructor() {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Renewal is in thirty days.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;&#34;&gt;#&lt;/span&gt;renewalDate &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;.now() &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; ONE_DAY&lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;30&lt;/span&gt;)

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// In a real app, you’d save the returned interval id
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and clear it when you no longer need it.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    setInterval(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.checkForRenewal, ONCE_A_DAY)
    checkForRenewal()
  }

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; checkForRenewal() {
    console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Checking if certificate needs to be renewed…&amp;#39;&lt;/span&gt;)
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; currentDate &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;()
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (currentDate &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;&#34;&gt;#&lt;/span&gt;renewalDate) {
      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.renewCertificate()
      console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Certificate renewed.\n&amp;#39;&lt;/span&gt;)
    } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; {
      console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Certificate does not need renewing yet.\n&amp;#39;&lt;/span&gt;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing?&lt;/p&gt;
&lt;p&gt;What if I told you it crashes when it tries to check for renewal the second time?&lt;/p&gt;
&lt;p&gt;Not very obvious is it?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;Checking if certificate needs to be renewed…
Certificate does not need renewing yet.

Checking if certificate needs to be renewed…
(node:3035) UnhandledPromiseRejectionWarning: TypeError: Cannot read private member #renewalDate from an object whose class did not declare it
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;(You might want to set the duration to something lower, like 3 seconds, if you don’t want to wait around for a day to see the error for yourself.) :)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;references-to-methods-in-javascript-are-a-runtime-minefield&#34;&gt;References to methods in JavaScript are a runtime minefield&lt;/h2&gt;
&lt;p&gt;So the problem is on this line in the constructor:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;setInterval(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.checkForRenewal, ONCE_A_DAY)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Can you see it yet?&lt;/p&gt;
&lt;p&gt;(I don’t blame you, if not.)&lt;/p&gt;
&lt;h2 id=&#34;do-as-i-think-not-as-i-say&#34;&gt;Do as I think, not as I say&lt;/h2&gt;
&lt;p&gt;Debugging is the subtle art of reconciling what you think you asked a computer to do with what you actually asked a computer to do.&lt;/p&gt;
&lt;p&gt;What we &lt;em&gt;think&lt;/em&gt; we’re doing here is passing a reference to the &lt;code&gt;checkForRenewal()&lt;/code&gt; &lt;em&gt;&lt;strong&gt;method&lt;/strong&gt;&lt;/em&gt; on the &lt;code&gt;Certificate&lt;/code&gt; class to the &lt;code&gt;setInterval()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;But what we’re &lt;em&gt;actually&lt;/em&gt; doing – &lt;em&gt;because method references are not closures in JavaScript&lt;/em&gt; – is passing a reference to the &lt;code&gt;checkForRenewal()&lt;/code&gt; &lt;em&gt;&lt;strong&gt;function&lt;/strong&gt;&lt;/em&gt; to &lt;code&gt;setInterval()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What’s the difference? Oh, about one crash.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;But wait a minute, if that’s the reason, isn’t the error message confusing? Yes, yes it is. (It’s so confusing that &lt;a href=&#34;https://twitter.com/kermiite/status/1369310399238205454&#34;&gt;I’m writing this revised blog post because of it.&lt;/a&gt; – thanks, Tom!)&lt;/p&gt;
&lt;h2 id=&#34;the-red-herring&#34;&gt;The red herring&lt;/h2&gt;
&lt;p&gt;Let’s remember what the error says:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;TypeError: Cannot read private member #renewalDate from an object whose class did not declare it
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This makes it sound like a private member called &lt;code&gt;#renewalDate&lt;/code&gt; exists on the &lt;code&gt;this&lt;/code&gt; reference but you don’t have permission to access it when actually, it doesn’t.&lt;/p&gt;
&lt;h2 id=&#34;the-actual-issue&#34;&gt;The actual issue&lt;/h2&gt;
&lt;p&gt;Let’s remove the use of private fields to make things clearer and easier to understand.&lt;/p&gt;
&lt;p&gt;Remove all the octothorpes from the code and run it again, adding a &lt;code&gt;console.log(this)&lt;/code&gt; statement to the start of the &lt;code&gt;checkForRenewal()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;When you run it, you should see something similar to the following when the first the renewal check is run as a method call:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;Certificate { renewalDate&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2021&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;03&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;09&lt;/span&gt;T17&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;36&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;40.821&lt;/span&gt;Z }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Right, so that’s what we expect. But the second time we see any console output, we’re running the unbound function reference, not the method, so we get the following for &lt;code&gt;this&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;Timeout {
  _idleTimeout&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;3000&lt;/span&gt;,
  _idlePrev&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;,
  _idleNext&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;,
  _idleStart&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;3057&lt;/span&gt;,
  _onTimeout&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; [AsyncFunction&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; checkForRenewal],
  _timerArgs&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;undefined&lt;/span&gt;,
  _repeat&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;3000&lt;/span&gt;,
  _destroyed&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;false&lt;/span&gt;,
  [Symbol(refed)]&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;,
  [Symbol(kHasPrimitive)]&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;false&lt;/span&gt;,
  [Symbol(asyncId)]&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;,
  [Symbol(triggerId)]&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So our initially unbound function reference has been bound by the &lt;code&gt;setInterval()&lt;/code&gt; method to a &lt;code&gt;Timeout&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;Also note what we don’t get: an error.&lt;/p&gt;
&lt;p&gt;As far as our code is concerned, the certificate does not need renewing. This is because of the implicit type conversions JavaScript performs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Our original conditional
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;currentDate &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.renewalDate

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Becomes:
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;Number&lt;/span&gt;(currentDate) &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Number&lt;/span&gt;(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.renewalDate)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Becomes:
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;Number&lt;/span&gt;(currentDate) &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;NaN&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// which is always false.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So without the use of private class fields, our code just &lt;em&gt;fails silently&lt;/em&gt; (the silent killer).&lt;/p&gt;
&lt;p&gt;Things are slightly better when you try to access a private class field from the callback function since at least it fails. But, sadly, we get a misleading error message that makes it sound like our initial unbound function reference was actually a closure all along.&lt;/p&gt;
&lt;h2 id=&#34;the-fix&#34;&gt;The fix&lt;/h2&gt;
&lt;p&gt;The fix, simply, is to make sure you bind the function reference to its instance:&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;setInterval(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.checkForRenewal.bind(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;), ONCE_A_DAY)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Now, I can bet that the number of times you’re going to write that code out of the gate without getting bitten by this bug is exactly zero. Even after you’ve read this.&lt;/strong&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;But maybe you’ll remember this post and catch yourself after you’ve written it and fix it.&lt;/p&gt;
&lt;p&gt;And it also shows you private class fields do help by at least not failing silently, even if the error they do fail with is confusing.&lt;/p&gt;
&lt;p&gt;Stay safe out there, kids! :)&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This little bug, looming in the latest &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; code is what took down the &lt;a href=&#34;https://small-web.org&#34;&gt;Small Web&lt;/a&gt; site yesterday when its Let’s Encrypt certificate expired because of it.&lt;/p&gt;
&lt;p&gt;I issued a new release of Site.js that fixes it a few hours after &lt;a href=&#34;https://mastodon.ar.al/@aral/105855090416887350&#34;&gt;I was informed of it&lt;/a&gt; and the server auto-healed at around 3:30AM when it auto-updated to the latest release. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;As &lt;a href=&#34;https://mdhughes.tech/&#34;&gt;Mark Hughes&lt;/a&gt; pointed out &lt;a href=&#34;https://appdot.net/@mdhughes/105861012258726171&#34;&gt;on the fediverse&lt;/a&gt;, you can also use a closure instead of the &lt;code&gt;bind()&lt;/code&gt; method. e.g.,&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;;(() =&amp;gt; setInterval(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.checkForRenewal, ONCE_A_DAY))()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Personally, I find that using the &lt;code&gt;bind()&lt;/code&gt; method expresses intent more clearly and is easier to understand but &lt;a href=&#34;https://appdot.net/@mdhughes/105861041343907741&#34;&gt;not everyone agrees&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is also exactly the kind of bug that is difficult to catch with tests. (I had tests for every method on there but I wasn’t calling them from unbound callbacks so I never triggered the issue.) &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>fs-extra to fs</title>
      <link>https://ar.al/2021/03/07/fs-extra-to-fs/</link>
      <pubDate>Sun, 07 Mar 2021 12:46:48 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/03/07/fs-extra-to-fs/</guid>
      <description>&lt;p&gt;I’m a big fan of the &lt;a href=&#34;https://github.com/jprichardson/node-fs-extra&#34;&gt;fs-extra&lt;/a&gt; module for Node.js. It has made my life much easier over the years. However, as I &lt;a href=&#34;https://ar.al/2021/01/27/commonjs-to-esm-in-node.js/&#34;&gt;migrate my modules from CommonJS to ECMAScript Modules (ESM)&lt;/a&gt; and as I’m looking at bundling &lt;a href=&#34;https://github.com/small-tech/place&#34;&gt;Place&lt;/a&gt; into a single JS file for deployment using &lt;a href=&#34;https://esbuild.github.io/&#34;&gt;esbuild&lt;/a&gt;, I’ve started removing third-party dependencies wherever I can easily replicate their behaviour using modules from the Node standard library.&lt;/p&gt;
&lt;p&gt;For the move from &lt;code&gt;fs-extra&lt;/code&gt; to &lt;code&gt;fs&lt;/code&gt;, this is helped by Node.js implementing some of the features that were hitherto unique to &lt;code&gt;fs-extra&lt;/code&gt; in version 14.x (LTS).&lt;/p&gt;
&lt;p&gt;(There still isn’t an equivalent for &lt;a href=&#34;https://github.com/jprichardson/node-fs-extra/blob/master/docs/copy.md&#34;&gt;recursive &lt;em&gt;copying&lt;/em&gt;&lt;/a&gt; in the standard library. So you’re probably better off sticking to &lt;code&gt;fs-extra&lt;/code&gt; is you need that functionality.)&lt;/p&gt;
&lt;p&gt;So, without further ado, here’s a short summary of &lt;code&gt;fs-extra&lt;/code&gt; methods and their equivalents in Node’s standard &lt;code&gt;fs&lt;/code&gt; module that I’ve encountered, both as a reference for you and for my future self. (I’ll add to the list as I find more.)&lt;/p&gt;
&lt;h2 id=&#34;methods-that-need-migrating&#34;&gt;Methods that need migrating&lt;/h2&gt;
&lt;p&gt;Not every &lt;code&gt;fs&lt;/code&gt; method is re-implemented in &lt;code&gt;fs-extra&lt;/code&gt;. It simply proxies calls to the built-in module for any methods it does not implement itself. These are the methods that have custom implementations that will need migration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;copy, emptyDir, ensureFile, ensureDir, ensureLink, ensureSymlink, mkdirp, mkdirs, move, outputFile, outputJson, pathExists, readJson, remove, writeJson
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This post is not an exhaustive give to every method, just the ones I’ve had to migrate myself.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;For simplicity, sync methods are shown, but the changes apply equally to the async methods.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;creating-recursive-directories&#34;&gt;Creating recursive directories&lt;/h2&gt;
&lt;h3 id=&#34;fs-extra&#34;&gt;fs-extra&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.mkdirpSync(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory/structure/&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;fs&#34;&gt;fs&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.mkdirSync(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory/structure/&amp;#39;&lt;/span&gt;, { recursive&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt; })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;removing-a-directory-and-all-its-contents-and-not-failing-if-the-directory-doesnt-exist&#34;&gt;Removing a directory and all its contents and not failing if the directory doesn’t exist&lt;/h2&gt;
&lt;h3 id=&#34;fs-extra-1&#34;&gt;fs-extra&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.removeSync(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fs-1&#34;&gt;fs&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.rmSync(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory&amp;#39;&lt;/span&gt;, { recursive&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;, force&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt; })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;regular-expression-findreplace&#34;&gt;Regular expression find/replace&lt;/h3&gt;
&lt;h4 id=&#34;find&#34;&gt;Find:&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-re&#34; data-lang=&#34;re&#34;&gt;fs&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;.&lt;/span&gt;removeSync&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;((.*?)&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;replace&#34;&gt;Replace:&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-re&#34; data-lang=&#34;re&#34;&gt;fs&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;.&lt;/span&gt;rmSync&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;$&lt;/span&gt;1&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;{&lt;/span&gt;recursive&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;:&lt;/span&gt; true&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;,&lt;/span&gt; force&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;:&lt;/span&gt; true&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;moving-a-directory-on-the-same-device-and-overwriting-the-destination-if-it-exists&#34;&gt;Moving a directory on the same device (and overwriting the destination if it exists)&lt;/h2&gt;
&lt;h3 id=&#34;fs-extra-2&#34;&gt;fs-extra&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.moveSync(sourcePath, destinationPath, { overwrite&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt; })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;fs-2&#34;&gt;fs&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.renameSync(sourcePath, destinationPath)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; these are not strictly equivalent. The &lt;code&gt;move&lt;/code&gt; methods in &lt;code&gt;fs-extra&lt;/code&gt; act like the &lt;code&gt;mv&lt;/code&gt; command and they will work across devices also. The &lt;code&gt;rename&lt;/code&gt; methods in &lt;code&gt;fs&lt;/code&gt; work like the &lt;code&gt;rename&lt;/code&gt; system call and will not work across devices. Having Node’s &lt;code&gt;rename&lt;/code&gt; methods work across devices is currently &lt;a href=&#34;https://github.com/nodejs/node/issues/19077&#34;&gt;marked as a “won’t fix.”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also, the &lt;code&gt;overwrite: true&lt;/code&gt; behaviour is the default/only behaviour in the standard library.&lt;/p&gt;
&lt;h3 id=&#34;regular-expression-findreplace-1&#34;&gt;Regular expression find/replace&lt;/h3&gt;
&lt;h4 id=&#34;find-1&#34;&gt;Find:&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-re&#34; data-lang=&#34;re&#34;&gt;fs&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;.&lt;/span&gt;moveSync&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;((.+?),&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;(.+?)(,&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;.+?)?&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;replace-1&#34;&gt;Replace:&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-re&#34; data-lang=&#34;re&#34;&gt;fs&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;.&lt;/span&gt;renameSync&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;$&lt;/span&gt;1&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;$&lt;/span&gt;2&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;copying-a-file&#34;&gt;Copying a file&lt;/h2&gt;
&lt;h3 id=&#34;fs-extra-3&#34;&gt;fs-extra&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.copySync(sourcePath, destinationPath)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fs-3&#34;&gt;fs&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.copyFileSync(sourcePath, destinationPath)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;copying-a-folder-recursively&#34;&gt;Copying a folder recursively&lt;/h2&gt;
&lt;p&gt;Sadly, there’s no simple way to do this using the standard &lt;code&gt;fs&lt;/code&gt; module as it only provides a &lt;code&gt;copyFile()&lt;/code&gt; method and does not have the equivalent of &lt;code&gt;fs-extra&lt;/code&gt;’s &lt;code&gt;copy()&lt;/code&gt; method.&lt;/p&gt;
&lt;h2 id=&#34;ensuring-that-a-directory-exists&#34;&gt;Ensuring that a directory exists&lt;/h2&gt;
&lt;h3 id=&#34;fs-extra-4&#34;&gt;fs-extra&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.ensureDirSync(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory/structure&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fs-4&#34;&gt;fs&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; ensureDirSync (directory) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;fs.existsSync(directory)) {
    fs.mkdirSync(directory, { recursive&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt; })
  }
}

ensureDirSync(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory/structure&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: &lt;code&gt;fs-extra&lt;/code&gt; also supports an option that is either an integer or &lt;code&gt;{mode: &amp;lt;integer&amp;gt;}&lt;/code&gt;. If you need that functionality, use the &lt;code&gt;fs.chmodSync()&lt;/code&gt; method after creating the directory.&lt;/p&gt;
&lt;h2 id=&#34;writing-a-file&#34;&gt;Writing a file&lt;/h2&gt;
&lt;h3 id=&#34;fs-extra-5&#34;&gt;fs-extra&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.outputFileSync(&lt;span style=&#34;&#34;&gt;…&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fs-5&#34;&gt;fs&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;fs.writeFileSync(&lt;span style=&#34;&#34;&gt;…&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that with &lt;code&gt;fs-extra&lt;/code&gt;, if the parent directory doesn’t exist, it is created.&lt;/p&gt;
&lt;h2 id=&#34;bonus-promises-and-asyncawait&#34;&gt;Bonus: promises and async/await&lt;/h2&gt;
&lt;p&gt;To use the asynchronous methods in Node’s &lt;code&gt;fs&lt;/code&gt; module with &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;, import the &lt;code&gt;fs/promises&lt;/code&gt; module instead. Note that the latter does not include the synchronous methods or stream methods.&lt;/p&gt;
&lt;p&gt;e.g., To remove a directory recursively and not fail if it doesn’t exist:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; fsPromises from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;fs/promises&amp;#39;&lt;/span&gt;

fsPromises.rm(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/some/directory&amp;#39;&lt;/span&gt;, {recursive&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;, force&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Cache busting in Node.js dynamic ESM imports</title>
      <link>https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/</link>
      <pubDate>Mon, 22 Feb 2021 08:18:12 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/02/22/cache-busting-in-node.js-dynamic-esm-imports/</guid>
      <description>&lt;p&gt;I’m porting &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JSDB&lt;/a&gt; to EcmaScript Modules (ESM) and one of the issues I had to look into was module cache invalidation.&lt;/p&gt;
&lt;p&gt;JSDB is my &lt;a href=&#34;https://ar.al/2020/10/20/introducing-jsdb/&#34;&gt;little in-memory native JavaScript Database&lt;/a&gt; that writes JavaScript operations to append-only JavaScript logs that have &lt;a href=&#34;https://garrettn.github.io/blog/2014/02/19/write-modular-javascript-that-works-anywhere-with-umd/&#34;&gt;UMD headers&lt;/a&gt;. And it loads in these tables either via a dynamic &lt;code&gt;require()&lt;/code&gt; call, or, for very large tables, by streaming them in and evaluating them line by line&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;When a table is loaded in, it is stored in the &lt;code&gt;require&lt;/code&gt; cache. Then, during a session, data might be written to it. If the table is then closed, we must invalidate the old version in the cache in case the table is opened again in the same session (otherwise, all the changes that were written to it in that session will be lost as the old version is read in from the cache).&lt;/p&gt;
&lt;p&gt;In the current CommonJS version, I lazily used a module called &lt;a href=&#34;https://www.npmjs.com/package/decache&#34;&gt;decache&lt;/a&gt; without giving it too much thought. The module works as described but is also not entirely necessary for my simple use case (as JSDB modules cannot include other modules so there isn’t a complicated cache hierarchy to navigate). I could easily have removed entries from the module cache manually by deleting keys on the &lt;code&gt;cache&lt;/code&gt; property of the &lt;code&gt;require&lt;/code&gt; function&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;module-cache-invalidation-in-commonjs&#34;&gt;Module cache invalidation in CommonJS&lt;/h2&gt;
&lt;p&gt;To test this out youself, create a file called &lt;em&gt;module.cjs&lt;/em&gt;, with the following content to display a random number:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (() =&amp;gt; &lt;span style=&#34;color:#007020&#34;&gt;Math&lt;/span&gt;.random())()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, create a file called &lt;em&gt;index.cjs&lt;/em&gt; and require your module twice:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; path &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; modulePath &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; path.resolve(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./module.cjs&amp;#39;&lt;/span&gt;)

console.log(require(modulePath))
console.log(require(modulePath))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run this and note that only one random number is displayed as the immediately-invoked function expression (IIFE) exported from your module is only run the first time your module is loaded in and then its value is cached.&lt;/p&gt;
&lt;p&gt;To clear the cache, we simply have to remove its entry from the &lt;code&gt;require.cache&lt;/code&gt; object.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; If you &lt;code&gt;console.log(require.cache)&lt;/code&gt; after the initial call to the &lt;code&gt;require()&lt;/code&gt; function in your code, you will see output similar to the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;[&lt;span style=&#34;color:#007020&#34;&gt;Object&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt; prototype] {
  &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/home/aral/sandbox/common-js-require-cache/index.cjs&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; Module {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  },
  &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/home/aral/sandbox/common-js-require-cache/module.cjs&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; Module {
    id&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/home/aral/sandbox/common-js-require-cache/module.cjs&amp;#39;&lt;/span&gt;,
    path&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/home/aral/sandbox/common-js-require-cache&amp;#39;&lt;/span&gt;,
    exports&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0.6628156804529053&lt;/span&gt;,
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//…
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To remove it from the cache, we simply delete its key. Add the following line of code between the two &lt;code&gt;console.log()&lt;/code&gt; statements:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;delete&lt;/span&gt; require.cache[modulePath]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(This is also why we used &lt;code&gt;path.resolve()&lt;/code&gt; in the original example so we’d have the absolute path to the module.)&lt;/p&gt;
&lt;p&gt;Now, when you run it, you should see two different numbers output as the module is loaded in fresh both times.&lt;/p&gt;
&lt;h2 id=&#34;cache-invalidation-in-esm-with-commonjs-style-requires&#34;&gt;Cache invalidation in ESM with CommonJS-style requires&lt;/h2&gt;
&lt;p&gt;If I wanted to, I could still have JSDB output its tables in CommonJS or UMD modules even though I was using ESM for it. Why would I want to do that?&lt;/p&gt;
&lt;p&gt;Well, for one thing, there is a very important difference in dynamic module loading between CommonJS and ESM: the former is synchronous while the latter is asynchronous. And refactoring previously-synchronous code to be asynchronous has cascading effects throughout our application and can complicate your library’s interface.&lt;/p&gt;
&lt;p&gt;So if I was lazy or if I didn’t want to make my API asynchronous (which is less of an issue now that we have top-level await), I could simply use the &lt;code&gt;createRequire()&lt;/code&gt; method in the built-in &lt;code&gt;module&lt;/code&gt; module to create my own &lt;code&gt;require()&lt;/code&gt; function that works under ESM. I detail how to do this in my previous &lt;a href=&#34;https://ar.al/2021/01/27/commonjs-to-esm-in-node.js/&#34;&gt;CommonJS to ESM in Node.js&lt;/a&gt; post.&lt;/p&gt;
&lt;p&gt;Here’s what the above example would look if that’s the direction you wanted to go.&lt;/p&gt;
&lt;p&gt;Your &lt;em&gt;module.cjs&lt;/em&gt; would stay the same and you’d load it in from an &lt;em&gt;index.mjs&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; path from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { createRequire } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; require &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; createRequire(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.url)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; modulePath &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; path.resolve(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./module.cjs&amp;#39;&lt;/span&gt;)

require(modulePath)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;delete&lt;/span&gt; require.cache[modulePath]
require(modulePath)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;cache-invalidation-in-esm-with-dynamic-imports&#34;&gt;Cache invalidation in ESM with dynamic imports&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This will leak memory and eventually crash your system. &lt;a href=&#34;https://github.com/nodejs/modules/issues/307&#34;&gt;Garbage collecting stale ESM modules is not a solved problem&lt;/a&gt; as of Feb, 2021.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The ESM version of JSDB, however, will be 100% ESM and so it will write out ESM modules and use dynamic &lt;code&gt;import()&lt;/code&gt; calls to load them in&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Cache invalidation with dynamic imports is different and in some ways much easier. You don’t have a &lt;code&gt;require.cache&lt;/code&gt; object to work with like you do in CommonJS. Instead, however, you can use a trick as old as the web itself (well, almost): just tack a random fragment to the URL of the module you’re loading.&lt;/p&gt;
&lt;p&gt;So this is what cache invalidation looks like in pure ESM:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; modulePath &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./module.mjs&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; importFresh(modulePath) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; cacheBustingModulePath &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;modulePath&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;?update=&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;.now()&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;(cacheBustingModulePath)).&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt;
}

console.log(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; importFresh(modulePath))
console.log(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; importFresh(modulePath))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One caveat is that the relative path I used in the example above works because the &lt;code&gt;importFresh()&lt;/code&gt; function is in the same file. If I was to refactor it out to a common module, you would have to use absolute paths. Otherwise, module resolution would occur from whatever path the file containing your &lt;code&gt;importFresh()&lt;/code&gt; function was itself imported from.&lt;/p&gt;
&lt;h2 id=&#34;to-npm-install-or-not-to-npm-install-that-is-the-question&#34;&gt;To npm install or not to npm install, that is the question&lt;/h2&gt;
&lt;p&gt;I actually went ahead and made a tiny Node module called &lt;a href=&#34;https://www.npmjs.com/package/@small-tech/import-fresh&#34;&gt;@small-tech/import-fresh&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(But, as I mention in the &lt;em&gt;readme&lt;/em&gt;, consider whether you really need to add yet another dependency to your project or whether you can get away with just using vanilla JavaScript based on what you know from above.)&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you’re using JSDB with datasets of these sizes you will also have to configure your system and Node.js to increase the heap size, etc. While there are legitimate use cases for such large datasets, it’s also possible that you’re up to no good. (Don’t use JSDB to farm people for their data or profile them or I’ll haunt you in your nightmares.) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Remember that everything is an object in JavaScript, including functions. And yes, functions can have properties attached to them. This is because JavaScript is a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes&#34;&gt;prototype-based language&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In more complicated applications, your modules will require modules and thus you’ll have to navigate the cache hierarchy and invalidate child modules, etc. Save yourself some trouble and use a ready-made module like &lt;a href=&#34;https://www.npmjs.com/package/decache&#34;&gt;decache&lt;/a&gt;, etc. (there are quite a number of them). &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In my tests with medium-sized datasets (~227MB), I didn’t perceive any substantial performance difference between dynamic imports in CommonJS vs ESM. The former is ever so slightly faster in real terms but they both have the same O(N) linear time complexity. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>CommonJS to ESM in Node.js</title>
      <link>https://ar.al/2021/01/27/commonjs-to-esm-in-node.js/</link>
      <pubDate>Wed, 27 Jan 2021 10:30:02 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2021/01/27/commonjs-to-esm-in-node.js/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2021/01/27/commonjs-to-esm-in-node.js//2021/01/27/commonjs-to-esm-in-node.js/lego.jpg&#34; alt=&#34;Legos&#34;&gt;&lt;/p&gt;
&lt;p&gt;Yesterday, I refactored &lt;a href=&#34;https://github.com/small-tech/place&#34;&gt;Place&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, which is a non-trivial &lt;a href=&#34;nodejs.org/&#34;&gt;Node.js&lt;/a&gt; app, to use &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&#34;&gt;ECMAScript Modules&lt;/a&gt; (ESM). Here’s &lt;a href=&#34;https://github.com/small-tech/place/commit/115854bc25dcd6b5b02cf3fb5c7391e7e203798d&#34;&gt;a diff of the full set of changes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is the approach I took and some of the issues I ran into, in case it helps someone else:&lt;/p&gt;
&lt;h2 id=&#34;find-and-replace&#34;&gt;Find and replace&lt;/h2&gt;
&lt;p&gt;First off, I started by running the following &lt;a href=&#34;https://regexr.com/&#34;&gt;regular expression&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to quickly convert &lt;a href=&#34;https://flaviocopes.com/commonjs/&#34;&gt;CommonJS&lt;/a&gt; &lt;code&gt;require&lt;/code&gt; syntax to ESM &lt;code&gt;import&lt;/code&gt; syntax:&lt;/p&gt;
&lt;h3 id=&#34;find&#34;&gt;Find&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-re&#34; data-lang=&#34;re&#34;&gt;const &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;(.*?)&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;s&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;*?=&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;s&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;*?&lt;/span&gt;require&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;((&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;.*?&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;replace&#34;&gt;Replace&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-re&#34; data-lang=&#34;re&#34;&gt;import &lt;span style=&#34;color:#666&#34;&gt;$&lt;/span&gt;1 from &lt;span style=&#34;color:#666&#34;&gt;$&lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While this will do a lot of the work for you, you’ll still have to go in and add &lt;code&gt;.js&lt;/code&gt; suffixes manually to any imports that refer to modules using a file path instead of the npm package name.&lt;/p&gt;
&lt;p&gt;Next, I updated the export statements:&lt;/p&gt;
&lt;h3 id=&#34;find-1&#34;&gt;Find&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; something
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;replace-1&#34;&gt;Replace&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; something
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I did this manually as sometimes that &lt;code&gt;something&lt;/code&gt; is an object you were assigning to &lt;code&gt;module.exports&lt;/code&gt; and you cannot do that in ESM as what you’re exporting are references (aka &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export&#34;&gt;live bindings&lt;/a&gt;. I’ll come back to this later with a specific example to demonstate what that means.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note that &lt;code&gt;.cjs&lt;/code&gt; denotes a CommonJS file and &lt;code&gt;.mjs&lt;/code&gt; denotes an ESM file in the following examples.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, an object exported by CommonJS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.cjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; { name, age, breed } &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./osky&amp;#39;&lt;/span&gt;)
console.log(name, age, breed)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// osky.cjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { name&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;, age&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;, breed&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Becomes the following when using ESM:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { name, age, breed } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./osky.mjs&amp;#39;&lt;/span&gt;
console.log(name, age, breed)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// osky.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; name &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; age &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; breed &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, instead of exporting each constant separately in &lt;em&gt;osky.mjs&lt;/em&gt;, we could have exported them all in one go:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; name &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; age &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; breed &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; { name, age, breed }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These two approaches are functionally equivalent.&lt;/p&gt;
&lt;p&gt;You could also be tempted to keep the default export and destructure it after import:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; osky from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./osky.mjs&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; { name, age, breed } &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; osky
console.log(name, age, breed)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// osky.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; { name&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;, age&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;, breed&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While this will result in identical behaviour in this example, that’s because the live bindings we’re exporting are constants. Let’s see what would happen if we were exporting variables whose values could be changed after the import by the module we’re importing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { name, age, breed } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./osky.mjs&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; updateStatus () {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;name&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; (a &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;breed&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;) is now &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;age&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; year&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;age &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; old.`&lt;/span&gt;)
}

updateStatus()
setInterval(updateStatus, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// osky.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; name &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; age &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; breed &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt;

setInterval(() =&amp;gt; age&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you run this, you will see that Oskar’s age increases by one every second and is reflected in the status update even though the updates take place in the module being imported and are logged in &lt;em&gt;index.mjs&lt;/em&gt;. This is what we mean when we say ESM modules export live bindings. Basically, what you’re exporting is a reference to your original variable.&lt;/p&gt;
&lt;p&gt;Now, change the export to the second approach we used:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; osky from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./osky.js&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; { name, age, breed } &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; osky
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// osky.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; name &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; age &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; breed &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt;

setInterval(() =&amp;gt; age&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; { name, age, breed }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you run this version, you’ll notice that Oskar never ages. That’s because when we destructure, we no longer have a reference to the originally-imported variable and the updates taking place in the original module are not reflected in the console output.&lt;/p&gt;
&lt;p&gt;If you did want to use a default export and keep the live bindings, this is how you’d do it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; dog from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./osky.js&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; updateStatus () {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;dog.name&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; (a &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;dog.breed&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;) is now &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;dog.age&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; year&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;dog.age &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; old.`&lt;/span&gt;)
}

updateStatus()
setInterval(updateStatus, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// osky.mjs
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; name &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Oskar&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; age &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; breed &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Huskamute&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; osky &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { name, age, breed }

setInterval(() =&amp;gt; osky.age&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; osky
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that I’ve purposefully stored the original values, as well as the &lt;code&gt;osky&lt;/code&gt; object in constants in &lt;code&gt;osky.mjs&lt;/code&gt; to illustrate the point even further of what’s being exported and what’s changing. (Remember that marking an object as a constant means that you cannot assign a different object to it, not that you cannot change the values of its properties or even add or remove properties from it.)&lt;/p&gt;
&lt;p&gt;Anyway, all this to say that you should keep in mind that ESM modules are not just a different syntax, they are fundamentally different to CommonJS modules in how they work.&lt;/p&gt;
&lt;p&gt;So, with the find and replace out of the way, I had to tackle a few other issues.&lt;/p&gt;
&lt;h2 id=&#34;dynamic-requires-and-imports&#34;&gt;Dynamic requires and imports&lt;/h2&gt;
&lt;p&gt;In Place, I was using dynamic requires to load in routes for the server. I needed to change these to use dynamic imports instead so you could use ESM for the server as well as the client.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This is not as straightforward as a simple find and replace because while dynamic requires are &lt;em&gt;synchronous&lt;/em&gt;, dynamic imports are &lt;em&gt;asychronous&lt;/em&gt;. And asynchronous code has a cascading effect&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;(Note that you can still do an old-school, synchronous require. You just have to create your &lt;code&gt;require()&lt;/code&gt; function yourself. See &lt;a href=&#34;a-require-by-any-other-name&#34;&gt;A require by any other name…&lt;/a&gt;, below, for details.)&lt;/p&gt;
&lt;h3 id=&#34;the-asynchronous-cascade&#34;&gt;The asynchronous cascade&lt;/h3&gt;
&lt;p&gt;For example, refactoring function &lt;code&gt;c()&lt;/code&gt;, below, to return a promise means that functions &lt;code&gt;b()&lt;/code&gt; and &lt;code&gt;a()&lt;/code&gt; become asynchronous also:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Sync
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; a () { console.log(b()) }
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; b () { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; c() }
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; c () { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello&amp;#39;&lt;/span&gt; }

a()

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Async
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; a () { console.log(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; b()) }
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; b () { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; c() }
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; c () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Promise&lt;/span&gt; (resolve =&amp;gt; setTimeout(resolve, &lt;span style=&#34;color:#40a070&#34;&gt;3000&lt;/span&gt;)
}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; a()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;awaiting-imports&#34;&gt;Awaiting imports&lt;/h3&gt;
&lt;p&gt;The switch from synchronous dynamic requires to asynchronous dynamic imports was the biggest change in the refactor. Here’s a relevant section of the diff, showing the before and after:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;color:#a00000&#34;&gt;-decache(routesJSFilePath)
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;-require(routesJSFilePath)(this.app)
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+// Ensure we are loading a fresh copy in case it has changed.
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+const cacheBustingRoutesJSFilePath = `${routesJSFilePath}?update=${Date.now()}`
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+;(await import(cacheBustingRoutesJSFilePath)).default(this.app)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So requiring a default function from a module and immediately executing it goes from:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;require(filePath)(args)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;;(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;(filePath)).&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt;(args)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the addition of &lt;code&gt;.default&lt;/code&gt;. That’s how you access the default export of a dynamically-imported module.&lt;/p&gt;
&lt;p&gt;Also note where the parentheses are around &lt;code&gt;await&lt;/code&gt;. The promise must resolve before you can access the module’s properties.&lt;/p&gt;
&lt;p&gt;And here’s something to watch out for: if you forget to &lt;code&gt;await&lt;/code&gt; an asynchronous call when using dynamic imports, Node.js will throw &lt;a href=&#34;https://github.com/nodejs/node/issues/32177&#34;&gt;a very unhelpful error message without a stack trace&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;SyntaxError: Unexpected reserved word
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you encounter that error, chances are you should be checking for missing &lt;code&gt;await&lt;/code&gt; statements in your code.&lt;/p&gt;
&lt;h3 id=&#34;cache-busting-with-dynamic-esm-imports&#34;&gt;Cache busting with dynamic ESM imports&lt;/h3&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This will leak memory and eventually crash your system. &lt;a href=&#34;https://github.com/nodejs/modules/issues/307&#34;&gt;Garbage collecting stale ESM modules is not a solved problem&lt;/a&gt; as of Feb, 2021.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In the example above, you can see that I also had to change the cache-busting strategy for when routes are reloaded at runtime.&lt;/p&gt;
&lt;p&gt;With dynamic requires, I was using the &lt;a href=&#34;https://www.npmjs.com/package/decache&#34;&gt;decache module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With dynamic imports, &lt;a href=&#34;https://github.com/nodejs/help/issues/1399#issuecomment-408499431&#34;&gt;you don’t have programmatic access to the module cache&lt;/a&gt; so I’m using an old client-side trick instead and appending a query string to the file path with the current timestamp in it.&lt;/p&gt;
&lt;p&gt;If you want to test this out quickly for yourself to see that it works, create a file called &lt;code&gt;loader.mjs&lt;/code&gt; with the following code in it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;setInterval(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; () =&amp;gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; cacheBustingPath &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`./message.mjs?update=&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;.now()&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;
  console.log((&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;(cacheBustingPath)).&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt;)
}, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, create another file called &lt;code&gt;message.mjs&lt;/code&gt; that contains the following line of code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, run the loader:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;node loader.mjs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see the message &lt;code&gt;hello&lt;/code&gt; printed out to your console every second.&lt;/p&gt;
&lt;p&gt;With that process still running, change the message in &lt;code&gt;message.mjs&lt;/code&gt; and save the file and you will see the message update in the console.&lt;/p&gt;
&lt;h3 id=&#34;will-no-one-think-of-the-loops&#34;&gt;Will no one think of the loops?&lt;/h3&gt;
&lt;p&gt;When using dynamic imports and asychronous calls in linear tasks, remember that you can’t just loop over promises like you can with synchronous calls, you must &lt;code&gt;await&lt;/code&gt; each result.&lt;/p&gt;
&lt;p&gt;So no &lt;code&gt;forEach()&lt;/code&gt;, etc., for you.&lt;/p&gt;
&lt;p&gt;Here’s a relevant diff from where I changed a synchronous &lt;code&gt;forEach()&lt;/code&gt; loop into an asychronous loop using &lt;a href=&#34;https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404&#34;&gt;Sebastien Chopin’s handy asyncForEach() function&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;color:#a00000&#34;&gt;-httpsPostRoutes.forEach(route =&amp;gt; {
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+asyncForEach(httpsPostRoutes, async route =&amp;gt; {
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;-  this.app.post(route.path, require(route.callback))
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+  this.app.post(route.path, (await import(route.callback)).default)
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;&lt;/span&gt; })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this is the &lt;code&gt;asyncForEach()&lt;/code&gt; function itself:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; asyncForEach(array, callback) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; index &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;; index &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; array.length; index&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;) {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;await&lt;/span&gt; callback(array[index], index, array);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;__dirname&#34;&gt;__dirname&lt;/h2&gt;
&lt;p&gt;Another issue that cropped up was the lack of &lt;a href=&#34;https://nodejs.org/docs/latest/api/modules.html#modules_dirname&#34;&gt;__dirname&lt;/a&gt; support with ESM under Node.js. This is easily fixed by declaring &lt;code&gt;__dirname&lt;/code&gt; as a global in your module:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { fileURLToPath } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; __dirname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; fileURLToPath(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; URL(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.url))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is such an easy, backwards-compatible fix that I don’t know why Node.js doesn’t automatically inject it into all ESM modules.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Note: This snippet was previously using the &lt;code&gt;.pathname&lt;/code&gt; property of the URL instance, &lt;a href=&#34;https://github.com/nodejs/node/issues/37845&#34;&gt;which is wrong and won’t work on Windows&lt;/a&gt;. The updated snippet, above, properly converts the URL to a file path.&lt;/p&gt;
&lt;h2 id=&#34;a-require-by-any-other-name&#34;&gt;A require by any other name…&lt;/h2&gt;
&lt;p&gt;A problem with a similar solution to the missing &lt;code&gt;__dirname&lt;/code&gt; functionality exists if you have code that uses &lt;code&gt;require&lt;/code&gt; to load in JSON files. ESM modules do not currently support this. So what can you use, given you can’t use &lt;code&gt;require&lt;/code&gt; with ESM modules?&lt;/p&gt;
&lt;p&gt;Why, &lt;code&gt;require&lt;/code&gt;, of course…&lt;/p&gt;
&lt;p&gt;No, that’s not a typo.&lt;/p&gt;
&lt;p&gt;You just have to &lt;a href=&#34;https://github.com/small-tech/place/compare/esm#diff-e727e4bdf3657fd1d798edcd6b099d6e092f8573cba266154583a746bba0f346R52&#34;&gt;create your own require function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(I know, I know… I’m shaking my head too.)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; { createRequire } from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; require &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; createRequire(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.url)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, you can require a JSON file just like you did before. e.g., along with the &lt;code&gt;__dirname&lt;/code&gt; constant, above, the following code will work if you have a file called &lt;code&gt;my.json&lt;/code&gt; in the same directory as your script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;JSON.parse(fs.readFileSync(path.join(__dirname, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;my.json&amp;#39;&lt;/span&gt;), &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, unless there are some edge cases that the Node development team knows of that I don’t, I don’t see why this isn’t default behaviour that’s injected into ECMAScript Modules in Node.js.&lt;/p&gt;
&lt;p&gt;The above two features – alongside doing the same for &lt;code&gt;__filename&lt;/code&gt;, etc. – would go a long way to making the migration process from CommonJS to ESM a far more seamless one for many developers.&lt;/p&gt;
&lt;h2 id=&#34;little-quirks&#34;&gt;Little quirks&lt;/h2&gt;
&lt;p&gt;The final thing I had to do was to set the &lt;code&gt;type&lt;/code&gt; property to &lt;code&gt;module&lt;/code&gt; in my &lt;code&gt;package.json&lt;/code&gt;. When you do this, all &lt;code&gt;.js&lt;/code&gt; files are treated as ESM by Node.js. Which can cause some unexpected quirks.&lt;/p&gt;
&lt;p&gt;For example, &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JSDB&lt;/a&gt; – my small, in-memory, streaming write-on-update JavaScript database – persists to JavaScript transaction logs and uses a CommonJS require to load them in.&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; Even though I was importing JSDB in from &lt;code&gt;node_modules&lt;/code&gt;, it crashed while trying to load data tables:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;Error &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;ERR_REQUIRE_ESM&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;: Must use import to load ES Module: /home/aral/small-tech/small-web/sandbox/sign-in.small-web.s
require&lt;span style=&#34;color:#666&#34;&gt;()&lt;/span&gt; of ES modules is not supported.
require&lt;span style=&#34;color:#666&#34;&gt;()&lt;/span&gt; of /home/aral/small-tech/small-web/sandbox/sign-in.small-web.org/.db/privateRoutes.js from /home/aral/small-.
Instead rename privateRoutes.js to end in .cjs, change the requiring code to use import&lt;span style=&#34;color:#666&#34;&gt;()&lt;/span&gt;, or remove &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt; .

    at Object.Module._extensions..js &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:1080:13&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Module.load &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:928:32&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Function.Module._load &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:769:14&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Module.require &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:952:19&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at require &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/helpers.js:88:18&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at JSTable.load &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;/home/aral/small-tech/small-web/place/app/node_modules/@small-tech/jsdb/lib/JSTable.js:161:20&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    …
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There seems to be some context leak between an app and the npm modules it imports whereby the type of the app overrides the type of the npm module declared in the module’s package file when using a dynamic &lt;code&gt;require()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The quick workaround I hacked together for the moment was to add a default (of type CommonJS) &lt;code&gt;package.json&lt;/code&gt; file to the database directory. In fact, I might just update &lt;code&gt;JSDB&lt;/code&gt; to do that by default so that it can be used in both CommonJS and ESM projects with a minimum of fuss.&lt;/p&gt;
&lt;p&gt;It feels hacky but I don’t think it’s worth the effort to create a non-hacky solution to something that is itself a huge hack (ESM in Node.js).&lt;/p&gt;
&lt;p&gt;The same issue also cropped up when I used Snowpack’s &lt;code&gt;loadConfiguration()&lt;/code&gt; method (which uses &lt;code&gt;require()&lt;/code&gt; internally) to load the Snowpack configuration for Place. &lt;a href=&#34;https://github.com/small-tech/place/compare/esm#diff-e727e4bdf3657fd1d798edcd6b099d6e092f8573cba266154583a746bba0f346L627&#34;&gt;The easy workaround&lt;/a&gt; to that was just to rename &lt;code&gt;snowpack.config.js&lt;/code&gt; to &lt;code&gt;snowpack.config.cjs&lt;/code&gt;. The irony of having to make my Snowpack configuration file a CommonJS file when Snowpack exists to make ESM easy to use is not lost on me.&lt;/p&gt;
&lt;h2 id=&#34;other-quirks&#34;&gt;Other quirks&lt;/h2&gt;
&lt;h3 id=&#34;error-stacks-are-different-between-commonjs-and-esm&#34;&gt;Error stacks are different between CommonJS and ESM&lt;/h3&gt;
&lt;p&gt;This is one you will probably not care about but it just bit me while converting &lt;a href=&#34;https://github.com/small-tech/auto-encrypt&#34;&gt;Auto Encrypt&lt;/a&gt; to ESM as I have custom error handling that uses the information provided in stack traces.&lt;/p&gt;
&lt;p&gt;To see the difference for yourself, create two files, &lt;code&gt;index.cjs&lt;/code&gt; and &lt;code&gt;index.mjs&lt;/code&gt;, both with the following line of code in them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;console.log((&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;().stack))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output you’ll see in each will differ (the actuals paths you see will, of course, be based on the directory structure of your local system).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CommonJS&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;Error
    at Object.&amp;lt;anonymous&amp;gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;/home/aral/sandbox/stack-get-file-name-esm-vs-commonjs/index.cjs:1:14&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Module._compile &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:1063:30&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Object.Module._extensions..js &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:1092:10&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Module.load &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:928:32&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Function.Module._load &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/cjs/loader.js:769:14&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at Function.executeUserEntryPoint &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;as runMain&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/run_main.js:72:12&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at internal/main/run_main_module.js:17:47
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;ESM&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;Error
    at file:///home/aral/sandbox/stack-get-file-name-esm-vs-commonjs/index.mjs:1:14
    at ModuleJob.run &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/esm/module_job.js:152:23&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at async Loader.import &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/modules/esm/loader.js:166:24&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
    at async Object.loadESM &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;internal/process/esm_loader.js:68:5&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just be aware of this if you are doing anything based on the contents of the error stack, especially with paths as ESM returns URLs whereas CommonJS returns file paths.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Prior to native support for ECMAScript Modules in Node.js, I saw no reason to use them. Now, I take it as given for future Node projects and look forward to working more easily with isomorphic JavaScript.&lt;/p&gt;
&lt;p&gt;I hope this post with my experiences from yesterday’s refactor are helpful for you if and when you undertake a similar task.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Place is a hard fork of &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; for creating &lt;a href=&#34;https://small-web.org/research-and-development&#34;&gt;Small Web&lt;/a&gt; places that is not ready for use yet (it won’† even run for you yet as it depends on &lt;a href=&#34;https://github.com/aral/snowpack&#34;&gt;my fork of Snowpack&lt;/a&gt;; &lt;a href=&#34;https://github.com/snowpackjs/snowpack/pulls/aral&#34;&gt;open pull requests&lt;/a&gt;). &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I use the ES6 &lt;code&gt;const&lt;/code&gt; keyword to declare my imported modules as constants. If you use ES5 &lt;code&gt;var&lt;/code&gt;, update the regular expression accordingly. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The main reason for my refactor was because Place uses ESM modules via &lt;a href=&#34;https://snowpack.dev&#34;&gt;Snowpack&lt;/a&gt; for the client and I wanted the server to use them too so I can write isomorphic JavaScript that can be run on both the client and server. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Making a sychronous function in a deep call stack asynchronous means that you have to make every function that calls it asynchronous and account for the change in execution nature in things like loops. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The only reason I can think of is because there are some edge cases that I haven’t considered but which the Node team ran into. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For larger databases, it streams the tables in but that’s outside the scope of this post. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Snowpack Hot Module Replacement (HMR) from scratch with vanilla JavaScript</title>
      <link>https://ar.al/2020/12/31/snowpack-hmr-from-scratch/</link>
      <pubDate>Thu, 31 Dec 2020 17:13:17 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/12/31/snowpack-hmr-from-scratch/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2020/12/31/snowpack-hmr-from-scratch/screenshot.png&#34; alt=&#34;Screenshot of the sample application running in browser. Text: Snowpack Hot Module Replacement (HMR) from scratch with vanilla JavaScript. This page has been open for 127 seconds. Green on white text, small caps: change this message and save the source file to see state-maintaining hot module replacement in action.&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you watched Rich Harris’s &lt;a href=&#34;https://svelte.dev/blog/whats-the-deal-with-sveltekit&#34;&gt;SvelteKit preview&lt;/a&gt;, you were probably swooning over the state-maintaining source updates he demoes in his talk. These are thanks to &lt;a href=&#34;https://www.snowpack.dev/&#34;&gt;Snowpack&lt;/a&gt;’s support for hot module replacement via &lt;a href=&#34;https://github.com/snowpackjs/esm-hmr&#34;&gt;esm-hmr&lt;/a&gt;. In this short post, I want to explain the concept from scratch using Snowpack and vanilla JavaScript.&lt;/p&gt;
&lt;p&gt;You can either type in the examples or, if you’re feeling lazy, &lt;a href=&#34;https://github.com/aral/snowpack-hmr-from-scratch&#34;&gt;clone the source code&lt;/a&gt; and follow along.&lt;/p&gt;
&lt;h2 id=&#34;set-up-the-project&#34;&gt;Set up the project&lt;/h2&gt;
&lt;p&gt;First, set up your project folder and initialise it as a Node project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;mkdir snowpack-hmr
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; snowpack-hmr
npm init -f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, install Snowpack.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm i --save-dev snowpack
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, initialise Snowpack to create a default &lt;code&gt;snowpack.config.js&lt;/code&gt; file in your project folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npx snowpack init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(We’re not going to customise the configuration in this example so we could have skipped this step but it’s good for you to know how to do it for when you need to in your own projects.)&lt;/p&gt;
&lt;h2 id=&#34;define-the-entry-point&#34;&gt;Define the entry point&lt;/h2&gt;
&lt;p&gt;Create a basic HTML page in your project folder called &lt;code&gt;index.html&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;lang&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;viewport&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;width=device-width, initial-scale=1.0&amp;#39;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;Snowpack Hot Module Replacement (HMR) from scratch with vanilla JavaScript&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;Snowpack Hot Module Replacement (HMR) from scratch with vanilla JavaScript&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;This page has been open for &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;timer&amp;#39;&lt;/span&gt;&amp;gt;0&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; seconds.&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;./index.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, run the Snowpack development server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npx snowpack dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, go to &lt;code&gt;http://localhost:8080&lt;/code&gt; and you should see the basic web page rendered in your browser. If you change anything in the source and save, your page will do a full reload.&lt;/p&gt;
&lt;p&gt;The timer is not active yet as we haven’t created the &lt;code&gt;index.js&lt;/code&gt; module that we’ve asked the browser to include. So let’s do that next.&lt;/p&gt;
&lt;h2 id=&#34;add-core-functionality&#34;&gt;Add core functionality&lt;/h2&gt;
&lt;p&gt;Create the &lt;code&gt;index.js&lt;/code&gt; module with the following initial content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; timer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; intervalId &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; setState(state) {
  timer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; state.timer
}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; create() {
  intervalId &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; setInterval(() =&amp;gt; {
    timer&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;
    &lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;.getElementById(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;timer&amp;#39;&lt;/span&gt;).innerText &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; timer
  }, &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;)

  message.innerText &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Change this message and save the source file to see state-maintaining hot module replacement in action.&amp;#39;&lt;/span&gt;
}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; destroy () {
  clearInterval(intervalId)
}

create()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Refresh the page in your browser and you should see that the timer is now active and starts counting up the seconds since the page was loaded.&lt;/p&gt;
&lt;p&gt;If you save the source file, you should see that the whole page reloads again and that the timer’s value resets. This is because we haven’t added any code to our module to handle hot module replacement. We shall do that a little later but first, how about we make things look a little less plain?&lt;/p&gt;
&lt;h2 id=&#34;add-a-spot-of-eye-candy&#34;&gt;Add a spot of eye candy&lt;/h2&gt;
&lt;p&gt;Create a file called &lt;code&gt;index.css&lt;/code&gt; in your project folder with the following content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-family&lt;/span&gt;: &lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;apple-system, BlinkMacSystemFont, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Segoe UI&amp;#39;&lt;/span&gt;, Roboto, Oxygen, Ubuntu, Cantarell, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Open Sans&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Helvetica Neue&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;sans-serif&lt;/span&gt;;
}

&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;max-width&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;760&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;margin-left&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;auto&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;margin-right&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;auto&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;text-align&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;center&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;1.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
}

&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-weight&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
}

#&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;timer&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
}

#&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;message&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-variant&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;small&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;caps;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;margin-top&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;white&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;seagreen&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Import it by adding the following line to the top of your &lt;code&gt;index.js&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./index.css&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ah, that looks a bit better.&lt;/p&gt;
&lt;h2 id=&#34;first-taste-of-hot-module-replacement&#34;&gt;First taste of hot module replacement&lt;/h2&gt;
&lt;p&gt;Now, change the &lt;code&gt;background-color&lt;/code&gt; property of the &lt;code&gt;#message&lt;/code&gt; rule from &lt;code&gt;seagreen&lt;/code&gt; to &lt;code&gt;hotpink&lt;/code&gt; and save and you’ll get your first taste of hot module replacement with Snowpack as the styles update without a full reload of the page.&lt;/p&gt;
&lt;p&gt;You didn’t have to do anything for this, Snowpack handles hot module replacement of CSS modules automatically for you. So that’s nice.&lt;/p&gt;
&lt;p&gt;Given it’s New Year’s Eve as I’m writing this, let’s also install a Node module for creating some festive confetti.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm i canvas-confetti
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, import the new module at the top of your &lt;code&gt;index.js&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt; confetti from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;canvas-confetti&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then run it at the top of the &lt;code&gt;create()&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; create() {
  confetti()
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//…
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yay, confetti!&lt;/p&gt;
&lt;p&gt;Also, did you notice… Snowpack magically enabled you to import your Node module using ECMAScript module syntax&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. If you want to learn more about how that works under the hood, check out the &lt;a href=&#34;https://github.com/snowpackjs/snowpack/tree/main/esinstall#readme&#34;&gt;esinstall&lt;/a&gt; module.&lt;/p&gt;
&lt;h2 id=&#34;implement-hot-module-replacement&#34;&gt;Implement hot module replacement&lt;/h2&gt;
&lt;p&gt;Although Snowpack can handle hot module replacement automatically for your CSS imports, it needs your help with JavaScript modules.&lt;/p&gt;
&lt;p&gt;To implement hot module replacement for your module, add the following code to the top of your &lt;code&gt;index.js&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.accept(({module}) =&amp;gt; {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Restore state on the new module.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    module.setState(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.data)
  })
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.dispose(() =&amp;gt; {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Save state before destroying the current module.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { timer }
    destroy()
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, whenever you save &lt;code&gt;index.js&lt;/code&gt;, the timer will keep its state.&lt;/p&gt;
&lt;h2 id=&#34;wait-what&#34;&gt;Wait, what?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2020/12/31/snowpack-hmr-from-scratch/how-to-draw-an-owl.jpeg&#34; alt=&#34;How to draw an owl: 1. draw some circles (two circles shown for the head and body of the owl) 2. draw the rest of the damn owl (with a beautifully draw owl in step 2)&#34;&gt;&lt;/p&gt;
&lt;p&gt;Right, it might feel like I just pulled a “how to draw an owl” meme on you so let’s break it down to see what’s happening here.&lt;/p&gt;
&lt;h2 id=&#34;again-more-slowly-this-time&#34;&gt;Again, more slowly this time&lt;/h2&gt;
&lt;p&gt;Basically, Snowpack will not just update your module for you without your permission because it could lead to all sorts of issues (with state, dependencies, etc.) So you have to expressly tell it that you want hot module replacement for your module.&lt;/p&gt;
&lt;p&gt;You opt into hot module replacement by calling &lt;code&gt;import.meta.hot.accept()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In fact, if you want to, replace the code we just added with that one line, refresh your page, and see what happens when you save &lt;code&gt;index.js&lt;/code&gt; a couple of times. The timer starts jumping around. That’s because the module is being loaded over and over so new timers are being created that start from zero and compete with each other.&lt;/p&gt;
&lt;p&gt;That’s why we need the code we added earlier.&lt;/p&gt;
&lt;p&gt;It has three parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3 id=&#34;the-initial-conditional&#34;&gt;The initial conditional&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We first check if &lt;code&gt;import.meta.hot&lt;/code&gt; is defined and only carry out the hot module replacement logic if it is. This means that not only will the code not run in production but, if you’re using a bundler like &lt;a href=&#34;https://esbuild.github.io/&#34;&gt;esbuild&lt;/a&gt;, it will also get &lt;a href=&#34;https://en.wikipedia.org/wiki/Tree_shaking&#34;&gt;tree shaken&lt;/a&gt; out of your final bundle.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;housekeeping-and-saving-state&#34;&gt;Housekeeping and saving state&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.dispose(() =&amp;gt; {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Save state before destroying the current module.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { timer }
    destroy()
  })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;dispose()&lt;/code&gt; method on &lt;code&gt;import.meta.hot&lt;/code&gt; gets called before the current module is replaced and before &lt;code&gt;accept()&lt;/code&gt; is called. This is a good place to store any state you want maintained and to do any housekeeping if you need to.&lt;/p&gt;
&lt;p&gt;You forward state by setting the &lt;code&gt;data&lt;/code&gt; property on &lt;code&gt;import.meta.hot&lt;/code&gt;. This property is also available from the &lt;code&gt;accept()&lt;/code&gt; method, which is where you can restore state.&lt;/p&gt;
&lt;p&gt;For housekeeping, we call the &lt;code&gt;destroy()&lt;/code&gt; method which clears the timer’s interval:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; destroy () {
  clearInterval(intervalId)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;restoring-state&#34;&gt;Restoring state&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.accept(({module}) =&amp;gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Restore state on the new module.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  module.setState(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;import&lt;/span&gt;.meta.hot.data)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;code&gt;accept()&lt;/code&gt; method, we use the reference to the new module we’re passed to call its &lt;code&gt;setState()&lt;/code&gt; method with the state we saved earlier in &lt;code&gt;import.meta.hot.data&lt;/code&gt; during the &lt;code&gt;dispose()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;In our simple example, the &lt;code&gt;setState()&lt;/code&gt; function merely restores the current timer value:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; setState(state) {
 timer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; state.timer
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that’s it!&lt;/p&gt;
&lt;h2 id=&#34;limitations-and-caveats&#34;&gt;Limitations and caveats&lt;/h2&gt;
&lt;p&gt;Of course, this is just a basic, harcoded example to demonstrate the core concepts.&lt;/p&gt;
&lt;p&gt;In a framework like SvelteKit, you will most likely never have to write code like this by hand as the framework will handle it for you.&lt;/p&gt;
&lt;p&gt;Also, state preservation during hot module reloading is not foolproof. To demonstrate this, change the initial value of the &lt;code&gt;timer&lt;/code&gt; variable and see what happens. e.g.,&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; timer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s right, nothing happens unless you refresh the page. That’s because our state saving and restoring code ignores that value. It was this issue that caused the &lt;a href=&#34;https://github.com/rixo/svelte-hmr&#34;&gt;svelte-hmr&lt;/a&gt; project to &lt;a href=&#34;https://github.com/rixo/svelte-hmr#preservation-of-local-state&#34;&gt;make state preservation an opt-in feature&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I look forward to seeing how all this is implemented in SvelteKit once it’s released.&lt;/p&gt;
&lt;p&gt;In the meanwhile, I hope this gives you an overview of how hot module replacement works with ECMAScript modules.&lt;/p&gt;
&lt;h2 id=&#34;happy-new-year&#34;&gt;Happy new year!&lt;/h2&gt;
&lt;p&gt;Now, save your &lt;code&gt;index.js&lt;/code&gt; file, enjoy the confetti, and here’s wishing you and your loved ones a happy new year and a 2021 that is better than the trainwreck that was 2020.&lt;/p&gt;
&lt;p&gt;💕️&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can also use Skypack, their Content Delivery Network (CDN) to import the package directly but I would strongly advise against using it for reasons that I detail in my previous post: &lt;a href=&#34;https://ar.al/2020/12/30/skypack-backdoor-as-a-service/&#34;&gt;Skypack: Backdoor as a Service?&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Skypack: Backdoor as a Service?</title>
      <link>https://ar.al/2020/12/30/skypack-backdoor-as-a-service/</link>
      <pubDate>Wed, 30 Dec 2020 12:48:57 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/12/30/skypack-backdoor-as-a-service/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2020/12/30/skypack-backdoor-as-a-service/backdoor.png&#34; alt=&#34;Black and white illustration: Silhouette of a door ajar in a pitch black room, with light spilling in.&#34;&gt;&lt;/p&gt;
&lt;p&gt;There’s some exciting work being done with projects like &lt;a href=&#34;https://svelte.dev/blog/whats-the-deal-with-sveltekit&#34;&gt;SvelteKit&lt;/a&gt; to reduce complexity and improve the developer experience when building web applications.&lt;/p&gt;
&lt;p&gt;At the heart of these efforts are basically three core elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A front-end framework like &lt;a href=&#34;https://svelte.dev/&#34;&gt;Svelte&lt;/a&gt; or &lt;a href=&#34;https://vuejs.org/&#34;&gt;Vue&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Native browser support for &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&#34;&gt;ECMAScript Modules&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Hot Module Replacement (e.g., see &lt;a href=&#34;https://github.com/snowpackjs/esm-hmr&#34;&gt;esm-hmr&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;At deployment, bundling as usual with a tool like &lt;a href=&#34;https://github.com/evanw/esbuild&#34;&gt;esbuild&lt;/a&gt; or &lt;a href=&#34;https://github.com/rollup/rollup&#34;&gt;rollup&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two development tools that support this workflow are &lt;a href=&#34;https://github.com/vitejs/vite&#34;&gt;Vite&lt;/a&gt;, from the folks behind Vue, and &lt;a href=&#34;https://www.snowpack.dev/&#34;&gt;Snowpack&lt;/a&gt;, from the folks behind a Content Delivery Network (CDN) called &lt;a href=&#34;https://www.skypack.dev/&#34;&gt;Skypack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Snowpack is interesting in that it’s what Rich Harris is using to build the aforementioned SvelteKit.&lt;/p&gt;
&lt;h2 id=&#34;what-is-your-business-model&#34;&gt;What is your business model?&lt;/h2&gt;
&lt;p&gt;A few weeks ago, while looking into all this, I found a quote from Evan You, author of Vue &lt;a href=&#34;https://twitter.com/aral/status/1338446547089035269&#34;&gt;and tweeted it&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“This also touches on something that bothers me a bit with Snowpack: that it&amp;rsquo;s owned by a startup that is using it as a strategy to promote its paid product. I just don&amp;rsquo;t feel comfortable betting the future of Vue tooling on Snowpack.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the ensuing discussion &lt;a href=&#34;https://twitter.com/youyuxi/status/1338537012849160196&#34;&gt;Evan asked&lt;/a&gt; about Skypack’s business model. &lt;a href=&#34;https://twitter.com/Rich_Harris/status/1338527388339200005&#34;&gt;As did Rich&lt;/a&gt;. I was also very interested in finding out as &lt;a href=&#34;https://www.skypack.dev/about&#34;&gt;there was no mention of it on their About page&lt;/a&gt;. &lt;a href=&#34;https://twitter.com/FredKSchott/status/1338537275915784192&#34;&gt;The reply from Fred K. Schott&lt;/a&gt;, their founder and CEO, didn’t really shed much light on it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Definitely! We&amp;rsquo;re building a business around the CDN &amp;amp; catalog discovery, but far from the the &amp;ldquo;pay $5 a month to unlock Snowpack Pro!&amp;rdquo; that Evan insinuated”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/aral/status/1338605745013747714&#34;&gt;So I followed up&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Hey Fred, I’m not sure I understand how you’ll be monetising the CDN (&amp;amp; I’m afraid I don’t know what ‘catalog discovery’ is). If you don’t mind, could you tell us who’ll be paying you and for what. Also, do you have any VC/angel investment and are you looking for an exit? Thanks!”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I didn’t get an answer.&lt;/p&gt;
&lt;p&gt;Fast forward to yesterday when &lt;a href=&#34;https://twitter.com/aral/status/1344037176867221504&#34;&gt;I stumbled onto a partial answer to my question&lt;/a&gt; in Skypak’s documentation for Skypack Pro, which “is currently in invite-only beta:”&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pro features let you customize Skypack to your organization, including:
Private Registry Support
Private Asset Hosting
Custom Subdomain Support
Service Level Agreement (SLA)
Audit Logs &amp;amp; Customer Support&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OK, so fair enough, it’s the &lt;a href=&#34;npmjs.com/&#34;&gt;npm&lt;/a&gt; model (core product is free, charge enterprises for pro features, then eventually &lt;a href=&#34;https://itsfoss.com/microsoft-npm-acquisition/&#34;&gt;get bought out by Microsoft&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Fair enough.&lt;/p&gt;
&lt;p&gt;Given the swamp that is Silicon Valley &lt;a href=&#34;https://small-tech.org/videos/small-technology/&#34;&gt;surveillance capitalism&lt;/a&gt;, it’s not the worst business model a mainstream company could have. (Of course, it’s still part of &lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;the same rotten system&lt;/a&gt; but we’re talking about a mainstream project here, they’re out to make money and get rich, not save the world.)&lt;/p&gt;
&lt;p&gt;So I kept looking into it a bit more and that’s when I realised that Skypack as a CDN has a huge glaring security hole that should really be the only thing anyone is talking about whenever its name is mentioned until it is fixed:&lt;/p&gt;
&lt;p&gt;Skypack does not support &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity&#34;&gt;subresource integrity&lt;/a&gt; (SRI).&lt;/p&gt;
&lt;h2 id=&#34;backdoor-as-a-service&#34;&gt;Backdoor as a Service&lt;/h2&gt;
&lt;p&gt;There isn’t a single mention of subresource integrity &lt;a href=&#34;https://docs.skypack.dev/&#34;&gt;in Skypack’s documentation&lt;/a&gt; and, given how it works, I’m not sure they can provide it without changing some of &lt;a href=&#34;https://docs.skypack.dev/#faster-code-for-modern-browsers&#34;&gt;their core offering&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Most JavaScript CDNs have to serve one file to all users … Skypack is the first CDN to automatically address this problem by skipping unnecessary compilation &amp;amp; polyfilling for modern browsers. When a user visits your site with a modern, up-to-date browser they&amp;rsquo;ll get a smaller, faster JavaScript response optimized for their exact browser.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why is subresource integrity such a big issue? Because without it, you can’t be sure what code you’re serving the people who use your app.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Remember that the code being served from Skypack becomes part of your app.&lt;/p&gt;
&lt;p&gt;If I were &lt;a href=&#34;https://www.iqt.org/&#34;&gt;In-Q-Tel&lt;/a&gt; right now, I’d be drooling as I wrote a check with lots of zeros in it for the Skypack folks because widespread use of Skypack would be any national security agency’s wet dream. Imagine being able to inject any code into any web application at any time to obtain login credentials, etc.&lt;/p&gt;
&lt;p&gt;This isn’t even a backdoor. This is a wide open frontdoor. It’s basically Backdoor as a Service.&lt;/p&gt;
&lt;h2 id=&#34;packing-it-in&#34;&gt;Packing it in&lt;/h2&gt;
&lt;p&gt;With &lt;a href=&#34;https://www.snowpack.dev/posts/2020-12-03-snowpack-3-release-candidate&#34;&gt;the upcoming Snowpack v3.0&lt;/a&gt; providing seamless support for Skypack via the &lt;code&gt;experiments.source&lt;/code&gt; flag, more people will no doubt be interested in trying Skypack.&lt;/p&gt;
&lt;p&gt;Until Skypack addresses this security issue in a satisfactory fashion (which I cannot imagine them not doing given the severity of it), I would highly recommend that you exercise caution and stick to only including code you can verify for yourself in your applications.&lt;/p&gt;
&lt;p&gt;As for the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; stuff we’re working on, I’m still iterating on the developer experience and I’m going to continue looking into Snowpack (you can use Snowpack without Skypack), SvelteKit, Vite, etc., but I have a feeling that we will end up going with a custom solution based on integrating some of the core libraries (like Svelte, esm-hmr, etc.) into a fork of &lt;a href=&#34;https://sitejs.org/&#34;&gt;Site.js&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update (Feb 4, 2021):&lt;/strong&gt; Just stumbled on a blog post Terence Eden wrote a little over two years ago titled &lt;a href=&#34;https://shkspr.mobi/blog/2018/11/dynamic-javascript-and-sri/&#34;&gt;Dynamic JavaScript and SRI&lt;/a&gt;, within the context of a service called Polyfill.io, that voices the same concerns and reaches the same conclusions. Also, I just opened &lt;a href=&#34;https://github.com/snowpackjs/snowpack/discussions/2569&#34;&gt;a discussion about this on the Snowpack forum&lt;/a&gt; in hopes that we can find a way to add SRI to Skypack.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Subresource integrity is especially important for the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt;, where &lt;a href=&#34;https://small-tech.org/research-and-development/&#34;&gt;we do not trust servers at all&lt;/a&gt; and so keep secrets only on the client. It is, thus, of primary importance that we know exactly what code is served to the client. Not just from third parties but even from our own server. In fact, even subresource integrity isn’t enough by itself as it doesn’t verify the root source (think: &lt;code&gt;index.html&lt;/code&gt;) so we cannot use it to create a root of trust. Instead we need to pair it with out-of-band verification of the root source, perhaps via the use of a browser extension. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Why I wrote 152 extra lines of code just to do the same thing (and why I’d do it again today)</title>
      <link>https://ar.al/2020/10/24/why-i-wrote-152-extra-lines-of-code-just-to-do-the-same-thing-and-why-id-do-it-again-today/</link>
      <pubDate>Sat, 24 Oct 2020 19:09:17 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/10/24/why-i-wrote-152-extra-lines-of-code-just-to-do-the-same-thing-and-why-id-do-it-again-today/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/10/24/why-i-wrote-152-extra-lines-of-code-just-to-do-the-same-thing-and-why-id-do-it-again-today/printout.jpg&#34;
         alt=&#34;An old bound dot-matrix printout of computer code.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Who else remembers printing out code on a dot matrix printer? Ah, those were the days… &lt;small&gt;(Image courtesy &lt;a href=&#39;https://commons.wikimedia.org/wiki/File:Bound_computer_printout.agr.jpg&#39;&gt;Arnold Reinhold.&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At the end of the week, I added the following regular expressions to &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JavaScript Database (JSDB)&lt;/a&gt;, to sanitise queries:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Disallow list.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.query &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.query.replace(&lt;span style=&#34;color:#235388&#34;&gt;/[;\\\+\`\{\}\$]/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Allow list.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; sieve &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.query
  .replace(&lt;span style=&#34;color:#235388&#34;&gt;/valueOf\..+?\.toLowerCase()\.(startsWith|endsWith|includes|startsWithCaseInsensitive|endsWithCaseInsensitive|includesCaseInsensitive)\(.+?\)/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
  .replace(&lt;span style=&#34;color:#235388&#34;&gt;/valueOf\..+?\.(startsWith|endsWith|includes|startsWithCaseInsensitive|endsWithCaseInsensitive|includesCaseInsensitive)\(.+?\)/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
  .replace(&lt;span style=&#34;color:#235388&#34;&gt;/valueOf\.[^\.]+?\s?(===|!==|&amp;lt;|&amp;gt;|&amp;lt;=|&amp;gt;=)\s?([\d_\.]+\s?|&amp;#39;.+?&amp;#39;|&amp;#34;.+?&amp;#34;|false|true)/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
  .replace(&lt;span style=&#34;color:#235388&#34;&gt;/\|\|/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
  .replace(&lt;span style=&#34;color:#235388&#34;&gt;/\&amp;amp;\&amp;amp;/g&lt;/span&gt;,&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
  .replace(&lt;span style=&#34;color:#235388&#34;&gt;/[&amp;#39;&amp;#34;\(\)\s]/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;They’re pretty straightforward.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(As far as regular expressions go, that is.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first expression strips out characters that can be used to carry out arbitrary code execution via injection attacks and the rest are used in a sieve to remove all the input we expect to be there.&lt;/p&gt;
&lt;p&gt;We then check to see if anything is left behind in the sieve. If the sieve is not empty, we treat it as a possible attack and return an empty result set, as shown below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (sieve &lt;span style=&#34;color:#666&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Sieve not empty, reject.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;&#34;&gt;#&lt;/span&gt;cachedResult &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; []
} &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Run the query.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;&#34;&gt;#&lt;/span&gt;cachedResult &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.data.filter(valueOf =&amp;gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;try&lt;/span&gt; {
      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;eval&lt;/span&gt;(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.query)
    } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;catch&lt;/span&gt; (error) {
      &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Eval failed. Possible injection attack, return false.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;false&lt;/span&gt;
    }
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’ve simplified the comments in the code snippets but, otherwise, they are &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/ef5736a43e9348f949d00c1b5bf0df4a1247184e/lib/QueryProxy.js#L44&#34;&gt;as I wrote them a couple of days ago&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;jsdb-now-with-20-more-code&#34;&gt;JSDB, now with 20% more code!&lt;/h2&gt;
&lt;p&gt;Now, &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/lib/QuerySanitiser.js&#34;&gt;that a look at what that code looks like as of yesterday&lt;/a&gt; (and keep in mind that it does exactly the same thing).&lt;/p&gt;
&lt;p&gt;The first thing you might notice is that it is now a 300-line file, including comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Wow, what happened?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I am now composing my regular expressions from constants&lt;/strong&gt; instead of using regular expression literals. e.g.,&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; join() { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Array&lt;/span&gt;.from(arguments).join(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) }
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; anyCharacter &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; oneOrMore    &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; nonGreedy    &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;?&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; get oneOrMoreCharactersNonGreedy () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.join(
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.anyCharacter,
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.oneOrMore,
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.nonGreedy
  )
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I now get the values for the allow list directly from the source&lt;/strong&gt; (from &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/lib/QueryOperators.js&#34;&gt;QueryOperators.js&lt;/a&gt; which is the same code that is used by the query generation methods).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; QueryOperators {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; STRICT_EQUALS &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;===&amp;#39;&lt;/span&gt;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; STRICT_DOES_NOT_EQUAL &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;!==&amp;#39;&lt;/span&gt;

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; RELATIONAL_OPERATORS &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {
    is&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.STRICT_EQUALS,
    isEqualTo&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.STRICT_EQUALS,
    equals&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.STRICT_EQUALS,

    isNot&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.STRICT_DOES_NOT_EQUAL,
    doesNotEqual&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.STRICT_DOES_NOT_EQUAL,

    isGreaterThan&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;gt;&amp;#39;&lt;/span&gt;,
    isGreaterThanOrEqualTo&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;gt;=&amp;#39;&lt;/span&gt;,
    isLessThan&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;&amp;#39;&lt;/span&gt;,
    isLessThanOrEqualTo&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;=&amp;#39;&lt;/span&gt;
  }

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; FUNCTIONAL_OPERATORS &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [
    &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;startsWith&amp;#39;&lt;/span&gt;,
    &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;endsWith&amp;#39;&lt;/span&gt;,
    &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;includes&amp;#39;&lt;/span&gt;,
    &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;startsWithCaseInsensitive&amp;#39;&lt;/span&gt;,
    &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;endsWithCaseInsensitive&amp;#39;&lt;/span&gt;,
    &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;includesCaseInsensitive&amp;#39;&lt;/span&gt;
  ]

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; get uniqueListOfRelationalOperators () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Array&lt;/span&gt;.from(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; Set(&lt;span style=&#34;color:#007020&#34;&gt;Object&lt;/span&gt;.values(QueryOperators.RELATIONAL_OPERATORS)))
  }
}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; RE {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; anyFunctionalOperator &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.anyOf(...QueryOperators.FUNCTIONAL_OPERATORS)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; anyRelationalOperator &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.anyOf(...QueryOperators.uniqueListOfRelationalOperators)
}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; Allowed {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Statements in the form valueOf.&amp;lt;property&amp;gt; === &amp;lt;value&amp;gt;, etc.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// /valueOf\.[^\.]+?\s?(===|!==|&amp;gt;|&amp;gt;=|&amp;lt;|&amp;lt;=)\s?([\d\._]+\s?|&amp;#39;.+?&amp;#39;|&amp;#34;.+?&amp;#34;|false|true)/g
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; relationalOperations &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalRegExp(
    RE.literal.valueOfDot,
    RE.oneOrMoreCharactersNonGreedyThatAreNotDots,
    RE.zeroOrMoreWhitespace,
    RE.anyRelationalOperator,
    RE.zeroOrMoreWhitespace,
    RE.anyValidRelationalOperationValue
  )
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The effects, subsequently, are three-fold:&lt;/p&gt;
&lt;h3 id=&#34;two-positives&#34;&gt;Two positives&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The intent and clarity of the code is improved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The maintainability of the code is improved and it is now safer as I can update the query generation code without worrying that I am going to forget to update the sanitisation code or that I might make typos while copying values over manually into regular expression literals.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;and-one-negative&#34;&gt;And one negative&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I’ve added 152 lines of extra code to the project.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;So do the first two positive effects outweigh the cost of the third, negative one?&lt;/strong&gt; Especially on a tool that had 755 lines of code to begin with? (The additional lines of code amount to a 20% increase of the codebase).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Definitely.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;fewer-lines-of-code-doesnt-always-mean-better-code&#34;&gt;Fewer lines of code doesn’t always mean better code.&lt;/h2&gt;
&lt;p&gt;While less code generally means an easier to maintain and safer application, this axiom does not exist in a vacuum. In over three decades of programming, I’ve come to respect that clarity of intent is the most important factor when writing code.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;is-what-i-wrote-what-i-had-actually-meant-to-write&#34;&gt;Is what I wrote what I had actually meant to write?&lt;/h2&gt;
&lt;p&gt;The following is confusing, especially to someone who isn’t versed in regular expressions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#235388&#34;&gt;/valueOf\.[^\.]+?\s?(===|!==|&amp;lt;|&amp;gt;|&amp;lt;=|&amp;gt;=)\s?([\d_\.]+\s?|&amp;#39;.+?&amp;#39;|&amp;#34;.+?&amp;#34;|false|true)/g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whereas the code below, that composes the regular expression from constants, can be read by almost anyone – even a non-programmer – to get the gist of what’s happening:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; relationalOperations &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalRegExp(
  RE.literal.valueOfDot,
  RE.oneOrMoreCharactersNonGreedyThatAreNotDots,
  RE.zeroOrMoreWhitespace,
  RE.anyRelationalOperator,
  RE.zeroOrMoreWhitespace,
  RE.anyValidRelationalOperationValue
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Perhaps even more importantly, if you look at where the constants are mapped to their regular expression counterparts, you can immediately see whether the regular expression I wrote actually expresses what I thought I was writing because I use the following declarative form:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; thisIsWhatIWantToArchive &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;This is how I think I archive it&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, now, anyone who knows regular expressions can read through the code and immediately see if there is a mistmatch between what I think I’m asking the computer to do and what I’m actually asking it to do. Furthermore, it is also easy to spot errors in the former for those cases where what I’m trying to do itself is not the right thing.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; RegularExpressionLiterals {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; dot &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;\\.&amp;#39;&lt;/span&gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; RE {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Concatenates into a string.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; join () {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Array&lt;/span&gt;.from(arguments).join(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Creates negated set (e.g., [^xy]).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; negatedSetOf () {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`[^&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;Array&lt;/span&gt;.from(arguments).join(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;]`&lt;/span&gt;
  }

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; literal &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; RegularExpressionLiterals
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; nonGreedy    &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;?&amp;#39;&lt;/span&gt;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; oneOrMore    &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// [^\\.]+?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt; get oneOrMoreCharactersNonGreedyThatAreNotDots () {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.join(
      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.negatedSetOf(
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.literal.dot
      ),
      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.oneOrMore,
      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.nonGreedy
    )
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;in-conclusion&#34;&gt;In conclusion&lt;/h2&gt;
&lt;p&gt;If you’ve made it this far, I’d really appreciate it if you could &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/lib/QuerySanitiser.js&#34;&gt;take a quick look at the JSDB Query Sanitiser&lt;/a&gt; (and maybe even the much simpler &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/lib/JSDF.js#L45&#34;&gt;String sanitiser in the JSDF parser&lt;/a&gt;) and see if you can spot anything I’ve missed. If you do notice anything amiss, I’d appreciate it if you could &lt;a href=&#34;https://github.com/small-tech/jsdb/issues&#34;&gt;open an issue&lt;/a&gt; and let me know.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What? Did I write this blog post just to get more eyes on the security code for my new database?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tsk, tsk…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maybe ;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PS. I do hope you enjoy using JSDB. I’m currently working on integrating it into &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; for use in &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; sites and apps.&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The statistics are compiled using &lt;a href=&#34;https://github.com/boyter/scc&#34;&gt;scc&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I am still happy to see that the whole of JSDB is just ~900 lines of code (not counting tests, and 1,443 lines counting tests with 100% code coverage). &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Introducing JSDB</title>
      <link>https://ar.al/2020/10/20/introducing-jsdb/</link>
      <pubDate>Tue, 20 Oct 2020 16:23:32 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/10/20/introducing-jsdb/</guid>
      <description>&lt;p&gt;Yesterday, I released version 1.0 of &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JavaScript Database (JSDB)&lt;/a&gt;, a new database for Node.js optimised for use with &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; sites and apps.&lt;/p&gt;
&lt;p&gt;It does things a little differently to other databases.&lt;/p&gt;
&lt;p&gt;It’s an in-memory JavaScript database that persists to an append-only transaction log.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And it’s all JavaScript.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What I mean by that is that even &lt;a href=&#34;https://ar.al/2020/09/23/what-if-data-was-code/&#34;&gt;the data is stored as JavaScript code&lt;/a&gt;. Not JSON. &lt;em&gt;JavaScript.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;(It’s also very fast and easy to use.)&lt;/p&gt;
&lt;h3 id=&#34;get-started&#34;&gt;Get started&lt;/h3&gt;
&lt;p&gt;You will need Node.js installed&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up your project and install JSDB.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a folder to hold your project and switch to it.&lt;/span&gt;
mkdir lovecraft-country
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; lovecraft-country

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Initialise it with a package.json file.&lt;/span&gt;
npm init -f

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install JSDB.&lt;/span&gt;
npm i @small-tech/jsdb

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a file to hold your code.&lt;/span&gt;
touch index.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Open a database and write something to it.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; JSDB &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/jsdb&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Open your database (creating it if it doesn’t exist).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// It will be stored in a directory called “db”.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; db &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSDB.open(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;db&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create a “table” on it. Tables can be arrays or objects.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// This is a list of the Lovecraft Country episodes and
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// their ratings from IMDB. (It’s missing the last three.)
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;db.episodes) {
  db.episodes &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Sundown&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt;},
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Whitey’s on the Moon&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt;},
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Holy Ghost&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.7&lt;/span&gt;},
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;A History of Violence&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.6&lt;/span&gt;},
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Strange Case&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt;},
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Meet me in Daegu&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt;},
    {title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;I am.&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt;}
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;Run it.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;node .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In your terminal, you should see output similar to the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;💾 ❨JSDB❩ No database found at …lovecraft-country/db; creating it.
💾 ❨JSDB❩ Creating and persisting table episodes…
💾 ❨JSDB❩  ╰─ Created and persisted table in 2.169 ms.
💾 ❨JSDB❩ Table episodes initialised.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The table is created and persisted into a file called &lt;code&gt;db/episodes.js&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;data-as-code&#34;&gt;Data as code&lt;/h3&gt;
&lt;p&gt;To &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/README.md#dispelling-the-magic-and-a-pointing-out-a-couple-of-gotchas&#34;&gt;dispel the magic&lt;/a&gt; and see &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/README.md#how-the-sausage-is-made&#34;&gt;how the sausage is made&lt;/a&gt;, take a look at what’s inside it this file by running the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;cat db/episodes.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;globalThis._ &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [ { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Sundown`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Whitey’s on the Moon`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;`Holy Ghost`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`A History of Violence`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Strange Case`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Meet me in Daegu`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`I am.`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; } ];
(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; define &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;function&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; define.amd) { define([], globalThis._); } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (typeofmodule &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; module.exports) { module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalThis._ } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; { globalThis.lovecraftCountryEpisodes &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalThis._ } })();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yes, that’s plain-old JavaScript. Not JSON. &lt;em&gt;JavaScript.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And it’s a bit hard to read. If you were to prettify it, this is what you’d get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;globalThis._ &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Sundown`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; },
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Whitey’s on the Moon`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; },
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Holy Ghost`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; },
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`A History of Violence`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; },
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Strange Case`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; },
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Meet me in Daegu`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; },
  { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`I am.`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }
]
;(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; define &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;function&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; define.amd) {
    define([], globalThis._)
  } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (typeofmodule &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; module.exports) {
    module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalThis._
  } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; {
    globalThis.lovecraftCountryEpisodes &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalThis._
  }
})()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is how JSDB stores data. &lt;a href=&#34;https://ar.al/2020/09/23/what-if-data-was-code/&#34;&gt;It stores it as code&lt;/a&gt; in a format I call &lt;a href=&#34;https://github.com/small-tech/jsdb#javascript-data-format-jsdf&#34;&gt;JavaScript Data Format (JSDF)&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;javascript-data-format-jsdf&#34;&gt;JavaScript Data Format (JSDF)&lt;/h3&gt;
&lt;p&gt;The first line is a single assignment of all the data that existed in the table when it was created or last loaded. In this case, it is basically the same array declaration you wrote to create the table.&lt;/p&gt;
&lt;p&gt;The second line is a &lt;a href=&#34;https://github.com/umdjs/umd&#34;&gt;UMD&lt;/a&gt;-style declaration. What this means is that you could, if you wanted to, use &lt;code&gt;require()&lt;/code&gt; in Node to load a JSDB table in directly or even load a JSDB table in using a script tag from the browser.&lt;/p&gt;
&lt;p&gt;For example, create a file called &lt;code&gt;index.html&lt;/code&gt; in the same folder with the following content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;db/episodes.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;Lovecraft Country&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Episodes and ratings from &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://www.imdb.com/title/tt6905686/episodes?season=1&amp;#39;&lt;/span&gt;&amp;gt;IMDB&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;:&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
  episodes.forEach(episode =&amp;gt; {
    &lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;.write(&lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;episode.title&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/strong&amp;gt; ⭐ &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;episode.rating&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;)
  })
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Display this web page in your browser using something like &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; and you should see the following.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/10/20/introducing-jsdb/lovecraft-country-episodes.png&#34;
         alt=&#34;A list of the first seven Lovecraft Country episodes from the first season along with their ratings from IMDB: Sundown ⭐ 8.4, Whitey’s on the Moon ⭐ 7.1, Holy Ghost ⭐ 7.7, A History of Violence ⭐ 7.6, Strange Case ⭐ 7.1, Meet me in Daegu ⭐ 8.4, I am. ⭐ 7.1&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A list of the first seven Lovecraft Country episodes from the first season.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;h3 id=&#34;important-security-note&#34;&gt;Important security note&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;JSDF is &lt;em&gt;not&lt;/em&gt; a data exchange format.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since JSDF is made up of JavaScript code that is evaluated at run time, you must only load JSDF files from domains that you own and control and have a secure connection to.&lt;/p&gt;
&lt;p&gt;To exchange data, use JSON, that’s what it’s for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;updating-data&#34;&gt;Updating data&lt;/h3&gt;
&lt;p&gt;So what happens when we update the existing data?&lt;/p&gt;
&lt;p&gt;To see this, let’s add the missing episodes from the first season to our database and watch what happens to the contents of the table as we do.&lt;/p&gt;
&lt;p&gt;Start by opening up an interactive Node.js session in one Terminal window (type &lt;code&gt;node&lt;/code&gt;) and tailing (following) the database table from another one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;tail -f db/episodes.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, let’s open our database again in the interactive Node.js session:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;JSDB &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/jsdb&amp;#39;&lt;/span&gt;)
db &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSDB.open(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;db&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, let’s add the missing episodes for the first season, one at a time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;db.episodes.push ({title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Jig-a-Bobo&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt;})
db.episodes.push ({title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Rewind 1921&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.6&lt;/span&gt;})
db.episodes.push ({title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Full Crcle [sic]&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.0&lt;/span&gt;})
db.episodes.push ({title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Happy ending&amp;#39;&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;10.0&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you add them, you should see them appear at the end of the table. When you’ve added all four, your table should resemble the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// first line is the same as before
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// second line is the same as before
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;_[&lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Jig-a-Bobo`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; };
_[&lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Rewind 1921`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.6&lt;/span&gt; };
_[&lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Full Crcle [sic]`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt; };
_[&lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Happy ending`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt; };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now you know how JSDB stores data that gets added or changed: it adds the code to affect that change to the end of the table. This is what makes it an append-only transaction log. And this is what keeps writes fast (in the single digit milliseconds for the most part on my development laptop) as they’re being streamed to the end of the file.&lt;/p&gt;
&lt;p&gt;Ah, but we’ve made some errors, so let’s correct them.&lt;/p&gt;
&lt;p&gt;First of all, there is no 11th episode called “Happy Ending”, so let’s remove that from the list:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;db.episodes.pop()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we made an intentional spelling mistake in the title of episode 9. Let’s fix it in our interactive Node.js session:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;db.episodes.where(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;).includes(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Full&amp;#39;&lt;/span&gt;).getFirst().title &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Full Circle&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Woah! What’s all that new stuff?&lt;/p&gt;
&lt;p&gt;It’s a query in a language I call &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/README.md#jsql-reference&#34;&gt;JavaScript Query Language (JSQL)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’ll see a bit more of JSQL a little later on.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; But for now, let’s take a penultimate look at the JSDF file for the episodes table. Here’s what it should look like now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;globalThis._ &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [ { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Sundown`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Whitey’s on the Moon`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;`Holy Ghost`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.7&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`A History of Violence`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.6&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Strange Case`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Meet me in Daegu`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`I am.`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt; } ];
(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; define &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;function&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; define.amd) { define([], globalThis._); } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (typeofmodule &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; module.exports) { module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalThis._ } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; { globalThis.episodes &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; globalThis._ } })();
_[&lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Jig-a-Bobo`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; };
_[&lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Rewind 1921`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.6&lt;/span&gt; };
_[&lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Full Crcle [sic]`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt; };
_[&lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Happy ending}`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt; };
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;delete&lt;/span&gt; _[&lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;];
_[&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;length&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;;
_[&lt;span style=&#34;color:#40a070&#34;&gt;9&lt;/span&gt;][&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Full Circle`&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hmm, all our changes are there but &lt;em&gt;do you see a problem?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Because I do.&lt;/p&gt;
&lt;p&gt;We’ve deleted the last episode but the actual data for it is still in our table, on disk. In this case, that’s not much of a problem. But what if instead of a TV episode, it was a private piece of information that you wanted to delete? For the sake of privacy, it’s important that data we &lt;em&gt;think&lt;/em&gt; is deleted is &lt;em&gt;actually&lt;/em&gt; deleted. This is a problem that all append-only logs suffer from due to their inherent nature and it’s one that JSDB solves using compaction.&lt;/p&gt;
&lt;h3 id=&#34;compaction&#34;&gt;Compaction&lt;/h3&gt;
&lt;p&gt;By default, every time a table is loaded, any changes that were made to it in the last session are compacted into the single-line initialisation statement on the first line. This &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/README.md#compaction&#34;&gt;compaction process&lt;/a&gt; is important for various reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It preserves privacy by actually deleting and updating changed data everywhere.&lt;/li&gt;
&lt;li&gt;It makes subsequent loads faster as only a single assignment statement is run to create the data graph in memory instead of possibly hundreds or thousands of statements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To see compaction in action, exit your current interactive Node.js session and start a new one. Then, open the database again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;JSDB &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/jsdb&amp;#39;&lt;/span&gt;)
db &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSDB.open(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;db&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see output that resembles the following:&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;💾 ❨JSDB❩ Loading table episodes…
💾 ❨JSDB❩  ╰─ Loading table synchronously.
💾 ❨JSDB❩  ╰─ Table loaded in 1.515 ms.
💾 ❨JSDB❩ Compacting and persisting table episodes…
💾 ❨JSDB❩  ╰─ Compacted and persisted table in 4.371 ms.
💾 ❨JSDB❩ Table episodes initialised
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, if you take one final look at the episodes table, you should see that it’s back to being two lines and that the first line, shown below, includes all the changes we made so far:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;globalThis._ &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [ { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Sundown`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Whitey’s on the Moon`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt;
&lt;span style=&#34;color:#4070a0&#34;&gt;`Holy Ghost`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.7&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`A History of Violence`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.6&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Strange Case`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Meet me in Daegu`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`I am.`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7.1&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Jig-a-Bobo`&lt;/span&gt;,rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.4&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Rewind 1921`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;8.6&lt;/span&gt; }, { title&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`Full Circle`&lt;/span&gt;, rating&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;7&lt;/span&gt; } ];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;javascript-query-language-jsql&#34;&gt;JavaScript Query Language (JSQL)&lt;/h3&gt;
&lt;p&gt;You got a tiny introduction to &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/README.md#jsql-reference&#34;&gt;JavaScript Query Language (JSQL)&lt;/a&gt; in the previous section. Now that we have all the episodes correctly entered in our database, let’s use JSQL one last time to get the names of the episodes that have ratings higher than 8 stars.&lt;/p&gt;
&lt;p&gt;In the same interactive Node.js session as before, enter the following query:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Get the highly rated episodes.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;highlyRatedEpisodes &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; db.episodes.where(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;rating&amp;#39;&lt;/span&gt;).isGreaterThan(&lt;span style=&#34;color:#40a070&#34;&gt;8&lt;/span&gt;).get()

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Print them out nicely.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;highlyRatedEpisodes.forEach(episode =&amp;gt; console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;episode.title&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; (⭐ &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;episode.rating&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;)`&lt;/span&gt;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see the following list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sundown (⭐ 8.4)&lt;/li&gt;
&lt;li&gt;Meet me in Daegu (⭐ 8.4)&lt;/li&gt;
&lt;li&gt;Jig-a-Bobo (⭐ 8.4)&lt;/li&gt;
&lt;li&gt;Rewind 1921 (⭐ 8.6)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope this has whetted your appetite and that you’ll have a play with &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JSDB&lt;/a&gt; and consider how you could use it in your own apps.&lt;/p&gt;
&lt;p&gt;Also, I’m in the process of integrating it into &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; so you can use it when creating &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; sites and apps.&lt;/p&gt;
&lt;p&gt;You can find further details and examples in &lt;a href=&#34;https://github.com/small-tech/jsdb/blob/master/README.md#compaction&#34;&gt;the JSDB documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enjoy and, no matter what you do, &lt;strong&gt;please &lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;do not use it to farm people for their data&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you don’t have Node.js installed, the easiest way to do so is by using &lt;a href=&#34;https://github.com/nvm-sh/nvm&#34;&gt;nvm&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Yes, we could have just written &lt;code&gt;db.episodes[9].title = &#39;Full Circle&#39;&lt;/code&gt; but where’s the fun in that? &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;When running in the interactive shell, you might also see the following message: &lt;code&gt;Expression assignment to _ now disabled.&lt;/code&gt; That’s just the shell telling you that the global &lt;code&gt;_&lt;/code&gt; variable is being used by JSDB. Usually, the shell uses this to contain the result of the last expression it evaluated. This is expected behaviour. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>What if data was code?</title>
      <link>https://ar.al/2020/09/23/what-if-data-was-code/</link>
      <pubDate>Wed, 23 Sep 2020 16:05:49 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/09/23/what-if-data-was-code/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/09/23/what-if-data-was-code/spiderman-pointing-meme.jpg&#34;
         alt=&#34;Spiderman pointing at Spiderman meme.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Code? Data? Data? Code?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; This is now a thing and it’s not called WhatDB but &lt;a href=&#34;https://github.com/small-tech/jsdb&#34;&gt;JavaScript Database (JSDB)&lt;/a&gt;. Read more about it in my &lt;a href=&#34;../../../10/20/introducing-jsdb&#34;&gt;Introducing JSDB post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a little idea I’m throwing around for &lt;a href=&#34;https://github.com/small-tech/whatdb&#34;&gt;WhatDB&lt;/a&gt;, the transparent in-memory JavaScript database for local server data that I’m working on for &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;, the &lt;a href=&#34;https://ar.al/2020/08/07/what-is-the-small-web/&#34;&gt;Small Web&lt;/a&gt; construction set:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What if I don’t persist the database as a serialised JSON structure but as the series of changes that created it instead?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What do I mean by that? Take a look at this quick proof-of-concept:&lt;/p&gt;
&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;h3 id=&#34;writejs&#34;&gt;write.js&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; fs &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; writeStream &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; fs.createWriteStream(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;data.js&amp;#39;&lt;/span&gt;)
writeStream.write(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;const data = []\n&amp;#39;&lt;/span&gt;)
writeStream.write(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module.exports = data\n&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1000&lt;/span&gt;; i&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;) {
  writeStream.write(&lt;span style=&#34;color:#4070a0&#34;&gt;`data.push(&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;i&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;n`&lt;/span&gt;)
}

writeStream.close()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;readjs&#34;&gt;read.js&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./data.js&amp;#39;&lt;/span&gt;)
console.log(data)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, run &lt;em&gt;write.js&lt;/em&gt;, then run &lt;em&gt;read.js&lt;/em&gt;. You should see the 1,000 item array logged to the console.&lt;/p&gt;
&lt;h2 id=&#34;why&#34;&gt;Why?&lt;/h2&gt;
&lt;p&gt;Even though WhatDB is for small amounts of data (since the Small Web discourages storage of privileged data on servers), having to execute a full JSON serialisation and write to disk on every write does not sit right with me.&lt;/p&gt;
&lt;h3 id=&#34;stringification-is-a-bitch&#34;&gt;Stringification is a bitch&lt;/h3&gt;
&lt;p&gt;Don’t get me wrong, it is definitely fast enough for smaller data sets. But even with 100,000 records of my test dataset I didn’t want to block the event loop in Node.js for the 40-50ms that it takes to stringify the database on every write.&lt;/p&gt;
&lt;h3 id=&#34;when-streaming-isnt-enough-to-lower-cpu-usage&#34;&gt;When streaming isn’t enough (to lower CPU usage)&lt;/h3&gt;
&lt;p&gt;So I implemented streaming JSON serialisation using the excellent &lt;a href=&#34;https://github.com/Faleij/json-stream-stringify&#34;&gt;json-stream-stringify&lt;/a&gt; and that reduced the loop delay to the sub-ms region. But I was still maxing out CPU.&lt;/p&gt;
&lt;p&gt;On a 1,000,000 record database (again, overkill for Small Web), that meant 100%+ CPU for the 40 seconds or so that serialisation was taking place. Actually writing the serialised data to disk, atomically, was trivial in comparison (a couple of seconds).&lt;/p&gt;
&lt;p&gt;So at this point I have something that works for its intended use case and I can just call it a day and move on but I can’t. Even if it’s within acceptable parameters, it feels wasteful. (Is there such a thing as a &lt;em&gt;code conscience?&lt;/em&gt; Who knows…)&lt;/p&gt;
&lt;p&gt;Anyway, so today I started &lt;a href=&#34;https://mastodon.ar.al/@aral/104914292257545465&#34;&gt;thinking out loud on the fediverse&lt;/a&gt; about how I could perform delta (partial) edits on the serialised data string instead of serialising the whole thing every time and that led to us throwing out ideas spanning &lt;a href=&#34;https://shitposter.club/objects/793e2269-f24e-4260-a6aa-e54b1e410afb&#34;&gt;CRDTs&lt;/a&gt;, &lt;a href=&#34;https://mastodon.social/@zensaiyuki/104914606759828129&#34;&gt;piece tables&lt;/a&gt;, and even Kappa architectures before I came to realise that all that would be overkill and instead…&lt;/p&gt;
&lt;h2 id=&#34;a-simple-idea-for-a-simple-database&#34;&gt;A simple idea for a simple database&lt;/h2&gt;
&lt;p&gt;What if I just stored all the instructions that are needed to create each table/object graph instead of a serialised copy of the data itself?&lt;/p&gt;
&lt;p&gt;In fact, what if the data itself was &lt;em&gt;a Node module?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is the point that some folks will stare at me with horror in their eyes. The sacrilege… the heresy! What will they do, I wonder, when they learn that I’ve done far, far darker things…&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;properties-of-such-a-system&#34;&gt;Properties of such a system&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Might be slow during initial object graph creation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is fine given that the initial object graph is created at server launch alongside other synchronous tasks. Performance isn’t a concern at that point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Will take up more disk space than serialisation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s ok, given disk space isn’t the bottleneck in an in-memory Node.js database, &lt;a href=&#34;https://www.the-data-wrangler.com/nodejs-memory-limits/&#34;&gt;Node’s memory limitations&lt;/a&gt; are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fast reads.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a property of having an in-memory data store that is not affected by this decision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fast writes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since the file format is basically an append-only log, streaming writes are quick and easy on the event loop and system resources.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Require snapshots?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since we’re losing the atomic writes due to the appends, it would make sense to have periodic snapshots.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Require compaction?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We could compact on initial load (on server start&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;) to ensure that deleted data is actually deleted. Compaction would basically result in a single-line &lt;code&gt;JSON.parse&lt;/code&gt; statement on the full serialised table.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Can’t wait to get my hands dirty and experiment with this in WhatDB.&lt;/p&gt;
&lt;h2 id=&#34;thanks&#34;&gt;Thanks&lt;/h2&gt;
&lt;p&gt;Thanks to everyone on the fediverse (and &lt;em&gt;that other proprietary platform&lt;/em&gt;) for being my rubber duck and for providing me with great resources and food for thought. Also, I just learned that what I’m doing has a name: &lt;a href=&#34;https://mastodon.social/@vertigo/104914921109409160&#34;&gt;object prevalence&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is not the first time I’ve played with a similar concept. Back in the Flash days (yes, I’m that old), I made &lt;a href=&#34;https://en.wikipedia.org/wiki/Adobe_Flash_Player#Data_formats&#34;&gt;a data format called SWX&lt;/a&gt; that basically allowed you to load data wrapped in the native SWF format. Thank goodness this time I don’t have to clean-room reverse-engineer SWF bytecode. If you’re into history, you can &lt;a href=&#34;https://doc.lagout.org/programmation/ActionScript/FriendsofED.The.Essential.Guide.to.Open.Source.Flash.Development.Jul.2008.pdf&#34;&gt;read about SWX in the chapter R. Jon MacDonald wrote&lt;/a&gt; on it for our The Essential Guide to Open Source Flash Development book. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or every &lt;em&gt;nth&lt;/em&gt; restart or on restart after &lt;em&gt;n&lt;/em&gt; days, etc. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>What is the Small Web?</title>
      <link>https://ar.al/2020/08/07/what-is-the-small-web/</link>
      <pubDate>Fri, 07 Aug 2020 13:03:28 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/08/07/what-is-the-small-web/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Updated June 19th, 2023&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;video id=&#34;small-is-beautiful&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/762676594/rendition/1080p/file.mp4?loc=external&amp;signature=6f327f42324b157a97add4fd4c532c69e0cdd29b206ff93f79f4cdae570ed922#t=185&#34;
    poster=&#34;https://small-tech.org/videos/small-is-beautiful-23/poster.jpg&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/https://small-tech.org/videos/small-is-beautiful-23/poster.jpg&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/playback/762676594/rendition/1080p/file.mp4?loc=external&amp;signature=6f327f42324b157a97add4fd4c532c69e0cdd29b206ff93f79f4cdae570ed922#t=27&#34;&gt;download
        Small Is Beautiful #23 directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Small Is Beautiful (Oct, 2022): What is the Small Web and why do we need it?&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Today, I want to introduce you to a concept – and a vision for the future of our species in the digital and networked age – that &lt;a href=&#34;https://small-tech.org/videos/creative-mornings-istanbul/&#34;&gt;I’ve spoken about&lt;/a&gt; for a while but never specifically written about:&lt;/p&gt;
&lt;p&gt;The Small Web.&lt;/p&gt;
&lt;p&gt;To understand what the Small Web is, let’s compare it to the Big Web. In other words, to the centralised Web we have today.&lt;/p&gt;
&lt;h2 id=&#34;the-big-web&#34;&gt;The Big Web&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/big-web.png&#34;
         alt=&#34;The Big Web: a grid of “users” are hosted on MegaCorp’s server farm and you rent your space at https://mega.corp/you&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;You are allowed to rent space on Megacorp’s servers in exchange for giving up your privacy, freedom of speech, and other human rights.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Big Web is the centralised web; it is a web in the sense of a spider’s web. The spiders that sit at its centre waiting to suck you dry are Big Tech &lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;people farmers&lt;/a&gt; like Facebook, Google, etc.&lt;/p&gt;
&lt;p&gt;The Big Web has “users” – a term Silicon Valley has borrowed from drug dealers to describe the people they addict to their services and exploit. We farm users in server farms. On the Big Web, we can fit thousands of users into a single server and Megacorps “scale” to run thousands upon thousands of servers in their farms.&lt;/p&gt;
&lt;p&gt;On the Big Web, you never own your own home. You must rent your home from Megacorps. Most often, you don’t have to pay for your home using money. You pay for it by forfeiting your privacy, freedom of speech, and your other human rights. Collectively, we pay for it by &lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;forfeiting a democratic future&lt;/a&gt;.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The mass surveillance and factory farming of human beings on a global scale is the business model of people farmers like Facebook and Google. It is the primary driver of the socioeconomic system we call &lt;a href=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/&#34;&gt;surveillance capitalism&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-small-web&#34;&gt;The Small Web&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/small-web.png&#34;
         alt=&#34;The Small Web: you have your server at https://your.site.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;On the Small Web, you own your own home.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Small Web, quite simply, is the polar opposite of the Big Web. It applies &lt;a href=&#34;https://small-tech.org/about#small-technology&#34;&gt;the Small Technology principles&lt;/a&gt; to the web.&lt;/p&gt;
&lt;h3 id=&#34;the-small-web-is-your-web&#34;&gt;The Small Web is Your Web&lt;/h3&gt;
&lt;p&gt;The Small Web is for people (not startups, enterprises, or governments). It is also made by people and small, independent organisations (not startups, enterprises, or governments&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;On the Small Web, you (and only you) own and control your own home (or homes).&lt;/p&gt;
&lt;h3 id=&#34;the-small-web-is-the-single-tenant-web&#34;&gt;The Small Web is the Single Tenant Web&lt;/h3&gt;
&lt;p&gt;Small Web applications and sites are single tenant. That means that one server hosts one application that serves just one person: you. On the Small Web, we do not have the concept of “users”. When we refer to people, we call them people.&lt;/p&gt;
&lt;p&gt;Another fundamental difference between the Big Web and Small Web is that on the Big Web we trust servers and distrust clients whereas on the Small Web, we distrust servers and trust clients. We treat servers as dumb delivery mechanisms. The client – under the control of the person who owns the site or app – is the only trusted environment.&lt;/p&gt;
&lt;p&gt;Our greatest usability challenge on the Small Web is making the ownership and control of your own web site or application as seamless as possible. It must be done in a manner that does not require any technical know-how  whatsoever. Our goal is to make owning and maintaining your own home on the web as easy as renting from a Megacorp without all of the toxic ramifications the latter entails.&lt;/p&gt;
&lt;p&gt;We are not there yet but the end (beginning?) is in sight.&lt;/p&gt;
&lt;h2 id=&#34;where-we-are-today&#34;&gt;Where we are today&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/kitten.png&#34;
         alt=&#34;A minimalist illustration of a cute Kitten’s head.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Kitten is a complete Small Web development kit (including server).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The first step to building the Small Web is to build tools for developers to empower them to build the Small Web.&lt;/p&gt;
&lt;p&gt;That’s why we’re building &lt;a href=&#34;https://codeberg.org/kitten/app#kitten&#34;&gt;Kitten&lt;/a&gt; at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Currently, all our developer tools and technical infrastructure comes from Big Tech and the Big Web. They are optimised for creating Big Tech and the Big Web. While we can repurpose some of them for our own uses, we also need tools specifically optimised for building single-tenant web applications and the Small Web.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/developer-tools-to-everyday-tools.png&#34;
         alt=&#34;On the left, a pink blob titled ‘developer tools’ points via an arrow towards a blue blob on the right labelled ‘everyday tools.’&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;We’re currently building the tools developers (including us) need to build the everyday tools everyone will use.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In the words of Audre Lorde:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The master’s tools will never dismantle the master’s house.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(In other words, &lt;em&gt;Big Tech’s tools will never dismantle Big Tech’s house&lt;/em&gt;. The best thing we can do as developers is to build our own tools so we can build our own houses in our own way.)&lt;/p&gt;
&lt;p&gt;Single tenancy affords us a great reduction in complexity that we must take full advantage of if we are to make the Small Web experience as good as (if not better) than the Big Web experience.&lt;/p&gt;
&lt;p&gt;The single tenant web is sustainable and scales differently. It does not have economies of scale. It does not scale vertically like the Big Web. It scales horizontally. As the Small Web scales, no single organisation or person scales alongside it. It does not centralise wealth and power.&lt;/p&gt;
&lt;h2 id=&#34;where-we-are-heading&#34;&gt;Where we are heading&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/small-web.jpg&#34;
         alt=&#34;Screenshot of a web browser showing the contents of https://small-web.org, on a white background, an image of eight multicoloured circles form a decentralised web, each one connected to every other via dotted grey lines.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Small Web is not a place; it is a public sphere. It’s the interconnections of individually-owned and controlled sovereign spaces on a global digital network.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now that Kitten is reaching a level of maturity, we are using it to build a tool called &lt;a href=&#34;https://codeberg.org/domain/app#domain-logo-domain-logo-svg-domain&#34;&gt;Domain&lt;/a&gt; for hosting Small Web sites and applications built with Kitten with the aim of making it as easy to have your own Small Web site at your own domain&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; as it is to sign up for a centralised social media platform.&lt;/p&gt;
&lt;p&gt;We will be hosting our own instance of Domain at &lt;a href=&#34;https://small-web.org&#34;&gt;small-web.org&lt;/a&gt;. The domain has already been included in the &lt;a href=&#34;https://publicsuffix.org/&#34;&gt;Public Suffix List&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This work has begun and is in the early stages.&lt;/p&gt;
&lt;p&gt;This service, once ready, will still initially be aimed at developers. However, with the infrastucture for instantly deploying any Small Web app or web site created, we (and other developers) can then turn our attention to creating beautiful, useful, and perhaps even delightful everyday things for everyday people on the Small Web that adhere to &lt;a href=&#34;https://small-tech.org/about#small-technology&#34;&gt;Small Tech principles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All our work is free and open source (released under AGPL version 3.0) and we invite and encourage others to build similar services and to cooperate with us in igniting the spark of the Small Web.&lt;/p&gt;
&lt;h2 id=&#34;our-vision-for-the-future&#34;&gt;Our vision for the future&lt;/h2&gt;
&lt;p&gt;You hear a lot of talk about blockchains and proof of work but this is not what the Small Web is about. Having a billion copies of the same database is not decentralisation. That’s centralisation. If you have a billion people, having a billion – two billion, three billion… – unique databases is decentralisation. In other words, decentralisation without &lt;em&gt;topological&lt;/em&gt; decentralisation is bullshit.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/08/07/what-is-the-small-web/topological-decentralisation.png&#34;
         alt=&#34;Three blobs (pink, blue, and yellow) with the labels ‘me’, ‘you’, and ‘them’ are all connected to one another with grey lines.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;When we say the Small Web is decentralised, we are talking about topological decentralisation.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;(And in case talk of databases sounds dry, technical, and irrelevant to you, remember that each one of those databases is an artefact of a soul’s exploration of itself and the world around them.)&lt;/p&gt;
&lt;p&gt;Our vision for the future is one where every person owns and controls their own place on our shared global network. This is the only way to ensure that we have a public sphere&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, human rights, and democracy in the digital network era.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Of course, you can rent on the Big Web and from Big Tech by paying both with money and the forfeiture of your human rights. As surveillance capitalists grow in power, they can demand both forms of payment for access to their services. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The Small Web can (and should) be funded from our common purse (our taxes) but the organisations building it must be independent of governments. Since that’s not going to happen from day one, we are doing our best to build a bridge from here to there within the limits of the capitalist system we find ourselves in. Our goal is to prove its feasibility and then ask entities like the European Union to support it in the interests of their citizens and the common good. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you want a commercial domain instead of a domain at the public &lt;em&gt;small-web.org&lt;/em&gt; suffix, you will be able to register any domain and point it at your server. It is &lt;em&gt;your&lt;/em&gt; server, after all. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On a digital network, public space is not a place (like Facebook), it is the interconnection of sovereign spaces owned and controlled by individuals. Furthermore, I use ownership not in the capitalist sense of the word but in the very specific sense of “not owned by anyone else.” &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Live Stream: A web site on your phone with Site.js</title>
      <link>https://ar.al/2020/07/12/live-stream-a-web-site-on-your-phone-with-site.js/</link>
      <pubDate>Sun, 12 Jul 2020 16:47:43 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/07/12/live-stream-a-web-site-on-your-phone-with-site.js/</guid>
      <description>&lt;p&gt;A sneak peek at hosting a web site on a &lt;a href=&#34;https://www.pine64.org/pinephone/&#34;&gt;PinePhone&lt;/a&gt; using &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; 14.2.0 Alpha.&lt;/p&gt;
&lt;div id=&#34;vimeo&#34; style=&#34;position:relative;&#34;&gt;&lt;iframe src=&#34;https://player.vimeo.com/video/437622034&#34; frameborder=&#34;0&#34; allow=&#34;autoplay; fullscreen&#34; allowfullscreen style=&#34;position:absolute;top:0;left:0;width:100%;height:100%;&#34;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p&gt;This event has now ended. You can watch the recording above.&lt;/p&gt;
&lt;style&gt;
#vimeo {
    padding:56.25% 0 0 0;
}
@media only screen and (max-width: 512px) {
  #vimeo {
    padding:100% 0 0 0;
    margin-top: -12.5%;
    margin-bottom: -12.5%;
  }
}
&lt;style&gt;

&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Mentoring the Eastern Partnership Civil Society Online Hackathon</title>
      <link>https://ar.al/2020/07/09/mentoring-the-eastern-partnership-civil-society-online-hackathon/</link>
      <pubDate>Thu, 09 Jul 2020 11:18:27 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/07/09/mentoring-the-eastern-partnership-civil-society-online-hackathon/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/07/09/mentoring-the-eastern-partnership-civil-society-online-hackathon/eastern-partnership-civil-society-online-hackathon.jpg&#34;
         alt=&#34;Image of a person conjuring a networked lightbulb from an iPad&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;No, we will not be teaching people to conjure lightbulbs from iPads.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I will be mentoring teams developing civil society applications at the &lt;a href=&#34;https://ict.eapcivilsociety.eu/en&#34;&gt;Eastern Partnership Civil Society Online Hackathon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The event kicks off today at 2PM Irish time with the introductory session.&lt;/p&gt;
&lt;p&gt;During the session, we will be briefly introducing ourselves, introducing the &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;, and the concept of Small Technology in general.&lt;/p&gt;
&lt;p&gt;We will also be encouraging the teams to start doing research on how they can develop their ideas so that they do not use surveillance-based technologies from companies like Google and Facebook and how they can develop them in a way that decentres themselves so that they don’t end up creating yet more colonial tools and centralised data silos.&lt;/p&gt;
&lt;p&gt;Our focus during the event will be to ensure that the project teams embrace respecting, protecting, and strengthening human rights and democracy as core tenets.&lt;/p&gt;
&lt;p&gt;As part of this, Laura will also be focussing on accessibility-related concerns.&lt;/p&gt;
&lt;p&gt;Laura and I will also hold a keynote session on Friday, July 17th.&lt;/p&gt;
&lt;p&gt;If you’re participating in the hackathon, here are some preliminary resources you should start familiarising yourself with:&lt;/p&gt;
&lt;h3 id=&#34;read&#34;&gt;Read:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/&#34;&gt;In 2020 and beyond, the battle to save personhood and democracy requires a radical overhaul of mainstream technology.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;Small Technology principles.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/&#34;&gt;GDMR; a proposal for a General Data Minimisation Regulation.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;play-with&#34;&gt;Play with:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;watch&#34;&gt;Watch:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/videos/small-technology/&#34;&gt;Small Technology&lt;/a&gt; (talk by Laura and me at ThinkAbout! conference in Cologne, Germany, May 2019).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/videos/disruptive-design/&#34;&gt;Disruptive Design: Harmful Patterns and Bad Practice&lt;/a&gt; (talk by Laura at From Business to Buttons in Stockholm, May 2019).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/videos/creative-mornings-istanbul/&#34;&gt;Beyond surveillance capitalism: alternatives, stopgaps, Small Web, and Site.js&lt;/a&gt; (my talk last month at Creative Mornings Istanbul).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>How to use the Zoom malware safely on Linux if you absolutely have to</title>
      <link>https://ar.al/2020/06/25/how-to-use-the-zoom-malware-safely-on-linux-if-you-absolutely-have-to/</link>
      <pubDate>Thu, 25 Jun 2020 16:34:03 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/06/25/how-to-use-the-zoom-malware-safely-on-linux-if-you-absolutely-have-to/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/06/25/how-to-use-the-zoom-malware-safely-on-linux-if-you-absolutely-have-to/zoom-in-firejail.jpg&#34;
         alt=&#34;Screenshot of Zoom running in Firejail with the output from Firejail shown in a Terminal window under it.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Zoom is malware… if you have to run it, run it in its own prison.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Update (8 Jul, 2020):&lt;/strong&gt; I ended up doing my talk over our Vimeo Live account instead.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&#34;https://small-tech.org/videos/creative-mornings-istanbul/&#34;&gt;watch the edited recording on our web site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We gave the folks in the Zoom meeting a link to my talk and they watched it there. I then did Q&amp;amp;A in the Zoom meeting via my iPhone (where Zoom is sandboxed). Don’t forget that anywhere on the Internet is just one link away. You are not stuck to using a certain venue just because a conference organiser is using it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you must use Zoom – sadly, it is one of the few options if you want to use simultaneous translation or have a sign language interpreter – do so using the web interface as that’s safer (the web interface is sandboxed by the browser). Failing that, use the app on iOS as that is also sandboxed.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;tl-dr-use-jitsi-or-something-else-instead&#34;&gt;TL; DR: Use Jitsi or something else instead&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.theguardian.com/technology/2020/apr/02/zoom-technology-security-coronavirus-video-conferencing&#34;&gt;“Zoom is malware.”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You should be using &lt;a href=&#34;https://jitsi.org/&#34;&gt;Jitsi&lt;/a&gt; instead. (Or, if want to live stream to lots of people, pay for something like &lt;a href=&#34;https://vimeo.com/features/livestreaming&#34;&gt;Vimeo Live&lt;/a&gt; if you can.)&lt;/p&gt;
&lt;h2 id=&#34;but-im-forced-to-use-zoom&#34;&gt;“But I’m forced to use Zoom!”&lt;/h2&gt;
&lt;p&gt;When I agreed to speak at &lt;a href=&#34;https://creativemornings.com/cities/ist&#34;&gt;Creative Mornings Istanbul&lt;/a&gt; &lt;a href=&#34;https://creativemornings.com/talks/aral-balkan&#34;&gt;tomorrow&lt;/a&gt;, I didn’t know they were holding the event over Zoom. I guess it’s fitting that the series I’m speaking at is called &lt;a href=&#34;https://creativemornings.com/themes/insecure&#34;&gt;Insecure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I use Linux, I’m going to take every precaution I can before running the Zoom malware on my machine. That means two things: using &lt;a href=&#34;https://wiki.ubuntu.com/AppArmor&#34;&gt;AppArmor&lt;/a&gt; and &lt;a href=&#34;https://firejail.wordpress.com/&#34;&gt;Firejail&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3 id=&#34;install-firejail&#34;&gt;Install Firejail.&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt install firejail firejail-profiles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;enable-apparmor-for-firejail&#34;&gt;Enable AppArmor for Firejail.&lt;/h3&gt;
&lt;p&gt;If you’re on an Ubuntu-based system, you should already have AppArmor enabled but you may not have its utilities installed (I didn’t on Pop!_OS 20.04).&lt;/p&gt;
&lt;p&gt;First, install the utilities:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt install apparmor-utils
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, apply the AppArmor profile for Firejail:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo aa-enforce firejail-default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;fix-the-firejail-profile-for-zoom&#34;&gt;Fix the Firejail profile for Zoom.&lt;/h3&gt;
&lt;p&gt;Out of the box, the profile that comes with Firejail did not work for me. Zoom was stuck on its “connecting” message.&lt;/p&gt;
&lt;p&gt;To fix this, we must override some of the default settings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Ensure local settings folder exists for Firejail.&lt;/span&gt;
mkdir -p ~/.config/firejail

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Add the custom settings to it.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;protocol unix,inet,inet6,netlink\nignore seccomp\nseccomp \x21chroot&amp;#34;&lt;/span&gt; &amp;gt; ~/.config/firejail/zoom.local
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;make-a-separate-home-folder-for-zoom-to-use&#34;&gt;Make a separate home folder for Zoom to use.&lt;/h3&gt;
&lt;p&gt;We don’t want Zoom to have access to our actual home folder so let’s create one it can use in its place:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;mkdir -p ~/.zoom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;test-it-out&#34;&gt;Test it out.&lt;/h3&gt;
&lt;p&gt;Now test it out and ensure that everything works:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;firejail --apparmor --private&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;/.zoom zoom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it all works, there’s just one more step to go.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;replace-the-zoom-shortcut&#34;&gt;Replace the Zoom shortcut.&lt;/h3&gt;
&lt;p&gt;Everytime we launch Zoom, we want it to launch in its sandbox and we don’t want to keep having to remember to use the Firejail command so let’s replace the Zoom shortcut with our own little script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo rm /usr/bin/zoom
sudo &lt;span style=&#34;color:#bb60d5&#34;&gt;me&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt; bash -c &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;echo -e &amp;#34;#!/bin/bash\nfirejail --apparmor --profile=/etc/firejail/zoom.profile --private=$me/.zoom /opt/zoom/ZoomLauncher&amp;#34; &amp;gt; /usr/bin/zoom&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, whenever you want to start Zoom relatively safely (nothing is entirely safe), you can simply open up a Terminal window and type:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;zoom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I went to all this trouble because I need to be able to present tomorrow using Zoom. In the future, I’ll be asking ahead of time if an event uses Zoom and, if so, I won’t be speaking at it.&lt;/p&gt;
&lt;p&gt;At the very least, I do not want to be the reason that people install malware on their computers and use a system that violates their privacy and human rights.&lt;/p&gt;
&lt;h2 id=&#34;friends-dont-let-friends-use-zoom&#34;&gt;Friends don’t let friends use Zoom&lt;/h2&gt;
&lt;p&gt;For event organisers: respect the privacy of your speakers and attendees and to not subject to surveillance and malware by Zoom.&lt;/p&gt;
&lt;p&gt;You have viable alternatives like &lt;a href=&#34;https://jitsi.org&#34;&gt;Jitsi&lt;/a&gt; and Vimeo (and many others)… use them.&lt;/p&gt;
&lt;p&gt;If you must join a Zoom meeting, try to do so using the web interface as that’s safer (the web interface is sandboxed by the browser). Failing that, use the app on iOS as that is also sandboxed.&lt;/p&gt;
&lt;p&gt;But remember that no matter how you use Zoom, your messages are not end-to-end encrypted and everything you do is monitored by Zoom and most likely by the Chinese government also.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PS. Even if you do all this, you won’t be able to join the meeting from your sandboxed Zoom if the event organiser has registrations turned on for the event. I stopped trying after this (see the update, above).&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://pvera.net/posts/apparmor-firejail-sandboxing/&#34;&gt;Simple application sandboxing using AppArmor and Firejail&lt;/a&gt; by Alex Jung&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/netblue30/firejail/issues/3428#issuecomment-632590095&#34;&gt;Answer to question “Zoom does not work with zoom.profile; stuck at ‘connecting’” on FireJail issues&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>How Apple and Google will cure COVID-19 and how you can opt into it if you want to keep your job</title>
      <link>https://ar.al/2020/04/11/how-apple-and-google-will-cure-covid-19-and-how-you-can-opt-into-it-if-you-want-to-keep-your-job/</link>
      <pubDate>Sat, 11 Apr 2020 12:23:42 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/04/11/how-apple-and-google-will-cure-covid-19-and-how-you-can-opt-into-it-if-you-want-to-keep-your-job/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/876997523.jpg?mw=2500&amp;mh=1406&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/406507022.hd.mp4?s=61ba0b63f278474c2c1d0bc0c5f7597d23a8e3d7&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Contact Tracing is all fun and games and opt-in… until it is not.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I was on &lt;a href=&#34;https://www.aljazeera.com/live/&#34;&gt;Al Jazeera Live&lt;/a&gt; today and spoke about why Apple and Google teaming up to offer a Contact Tracing Framework from iOS and Android devices is worrying to say the least.&lt;/p&gt;
&lt;h2 id=&#34;quick-take-aways&#34;&gt;Quick take-aways:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;These two US companies, between them, control what we can and cannot do on 99% of the world’s everyday mobile devices. We are talking about a de facto monopoly on the bounds of human communication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There will not be a magic silver bullet app to rid humanity of COVID-19.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Google’s business model is to track you: when you use their products and services and both on and off the web and to collate all this information, analyse it, and use their intimate insight into your life to manipulate your behaviour. Google is a surveillance capitalist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Could an app like this be built in a decentralised manner so only the people using it are informed? Yes. But don’t expect that of Big Tech, the epitome of centralisation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Even if this framework protects your privacy to some degree today and is opt-in, what will happen once it’s on every iOS and Android device and governments want to start easing the lockdown and you’re told “we’d love you to go back to work but you need to opt in to using the contact tracing framework to do so.” Well, you’ll still have a choice. You can choose not to work. (After all, what are we, China?)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Given that we’re not China, Apple would never tolerate abuses of its platform. Turns out, though, that if you’re a Saudi man who wants to track his wife, there’s an app for that and Apple’s fine with having it in their App Store. I guess it’s good that those developers will have some new native APIs to improve their app with.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;a-contact-tracing-framework-isnt-just-for-covid-19&#34;&gt;A contact tracing framework isn’t just for COVID-19…&lt;/h2&gt;
&lt;p&gt;I am going to reiterate what I said &lt;a href=&#34;../03/al-jazeera-live-interview-on-corporate-and-government-mass-surveillance-in-the-time-of-covid-19&#34;&gt;in my previous interview&lt;/a&gt;: The hard-won civil liberties we hand over today out of fear will not be easy to regain, if we do regain them at all.&lt;/p&gt;
&lt;p&gt;Remember that the framework isn’t called the COVID-19 Contact Tracing Framework. It’s called the Contact Tracing Framework.&lt;/p&gt;
&lt;p&gt;Once this is up and running, there are a lot of people who will want to get their hands on that data eventually. Don’t forget that the type of data collected is the same kind of data that the United States government uses to decide who is and isn’t a terrorist before ordering them killed using drone strikes.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Al Jazeera Live interview on corporate and government mass surveillance in the time of COVID-19</title>
      <link>https://ar.al/2020/04/03/al-jazeera-live-interview-on-corporate-and-government-mass-surveillance-in-the-time-of-covid-19/</link>
      <pubDate>Fri, 03 Apr 2020 20:17:59 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/04/03/al-jazeera-live-interview-on-corporate-and-government-mass-surveillance-in-the-time-of-covid-19/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/873414877.jpg?mw=2500&amp;mh=1406&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/403808336.hd.mp4?s=71158f937ca8d96f1979459d777bda6a69ae5f4f&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;The hard-won civil liberties we hand over today out of fear will not be easy to regain, if we do regain them at all.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I was on &lt;a href=&#34;https://www.aljazeera.com/live/&#34;&gt;Al Jazeera Live&lt;/a&gt; today and spoke about how we must remain vigilant in the face of surveillance capitalists and governments that want to use the COVID-19 pandemic as an excuse to widen their dragnets.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Apple just killed Offline Web Apps while purporting to protect your privacy: why that’s A Bad Thing and why you should care</title>
      <link>https://ar.al/2020/03/25/apple-just-killed-offline-web-apps-while-purporting-to-protect-your-privacy-why-thats-a-bad-thing-and-why-you-should-care/</link>
      <pubDate>Wed, 25 Mar 2020 10:28:06 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/03/25/apple-just-killed-offline-web-apps-while-purporting-to-protect-your-privacy-why-thats-a-bad-thing-and-why-you-should-care/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/03/25/apple-just-killed-offline-web-apps-while-purporting-to-protect-your-privacy-why-thats-a-bad-thing-and-why-you-should-care/./baby-bathwater-wide.jpg&#34;
         alt=&#34;A wood carving, colourised with a blue tint, of a woman throwing the baby out with the bathwater&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple just threw the baby out with the bathwater by killing offline web apps (purportedly to protect your privacy).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;blocking-third-party-cookies-good-killing-offline-web-apps-bad&#34;&gt;Blocking third-party cookies, good. Killing offline web apps, bad.&lt;/h2&gt;
&lt;p&gt;On the face of it, WebKit’s announcement yesterday titled &lt;a href=&#34;https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/&#34;&gt;Full Third-Party Cookie Blocking and More&lt;/a&gt; sounds like something I would wholeheartedly welcome. Unfortunately, I can’t because the “and more” bit effectively kills off Offline Web Apps and, with it, the chance to have privacy-respecting apps like &lt;a href=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2/&#34;&gt;the prototype I was exploring earlier in the year&lt;/a&gt; based on &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Block all third-party cookies, yes, by all means&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. But deleting all local storage (including Indexed DB, etc.) after 7 days effectively blocks any future decentralised apps using the browser (client side) as a trusted replication node in a peer-to-peer network. And that’s a huge blow to the future of privacy.&lt;/p&gt;
&lt;h2 id=&#34;but-apple-cares-about-your-privacy&#34;&gt;But Apple cares about your privacy…&lt;/h2&gt;
&lt;p&gt;Do they, though?&lt;/p&gt;
&lt;p&gt;If they care about your privacy, why is the Apple News app a sewer of surveillance capitalism? If they did care about your privacy, here’s what they’d do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Implement all of the privacy protections they have in Safari in the Apple News app also.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow content blockers like &lt;a href=&#34;https://better.fyi&#34;&gt;Better&lt;/a&gt; to protect your privacy in Apple News app.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Heck, they could even go further and ban apps from corporations like Facebook, Inc., and Alphabet, Inc., that have violating your privacy as the core tenet of their business model.&lt;/p&gt;
&lt;p&gt;Instead, what do they do? They kill offline web apps.&lt;/p&gt;
&lt;p&gt;You’d almost think they had an App Store to promote or something.&lt;/p&gt;
&lt;h2 id=&#34;a-reevaluation&#34;&gt;A reevaluation&lt;/h2&gt;
&lt;p&gt;In a blog post I wrote at the start of 2015 titled &lt;a href=&#34;https://ar.al/notes/apple-vs-google-on-privacy-a-tale-of-absolute-competitive-advantage/&#34;&gt;Apple vs Google on privacy: a tale of absolute competitive advantage&lt;/a&gt;, I said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So riddle me this: if you have an absolute competitive advantage – if you have something that you can do that your competitors cannot – would you throw it away?&lt;/p&gt;
&lt;p&gt;Only if you’re an idiot.&lt;/p&gt;
&lt;p&gt;And something tells me Tim Cook isn’t an idiot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sadly, I was wrong.&lt;/p&gt;
&lt;h2 id=&#34;update-25-march-9pm&#34;&gt;Update (25 March, 9PM)&lt;/h2&gt;
&lt;p&gt;Looks like Apple &lt;a href=&#34;https://web.archive.org/web/20200325201951/https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/&#34;&gt;updated their post&lt;/a&gt; (thanks for the heads up, &lt;a href=&#34;https://fedi.xerz.one/objects/53242cad-e0eb-4aa4-8459-af5ab9a57884&#34;&gt;Xerz!&lt;/a&gt;) to add the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Note On Web Applications Added to the Home Screen&lt;/p&gt;
&lt;p&gt;As mentioned, the seven-day cap on script-writable storage is gated on “after seven days of Safari use without user interaction on the site.” That is the case in Safari. Web applications added to the home screen are not part of Safari and thus have their own counter of days of use. Their days of use will match actual use of the web application which resets the timer. We do not expect the first-party in such a web application to have its website data deleted.&lt;/p&gt;
&lt;p&gt;If your web application does experience website data deletion, please let us know since we would consider it a serious bug. It is not the intention of Intelligent Tracking Prevention to delete website data for first parties in web applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now I’m confused and have questions:&lt;/p&gt;
&lt;p&gt;Take Jim Pick’s excellent &lt;a href=&#34;https://blog.datproject.org/2018/05/14/dat-shopping-list/&#34;&gt;Collaborative Shopping List Built On Dat&lt;/a&gt;…&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If I use the app in Safari on iOS without adding it to Home Screen and leave it for seven days, will my shopping list be deleted?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If I do the same thing on Safari for macOS (which doesn’t have a Home Screen), will my shopping list be deleted?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I really hope this was just a badly-thought out decision (this is your out guys, take it) and that it will be reversed entirely.&lt;/p&gt;
&lt;p&gt;Andre Garzia has also written on the subject in a post titled &lt;a href=&#34;https://andregarzia.com/2020/03/private-client-side-only-pwas-are-hard-but-now-apple-made-them-impossible.html&#34;&gt;Private client-side-only PWAs are hard, but now Apple made them impossible&lt;/a&gt;. Go read that one too.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Not that blocking third party cookies is going to kill off surveillance capitalism by any means. Remember that Google is on board with this and plans to implement it themselves by 2022. Which means that by that date they don’t foresee themselves needing third-party cookies to track you. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Fail-fast on missing required arguments in JavaScript using default values that throw</title>
      <link>https://ar.al/2020/03/23/fail-fast-on-missing-required-arguments-in-javascript-using-default-values-that-throw/</link>
      <pubDate>Mon, 23 Mar 2020 20:01:10 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/03/23/fail-fast-on-missing-required-arguments-in-javascript-using-default-values-that-throw/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/03/23/fail-fast-on-missing-required-arguments-in-javascript-using-default-values-that-throw/road-runner-exploding-box.jpg&#34;
         alt=&#34;Screen grab of road running with an ACME box exploding on his face&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Missing arguments are not fun.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve been working on &lt;a href=&#34;https://source.small-tech.org/site.js/lib/auto-encrypt/&#34;&gt;Auto Encrypt&lt;/a&gt; to use in &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; for the past few weeks and it’s at the point now where I’m going through the codebase to increase coverage and improve quality. As part of that process, a few moments ago, I had an idea about implementing error-checking on missing arguments that I thought I’d share with you.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;/h2&gt;
&lt;p&gt;JavaScript doesn’t automatically throw an error if the argument for a required parameter is missing. Which means that it is possible for things to fail not where the problem is (at the function call) but later on.&lt;/p&gt;
&lt;p&gt;To avoid that, you have to implement some sort of error checking yourself. However, doing so is rather verbose and adds noise to your functions. And so it’s rarely done.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; foo (required, optional &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;default value&amp;#39;&lt;/span&gt;) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (required &lt;span style=&#34;color:#666&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;undefined&lt;/span&gt;) {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;`Missing argument.`&lt;/span&gt;)
  }
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// It’s safe to use required parameter from here on.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the example above&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, you can see how things would quickly get out of hand if you had, say, four or five required arguments. You’d have to write (and read through) a lot of filler before getting to the crux of your method.&lt;/p&gt;
&lt;p&gt;Ugly. And error prone.&lt;/p&gt;
&lt;p&gt;In the listing, you also see an example of an optional argument that has a default value specified. What if there was a more elegant way of failing fast on missing required arguments using optional argument syntax? Spoiler: there is!&lt;/p&gt;
&lt;p&gt;So check this out:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; required &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; () =&amp;gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Missing argument.&amp;#39;&lt;/span&gt;) }

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; foo (required &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; required(), optional &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;default value&amp;#39;&lt;/span&gt;) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// It’s safe to use required parameter from here on.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well that’s nicer to read, isn’t it?&lt;/p&gt;
&lt;p&gt;What we’re doing is converting the required argument into an optional argument but specifying its default value as a method call that throws.&lt;/p&gt;
&lt;p&gt;Of course, you can pop that &lt;code&gt;required()&lt;/code&gt; function into a module and re-use it in all your files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// required.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Missing argument.&amp;#39;&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; required &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./required&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; foo (required &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; required(), optional &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;default&amp;#39;&lt;/span&gt;) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// It’s safe to use required parameter from here on.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pretty neat, huh?&lt;/p&gt;
&lt;p&gt;It would be even better if we didn’t have to keep the poor developer struggling with your code (probably future you) in the dark about exactly which argument is missing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// required.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (argumentName &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;false&lt;/span&gt;) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;`Missing argument&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;argumentName &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;` (&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;argumentName&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;)`&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;.`&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; required &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;./required&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; foo (puppy &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; required(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;puppy&amp;#39;&lt;/span&gt;)) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// It’s safe to use puppy from here on.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Output: Error: Missing argument (puppy).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;foo()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I haven’t actually implemented this in &lt;a href=&#34;https://source.small-tech.org/site.js/lib/auto-encrypt/&#34;&gt;Auto Encrypt&lt;/a&gt; yet (or &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;, or anywhere else) but I think I’m going to. I have already started using &lt;a href=&#34;https://source.small-tech.org/site.js/lib/auto-encrypt/-/blob/master/lib/saferAndDRYerErrorHandlingMixin.js&#34;&gt;a mixin to provide better error handling using Symbols&lt;/a&gt; in Auto Encrypt so I will most likely add this to that.&lt;/p&gt;
&lt;p&gt;Thoughts? Questions? Suggestions? &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;Hit me up on the fediverse.&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;update&#34;&gt;Update&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.npmjs.com/package/@small-tech/required&#34;&gt;I just chucked this up on npm&lt;/a&gt; (&lt;a href=&#34;https://source.small-tech.org/aral/required&#34;&gt;source code&lt;/a&gt;) so you can use it in your projects via:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm i @small-tech/required
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// index.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; required &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/required&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; foo (puppy &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; required(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;puppy&amp;#39;&lt;/span&gt;)) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// It’s safe to use puppy from here on.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Output: Error: Missing argument (puppy).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;foo()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;See what I did there? If this becomes popular, I look forward to seeing “&lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;small tech&lt;/a&gt; required” in source code listings everywhere ;)&lt;/p&gt;
&lt;h2 id=&#34;update-2&#34;&gt;Update #2&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://kith.kitchen/@Paul/103874604984057576&#34;&gt;Paul suggested on the fediverse&lt;/a&gt; that I use &lt;code&gt;Error.captureStackTrace()&lt;/code&gt; to remove the &lt;code&gt;required()&lt;/code&gt; function itself from the error’s stack trace, so I did. (Thanks, Paul!) With that change, the module looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// required.js
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; required (argumentName &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;false&lt;/span&gt;) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; error &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;`Missing argument&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;argumentName &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;` (&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;argumentName&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;)`&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;.`&lt;/span&gt;)
  &lt;span style=&#34;color:#007020&#34;&gt;Error&lt;/span&gt;.captureStackTrace(error, required)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;throw&lt;/span&gt; error
}

module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; required
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can find the updated version in &lt;a href=&#34;https://www.npmjs.com/package/@small-tech/required&#34;&gt;the npm module&lt;/a&gt;. And I went ahead and implemented 100% code coverage for the module because apparently I have no life :)&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In the examples, we’re using loose equality (&lt;code&gt;==&lt;/code&gt;) to ensure that the argument is not &lt;code&gt;undefined&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt;. If you wanted to allow null, you would use strict equality here (&lt;code&gt;===&lt;/code&gt;). &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Flying to Antwerp tomorrow to present the opening keynote at Dig It Up on Thursday</title>
      <link>https://ar.al/2020/02/18/flying-to-antwerp-tomorrow-to-present-the-opening-keynote-at-dig-it-up-on-thursday/</link>
      <pubDate>Tue, 18 Feb 2020 19:46:47 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/02/18/flying-to-antwerp-tomorrow-to-present-the-opening-keynote-at-dig-it-up-on-thursday/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/02/18/flying-to-antwerp-tomorrow-to-present-the-opening-keynote-at-dig-it-up-on-thursday/aral_balkan_copyright_%20re_publica_Gregor%20Fischer.jpg&#34;
         alt=&#34;A photo of me during my talk at Re Publica&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;I will be presenting in Antwerp on Thursday. It will look something like this. Photo: Gregor Fischer.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;links&#34;&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cultuurconnect.be/events/dig-it-up2020&#34;&gt;The conference web site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cultuurconnect.be/nieuws/maatschappelijke-winst-moet-opnieuw-de-drijfveer-worden-voor-het-vormgeven-van-technologie&#34;&gt;An article on my involvement&lt;/a&gt; (in Dutch).&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cultuurconnect.be/about-us&#34;&gt;About Cultuurconnect&lt;/a&gt; (in English).&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>A happy ending to the Better Blocker saga</title>
      <link>https://ar.al/2020/01/20/a-happy-ending-to-the-better-blocker-saga/</link>
      <pubDate>Mon, 20 Jan 2020 10:38:23 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/01/20/a-happy-ending-to-the-better-blocker-saga/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/20/a-happy-ending-to-the-better-blocker-saga/little-britain-complaint-form.jpeg&#34;
         alt=&#34;Still from TV show Little Britain with the Computer Says No Lady holding up a Complaint Form&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;It’s going to work out in the end.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL; DR:&lt;/strong&gt; Apple have been in touch and offered us a way to migrate to our new not-for-profit without impacting the experience of existing Better Blocker customers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At the start of this month, &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I &lt;a href=&#34;https://ar.al/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/&#34;&gt;asked Apple for some help&lt;/a&gt; to migrate our Apple Developer account (and &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;) from &lt;a href=&#34;https://ind.ie&#34;&gt;our previous not-for-profit&lt;/a&gt; to &lt;a href=&#34;https://small-tech.org&#34;&gt;our new one&lt;/a&gt;, following &lt;a href=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/&#34;&gt;our move to Ireland&lt;/a&gt;. Initially, we weren’t able to reach anyone beyond first-level support and &lt;a href=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/&#34;&gt;Apple said “no.”&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;moving-on&#34;&gt;Moving on&lt;/h2&gt;
&lt;p&gt;Based on Apple’s answer we took the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/better/app/-/milestones/2&#34;&gt;Triaged and fixed all the open bugs we could find&lt;/a&gt; in the Better Blocker iOS and macOS apps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Released app updates for both &lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1080964978&#34;&gt;the iOS&lt;/a&gt; and &lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1121192229?mt=12&#34;&gt;macOS apps&lt;/a&gt; (update version 2020.1).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the price of both apps to free so that people would not be fooled into buying an app that we could not keep updating.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Notified our customers of the migration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;a-turn-of-events&#34;&gt;A turn of events&lt;/h2&gt;
&lt;p&gt;Towards the end of last week, we were contacted by Apple who told us they could help us avoid this situation. I wrote back and accepted their offer.&lt;/p&gt;
&lt;p&gt;Based on this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I submitted another update for both apps this morning (version 2020.2) to update our existing customers about this latest development and remove the previous notice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that we know we can keep updating the apps, we’ve reverted the apps from free to paid. Going forward, both apps will be priced at Tier 2 ($1.99/₤1.99/€2.29). You can get both for less than the price of a beer.&lt;/p&gt;
&lt;p&gt;All funds go to &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;, our tiny two-person not-for-profit working to create tools to safeguard and promote human rights and democracy in the digital network age.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;what-this-means-for-you&#34;&gt;What this means for you&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Everything will continue as before, you don’t need to do anything.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The app will continue to get updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At some point, you will see our organisation name change from Ind.ie (Article 12) to Small Technology Foundation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am also attempting to ensure that not-for-profits in the future that might find themselves in similar circumstances (as niche as it may be) will not have to struggle as we did. Fingers crossed on that one.&lt;/p&gt;
&lt;h2 id=&#34;what-this-means-for-us&#34;&gt;What this means for us&lt;/h2&gt;
&lt;p&gt;I’d like to thank Apple for reaching out to help us with this. Just for the record, here’s a summary of the impact this process has had on our tiny two-person not-for-profit, both the negatives and positives.&lt;/p&gt;
&lt;h3 id=&#34;the-negatives&#34;&gt;The negatives&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;January was a write-off for &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;research and development&lt;/a&gt; and further work that was planned for &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;. I look forward to putting this behind us so we can get back to work on building technological infrastructure to protect and promote human rights and democracy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We lost over a week of revenue from app sales.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We had to sign up for a new Developer Account which we now don’t need and have asked to shut down (cost: $99, no refund available).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;the-positives&#34;&gt;The positives&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You have all been absolutely lovely. Thank you all for your lovely words of support and encouragement throughout this ordeal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/fund-us&#34;&gt;We gained 12 new patrons&lt;/a&gt; and our patronage went up from €320/month to €508/month.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We received ~€1,400 in donations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We got the opportunity to improve both apps.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;heres-to-happy-endings&#34;&gt;Here’s to happy endings&lt;/h2&gt;
&lt;p&gt;I don’t know exactly when our account migration from Ind.ie (Article 12) to Small Technology Foundation will be complete but, barring any further complications, you should not have to know or care about it. You really shouldn’t have had to to begin with.&lt;/p&gt;
&lt;p&gt;On behalf of Laura and myself, thank you again for being so lovely about it all and for your support during this difficult process. I’m glad it looks like the story will have a happy ending.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Apple App Review: resistance is futile!</title>
      <link>https://ar.al/2020/01/14/apple-app-review-resistance-is-futile/</link>
      <pubDate>Tue, 14 Jan 2020 19:13:39 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/01/14/apple-app-review-resistance-is-futile/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/14/apple-app-review-resistance-is-futile/computer-says-blub-blub-blub.jpg&#34;
         alt=&#34;Still from British TV series Little Britain with the “Computer says no!” woman with googly eyes, tongue sticking out, question marks floating around her head and an alarm clock at the side&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple: “Computer says… blub-blub-blub’”&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;../apple-app-review-says-maybe-the-whims-of-trillion-dollar-gatekeepers&#34;&gt;Last time on Dances With Drones&lt;/a&gt;, Apple had accepted the iOS version of &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; but rejected the macOS one because of invalid app metadata &lt;strong&gt;even though both apps had the same metadata&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In response to my asking for some consistency (either remove the macOS one also or accept the iOS one too), this is what 3 of 5 at App Store Review replied:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hello,&lt;/p&gt;
&lt;p&gt;Thank you for your response.&lt;/p&gt;
&lt;p&gt;We are unable to comment on any previous approvals or rejections. Please note that each submission is reviewed as is against the current App Store Guidelines, and not on the precedent basis. Therefore prior approvals do not guarantee automatic approvals of subsequent submissions.&lt;/p&gt;
&lt;p&gt;As previously advised, please remove all content that is not directly relevant to your app’s features and functionality from your App Store metadata.&lt;/p&gt;
&lt;p&gt;We will be unable to proceed with your review until this issue is resolved.&lt;/p&gt;
&lt;p&gt;We appreciate your cooperation and look forward to reviewing your app.&lt;/p&gt;
&lt;p&gt;Best regards,&lt;/p&gt;
&lt;p&gt;App Store Review&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And I can see their point. I mean the iOS version &lt;em&gt;was&lt;/em&gt; submitted about an hour after the macOS version. That’s plenty of time for the App Store Guidelines to have been updated.&lt;/p&gt;
&lt;p&gt;With no other option but to censor ourselves and be prevented from explaining to our customers on iOS &lt;a href=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/&#34;&gt;why we have to move the app to a new account&lt;/a&gt;, I’ve now removed &lt;a href=&#34;../apple-app-review-says-maybe-the-whims-of-trillion-dollar-gatekeepers&#34;&gt;the bits Apple wanted to censor&lt;/a&gt; and the iOS version of the app is now also waiting for review.&lt;/p&gt;
&lt;p&gt;And I’m slowly losing my will to have anything to do with this Borg-like corporation.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Apple App Review says “maybe”: the whims of trillion-dollar gatekeepers</title>
      <link>https://ar.al/2020/01/14/apple-app-review-says-maybe-the-whims-of-trillion-dollar-gatekeepers/</link>
      <pubDate>Tue, 14 Jan 2020 11:48:05 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/01/14/apple-app-review-says-maybe-the-whims-of-trillion-dollar-gatekeepers/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/14/apple-app-review-says-maybe-the-whims-of-trillion-dollar-gatekeepers/computer-says-maybe.jpg&#34;
         alt=&#34;Still from British TV series Little Britain with the “Computer says no!” woman with question marks floating around her head&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple: “Computer says… maybe?’”&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Yesterday, I wrote about how &lt;a href=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/&#34;&gt;Apple’s refusal to update a couple of fields in their database&lt;/a&gt; has impacted the future of &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;, the tracker blocker that &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I build at our tiny two-person not-for-profit, &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also shared &lt;a href=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/#how-will-we-handle-the-process&#34;&gt;our plan&lt;/a&gt; for dealing with this situation.&lt;/p&gt;
&lt;p&gt;Yesterday, we were at Step 3 of our plan. We’d submitted the version 2020.1 updates to Better for macOS and iOS from our old developer account and we were waiting for Apple to approve them.&lt;/p&gt;
&lt;p&gt;Today, we are half-at step 4 because &lt;strong&gt;Apple has approved the macOS app and rejected the iOS app.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;why-did-they-reject-the-ios-app&#34;&gt;Why did they reject the iOS app?&lt;/h2&gt;
&lt;p&gt;Because of the metadata.&lt;/p&gt;
&lt;h2 id=&#34;which-metadata&#34;&gt;Which metadata?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The exact same metadata that they approved for &lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1121192229?mt=12&#34;&gt;the macOS version&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Specifically, the App Store reviewer for the iOS version of Better Blocker took offence at the following description in the &lt;em&gt;What’s New&lt;/em&gt; metadata, stating that it failed &lt;em&gt;Guideline 2.3 - Performance - Accurate Metadata&lt;/em&gt; (“We noticed that your app&amp;rsquo;s metadata includes the following information, which is not relevant to the app&amp;rsquo;s content and functionality:”)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you’d like to understand why we’ve been forced into taking this decision, please read on.&lt;/p&gt;
&lt;h3 id=&#34;the-backstory&#34;&gt;The backstory&lt;/h3&gt;
&lt;p&gt;We (Laura and Aral) released Better in June 2016 for iOS and shortly thereafter for macOS. Back then, we were living in the UK and had a not-for-profit there called Article 12 (Ind.ie). Our Apple Developer account was registered with this organisation.&lt;/p&gt;
&lt;p&gt;Fast forward four years and we now live in Ireland. Last year, we set up a separate not-for-profit here called Small Technology Foundation. It’s still just the two of us and we’re still working on technology to protect human rights and democracy.&lt;/p&gt;
&lt;p&gt;Since we’re self-funded and have limited resources, we cannot keep both organisations going so we are in the process of shutting down Article 12 (Ind.ie) in the UK. This means, sadly, that we cannot keep updating Better under our current Apple Developer account. Instead, we have to remove it from sale from our Ind.ie account and upload it as a new app from our Small Technology Foundation account.&lt;/p&gt;
&lt;p&gt;If that sounds silly to you, you’re right, it is. Someone at Apple could have written a couple of SQL statements and saved us this effort. But, sadly, that is not what happened and we have been left with no other choice.&lt;/p&gt;
&lt;p&gt;We reached out to Apple to ask if they would simply move our developer account to Small Technology Foundation. Computer said “no.” We also asked if we could migrate the apps to our new account. Computer said “no.” (You cannot move apps between accounts if they use iCloud features. Better uses iCloud to sync your ‘Do Not Block’ list between devices.)&lt;/p&gt;
&lt;p&gt;We even wrote a blog post and appealed to anyone at Apple who might be able to help. But no one got in touch. So we really did try everything we could to avoid this. We’d rather not do this either: it means we’re going to lose over three years of history and our placement on the App Store and probably upset a bunch of people in the process.&lt;/p&gt;
&lt;p&gt;We hope you understand why we had to do this and we hope that you will continue to support us in our work to create technology that protects human rights and democracy.&lt;/p&gt;
&lt;p&gt;If you have any questions or want to get in touch with us, please email us at &lt;a href=&#34;mailto:hello@small-tech.org&#34;&gt;hello@small-tech.org&lt;/a&gt;.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They also took issue with the following copy, failing it under &lt;em&gt;Guideline 2.3.10 - Performance - Accurate Metadata&lt;/em&gt; (“We noticed that your app includes the following information in the “What’s New” text, which is not relevant to the app&amp;rsquo;s content and functionality:”)&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Due to Apple’s rules, the only way we can keep Better on the App Store is to remove it from sale from our Ind.ie developer account and submit it as a new app under our Small Technology Foundation account.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;so-what-happens-next&#34;&gt;So what happens next?&lt;/h2&gt;
&lt;p&gt;I just submitted the following appeal to Apple and I am now waiting for their response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi there,&lt;/p&gt;
&lt;p&gt;Thank you for your feedback. I’m confused, the exact same metadata was accepted for our macOS app (&lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1121192229?mt=12&#34;&gt;https://apps.apple.com/us/app/better-blocker/id1121192229?mt=12&lt;/a&gt;) and it is now live on the App Store. Do the iOS App Store and macOS App Store have different criteria for Guideline 2.3?&lt;/p&gt;
&lt;p&gt;For the sake of consistency, please either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Approve this update.&lt;/li&gt;
&lt;li&gt;Issue an after-the-fact rejection of the macOS app and remove it from sale.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would urge you to take the first step as the metadata included is entirely relevant to this update and is the sole reason for it and explains to our customers why we are forced to take a step that will negatively impact their experience with our app.&lt;/p&gt;
&lt;p&gt;I look forward to hearing from you and hope that you’re having a good week so far.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;the-moral-of-the-story&#34;&gt;The moral of the story&lt;/h2&gt;
&lt;p&gt;Big Tech will tell you that they are objective; that they are governed by guidelines, protocols, and algorithms… all very mundane, professional, and inevitable.&lt;/p&gt;
&lt;p&gt;Nothing could be further from the truth.&lt;/p&gt;
&lt;p&gt;Big Tech are fiefdoms governed by people like you or me. The decisions they take are anything but objective. The specific brand of technology they peddle and the vector they paint for ‘progress’ isn’t inevitable, it is arbitrary. It’s based on experiences, beliefs (or lack thereof), whims, and countless other subjective aspects that make the people who take decisions at these little monarchies take them in the manner in which they do. And as this little incident elucidates, even with policies, guidelines, and mission statements, two different people can take entirely different decisions given exactly the same input.&lt;/p&gt;
&lt;p&gt;So the question we have to ask ourselves is: do we want a world governed by the whims of a handful of individuals in Big Tech or one where we each get a voice?&lt;/p&gt;
&lt;p&gt;If you’re happy with the former, you don’t have to do anything. Carry on as you are. You’re all set because that’s where we are today.&lt;/p&gt;
&lt;p&gt;If you’re not happy with it, consider what the alternative could look like. To me, the alternative to Big Tech is simple: it’s &lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;Small Tech&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>“Apple Says ‘No!’” and what that means for the future of Better Blocker following our move to Ireland</title>
      <link>https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/</link>
      <pubDate>Mon, 13 Jan 2020 11:59:47 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/important-message.png&#34;
         alt=&#34;Screenshot of the alert people will see when they launch the latest macOS app, telling them about the migration and what to expect.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;We didn’t want to do this.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;whats-going-on&#34;&gt;What’s going on?&lt;/h2&gt;
&lt;p&gt;In my &lt;a href=&#34;https://ar.al/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/&#34;&gt;previous blog post&lt;/a&gt;, I asked Apple for a little help with a problem we had with &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; after moving to Ireland and setting up a new not-for-profit here:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We’re &lt;a href=&#34;https://small-tech.org/about&#34;&gt;a tiny two-person not-for-profit&lt;/a&gt;. We used to be based in the UK, where we were known as &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; (and incorporated as a not-for-profit called Article 12). We left the UK (&lt;a href=&#34;https://ar.al/notes/so-long-and-thanks-for-all-the-fish/&#34;&gt;for reasons&lt;/a&gt;) and now we have a not-for-profit here in Ireland called &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;. It’s still just &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and me (and our huskamute, Oskar).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;We have an app called &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; … The problem is that we are now faced with having to take it off the App Store as we don’t seem to have a way to migrate our developer account to our new not-for-profit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;what-did-apple-say&#34;&gt;What did Apple say?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/computer-says-no.jpg&#34;
         alt=&#34;Still from British TV series Little Britain with the “Computer says no!” woman&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple: “Computer says no.”&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Last week, we got a definite “computer says no” from Apple’s “developer support.”&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/13/apple-says-no-and-what-that-means-for-the-future-of-better-blocker-following-our-move-to-ireland/apple-developer-account-screenshot.png&#34;
         alt=&#34;Screenshot of our Apple Developer account, with the company name and address area (redacted) highlighted with red rectangles.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;How Apple could have helped: by running a few queries to update a handful of fields in their database.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;what-does-that-mean&#34;&gt;What does that mean?&lt;/h2&gt;
&lt;p&gt;That means that we are forced to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove Better Blocker from our Ind.ie account.&lt;/li&gt;
&lt;li&gt;Re-publish Better Blocker from our Small Technology Foundation account.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;what-does-this-mean-for-you-if-you-bought-better-blocker-from-indie&#34;&gt;What does this mean for you if you bought Better Blocker from Ind.ie?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You can continue using Better on your devices.&lt;/li&gt;
&lt;li&gt;You will continue to receive blocking rule updates.&lt;/li&gt;
&lt;li&gt;You will &lt;strong&gt;not&lt;/strong&gt; receive any further updates to the app itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;how-will-we-handle-the-process&#34;&gt;How will we handle the process?&lt;/h2&gt;
&lt;p&gt;Laura and I have made a plan to try and handle this as well as we can given what we do and don’t have control over. Here’s what we’ve done so far and what our plan is for the coming weeks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Last week, &lt;a href=&#34;https://source.ind.ie/better/app/-/milestones/2&#34;&gt;I fixed all the outstanding bugs and implemented a couple of enhancements&lt;/a&gt; on the existing macOS and iOS apps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This morning, I submitted the Better Blocker 2020.1 update for macOS and iOS to Apple from the Ind.ie account as the last update we will be releasing under that account. My hope is that this version will serve you well at least until Apple breaks something in their next OS update.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Just a few moments ago, I made both the macOS and iOS apps free on the App Store. Now that we know we cannot update the app under the Ind.ie account in the future, we cannot keep selling it from that account with a clear conscience.&lt;/p&gt;
&lt;p&gt;If you bought Better Blocker recently and want a refund, &lt;a href=&#34;https://support.apple.com/en-us/HT204084&#34;&gt;please ask for one from Apple&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;[We are here right now]&lt;/strong&gt; Once the 2020.1 updates have been approved by Apple (this could take anywhere from a couple of days to goodness knows when, given that the process – like everything else at Apple – is one giant black box), we will give existing customers two weeks to update their apps.&lt;/p&gt;
&lt;p&gt;After this time, we will remove Better Blocker from the App Store under the Ind.ie Apple Developer account and close the Ind.ie Apple Developer account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, we will then submit Better Blocker to Apple for review from the Small Technology Foundation account. As and when Apple approves it, Better Blocker will be available for sale again on the App Store for macOS and iOS under the Small Technology Foundation account.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;what-does-this-mean-for-better-blocker-and-our-tiny-two-person-not-for-profit&#34;&gt;What does this mean for Better Blocker and our tiny two-person not-for-profit?&lt;/h2&gt;
&lt;p&gt;It means that we will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lose four years of reviews and App Store history.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lose our placement on the Safari App Extensions page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lose income that was some months almost paying the rent for us for at least a month or more. Depending on how much of the sales were based on our Safari App Extensions page placement and organic links from around the Web, the money we make from Better might be substantially less than before.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most likely piss a lot of people off and get a bunch of 1-star reviews (always fun!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;this-sucks&#34;&gt;This sucks!&lt;/h2&gt;
&lt;p&gt;This sucks. It sucks for you and it sucks for us. We didn’t want this.&lt;/p&gt;
&lt;p&gt;That said, this is just how life is when you’re dealing with trillion-dollar faceless corporations. It’s just one reason why it’s so important that we fund and develop human-scale &lt;a href=&#34;https://small-tech.org&#34;&gt;small tech&lt;/a&gt; as an alternative to the strangehold of big tech on our lives. And that’s exactly what I shall return to working on next.&lt;/p&gt;
&lt;p&gt;Laura and I are sorry if this causes you any inconvenience. We tried everything we could to avoid this. Sadly, we simply cannot afford to keep two organisations running in two different countries.&lt;/p&gt;
&lt;p&gt;Personally, I look forward to the day when our ability to pay the rent is in no way linked to any trillion-dollar corporation.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;style&gt;figure:first-of-type &gt; figcaption { margin-top: -2em; } &lt;/style&gt;
</description>
    </item>
    
    <item>
      <title>Dear Apple, a little help here? How hard can it be to move our developer account to our new not-for-profit?</title>
      <link>https://ar.al/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/</link>
      <pubDate>Thu, 02 Jan 2020 11:36:02 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/computer-says-no.jpg&#34;
         alt=&#34;Still from British TV series Little Britain with the “Computer says no!” woman&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple: Computer says no?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Dear Apple,&lt;/p&gt;
&lt;p&gt;We’re &lt;a href=&#34;https://small-tech.org/about&#34;&gt;a tiny two-person not-for-profit&lt;/a&gt;. We used to be based in the UK, where we were known as &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; (and incorporated as a not-for-profit called Article 12). We left the UK (&lt;a href=&#34;https://ar.al/notes/so-long-and-thanks-for-all-the-fish/&#34;&gt;for reasons&lt;/a&gt;) and now we have a not-for-profit here in Ireland called &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;. It’s still just &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and me (and our huskamute, Oskar).&lt;/p&gt;
&lt;p&gt;We have an app called &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; that’s on sale on the &lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1080964978&#34;&gt;iOS&lt;/a&gt; and &lt;a href=&#34;https://apps.apple.com/us/app/better-blocker/id1121192229?mt=12&#34;&gt;macOS&lt;/a&gt; app stores. Some months, the revenue we get from it helps us cover the rent. Better is listed on your &lt;a href=&#34;https://apps.apple.com/us/story/id1377753262&#34;&gt;Get Started: Safari Extensions&lt;/a&gt; page and has three years of history on the App Store.&lt;/p&gt;
&lt;p&gt;The problem is that we are now faced with having to take it off the App Store as we don’t seem to have a way to migrate our developer account to our new not-for-profit. We also tried setting up a new developer account for Small Technology Foundation and moving Better over to it but we cannot since Better uses iCloud and &lt;a href=&#34;https://help.apple.com/app-store-connect/#/devaf27784ff&#34;&gt;apps that use iCloud cannot be moved between developer accounts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I can see the logistical difficulty of moving apps between different developer accounts if they use iCloud if this is not a feature that’s already built into the platform at Apple. But what I don’t understand is why our developer account cannot be updated to belong to Small Technology Foundation instead of Article 12. Laura and I are co-founders of both not-for-profits (we are in the process of shutting Article 12 down) and we can prepare and sign any necessary contracts for this to happen.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2020/01/02/dear-apple-a-little-help-here-how-hard-can-it-be-to-move-our-developer-account-to-our-new-not-for-profit/apple-developer-account-screenshot.png&#34;
         alt=&#34;Screenshot of our Apple Developer account, with the company name and address area (redacted) highlighted with red rectangles.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Can we please update these two fields in your database?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So if anyone at Apple is reading this, it would really help a tiny not-for-profit out if you could help us with this. We have been trying to reach out but we haven’t had any luck in actually speaking to a human being about this.&lt;/p&gt;
&lt;p&gt;If we cannot move our developer account over, we are either going to have to remove Better from sale and re-publish it under a separate Small Technology Foundation account (which means that people who bought Better will no longer get updates unless they buy the new app and that we will lose three years of history and most likely our placement on the aforementioned Safari Extensions article on Apple.com) or, if we want to avoid the  possible backlash of having to do this, remove Better from sale permanently and focus all our energy on &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;our current projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I do hope someone from Apple will get in touch and we can simply update our developer account to migrate it to Small Technology Foundation so we can continue to offer Better for sale and focus on building tools to safeguard human rights and democracy in the digital network age.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>In 2020 and beyond, the battle to save personhood and democracy requires a radical overhaul of mainstream technology</title>
      <link>https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/</link>
      <pubDate>Wed, 01 Jan 2020 15:50:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/</guid>
      <description>&lt;style&gt;
/* Because CSS sucks. Hack courtesy of https://css-tricks.com/NetMag/FluidWidthVideo/Article-FluidWidthVideo.php */

.videoWrapper {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 */
  padding-top: 25px;
  height: 0;
}

.videoWrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

figcaption {
  margin-top: 1em;
}
&lt;/style&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2020/01/01/in-2020-and-beyond-the-battle-to-save-personhood-and-democracy-requires-a-radical-overhaul-of-mainstream-technology/australia-fires.jpeg&#34; alt=&#34;Photo of a boy driving in a boat on a lake during the fires in Australia. Photo credit: Allison Marion.&#34;&gt;&lt;/p&gt;
&lt;p&gt;As we enter a new decade, humankind faces several existential emergencies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The climate emergency&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;The democracy emergency&lt;/li&gt;
&lt;li&gt;The personhood emergency&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In no small part thanks to Greta Thunberg, we’re definitely talking about the first. Whether we’re actually doing anything about it, of course, is very much up for debate&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Similarly, thanks to the rise of the far right around the globe in the shape of (among others) Trump in the US, Johnson in the UK, Bolsonaro in Brazil, Orban in Hungary, and Erdoğan in Turkey, we are also talking about the second, including the role of propaganda (so-called “fake news”) and social media in perpetrating it.&lt;/p&gt;
&lt;p&gt;What we seem entirely clueless and ambivalent about is the third even though all the others stem from it and are symptoms of it. It is the emergency without a name. Well, until now, that is.&lt;/p&gt;
&lt;h2 id=&#34;the-personhood-emergency&#34;&gt;The personhood emergency&lt;/h2&gt;
&lt;p&gt;You cannot understand the personhood emergency without understanding the role that mainstream digital and networked technology plays in perpetuating it.&lt;/p&gt;
&lt;h3 id=&#34;your-tv-wasnt-watching-you-youtube-is&#34;&gt;Your TV wasn’t watching you, YouTube is&lt;/h3&gt;
&lt;p&gt;Traditional – non-digital, non-networked – technology was a one-way broadcast medium. That’s the one thing that a book printed on the Gutenberg press and your analog TV set had in common.&lt;/p&gt;
&lt;p&gt;It used to be that when you read a newspaper, the newspaper did not also read you. When you watched TV, your TV did not also watch you (unless you specifically allowed an audience measurement company like Nielsen to attach a ratings meter to your television set, that is).&lt;/p&gt;
&lt;p&gt;Today, as you read The Guardian newspaper online, The Guardian – &lt;a href=&#34;https://better.fyi/sites/theguardian.com/&#34;&gt;and over two dozen other third-parties, including the aforementioned Nielsen&lt;/a&gt; – also reads you. When you watch YouTube, YouTube also watches you.&lt;/p&gt;
&lt;p&gt;This is not some tin-foil hat conspiracy theory, it’s simply the business model of mainstream technology. I call this business model &lt;em&gt;people farming&lt;/em&gt;. It’s part of the greater socio-economic system we inhabit that Shoshana Zuboff calls &lt;em&gt;&lt;a href=&#34;https://www.bbc.co.uk/ideas/videos/surveillance-capitalism-has-led-us-into-a-dystopia/p06p0tdy&#34;&gt;surveillance capitalism&lt;/a&gt;&lt;/em&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And it gets worse: Alphabet Inc., which owns Google and YouTube, doesn’t just watch you when you use one of their services but they also follow you around the Web as you go from site to site. &lt;a href=&#34;https://threader.app/thread/1191283230894415872&#34;&gt;Google alone has eyes on 70-80% of the Web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But they don’t stop there, either. People farmers also buy data from &lt;a href=&#34;https://www.vice.com/en_us/article/bjpx3w/what-are-data-brokers-and-how-to-stop-my-private-data-collection&#34;&gt;data brokers&lt;/a&gt;, &lt;a href=&#34;https://www.reuters.com/article/us-facebook-privacy-congress-idUSKCN1J11TY&#34;&gt;share data with other people farmers&lt;/a&gt;, and &lt;a href=&#34;https://www.technologyreview.com/s/607938/google-now-tracks-your-credit-card-purchases-and-connects-them-to-its-online-profile-of-you/&#34;&gt;even know when you use your credit card in brick and mortar stores&lt;/a&gt;. And they combine all of this information to create profiles of you which are constantly analysed, updated, and improved.&lt;/p&gt;
&lt;p&gt;We can consider these profiles to be simulations of us. They contain aspects of us. They can be (and are) used as proxies of us. They contain highly sensitive and intimate information about us. But we do not own them, the people farmers do.&lt;/p&gt;
&lt;p&gt;It is not too much of a stretch to say that within this system, we do not fully own ourselves. In such a system, where &lt;a href=&#34;https://www.theguardian.com/technology/2017/apr/19/facebook-mind-reading-technology-f8&#34;&gt;our very thoughts are at risk of being read by corporations&lt;/a&gt;, our very personhood and the concept of self-determination itself is put at risk.&lt;/p&gt;
&lt;p&gt;We stand at the precipice of reverting from being people to being property again, hacked via a digital and networked backdoor, the existence of which we continue to deny at our peril. The prerequisites for a free society hang in the balance of our understanding this basic reality.&lt;/p&gt;
&lt;h3 id=&#34;if-we-extend-ourselves-using-technology-we-must-extend-the-scope-of-human-rights-law-to-include-this-extended-self&#34;&gt;If we extend ourselves using technology, we must extend the scope of human rights law to include this extended self.&lt;/h3&gt;
&lt;p&gt;If we cannot &lt;a href=&#34;https://cyborgrights.eu&#34;&gt;define the boundaries of a person properly&lt;/a&gt;, how can we hope to protect people or personhood in the digital network age?&lt;/p&gt;
&lt;p&gt;Today, we are sharded beings. The boundaries of our selves do not end at our biological boundaries. Aspects of our selves live on bits of silicon that may reside thousands of miles away.&lt;/p&gt;
&lt;p&gt;It is imperative that we acknowledge that the boundaries of the self in the digital network age have transcended the biological boundaries of our physical bodies and that this new boundary – the extended self; the sharded totality of the self – constitutes our new digital skin and that its integrity must be protected by human rights law.&lt;/p&gt;
&lt;p&gt;Unless we do this, we are bound to flail around at the surface of the problem, making what are no more than cosmetic changes to a system that is quickly evolving towards &lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;a new type of slavery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is the personhood emergency.&lt;/p&gt;
&lt;h2 id=&#34;a-radical-overhaul-of-mainstream-technology&#34;&gt;A radical overhaul of mainstream technology&lt;/h2&gt;
&lt;p&gt;If we want to tackle the personhood emergency, nothing short of a radical overhaul of mainstream technology will do.&lt;/p&gt;
&lt;p&gt;We must first understand that while &lt;a href=&#34;https://ar.al/2019/05/11/the-dos-and-donts-of-tech-regulation/&#34;&gt;regulating people farmers and surveillance capitalists&lt;/a&gt; is important to reduce their harms, it is both &lt;a href=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/&#34;&gt;an uphill struggle against institutional corruption&lt;/a&gt; and that it will not, by itself, magically result in the emergence of a radically different technological infrastructure. And the latter is the only thing that can tackle the personhood emergency.&lt;/p&gt;
&lt;h3 id=&#34;imagine-a-different-world&#34;&gt;Imagine a different world&lt;/h3&gt;
&lt;p&gt;Humour me for a second and imagine this: Let’s say your name is Jane Smith and I want to talk to you. I go to &lt;strong&gt;jane.smith.net.eu&lt;/strong&gt; and ask to follow you. Who am I? I’m &lt;strong&gt;aral.balkan.net.eu&lt;/strong&gt;. You allow me to follow you and we start chatting… privately.&lt;/p&gt;
&lt;p&gt;Imagine further that we can create groups – maybe for the school that our children go to or for our local neighbourhood. In such a system, we all own and control our own place on the Internet. We can do all the things you can do on Facebook today, just as easily, but without Facebook in the middle, watching and exploiting us.&lt;/p&gt;
&lt;p&gt;What we need is &lt;a href=&#34;https://small-tech.org/research-and-development/&#34;&gt;a peer-to-peer system that bridges to the existing world wide web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What we need is the opposite of Big Tech. We need &lt;a href=&#34;https://small-tech.org/about/#small-technology&#34;&gt;Small Tech&lt;/a&gt; – everyday tools for everyday people designed to increase human welfare, not corporate profits.&lt;/p&gt;
&lt;h3 id=&#34;practical-steps&#34;&gt;Practical steps&lt;/h3&gt;
&lt;p&gt;At &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;, &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I have already started building &lt;a href=&#34;https://sitejs.org&#34;&gt;some of the fundamental pieces&lt;/a&gt; of one possible bridge from surveillance capitalism to a radically democratic, peer-to-peer future. And we will continue to work on &lt;a href=&#34;https://small-tech.org/research-and-development/&#34;&gt;the other pieces&lt;/a&gt; this year and beyond. But there are practical steps we can all take to help move things in this direction.&lt;/p&gt;
&lt;p&gt;Here are some practical suggestions for various groups:&lt;/p&gt;
&lt;h3 id=&#34;everyday-people&#34;&gt;Everyday people&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Don’t blame yourselves; you are the victims here. When 99.99999% of all technology investment goes to people farmers, don’t let anyone tell you you should feel bad for being forced into using their services due to lack of alternatives.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;That said, &lt;a href=&#34;https://switching.software&#34;&gt;there are alternatives&lt;/a&gt;. Seek them out. Use them. Support the people who make them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understand that this problem exists. Call out the people responsible and defend others who do so. At the very least, don’t brush aside the concerns and efforts of those of us who are trying to do something about it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;developers&#34;&gt;Developers&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Stop embedding the surveillance devices of companies like Google and Facebook in your web sites and apps. Stop exposing the people who use your services to surveillance capitalism.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start looking at &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;alternative ways of funding and building technology&lt;/a&gt; that do not follow the toxic Silicon Valley model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drop “growth” as your success metric. Build tools that individuals own and control, not your company or organisation. Build single-tenant web apps. Support free (as in freedom) and decentralised platforms (without getting mired in the blockchain swamp).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;the-european-union&#34;&gt;The European Union&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Stop investing in startups and acting like &lt;a href=&#34;https://ar.al/2019/06/22/have-you-heard-about-silicon-valleys-unpaid-research-and-development-department-its-called-the-eu/&#34;&gt;Silicon Valley’s unpaid research and development department&lt;/a&gt; and &lt;a href=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/&#34;&gt;invest in stayups&lt;/a&gt; instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a noncommercial top-level domain (TLD) open to anyone in the world where anyone can register a domain name (with an automatic Let’s Encrypt certificate) for zero cost with a single API call.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build upon the previous step to offer every EU citizen, paid for by EU taxpayer money, a basic virtual private server with a basic amount of resources to host an always-on node in a peer-to-peer system that would unshackle them from the Googles and Facebooks of the world and create new opportunities for people to communicate privately as well as to express political will in a decentralised manner.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And, in general, at the very least, it’s time for every one of us to pick a side.&lt;/p&gt;
&lt;p&gt;The side you pick will decide whether we live as people or as products. The side you pick will decide whether we live in a democracy or under capitalism.&lt;/p&gt;
&lt;h2 id=&#34;democracy-or-capitalism-pick-one&#34;&gt;Democracy or capitalism? Pick one.&lt;/h2&gt;
&lt;p&gt;If, like me, you grew up in the 80s, you probably unthinkingly accepted the neoliberal maxim that democracy and capitalism go hand-in-hand. This is one of the greatest lies ever told. Democracy and capitalism are polar opposites.&lt;/p&gt;
&lt;p&gt;You cannot have a functional democracy and billionaires and trillion-dollar corporate interests and Silicon Valley’s Big Tech misinformation and exploitation machinery. What we’re seeing is the clash of capitalism and democracy and capitalism is winning.&lt;/p&gt;
&lt;p&gt;Are we past a tipping point? I don’t know. Perhaps. But we can’t think like that.&lt;/p&gt;
&lt;p&gt;Personally, I’m going to keep working to effect change where I feel I can be effective: in creating alternative technological infrastructure to support individual freedoms and democracy.&lt;/p&gt;
&lt;p&gt;We’ve already laid the infrastructure of techno-fascism. We’ve already created (and are creating) the panopticons. All the fascists need to do is move in and take the controls. And they will do so democratically, before destroying democracy, just as Hitler did.&lt;/p&gt;
&lt;p&gt;And if you think the 1930s and 40s were something, remember that the most advanced tools to amplify the destructive ideologies of the time were less powerful than the computers you have in your pockets today. Today we have machine learning and are on the brink of unlocking quantum computing.&lt;/p&gt;
&lt;p&gt;We must ensure the 2030s are not like the 1930s. Because our advanced centralised systems of data capture, classification, and prediction plus a hundred years of exponential increase in processing power (note: I do not use the word “progress”) mean the 2030s will be exponentially worse.&lt;/p&gt;
&lt;p&gt;Whoever you are, wherever you are, we have a common enemy: the nationalist international. The problems of our time transcend national borders. The solutions must also. The systems we build must be both local and global at once. The network we must build is one of solidarity.&lt;/p&gt;
&lt;p&gt;We created the present. We will create the future. Let’s work together to ensure that that future is the one we want to live in ourselves.&lt;/p&gt;
&lt;figure&gt;
&lt;div class=&#39;videoWrapper&#39;&gt;
  &lt;iframe sandbox=&#34;allow-same-origin allow-scripts&#34; src=&#34;https://video.lqdn.fr/videos/embed/70f2128c-8c06-4cc4-8a5a-bf77e765c8fd?title=0&amp;warningTitle=0&#34; frameborder=&#34;0&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;figcaption&gt;My speech at the European Parliament at the end of last year at the Future of Internet Regulation event.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or, more accurately, our habitat emergency as that’s what we’re at risk of losing unless we act decisively and do so now. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Spoiler alert: we’re not. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Although I am fond of surveillance capitalism as a term – it is precise and accurate in describing the latest iteration of capitalism that we inhabit – &lt;a href=&#34;https://twitter.com/aral/status/1157579927308386305&#34;&gt;Shoshana and I differ on how we define it&lt;/a&gt;. Shoshana sees it as “a rogue mutation of capitalism”, somehow implying that capitalism is otherwise fine. I see surveillance capitalism as a natural evolution of capitalism. Systems of corporate surveillance do not corrupt capitalism, they amplify it and its extractive and exploitative nature. Surveillance capitalism, in my definition, is the interaction between surveillance and capitalism. Capitalism is about accrual of wealth. What happens when those with accrued wealth invest that wealth in mechanisms of surveillance that enables them to gather intimate insight about our lives which they then use to manipulate our behaviour to accue even greater wealth? You get the feedback loop between surveillance and capitalism that is surveillance capitalism. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>The Future of Internet Regulation at the European Parliament</title>
      <link>https://ar.al/2019/11/29/the-future-of-internet-regulation-at-the-european-parliament/</link>
      <pubDate>Fri, 29 Nov 2019 10:21:23 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/11/29/the-future-of-internet-regulation-at-the-european-parliament/</guid>
      <description>&lt;style&gt;
/* Because CSS sucks. Hack courtesy of https://css-tricks.com/NetMag/FluidWidthVideo/Article-FluidWidthVideo.php */

.videoWrapper {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 */
  padding-top: 25px;
  height: 0;
}

.videoWrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

figcaption {
  margin-top: 1em;
}
&lt;/style&gt;
&lt;figure&gt;
&lt;div class=&#39;videoWrapper&#39;&gt;
  &lt;iframe sandbox=&#34;allow-same-origin allow-scripts&#34; src=&#34;https://video.lqdn.fr/videos/embed/70f2128c-8c06-4cc4-8a5a-bf77e765c8fd?title=0&amp;warningTitle=0&#34; frameborder=&#34;0&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;figcaption&gt;My speech at the European Parliament last week at the Future of Internet Regulation event.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Last week, I gave three talks&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; in Belgium, starting with one titled “Dear regulators, please don’t throw the baby out with the bathwater” at an event on The Future of Internet Regulation organised by the Greens and Pirates at the European Parliament on Tuesday.&lt;/p&gt;
&lt;p&gt;You can watch it via the &lt;a href=&#34;https://joinpeertube.org/&#34;&gt;Peertube&lt;/a&gt; embed, above, thanks to the lovely folks at &lt;a href=&#34;https://www.laquadrature.net/en/&#34;&gt;La Quadrature du Net&lt;/a&gt; who took the initiative to rip the recording of the live stream and host it on their own instance. They also edited together a version with &lt;a href=&#34;https://small-tech.slides.com/aral/dear-regulators-dont-throw-the-baby-out-with-the-bathwater#/&#34;&gt;my slides&lt;/a&gt;, captions, and translations. I’ve embedded that version at the end of this post.&lt;/p&gt;
&lt;h2 id=&#34;the-personhood-and-democracy-crisis&#34;&gt;The personhood and democracy crisis&lt;/h2&gt;
&lt;p&gt;During my talk, I tried to impress upon the audience of MEPs, policymakers, and bureaucrats the urgency of the personhood and democracy crisis that we are faced with due to &lt;a href=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/&#34;&gt;surveillance capitalism&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We have an opportunity to do things differently in the EU. We must &lt;a href=&#34;https://ar.al/2019/05/11/the-dos-and-donts-of-tech-regulation/&#34;&gt;regulate surveillance capitalists&lt;/a&gt; – and move beyond data &lt;em&gt;protection&lt;/em&gt; to legislate for algorithmic transparency and, ultimately, &lt;a href=&#34;https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/&#34;&gt;data minimisation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;small-tech-is-the-antidote-to-big-tech&#34;&gt;Small Tech is the antidote to Big Tech&lt;/h2&gt;
&lt;p&gt;But that’s only one half of the story. Regulating to reduce harm is important but it must go hand-in-hand with regulating to incentivise &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;an alternative ethical approach to building technology&lt;/a&gt; with &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;different criteria for success&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We must fund &lt;a href=&#34;https://small-tech.org/about#small-technology&#34;&gt;small tech&lt;/a&gt; as the antidote to big tech.&lt;/p&gt;
&lt;p&gt;The immediate feedback from the room ranged from shock and bruised egos&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to enthusiastic hugs and MEPs asking for appointments to talk (sorry I haven’t been able to get back to you all, it’s hard being &lt;a href=&#34;https://small-tech.org&#34;&gt;a tiny not-for-profit&lt;/a&gt;, but I will).&lt;/p&gt;
&lt;p&gt;Here’s hoping the talk will have an effect.&lt;/p&gt;
&lt;p&gt;We can only try, I guess.&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&#39;videoWrapper&#39;&gt;
    &lt;iframe sandbox=&#34;allow-same-origin allow-scripts&#34; src=&#34;https://video.lqdn.fr/videos/embed/861c07f7-7e9b-4e64-9765-cf1de592c8a0?title=0&amp;warningTitle=0&#34; frameborder=&#34;0&#34; allowfullscreen&gt;&lt;/iframe&gt;
  &lt;/div&gt;
&lt;figcaption&gt;A version of my talk with captions, slides, and subtitles in multiple languages.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The other two were &lt;a href=&#34;https://creativeville.be/en-gb/home&#34;&gt;my opening keynote at Creative Ville in Antwerp&lt;/a&gt; and my talk to students at &lt;a href=&#34;https://www.howest.be/en/study/howest-international&#34;&gt;Howest International&lt;/a&gt; in Kortrijk. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;One of the panelists, a corporate lobbyist, actually tried to tone police me at the end of his own panel when I asked a question. I was apparently using my “outside voice” and should have used my “inside voice.” Uh-huh. You can see the exchange start at ~12:00:17 on &lt;a href=&#34;https://web-greensefa.streamovations.be/index.php/event/stream/the-future-internet-regulation&#34;&gt;the recording of the web stream&lt;/a&gt;, following my question on corporate lobbying and &lt;a href=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/&#34;&gt;institutional corruption&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Small Technology Foundation Personal Web Prototype-01: a mobile personal web server</title>
      <link>https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/</link>
      <pubDate>Wed, 13 Nov 2019 15:06:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/prototype-01-front.jpeg&#34;
         alt=&#34;Photo of my hand on top of a wooden table, holding the prototype described in this article.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Small Technology Foundation Personal Web Prototype-01: an always-connected portable personal web server that fits in your pocket.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Imagine holding your personal web site in the palm of your hand. Imagine carrying the digital aspects of your self in your pocket instead of having them on some abstract cloud under the watchful eye of some faceless multinational corporation&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Personal Web Prototype-01 from &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt; &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;R&amp;amp;D&lt;/a&gt; achieves the former using widely available off-the-shelf parts and &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;. Hopefully, it will also start you thinking in a similar vein about building things that enable the latter.&lt;/p&gt;
&lt;p&gt;In this post, I will outline what Prototype-01 is and how you can build a similar personal web device yourself. The audience for this post is tinkerers and developers&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-hardware&#34;&gt;The hardware&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/prototype-01-components.jpeg&#34;
         alt=&#34;Photo of the components of prototype 01 in a small fabric case labelled with letters.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Prototype 01: the hardware.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;style&gt;
  #lettered-list + ol {
    list-style-type: lower-alpha !important;
  }
  #lettered-list + ol li::marker {
    color: #E11064;
  }
&lt;/style&gt;
&lt;p&gt;Here are the parts I used to build Prototype-01, as labelled in the first photo, above. The main ones are in boldface.&lt;/p&gt;
&lt;div id=&#39;lettered-list&#39;&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;USB Micro Type-B (male) to USB Type-A (female) cable&lt;/li&gt;
&lt;li&gt;14500 Lithium-ion battery (3.7v; rechargeable)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Piz-UpTime&lt;/strong&gt; (the battery/UPS board)&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raspberry Pi Zero W&lt;/strong&gt; (with 32GB &lt;a href=&#34;https://www.raspberrypi.org/documentation/installation/noobs.md&#34;&gt;NOOBS&lt;/a&gt; SD-Card and solderless GPIO hammer header)&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Huawei E3372 LTE USB stick&lt;/strong&gt; with mobile data SIM card&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Velcro strap (for attaching the LTE USB stick to the rest of it)&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In addition, you will need &lt;strong&gt;a domain name&lt;/strong&gt; (I use and can recommend &lt;a href=&#34;https://iwantmyname.com&#34;&gt;IWantMyName.com&lt;/a&gt; for mine) and you will need to know how to update your DNS settings for it.&lt;/p&gt;
&lt;p&gt;To point your domain name to your device’s ever-changing IP address you will also need to use &lt;strong&gt;a service such as &lt;a href=&#34;https://ngrok.com/&#34;&gt;Ngrok&lt;/a&gt;&lt;/strong&gt; (which is what I use here) or a dynamic DNS solution of some sort (which will likely need further setup)&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Finally, to initially set up the Pi, you will also need a keyboard, mouse, and display to connect to it&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;building-it&#34;&gt;Building it&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/prototype-01-power.jpeg&#34;
         alt=&#34;Photo of Prototype-01 on a table showing the battery module and LTE USB stick connected to the Raspberry Pi.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Small Technology Foundation Personal Web Prototype-01: plug-and-play (at least on the hardware side).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is the simplest step. Simply snap the Piz-UpTime with the 14500 battery to the Raspberry Pi Zero using the GPIO header&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; and connect the LTE USB stick to the USB port on the Raspberry Pi&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. Ensure that you use the power port on the PiZ-UpTime and not the one on the Pi itself to charge the battery and power the unit when connected.&lt;/p&gt;
&lt;h2 id=&#34;preparing-the-pi-zero&#34;&gt;Preparing the Pi Zero&lt;/h2&gt;
&lt;p&gt;To prepare your Pi to use as a headless web server, connect a keyboard, mouse, and screen to it. With the NOOBS SD-Card inserted, turn on your Pi. Once it boots, open up a terminal window and carry out the following steps.&lt;/p&gt;
&lt;h3 id=&#34;set-up-ssh-access-with-public-key-authentication&#34;&gt;Set up SSH access with public-key authentication&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;On the Raspberry Pi, select &lt;em&gt;Preferences → Raspberry Pi Configuration&lt;/em&gt; from the Raspberry menu.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the settings box that appears, set the &lt;code&gt;subdomain.your.domain&lt;/code&gt; name you will serve your site from in the &lt;em&gt;Hostname&lt;/em&gt; field.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the Interfaces tab, select the &lt;em&gt;Enable&lt;/em&gt; radio button next for the &lt;em&gt;SSH&lt;/em&gt; field.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;em&gt;OK&lt;/em&gt; to save your settings and exit settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt; into your Pi using the IP address of the Pi (which you can find by running &lt;code&gt;ifconfig&lt;/code&gt;) and your Pi account name and password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add your SSH public key from your main computer (e.g., from &lt;code&gt;~/.ssh/id_ed25519.pub&lt;/code&gt;) to your Pi (e.g., into the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file).&lt;/p&gt;
&lt;p&gt;If you do not have SSH keys generated on your main computer yet, &lt;a href=&#34;https://www.unixtutorial.org/how-to-generate-ed25519-ssh-key&#34;&gt;follow the instructions here to create a pair&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log out of your &lt;code&gt;ssh&lt;/code&gt; session and reconnect without using your password to test that the public-key authentication you just set up works.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/prototype-01-4g-modem.jpeg&#34;
         alt=&#34;Close-up of the Huawei Mobile Broadband LTE USB Stick E3372 used in the prototype.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Small Technology Foundation Personal Web Prototype-01: close-up of the LTE USB Stick.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;set-up-ngrok&#34;&gt;Set up ngrok&lt;/h3&gt;
&lt;p&gt;You need to configure ngrok to create three secure tunnels to the Raspberry Pi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HTTP tunnel for port 80&lt;/strong&gt; so Site.js can handle Let’s Encrypt challenges using the &lt;a href=&#34;https://letsencrypt.org/docs/challenge-types#http-01-challenge&#34;&gt;HTTP-01 challenge&lt;/a&gt; method.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TLS tunnel for port 443&lt;/strong&gt; that Site.js will use to serve your web site on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TCP tunnel for port 22&lt;/strong&gt; so you can access your Pi at your domain name over SSH.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To do this, create a file called &lt;code&gt;ngrok.yml&lt;/code&gt; in the &lt;code&gt;~/.ngrok2&lt;/code&gt; directory (create the directory if it doesn’t already exist):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;mkdir -p ~/.ngrok2
touch ~/.ngrok2/ngrok.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then add the following configuration, customising it for your needs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;authtoken&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;your-auth-token&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;region&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;eu&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;tunnels&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;insecure-web&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;addr&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;80&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;proto&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;http&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;hostname&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;your-reserved-subdomain.your.domain&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;bind-tls&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;false&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;secure-web&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;addr&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;443&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;proto&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tls&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;hostname&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;your-reserved-subdomain.your.domain&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ssh&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;addr&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;22&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;proto&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tcp&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;remote_addr&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;your-reserved-tcp-address&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can find your &lt;code&gt;authtoken&lt;/code&gt; on your &lt;a href=&#34;https://dashboard.ngrok.com/auth&#34;&gt;ngrok Auth page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To learn how to reserve a custom domain, see &lt;a href=&#34;https://ngrok.com/docs/2&#34;&gt;the ngrok documentation&lt;/a&gt; for &lt;a href=&#34;https://ngrok.com/docs/2#http-custom-domains&#34;&gt;tunnels on custom domains&lt;/a&gt; and &lt;a href=&#34;https://ngrok.com/docs/2#tcp-remote-addr&#34;&gt;TCP Tunnels: listening on a reserved remote address&lt;/a&gt;. Also, alter the &lt;code&gt;region&lt;/code&gt; to match the one you reserved your ngrok domains on.&lt;/p&gt;
&lt;p&gt;Once you’ve customised and saved your ngrok configuration file, you can create your tunnels at any time using:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;ngrok start --all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, while that’s good for testing, what we want is for ngrok to start automatically when the Raspberry Pi does. This will allow us to SSH into the Pi so we can use it headlessly from now on and it will mean that once it has power, our web site will be reachable from anywhere in the world.&lt;/p&gt;
&lt;p&gt;To do this, edit the &lt;code&gt;/etc/xdg/lxsession/LXDE-pi/autostart&lt;/code&gt; file, add the following line to it, and save it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;@ngrok start --all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will automatically run your ngrok tunnels in the background whenever the Pi boots up.&lt;/p&gt;
&lt;h3 id=&#34;install-sitejshttpssitejsorg-and-run-it-as-a-startup-daemon&#34;&gt;Install &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; and run it as a startup daemon&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Install Site.js&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;wget -qO- https://sitejs.org/install | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Create a basic web site:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a folder to hold the web site.&lt;/span&gt;
mkdir -p ~/public

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Switch to that folder.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; public

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a simple “Hello, world!” page.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello, world!&amp;#39;&lt;/span&gt; &amp;gt; index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;Create a startup daemon using Site.js to serve your site (this will survive restarts and crashes, etc.):&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;site &lt;span style=&#34;color:#007020&#34;&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;testing-it&#34;&gt;Testing it&lt;/h2&gt;
&lt;p&gt;You should now be able to hit &lt;code&gt;https://subdomain.your.domain&lt;/code&gt; and see your “Hello, world!” page.&lt;/p&gt;
&lt;h2 id=&#34;updating-your-site&#34;&gt;Updating your site&lt;/h2&gt;
&lt;p&gt;Now that your site is live, how do you make it a bit more exciting?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;On your main computer, create a more interesting web site than “Hello, world”.&lt;/p&gt;
&lt;p&gt;If you want to get an idea for the types of things you can build with Site.js, see the tutorial I wrote recently on how to &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;build a simple chat app with Site.js&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make sure Site.js is also installed on your main computer and then issue the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;site --sync-to&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;subdomain.your.domain --exit-on-sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hit &lt;code&gt;https://subdomain.your.domain&lt;/code&gt; again and you should see your new site.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To learn more about Site.js, read the &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md&#34;&gt;Site.js documentation&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/11/small-technology-foundation-personal-web-prototype-01-a-mobile-portable-personal-web-server/prototype-01-back.jpeg&#34;
         alt=&#34;Photo of Prototype-01 bound with the velcro strap and in my hand showing the back of the Raspberry Pi with the raspberry logo.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Small Technology Foundation Personal Web Prototype-01: all together now.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;what-next&#34;&gt;What next?&lt;/h2&gt;
&lt;p&gt;This is just the first prototype to come out of our work on &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; as part of our &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;research and development&lt;/a&gt; work at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;. It doesn’t represent a specific product we are thinking of building but rather more the direction in which we’re thinking in general (which might result in products later on).&lt;/p&gt;
&lt;p&gt;I hope, though, that it does help demonstrate two important points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;We all already carry much more powerful and refined versions of this prototype in our pockets (it’s your smart phone) and yet we cannot run our own web sites on them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why not?&lt;/p&gt;
&lt;p&gt;Because the Big Tech corporations that make these devices don’t want us to.&lt;/p&gt;
&lt;p&gt;I hope this gives folks like &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt; and &lt;a href=&#34;https://e.foundation/&#34;&gt;e.foundation&lt;/a&gt; some ideas about what they could do in this area to differentiate themselves from the likes of Apple, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If we want the sort of freedom devices like this can offer people, we must ensure that they just work out of the box.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While I hope that you have found this post interesting if you’re a tinkerer or developer, I also hope that it highlights the mountain of work that must be done if we want all the various aspects outlined here to work seamlessly when our audience is everyday people who use technology as everyday things.&lt;/p&gt;
&lt;p&gt;I hope this post has inspired to you think about what you can make that everyday people can use to take ownership and control of the digital and connected aspects of their lives.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As always, please feel free to &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;let me know your thoughts&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Each one of us has the raw technology to achieve this in our pockets in the form of a smartphone but the smartphone manufacturers do not let us do this because they do not want us to have ownership and control the digital and connected aspects of our lives – they want to be able to filter our experiences, gatekeep our access, and manipulate our behaviour for profit. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If we want simple everyday technology for everyday people to use, we must first empower those who make such things with the tools they need to do so without being beholden to the tools of Big Tech. To paraphrase Audre Lorde: the master’s tools will never dismantle the master’s house and neither will the master’s tools ever let you build your own house. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There are several UPS / battery solutions for the Raspberry Pi Zero. The one I’m using here is the PiZ-UpTime 1.0. The latest, currently available version is the &lt;a href=&#34;http://alchemy-power.com/piz-uptime-2-0/&#34;&gt;PiZ-UpTime 2.0&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Other power options include the &lt;a href=&#34;https://hackaday.io/project/164733-pisugar-battery-for-raspberry-pi-zero&#34;&gt;PiSugar&lt;/a&gt; – &lt;a href=&#34;https://twitter.com/aral/status/1193478863956959233&#34;&gt;which I’d love to get my hands on&lt;/a&gt; and the &lt;a href=&#34;https://juiceboxzero.com/product/juicebox-zero-battery-management-board/&#34;&gt;JuiceBox Zero&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you do end up using the PiZ-Uptime, you will want to install &lt;a href=&#34;http://alchemy-power.com/downloads/&#34;&gt;a cron job to ensure that the board is cleanly shut down if the battery runs low on power&lt;/a&gt;. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m using a Raspberry Pi W which doesn’t come with a GPIO header. You don’t need the GPIO header to connect the UPS (you can also use a USB Micro-B male-to-male cable/connector). If you do want to use the header, either get the Raspberry Pi WH model (which comes with it pre-soldered) or you can do what I did, which was to use a &lt;a href=&#34;https://shop.pimoroni.com/products/gpio-hammer-header?variant=35643318026&#34;&gt;solderless GPIO hammer header from Pimoroni&lt;/a&gt;. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If the LTE USB stick is not automatically recognised, &lt;a href=&#34;https://www.raspberrypi.org/forums/viewtopic.php?p=1197625&#34;&gt;see this&lt;/a&gt;. Also, make sure you put the SIM card in to the SIM card slot and not the SD-Card slot on the LTE USB stick. They’re easy to confuse. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;A wad of blue/white tack or a strong rubber band works just as well; just use whatever you have lying around. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In this post, I will only cover Ngrok, with an Ngrok Pro account which is necessary for using custom domain names. &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Since the Pi Zero only has one USB port, you will either need a bluetooth keyboard/mouse or a combination USB keyboard/mouse or a USB hub.&lt;/p&gt;
&lt;p&gt;If you don’t want to keep connecting a keyboard and mouse, look at &lt;a href=&#34;https://symless.com/synergy&#34;&gt;Synergy&lt;/a&gt;. It enables you to share one keyboard and mouse across several devices. If you want a free and open source version, check out &lt;a href=&#34;https://github.com/debauchee/barrier&#34;&gt;Barrier&lt;/a&gt; (which I learned about only later and haven’t tried myself yet).&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/102976943949623290/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
 &lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Introducing @small-tech/https, a batteries-included drop-in replacement for the Node.js https module</title>
      <link>https://ar.al/2019/11/08/introducing-small-tech-https-a-batteries-included-drop-in-replacement-for-the-node.js-https-module/</link>
      <pubDate>Fri, 08 Nov 2019 18:54:12 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/11/08/introducing-small-tech-https-a-batteries-included-drop-in-replacement-for-the-node.js-https-module/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/08/introducing-small-tech-https-a-batteries-included-drop-in-replacement-for-the-node.js-https-module/global.jpeg&#34;
         alt=&#34;Screenshot of @small-tech/https example app running in terminal with globally-trusted Let’s Encrypt certificates&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;@small-tech/https with globally-trusted Let’s Encrypt certificates&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Today’s my birthday so I thought I’d give you a little present: &lt;a href=&#34;https://source.ind.ie/site.js/lib/https/&#34;&gt;&lt;code&gt;@small-tech/https&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;plug-and-play-https&#34;&gt;Plug-and-play https&lt;/h2&gt;
&lt;p&gt;This is essentially a drop-in, batteries-included version of &lt;a href=&#34;https://nodejs.org/api/all.html#https_https&#34;&gt;the Node.js &lt;code&gt;https&lt;/code&gt; module&lt;/a&gt; that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automatically provisions locally-trusted TLS certificates for localhost&lt;/strong&gt; for development use (courtesy of &lt;a href=&#34;https://github.com/FiloSottile/mkcert&#34;&gt;mkcert&lt;/a&gt; seamlessly integrated via &lt;a href=&#34;https://source.ind.ie/hypha/tools/nodecert&#34;&gt;Nodecert&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automatically provisions globally-trusted TLS certificates for your domain(s)&lt;/strong&gt; for cross-browser testing, staging, and production courtesy of &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm i @small-tech/https
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;p&gt;Here’s a basic HTTPS server that responds to every &lt;code&gt;GET&lt;/code&gt; request with a simple “hello, world” page.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3 id=&#34;set-up&#34;&gt;Set up&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the project folder and switch to it.&lt;/span&gt;
mkdir example &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; example

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a new npm module for the example.&lt;/span&gt;
npm init --yes

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install dependencies.&lt;/span&gt;
npm i @small-tech/https

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Open up the main file in your default editor.&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;$EDITOR&lt;/span&gt; index.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;code-indexjs&#34;&gt;Code (index.js)&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; https &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@small-tech/https&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Helpers
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; html(message) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;!doctype html&amp;gt;&amp;lt;html lang=&amp;#39;en&amp;#39;&amp;gt;&amp;lt;head&amp;gt;&amp;lt;meta charset=&amp;#39;utf-8&amp;#39;/&amp;gt;&amp;lt;title&amp;gt;Hello, world!&amp;lt;/title&amp;gt;&amp;lt;style&amp;gt;body{background-color: white; font-family: sans-serif;}&amp;lt;/style&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`&lt;/span&gt;
}
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; headers &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;}

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; options &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// For globally-trusted Let’s Encrypt certificates uncomment options.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// To provision certificates, also remove “staging: true” property.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// options = {
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//   domain: &amp;#39;hostname&amp;#39;,
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;//   staging: true
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// }
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Default (no options): create HTTPS server at https://localhost
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// with locally-trusted certificates.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; server &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; https.createServer(options, (request, response) =&amp;gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Respond to all routes with the same page.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  response.writeHead(&lt;span style=&#34;color:#40a070&#34;&gt;200&lt;/span&gt;, headers)
  response.end(html(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello, world!&amp;#39;&lt;/span&gt;))
})

server.listen(&lt;span style=&#34;color:#40a070&#34;&gt;443&lt;/span&gt;, () =&amp;gt; {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39; 🎉 Server running on port 443.&amp;#39;&lt;/span&gt;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&#34;run&#34;&gt;Run&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;node index
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hit &lt;code&gt;https://localhost&lt;/code&gt; and you should see your site served with locally-trusted TLS certificates.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/11/08/introducing-small-tech-https-a-batteries-included-drop-in-replacement-for-the-node.js-https-module/global.jpeg&#34;
         alt=&#34;Screenshot of @small-tech/https example app running in terminal with locally-trusted TLS certificates&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;@small-tech/https with locally-trusted certificates courtesy of mkcert&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;lets-encrypt&#34;&gt;Let’s encrypt!&lt;/h2&gt;
&lt;p&gt;To provision globally-trusted Let’s Encrypt certificates instead, just uncomment the &lt;code&gt;options&lt;/code&gt; object. Test it out with the &lt;code&gt;staging&lt;/code&gt; flag set to true first and, once you verify that certificates are being properly generated, remove the flag to actually provision the certificates the first time your server is hit.&lt;/p&gt;
&lt;h2 id=&#34;tl-dt&#34;&gt;TL; DT&lt;/h2&gt;
&lt;p&gt;Too long, didn’t type?&lt;/p&gt;
&lt;p&gt;You can find a version of this example in the &lt;code&gt;/example&lt;/code&gt; folder. To download and run it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Clone this repository.&lt;/span&gt;
git clone https://source.ind.ie/site.js/lib/https.git

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Switch to the directory.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; https

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install dependencies.&lt;/span&gt;
npm i

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Run the example.&lt;/span&gt;
npm run example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;happy-birthday-to-you&#34;&gt;Happy birthday to you!&lt;/h2&gt;
&lt;p&gt;I hope you enjoy my little birthday present and I hope that it makes your life easier.&lt;/p&gt;
&lt;p&gt;Please &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;let me know&lt;/a&gt; how you get on it with.&lt;/p&gt;
&lt;figure&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/103102778422291371/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;PS. I also got some lovely presents today ;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Site.js: now with auto updates in production</title>
      <link>https://ar.al/2019/11/03/site.js-now-with-auto-updates-in-production/</link>
      <pubDate>Sun, 03 Nov 2019 11:12:31 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/11/03/site.js-now-with-auto-updates-in-production/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/827927043.jpg?mw=2500&amp;mh=1406&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/370643404.hd.mp4?s=4072ee1788c981d32956733956ebb9fd24c4495a&amp;profile_id=169&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/370643404.m3u8?s=0e94c74434cf5930a8c95ad9c41c92d9f9b47b3f&#39;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a href=&#39;https://player.vimeo.com/external/370643404.hd.mp4?s=4072ee1788c981d32956733956ebb9fd24c4495a&amp;profile_id=169&amp;download=1&#39;&gt;download this video directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;A quick demonstration of the new auto-reload feature.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; version 12.10.2 introduces &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#automatic-updates-in-production-as-of-version-12100&#34;&gt;automatic updates in production&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;why-auto-update&#34;&gt;Why auto update?&lt;/h2&gt;
&lt;p&gt;Site.js is a personal web tool for individual developers – not startups or enterprises. It’s a tool for building everyday things for everyday people that do exactly what they say on the tin and nothing more.&lt;/p&gt;
&lt;p&gt;In other words, Site.js is a tool for building &lt;a href=&#34;https://small-tech.org/about#small-technology&#34;&gt;small technology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With that in mind, it goes without saying that the sites and apps you build and serve with Site.js will not have dedicated operations teams to keep them up to date and secure. And while you may be a dev-ops unicorn who also plays smashing electric guitar, not everyone is. And the people who will run their own instances of the things you build with Site.js definitely won’t be. So Site.js has to be your operations team and come with secure defaults out of the box.&lt;/p&gt;
&lt;h2 id=&#34;because-security&#34;&gt;Because security&lt;/h2&gt;
&lt;p&gt;One of the biggest security issues in tech is running outdated software. While I was running my servers on nginx, I couldn’t tell you when they were last upgraded as the process was so convoluted. With Site.js, running your own web server has to be deploy and forget. We cannot assume that folks are going to update their servers.&lt;/p&gt;
&lt;p&gt;So auto updates of production servers is a crucial security feature.&lt;/p&gt;
&lt;h2 id=&#34;manual-updates-during-development-and-testing&#34;&gt;Manual updates during development and testing&lt;/h2&gt;
&lt;p&gt;Automatic updates are a feature &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#production&#34;&gt;for production servers&lt;/a&gt; only (in other words, whenever you launch Site.js as a service/daemon using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#production&#34;&gt;the &lt;code&gt;enable&lt;/code&gt; command&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;When running Site.js during development and testing, you can check for updates manually using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#update-as-of-version-1295-properly-functioning-as-of-version-1296&#34;&gt;the &lt;code&gt;update&lt;/code&gt; command&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Site.js: now with auto server reload on source code changes</title>
      <link>https://ar.al/2019/10/30/site.js-now-with-auto-server-reload-on-source-code-changes/</link>
      <pubDate>Wed, 30 Oct 2019 19:21:35 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/30/site.js-now-with-auto-server-reload-on-source-code-changes/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/826987850.jpg?mw=2500&amp;mh=1406&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/369910589.hd.mp4?s=dca6f26bc0bf514e9cb92c3536257f3b2e3e85b0&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/369910589.m3u8?s=625550d5d3def50ea3084d7444b8666836de02d1&#39; type=&#39;video/mp4&#39;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a href=&#39;https://player.vimeo.com/external/369910589.hd.mp4?s=dca6f26bc0bf514e9cb92c3536257f3b2e3e85b0&amp;profile_id=175&amp;download=1&#39;&gt;download this video directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;A quick demonstration of the new auto-reload feature.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;auto-reload&#34;&gt;Auto reload&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; version 12.9.7 brings the second developer experience improvement to Site.js in as many days with an integrated auto reload feature that responds to source code changes on dynamic sites.&lt;/p&gt;
&lt;h3 id=&#34;live-reload-yesterday-auto-reload-today&#34;&gt;Live reload yesterday, auto reload today&lt;/h3&gt;
&lt;p&gt;Following yesterday’s addition of &lt;a href=&#34;https://ar.al/2019/10/29/site.js-now-with-live-reload/&#34;&gt;live reload for static sites&lt;/a&gt;, today you get auto server reloads when the source code of your &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#dynamic-sites&#34;&gt;dynamic site&lt;/a&gt; changes.&lt;/p&gt;
&lt;p&gt;This is the sort of functionality you’d normally implement by using excellent third-party tools like Remy’s &lt;a href=&#34;https://nodemon.io/&#34;&gt;nodemon&lt;/a&gt;. With Site.js, you now get it out of the box. No external process manager required.&lt;/p&gt;
&lt;p&gt;So now, if you change the code in a &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#dotjs&#34;&gt;DotJS route&lt;/a&gt; or even if you add or remove a new node module, the server will automatically restart.&lt;/p&gt;
&lt;p&gt;Note that during development, it’s the server that restarts, not your whole process. That’s faster than a full reload of the whole Site.js process.&lt;/p&gt;
&lt;h3 id=&#34;seamless-restarts-on-deployment-when-necessary&#34;&gt;Seamless restarts on deployment when necessary&lt;/h3&gt;
&lt;p&gt;While the auto reload feature is a great help during development, it also makes your life much easier during deployment.&lt;/p&gt;
&lt;p&gt;If you &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#production&#34;&gt;run Site.js in production&lt;/a&gt; using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#production&#34;&gt;the &lt;code&gt;enable&lt;/code&gt; command&lt;/a&gt;, your server will now automatically restart when you &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#deployment-live-and-one-time-sync&#34;&gt;sync your changes to it&lt;/a&gt;. (And do so only if it needs to.)&lt;/p&gt;
&lt;p&gt;None of this changes the behaviour of static page and asset updates which have always been, and continue to be, immediately available on deployment without requiring a server restart.&lt;/p&gt;
&lt;h3 id=&#34;have-a-play&#34;&gt;Have a play!&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/30/site.js-now-with-auto-server-reload-on-source-code-changes/../../18/site.js-and-pi/site-js-chat-on-raspberry-pi-1.jpeg&#34;
         alt=&#34;Screenshot of the Site.js basic chat example running on a Raspberry Pi 4B.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Yes, I’m going to keep mentioning the tutorial until you try it!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you’re wondering what Site.js is, check out &lt;a href=&#34;https//sitejs.org&#34;&gt;the Site.js web site&lt;/a&gt; and have a glance at &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md&#34;&gt;the Site.js documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There’s also a tutorial I released a few weeks ago that takes you through building static and dynamic sites with Site.js as you &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;build a simple chat app using WebSockets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope these improvements make your experience of creating with Site.js more delightful.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

</description>
    </item>
    
    <item>
      <title>Site.js: now with live reload</title>
      <link>https://ar.al/2019/10/29/site.js-now-with-live-reload/</link>
      <pubDate>Tue, 29 Oct 2019 18:28:29 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/29/site.js-now-with-live-reload/</guid>
      <description>&lt;figure&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/103042273071351377/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;Laura demonstrating the new live reload feature last night.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I just released version 12.9.6 of &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; with live reload support for static pages.&lt;/p&gt;
&lt;p&gt;There’s also a fix for &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#update-as-of-v1295-properly-functioning-as-of-v1296&#34;&gt;the &lt;code&gt;update&lt;/code&gt; command&lt;/a&gt;, so please update to this version so that you can keep updating using the &lt;code&gt;update&lt;/code&gt; command when we move to 12.10.x and beyond.&lt;/p&gt;
&lt;h2 id=&#34;get-sitejs-version-1296&#34;&gt;Get Site.js version 12.9.6&lt;/h2&gt;
&lt;h3 id=&#34;update-to-it&#34;&gt;Update to it&lt;/h3&gt;
&lt;p&gt;To update to Site.js version 12.9.6 from Site.js version 12.9.5, you can use the &lt;code&gt;update&lt;/code&gt; command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;site update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;install-it-from-scratch&#34;&gt;Install it from scratch&lt;/h3&gt;
&lt;p&gt;If you’re running an earlier version of Site.js or if you are going to install it for the first time:&lt;/p&gt;

&lt;p&gt;Copy and paste the following command into &lt;span id=&#39;terminal-copy&#39;&gt;your terminal&lt;/span&gt;. &lt;strong&gt;Before you pipe any script into your computer, always &lt;span id=&#39;view-installation-script-source&#39;&gt;view the source code (&lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install&#34;&gt;Linux and macOS&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install.txt&#34;&gt;Windows&lt;/a&gt;)&lt;/span&gt; and make sure you understand what it does.&lt;/strong&gt;&lt;/p&gt;

&lt;section id=&#39;install-linux&#39; class=&#39;installation-instructions&#39;&gt;
  &lt;h3&gt;Linux&lt;/h3&gt;
  &lt;div class=&#39;code-with-copy-button&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;wget -qO- https://sitejs.org/install | bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;button hidden class=&#39;copy-to-clipboard&#39; onclick=&#39;copyInstallationInstructionsToClipboardFor(&#34;linux&#34;)&#39;&gt;Copy&lt;/button&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;section id=&#39;install-mac&#39; class=&#39;installation-instructions&#39;&gt;
  &lt;h3&gt;macOS&lt;/h3&gt;
  &lt;div class=&#39;code-with-copy-button&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;curl -s https://sitejs.org/install | bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;button hidden class=&#39;copy-to-clipboard&#39; onclick=&#39;copyInstallationInstructionsToClipboardFor(&#34;mac&#34;)&#39;&gt;Copy&lt;/button&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;section id=&#39;install-windows&#39; class=&#39;installation-instructions&#39;&gt;
  &lt;h3&gt;Windows 10&lt;/h3&gt;
  &lt;div class=&#39;code-with-copy-button&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PowerShell&#34; data-lang=&#34;PowerShell&#34;&gt;iex(&lt;span style=&#34;color:#007020&#34;&gt;iwr &lt;/span&gt;-UseBasicParsing https&lt;span style=&#34;&#34;&gt;:&lt;/span&gt;//sitejs.org/install.txt).Content&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;button hidden class=&#39;copy-to-clipboard&#39; onclick=&#39;copyInstallationInstructionsToClipboardFor(&#34;windows&#34;)&#39;&gt;Copy&lt;/button&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;p id=&#39;links-to-instructions-for-other-platforms&#39; hidden&gt;
  &lt;strong&gt;Instructions for other platforms:&lt;/strong&gt;
  &lt;span id=&#39;link-linux&#39;&gt;&lt;a onclick=&#39;displayInstallationInstructionsFor(&#34;linux&#34;)&#39;&gt;Linux&lt;/a&gt; | &lt;/span&gt;
  &lt;span id=&#39;link-mac&#39;&gt;&lt;a onclick=&#39;displayInstallationInstructionsFor(&#34;mac&#34;)&#39;&gt;macOS&lt;/a&gt;&lt;span id=&#39;pipe-before-windows&#39;&gt; | &lt;/span&gt;&lt;/span&gt;
  &lt;a id=&#39;link-windows&#39; onclick=&#39;displayInstallationInstructionsFor(&#34;windows&#34;)&#39;&gt;Windows&lt;/a&gt;
&lt;/p&gt;


&lt;script&gt;
  
  
  const userAgent = navigator.userAgent.toLowerCase()
  let currentPlatform = &#39;unknown&#39;
  if (userAgent.includes(&#39;linux&#39;)) { currentPlatform = &#39;linux&#39; }
  if (userAgent.includes(&#39;mac os x&#39;)) { currentPlatform = &#39;mac&#39; }
  if (userAgent.includes(&#39;windows&#39;)) { currentPlatform = &#39;windows&#39;}

  displayInstallationInstructionsFor(currentPlatform)

  function displayInstallationInstructionsFor(currentPlatform) {
    if (currentPlatform !== &#39;unknown&#39;) {
      
      [&#39;linux&#39;, &#39;mac&#39;, &#39;windows&#39;].forEach(platform =&gt; {
        document.querySelector(`#install-${platform}`).hidden = !(platform === currentPlatform)
        document.querySelector(`#link-${platform}`).hidden = (platform === currentPlatform)
      })

      
      document.querySelector(&#39;#pipe-before-windows&#39;).hidden = (currentPlatform === &#39;windows&#39;)

      
      document.querySelector(&#39;#links-to-instructions-for-other-platforms&#39;).hidden = false

      
      document.querySelectorAll(&#39;.copy-to-clipboard&#39;).forEach(button =&gt; button.hidden = false)

      
      
      document.querySelector(&#39;#view-installation-script-source&#39;).innerHTML = (currentPlatform === &#39;windows&#39;) ? &#39;&lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install.txt&#34;&gt;view the source code&lt;/a&gt;&#39; : &#39;&lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install&#34;&gt;view the source code&lt;/a&gt;&#39;

      
      
      document.querySelector(&#39;#terminal-copy&#39;).innerHTML = (currentPlatform === &#39;windows&#39;) ? &#39;a PowerShell session running under &lt;a href=&#34;https://github.com/Microsoft/Terminal&#34;&gt;Windows Terminal&lt;/a&gt;&#39; : &#39;your terminal&#39;

      
      document.querySelectorAll(&#39;.windows-only&#39;).forEach(node =&gt; node.hidden = !(currentPlatform === &#39;windows&#39;))
    }
  }

  function copyInstallationInstructionsToClipboardFor(platform) {
    const installationCommand = document.querySelector(`section#install-${platform} code`)

    
    
    window.getSelection().removeAllRanges()

    const selectedCode = document.createRange()
    selectedCode.selectNode(installationCommand)
    window.getSelection().addRange(selectedCode)

    try {
      const success = document.execCommand(&#39;copy&#39;)
      if (!success) console.log(&#39;Failed to copy installation command.&#39;)
    } catch(error) {
      console.log(&#39;Copy command threw an error&#39;, error)
    }

    
    
    window.getSelection().removeRange(selectedCode)
  }
&lt;/script&gt;

&lt;h2 id=&#34;live-reload&#34;&gt;Live reload&lt;/h2&gt;
&lt;p&gt;Site.js is the easiest way to develop, test, and deploy a static web site but, until today, it wasn’t necessarily a delightful development experience as you needed to refresh your static pages manually to see the changes you made… like it was 2009 or something!&lt;/p&gt;
&lt;p&gt;Well, take your hand off that refresh button because the latest version of Site.js comes with live reload out of the box.&lt;/p&gt;
&lt;h2 id=&#34;server-sent-events-to-the-rescue&#34;&gt;Server-sent events to the rescue&lt;/h2&gt;
&lt;figure&gt;
  &lt;iframe src=&#34;https://mastodon.ar.al/@aral/103022202114228926/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;figcaption&gt;Me, in my naïve younger days, earlier this week.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I implemented the live reload functionality using &lt;a href=&#34;https://github.com/aral/instant&#34;&gt;my fork of&lt;/a&gt; the excellent &lt;a href=&#34;https://github.com/fgnass/instant&#34;&gt;&lt;code&gt;instant&lt;/code&gt; module&lt;/a&gt;, which in turn uses &lt;a href=&#34;https://github.com/aral/sendevent&#34;&gt;my fork of&lt;/a&gt; the also excellent &lt;a href=&#34;https://github.com/fgnass/sendevnet&#34;&gt;&lt;code&gt;sendevent&lt;/code&gt; module&lt;/a&gt;, both of which are by &lt;a href=&#34;https://github.com/fgnass&#34;&gt;Felix Gnass&lt;/a&gt; and use &lt;a href=&#34;https://en.wikipedia.org/wiki/Server-sent_events&#34;&gt;server-sent events&lt;/a&gt; (SSE; also known as &lt;code&gt;EventSource&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&#34;rescuing-server-sent-events-from-a-fox&#34;&gt;Rescuing server-sent events from a fox&lt;/h2&gt;
&lt;figure&gt;
  &lt;iframe src=&#34;https://mastodon.ar.al/@aral/103023932351097430/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
  &lt;figcaption&gt;Theory, meet practice.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While I initially thought that I’d be done implementing the feature in mere minutes thanks to the &lt;code&gt;instant&lt;/code&gt; module, it actually took me two days. And we have Mr. Firefox Browser to thank for that.&lt;/p&gt;
&lt;p&gt;Turns out, Firefox has a peculiar quirk&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; with &lt;code&gt;EventSource&lt;/code&gt; when you’re testing a page and your &lt;code&gt;host&lt;/code&gt; is &lt;code&gt;localhost&lt;/code&gt;.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; Basically, if you refresh the page from Firefox’s memory cache without doing a force refresh, your &lt;code&gt;EventSource&lt;/code&gt; connection disconnects after 30 seconds.&lt;/p&gt;
&lt;figure&gt;
  &lt;iframe src=&#34;https://mastodon.ar.al/@aral/103025061028864594/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
  &lt;figcaption&gt;Ooh, we’re half way there…&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It took me about 20 minutes to integrate the &lt;code&gt;instant&lt;/code&gt; module into Site.js and two days to narrow down and fix the issues with Firefox and contribute my fixes upstream. And that, ladies and gentlemen, is the wacky world of development that we all know and love.&lt;/p&gt;
&lt;p&gt;It was worth it, though, because now Firefox behaves exactly like Chrom(ium) with &lt;code&gt;EventSource&lt;/code&gt; connections over &lt;code&gt;localhost&lt;/code&gt; when using the &lt;code&gt;instant&lt;/code&gt; module&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;iframe src=&#34;https://mastodon.ar.al/@aral/103029600133835839/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
  &lt;figcaption&gt;Development would be harder if you couldn’t bitch on Mastodon.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;and-a-fix-for-the-update-command&#34;&gt;And a fix for the update command&lt;/h2&gt;
&lt;p&gt;Some days when you’re making things you have these “doh!” moments where you realise you did something really silly.&lt;/p&gt;
&lt;p&gt;I had one of those today while attempting to use &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#update-as-of-v1295-properly-functioning-as-of-v1296&#34;&gt;the Site.js &lt;code&gt;update&lt;/code&gt; command&lt;/a&gt; to update from version 12.9.5 to this release, which was originally meant to be version 12.10.0.&lt;/p&gt;
&lt;p&gt;Long story short, Site.js told me I was running a newer version than the latest released version because I, in all my glory, thought I was doing integer comparison while actually doing string comparison and, lexographically, &lt;code&gt;10&lt;/code&gt; comes before &lt;code&gt;9&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Yay, me.&lt;/p&gt;
&lt;p&gt;So that’s fixed now too&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/29/site.js-now-with-live-reload/../../18/site.js-and-pi/site-js-chat-on-raspberry-pi-1.jpeg&#34;
         alt=&#34;Screenshot of the Site.js basic chat example running on a Raspberry Pi 4B.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;I followed a tutorial and all I got was this lousy chat app…&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you’re wondering what Site.js is, check out &lt;a href=&#34;https//sitejs.org&#34;&gt;the Site.js web site&lt;/a&gt; and have a glance at &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md&#34;&gt;the Site.js documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There’s also a tutorial I released a few weeks ago that takes you through building static and dynamic sites with Site.js as you &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;build a simple chat app using WebSockets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope the new live reload feature makes your life easier and, as always, if you have any thoughts or suggestions – or if you run into any issues – &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;please let me know&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;See my merge requests for the &lt;code&gt;instant&lt;/code&gt; (&lt;a href=&#34;https://github.com/fgnass/instant/pull/17&#34;&gt;#17&lt;/a&gt;) and &lt;code&gt;sendevent&lt;/code&gt; (&lt;a href=&#34;https://github.com/fgnass/sendevent/pull/4&#34;&gt;#4&lt;/a&gt;) modules for further details. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And I literally mean &lt;code&gt;host&lt;/code&gt;, not even &lt;code&gt;hostname&lt;/code&gt; – the quirk does not manifest, for example, if you’re testing over &lt;code&gt;localhost:999&lt;/code&gt;, only &lt;code&gt;localhost:443&lt;/code&gt; (I did not test over port 80 because, well, it’s late 2019.) &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For now you can use &lt;a href=&#34;https://github.com/aral/instant&#34;&gt;my fork of &lt;code&gt;instant&lt;/code&gt;&lt;/a&gt; but keep an eye on &lt;a href=&#34;https://github.com/fgnass/instant/pull/17&#34;&gt;my pull request&lt;/a&gt; in &lt;a href=&#34;https://github.com/fgnass/instant&#34;&gt;Felix’s original repository&lt;/a&gt; and switch to using the original module once my changes have been merged. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’ll write up a short post with code snippets when I get a moment so we can all share a laugh. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Fixing read-only file system errors after do-release-upgrade from Ubuntu 14.04 LTS to 16.04 LTS</title>
      <link>https://ar.al/2019/10/24/fixing-read-only-file-system-errors-after-do-release-upgrade-from-ubuntu-14.04-lts-to-16.04-lts/</link>
      <pubDate>Thu, 24 Oct 2019 12:34:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/24/fixing-read-only-file-system-errors-after-do-release-upgrade-from-ubuntu-14.04-lts-to-16.04-lts/</guid>
      <description>&lt;p&gt;I upgraded an old server from Ubuntu 14.04 LTS to 16.04 LTS today and, when it restarted, I started getting “Read-only file system” errors on the root partition.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ouch!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here’s how I investigated and fixed the issue, including a list of the sites I found that helped me along the way.&lt;/p&gt;
&lt;h2 id=&#34;am-i-out-of-disk-space&#34;&gt;Am I out of disk space?&lt;/h2&gt;
&lt;p&gt;I wasn’t sure if perhaps the installation had failed and corrupted something because I was out of disk space. So first I checked for that and saw that it wasn’t the case:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;► df

Filesystem     1K-blocks     Used Available Use% Mounted on
/dev/vda1       &lt;span style=&#34;color:#40a070&#34;&gt;61796348&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;44194416&lt;/span&gt;  &lt;span style=&#34;color:#40a070&#34;&gt;14439820&lt;/span&gt;  76% /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;is-the-disk-ok&#34;&gt;Is the disk OK?&lt;/h2&gt;
&lt;p&gt;Next, I used &lt;code&gt;fsck&lt;/code&gt; to see if the partition was all right. It was (&lt;em&gt;phew&lt;/em&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;► sudo fsck /dev/vda1

fsck from util-linux 2.27.1
e2fsck 1.42.13 &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;17-May-2015&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
DOROOT: clean, 289048/3932160 files, 11328157/15728640 blocks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;is-the-mount-properly-configured&#34;&gt;Is the mount properly configured?&lt;/h2&gt;
&lt;p&gt;Since the partition looked OK, I wanted to see if the configuration instructions for the disk were correct so I asked to see a listing of the relevant file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;► cat /etc/fstab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And &lt;em&gt;eureka&lt;/em&gt;, it looks like the upgrade changed the configuration to use a UUID:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# / was on /dev/vda1 during installation&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;UUID&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;815063a9-c956-44a6-ab11-05e1d0bb3a58 /  ext4  &lt;span style=&#34;color:#bb60d5&#34;&gt;errors&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;remount-ro  &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;  &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the question was, of course, is the &lt;code&gt;UUID&lt;/code&gt; correct? So I checked what the &lt;code&gt;UUID&lt;/code&gt; of &lt;code&gt;/dev/vda1&lt;/code&gt; was:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;► ls -l /dev/disk/by-uuid/ | grep vda1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which resulted in:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;lrwxrwxrwx &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; root root &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt; Oct &lt;span style=&#34;color:#40a070&#34;&gt;24&lt;/span&gt; 04:26 88f2ec31-89b7-4164-8fb8-c7736b549505 -&amp;gt; ../../vda1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, there was our culprit. The &lt;code&gt;UUID&lt;/code&gt;s did not match.&lt;/p&gt;
&lt;h2 id=&#34;lets-fix-it&#34;&gt;Let’s fix it!&lt;/h2&gt;
&lt;p&gt;Since things were working previously with the disk specified as &lt;code&gt;/dev/vda1&lt;/code&gt; instead of via &lt;code&gt;UUID&lt;/code&gt;, I thought I’d just edit the &lt;code&gt;fstab&lt;/code&gt; file and be done with it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;► sudo nano /etc/fstab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But not so fast:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;Unable to create directory /root/.nano: Read-only file system
It is required &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; saving/loading search &lt;span style=&#34;color:#007020&#34;&gt;history&lt;/span&gt; or cursor positions.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Doh!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Of course, the whole issue is that the file system is read-only so how am I going to update the configuration file.&lt;/p&gt;
&lt;h2 id=&#34;remount-the-drive&#34;&gt;Remount the drive&lt;/h2&gt;
&lt;p&gt;Again, since things were working properly and there didn’t seem to be a file system corruption, I thought I’d just try remounting the drive using a correct identifier:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;mount -o remount,rw /dev/vda1 /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that worked!&lt;/p&gt;
&lt;p&gt;So I then updated the mounting configuration in &lt;code&gt;/etc/fstab&lt;/code&gt; to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;/dev/vda1 /  ext4  &lt;span style=&#34;color:#bb60d5&#34;&gt;errors&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;remount-ro  &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;  &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;turn-it-off-and-back-on-again&#34;&gt;Turn it off and back on again&lt;/h2&gt;
&lt;p&gt;Finally, I rebooted.&lt;/p&gt;
&lt;p&gt;And now I once again have a properly mounted, read-write root partition.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://askubuntu.com/questions/807847/upgrading-to-16-04-lts-results-in-read-only-file-system-and-can-not-start-deskto&#34;&gt;Upgrading to 16.04 LTS results in read only file system and can not start desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://unix.stackexchange.com/questions/185026/how-to-edit-etc-fstab-when-system-boots-to-read-only-file-system&#34;&gt;How to edit /etc/fstab when system boots to read only file system?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://unix.stackexchange.com/questions/442926/what-is-correct-fstab-line-for-root-file-system-in-ubuntu-16-04&#34;&gt;What is correct fstab line for root file system in Ubuntu 16.04?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>How to migrate from VSCode to VSCodium (the best code editor ever minus the corporate bullshit)</title>
      <link>https://ar.al/2019/10/24/how-to-migrate-from-vscode-to-vscodium-the-best-code-editor-ever-minus-the-corporate-bullshit/</link>
      <pubDate>Thu, 24 Oct 2019 09:45:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/24/how-to-migrate-from-vscode-to-vscodium-the-best-code-editor-ever-minus-the-corporate-bullshit/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/24/how-to-migrate-from-vscode-to-vscodium-the-best-code-editor-ever-minus-the-corporate-bullshit/writing-this-post-in-vscodium.jpeg&#34;
         alt=&#34;Screenshot of this post as I write it in VSCodium.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Blogception: a post on VSCodium as it’s being written in VSCodium.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I am writing this blog post in &lt;a href=&#34;https://github.com/VSCodium/&#34;&gt;VSCodium&lt;/a&gt;. What? Is that like VSCode?&lt;/p&gt;
&lt;p&gt;Yes, it’s basically VSCode minus the corporate bullshit like surveillance and proprietary-licensed binaries.&lt;/p&gt;
&lt;h2 id=&#34;an-ode-to-vscode&#34;&gt;An ode to VSCode&lt;/h2&gt;
&lt;p&gt;VSCode is the best code editor I’ve ever used.&lt;/p&gt;
&lt;p&gt;It’s actually rather delightful.&lt;/p&gt;
&lt;p&gt;There, I’ve said it – and I’ve used a lot of editors across 30+ years of programming.&lt;/p&gt;
&lt;h2 id=&#34;vs-what&#34;&gt;VS-what?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/24/how-to-migrate-from-vscode-to-vscodium-the-best-code-editor-ever-minus-the-corporate-bullshit/vscode-logo.jpeg&#34;
         alt=&#34;Screenshot of the VSCode logo&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;VSCode: it’s not just for code (ok, it is).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For those of you who are not programmers (yet!) or have been living under a rock, VSCode is an open source code editor from Microsoft.&lt;/p&gt;
&lt;p&gt;I know, I was shocked too.&lt;/p&gt;
&lt;h2 id=&#34;enter-the-bullshit&#34;&gt;Enter, the bullshit&lt;/h2&gt;
&lt;p&gt;Even though it’s awesome, Microsoft is still Microsoft so – of course – it comes with telemetry (read: surveillance) by default. So while I love using it, I had to qualify every recommendation to others with “and remember to turn telemetry off.”&lt;/p&gt;
&lt;p&gt;Well, no longer!&lt;/p&gt;
&lt;h2 id=&#34;beyond-the-bullshit&#34;&gt;Beyond the bullshit&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://mastodon.ar.al/@aral/102999641354210259&#34;&gt;When I mentioned VSCode recently&lt;/a&gt; on &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;my Mastodon&lt;/a&gt;, janči responded with the following toot:&lt;/p&gt;
&lt;iframe src=&#34;https://mstdn.io/@janci/103001544884674786/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;
&lt;p&gt;It sounded great so I decided to check it out and I’m very glad I did (thanks, janči).&lt;/p&gt;
&lt;h2 id=&#34;enter-vscodium&#34;&gt;Enter VSCodium&lt;/h2&gt;
&lt;!-- Bye, bye validation. Oh, well… --&gt;
&lt;style&gt;img[src~=&#34;vscodium.jpeg&#34;] { border: 2px solid #333; }&lt;/style&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/24/how-to-migrate-from-vscode-to-vscodium-the-best-code-editor-ever-minus-the-corporate-bullshit/vscodium.jpeg&#34;
         alt=&#34;Screenshot of the VSCodium page on GitHub showing the VSCodium logo – looks like a blue coral or mycelium of some sort – and reads “VSCodium: Free/Libre Open Source Software Binaries of VSCode”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The VSCodium project page on Microsoft GitHub.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So it turns out that some enterprising freedom-lovers created a project called VSCodium that takes the MIT-licensed source code, removes the telemetry (read: surveillance) from the codebase (along with the branding&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;) and licenses the resulting binaries under the MIT license, just like the code itself&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;How yummy!&lt;/p&gt;
&lt;h2 id=&#34;bonus-auto-updates&#34;&gt;Bonus: auto updates!&lt;/h2&gt;
&lt;p&gt;Also, given that my main development machine is a laptop running Linux (&lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;), I had to keep installing VSCode updates by hand as Microsoft does not make automatic updates available for my platform. VSCodium fixes that for me as &lt;a href=&#34;https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo#how-to-install-for-debianubuntulinux-mint&#34;&gt;they have an apt repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;shut-up-and-dont-take-my-money&#34;&gt;Shut up and don’t take my money!&lt;/h2&gt;
&lt;p&gt;So how does one install VSCodium? Also, given there’s a high chance you’re already running VSCode (because, like, who isn’t, amirite?), how do you migrate your extensions and other settings to it?&lt;/p&gt;
&lt;p&gt;Easy peasy!&lt;/p&gt;
&lt;p&gt;If you’re on a Debian-derived operating system, you can just copy/paste the following commands and be up and running in the next few seconds.&lt;/p&gt;
&lt;p&gt;For other platforms, it’s just as easy if not easier (&lt;a href=&#34;https://github.com/VSCodium/vscodium#downloadinstall&#34;&gt;read the download/install instructions&lt;/a&gt; and the &lt;a href=&#34;https://github.com/VSCodium/vscodium/blob/master/DOCS.md#migrating-from-visual-studio-code-to-vscodium&#34;&gt;migrating from Visual Studio Code to VSCodium&lt;/a&gt; section of the documentation for details).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h2 id=&#34;install-vscodium-with-auto-updates&#34;&gt;Install VSCodium with auto updates&lt;/h2&gt;
&lt;p&gt;On Debian-derived operating systems that have the &lt;code&gt;apt&lt;/code&gt; package manager:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Authorise Paul Carroty’s repository.&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (https://twitter.com/paulcarroty)&lt;/span&gt;
wget -qO - https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg | sudo apt-key add -

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Add Paul’s custom apt repository to your apt sources list.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;deb https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/repos/debs/ vscodium main&amp;#39;&lt;/span&gt; | sudo tee --append /etc/apt/sources.list

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Update your apt package list and install VSCodium.&lt;/span&gt;
sudo apt update
sudo apt install codium&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;copy-your-extensions-and-settings-over-from-vscode-to-vscodium&#34;&gt;Copy your extensions and settings over from VSCode to VSCodium.&lt;/h2&gt;
&lt;p&gt;On Linux:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the folder to host your extensions.&lt;/span&gt;
mkdir -p ~/.vscode-oss/

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Copy your extensions over from VSCode.&lt;/span&gt;
cp -R ~/.vscode/extensions ~/.vscode-oss/

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the folder to hold your settings.&lt;/span&gt;
mkdir -p &lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;/.config/VSCodium/

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Copy your settings over from VSCode.&lt;/span&gt;
cp -R &lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;/.config/Code/User &lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;/.config/VSCodium/User/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After you’ve followed the above instructions – or those for your own platform – you should be able to launch VSCodium by just typing &lt;code&gt;codium&lt;/code&gt; in Terminal&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;need-something-to-code&#34;&gt;Need something to code?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/24/how-to-migrate-from-vscode-to-vscodium-the-best-code-editor-ever-minus-the-corporate-bullshit/../../18/site.js-and-pi/site-js-chat-on-raspberry-pi-1.jpeg&#34;
         alt=&#34;Screenshot of the Site.js basic chat example running on a Raspberry Pi 4B.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;According to sources within Number 10, a chat app with Site.js is the perfect thing to code using VSCodium.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I hope you enjoy using VSCodium as much as I do. And hey, if you’re looking for something fun to code with it, &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;check out my recent tutorial&lt;/a&gt; on building a WebSocket-based chat app with &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&#34;https://code.visualstudio.com/docs/remote/ssh&#34;&gt;the remote development feature in VSCode&lt;/a&gt;, you can even &lt;a href=&#34;https://ar.al/2019/10/18/site.js-and-pi/&#34;&gt;do it on a Raspberry Pi&lt;/a&gt; (yes, &lt;a href=&#34;https://ar.al/2019/10/22/the-little-raspberry-pi-that-could-serve-a-web-site/&#34;&gt;even on a Raspberry Pi Zero!&lt;/a&gt;).&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;VSCodium couldn’t keep the branding even if it wanted to as they would be violating Microsoft’s trademarks. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The VSCode binaries carry a proprietary license. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Pop!_OS is built on Ubuntu. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;To launch VSCodium using the same shortcut for VSCode, just alias it in your shell configuration file (e.g., &lt;code&gt;~/.zshrc&lt;/code&gt; for zsh&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;). &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You haven’t lived until you’ve tried &lt;a href=&#34;https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH&#34;&gt;zsh&lt;/a&gt; with &lt;a href=&#34;https://ohmyz.sh/&#34;&gt;oh-my-zsh&lt;/a&gt; and the &lt;a href=&#34;https://github.com/agnoster/agnoster-zsh-theme&#34;&gt;agnoster theme&lt;/a&gt; with &lt;a href=&#34;https://github.com/powerline/fonts&#34;&gt;powerline fonts&lt;/a&gt; in &lt;a href=&#34;https://gnunn1.github.io/tilix-web/&#34;&gt;tilix&lt;/a&gt;… &lt;em&gt;just sayin’&lt;/em&gt;. PS. To install the powerline fonts on a Debian-derived Linux distribution, just use &lt;code&gt;apt install fonts-powerline&lt;/code&gt;, and to activate the agnoster theme, set &lt;code&gt;ZSH_THEME=&amp;quot;agnoster&amp;quot;&lt;/code&gt; in your &lt;code&gt;~/.zshrc&lt;/code&gt;. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>The little Raspberry Pi that could (serve a web site)</title>
      <link>https://ar.al/2019/10/22/the-little-raspberry-pi-that-could-serve-a-web-site/</link>
      <pubDate>Tue, 22 Oct 2019 18:08:06 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/22/the-little-raspberry-pi-that-could-serve-a-web-site/</guid>
      <description>&lt;p&gt;Yesterday, I asked folks following me &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;on my Mastodon&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, if they’d help me blow up my &lt;a href=&#34;https://magpi.raspberrypi.org/articles/pi-zero-w&#34;&gt;Raspberry Pi Zero W&lt;/a&gt;:&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/103001447721807970/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;h2 id=&#34;a-story-in-three-toots&#34;&gt;A story in three toots…&lt;/h2&gt;
&lt;p&gt;Earlier this week, I got to test &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; on the Pi Zero and saw that it runs flawlessly. Using &lt;a href=&#34;https://ngrok.com&#34;&gt;ngrok&lt;/a&gt;, I exposed the Pi to the harsh and cruel Interwebs and then – for the fun of it – decided to see what would happen if lots of folks hit it at the same time. And…&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/103001381563324542/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;h2 id=&#34;oh-the-suspense&#34;&gt;Oh, the suspense…&lt;/h2&gt;
&lt;p&gt;Turns out, the little thing is a trooper.&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/103001463882277099/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;h2 id=&#34;the-little-pi-that-could&#34;&gt;The little Pi that could&lt;/h2&gt;
&lt;p&gt;During our little experiement, Site.js on the Pi Zero served over 7,000 requests. And since I made the ephemeral statistics URL public, folks creatively started using it to send me messages via 404 errors.&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/103001652098940317/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34; allowfullscreen=&#34;allowfullscreen&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;  
&lt;h2 id=&#34;wait-whats-sitejs-again&#34;&gt;Wait, what’s Site.js again?&lt;/h2&gt;
&lt;p&gt;Site.js is a free and open source web tool for developers. I would argue that is the easiest way to develop, test, and deploy secure static and dynamic web sites. Check out &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;this tutorial&lt;/a&gt; to get started and view the documentation if you want to learn every little detail.&lt;/p&gt;
&lt;p&gt;It’s the first piece of &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;the bridge we’re building&lt;/a&gt; at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt; between &lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;the centralised surveillance-based exploitative web we have&lt;/a&gt; and &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;the peer-to-peer ethical web we want&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Site.js runs on Linux, macOS, and Windows (and, of course, Raspberry Pis&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;Why not &lt;a href=&#34;https://sitejs.org&#34;&gt;have a play?&lt;/a&gt;&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I also asked on &lt;em&gt;(spit)&lt;/em&gt; &lt;a href=&#34;https://twitter.com/aral&#34;&gt;the @aral Twitter account&lt;/a&gt;. The difference is that while I own and control my own Mastodon instance, Twitter, Inc., owns and controls my account on Twitter.com. On my Mastodon, I see everything that everyone sends me and I decide what I say and how (and I’m still bound my the laws of the jurisdiction that I live in and I don’t have the right to compel anyone to listen to me – a common misconception of free speech advocates). On Twitter.com, I see what their algorithms show me and I am able to say what Twitter, Inc. deems acceptable. For folks in Turkey, for example, that means &lt;a href=&#34;https://twitter.com/aral/status/1176767399825235968&#34;&gt;they don’t see what the Turkish government doesn’t want them to see&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Which run Linux on ARM. I’ve tested Site.js on the 3B+, 4B, and Zero W models. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Site.js: now easier than ever to update</title>
      <link>https://ar.al/2019/10/21/site.js-now-easier-than-ever-to-update/</link>
      <pubDate>Mon, 21 Oct 2019 11:48:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/21/site.js-now-easier-than-ever-to-update/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/21/site.js-now-easier-than-ever-to-update/site.js-updating-sitejs.org.jpeg&#34;
         alt=&#34;Screenshot of Tilix (terminal app) running on my Linux box. I’ve ssh’ed into the server that runs sitejs.org and run the new update command. The output shows that the server was seamlessly updated and restarted.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Earlier today: the new update command seamlessly updating and restarting Site.js on SiteJS.org.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I just released &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; version 12.9.5. This version brings with it some new commands, the most important of which is the &lt;code&gt;update&lt;/code&gt; command&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Update Site.js to the latest version.&lt;/span&gt;
site update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;update&lt;/code&gt; command does what it says on the tin and updates your copy of Site.js to the latest version.&lt;/p&gt;
&lt;p&gt;If &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#production&#34;&gt;Site.js is running as a daemon&lt;/a&gt;, it is seamlessly restarted for you.&lt;/p&gt;
&lt;p&gt;If you’re running instances of Site.js as regular processes, they will continue to run the older version until the next time you run the &lt;code&gt;site&lt;/code&gt; command.&lt;/p&gt;
&lt;h2 id=&#34;better-daemon-control&#34;&gt;Better daemon control&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/21/site.js-now-easier-than-ever-to-update/site.js-help.jpeg&#34;
         alt=&#34;Screenshot of the Site.js help command output in terminal (Tilix).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The help command is a good way to discover what Site.js is capable of.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Until version 12.9.5, you were limited in your control over a Site.js daemon to the &lt;code&gt;enable&lt;/code&gt; and &lt;code&gt;disable&lt;/code&gt; commands.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;enable&lt;/code&gt; command both installs the &lt;a href=&#34;https://freedesktop.org/wiki/Software/systemd/&#34;&gt;systemd&lt;/a&gt; unit and starts the service and the &lt;code&gt;disable&lt;/code&gt; command both stops the service and uninstalls it.&lt;/p&gt;
&lt;p&gt;Today, you get three new commands – &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, and &lt;code&gt;restart&lt;/code&gt; – that enable you to control the status of the service without affecting whether it is installed or not.&lt;/p&gt;
&lt;h2 id=&#34;learn-sitejs-the-fun-way&#34;&gt;Learn Site.js the fun way&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/21/site.js-now-easier-than-ever-to-update/../../18/site.js-and-pi/site-js-chat-on-raspberry-pi-1.jpeg&#34;
         alt=&#34;Screenshot of the Site.js basic chat example running on a Raspberry Pi 4B.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Learn about Site.js while building a chat app… you can even do it on a Raspberry Pi if you like!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you’re wondering what Site.js is and what you can do with it, the easiest way to get started is &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;a tutorial I wrote this month&lt;/a&gt; that takes you step-by-step through building a simple WebSocket-based chat app. And &lt;a href=&#34;https://ar.al/2019/10/18/site.js-and-pi/&#34;&gt;since Site.js version 12.8.0&lt;/a&gt;, you can even follow along on your Raspberry Pi if you have one.&lt;/p&gt;
&lt;p&gt;Here are links to both resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;Build a simple chat app with Site.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/10/18/site.js-and-pi/&#34;&gt;Site.js and Pi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;sitejs-is-cross-platform&#34;&gt;Site.js is cross-platform&lt;/h2&gt;
&lt;p&gt;Site.js runs on Linux (x64 and ARM&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;), macOS, and Windows 10 so grab your computer, fire up Terminal and your favourite editor and discover the easiest way to create, test, and host your own static and dynamic web sites.&lt;/p&gt;
&lt;p&gt;As always, please do &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;let me know how you get on&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Given that the update command is released for the first time in version 12.9.5, you will have to wait for the next release before you can use it to actually update your version of Site.js. To update to version 12.9.5, please use the install command from &lt;a href=&#34;https://sitejs.org&#34;&gt;SiteJS.org&lt;/a&gt; as before. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Tested on my Raspberry Pi 3B+ and 4B. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Scary git-lfs “bad object” error not so scary after all</title>
      <link>https://ar.al/2019/10/19/scary-git-lfs-bad-object-error-not-so-scary-after-all/</link>
      <pubDate>Sat, 19 Oct 2019 18:00:53 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/19/scary-git-lfs-bad-object-error-not-so-scary-after-all/</guid>
      <description>&lt;p&gt;I just got the following error while trying to push to my &lt;a href=&#34;&#34;&gt;git-lfs&lt;/a&gt;-enabled repository:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;ref master:: Error in git rev-list --stdin --objects --not --remotes&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;origin --: &lt;span style=&#34;color:#007020&#34;&gt;exit&lt;/span&gt; status &lt;span style=&#34;color:#40a070&#34;&gt;128&lt;/span&gt; fatal: bad object f895c581f0ff52b2272318105ad7bcc09b5fb8eb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some quick &lt;a href=&#34;https://duckduckgo.com&#34;&gt;ducking&lt;/a&gt; revealed scary stories of repository corruptions. &lt;em&gt;Eek!&lt;/em&gt; Not what I wanted to hear.&lt;/p&gt;
&lt;p&gt;Turns out, however, that the error message is not as scary as it looks.&lt;/p&gt;
&lt;p&gt;Here’s when/why you get this scary error:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Your repository is head of master with a commit where the only changes are git-lfs changes and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your repository is actually also behind master with non-git-lfs changes (these don’t appear in &lt;code&gt;git status&lt;/code&gt; for some reason when your working copy is in this state) and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You do a &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fix, of course, is to &lt;code&gt;git pull&lt;/code&gt; and merge your non-git-lfs changes and then push again.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Whew!&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Install git-lfs on a Raspberry Pi</title>
      <link>https://ar.al/2019/10/19/install-git-lfs-on-a-raspberry-pi/</link>
      <pubDate>Sat, 19 Oct 2019 16:31:38 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/19/install-git-lfs-on-a-raspberry-pi/</guid>
      <description>&lt;h2 id=&#34;what-is-git-lfs&#34;&gt;What is git-lfs?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://git-lfs.github.com/&#34;&gt;Git Large File Storage&lt;/a&gt; is “an open source Git extension for versioning large files.” I use it on a number of &lt;a href=&#34;https://source.ind.ie&#34;&gt;our git repositories&lt;/a&gt; at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;, including for the &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js web site&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;why-pi&#34;&gt;Why Pi?&lt;/h2&gt;
&lt;p&gt;As I announced yesterday, since &lt;a href=&#34;https://ar.al/2019/10/18/site.js-and-pi/&#34;&gt;version 12.8.0 of Site.js&lt;/a&gt; with Raspberry Pi support, I’ve started building Site.js on my Raspberry Pi 3B+. And it works perfectly, except for one thing: I can’t check the release binaries in without LFS and installing git-lfs on a Raspberry Pi wasn’t entirely straightforward.&lt;/p&gt;
&lt;h2 id=&#34;how-to&#34;&gt;How-to&lt;/h2&gt;
&lt;p&gt;Here’s how you can install git-lfs on your Raspberry Pi:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Download the latest armv6l version of go (as of this writing, 1.13.3)&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# You can check for the latest version here: https://golang.org/dl/&lt;/span&gt;
wget https://dl.google.com/go/go1.13.3.linux-armv6l.tar.gz

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Unarchive it.&lt;/span&gt;
sudo tar -C /usr/local -xzf go1.13.3.linux-armv6l.tar.gz

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Add the binary path to your system path.&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (Add this to your .bashrc or .zshrc if you want to persist it across reboots.)&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;PATH&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$PATH&lt;/span&gt;:/usr/local/go/bin

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create and export your go path.&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (Again, add it to your shell configuration file if you want to persist it.)&lt;/span&gt;
mkdir ~/go
&lt;span style=&#34;color:#007020&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;GOPATH&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;~/go

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Download and compile git-lfs.&lt;/span&gt;
go get github.com/github/git-lfs

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Add the compiled binary to your path.&lt;/span&gt;
sudo cp ~/go/bin/git-lfs /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;test-it-out&#34;&gt;Test it out!&lt;/h2&gt;
&lt;p&gt;You should now be able to use git-lfs as usual. To test it out, &lt;code&gt;cd&lt;/code&gt; into the working copy of the project you want to use git-lfs on and enter the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;git lfs install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If all goes well, you should see output resembling the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;Updated git hooks.
Git LFS initialized.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;Gist: &lt;a href=&#34;https://gist.github.com/marcelitocs/19bdfd22befecbe0d3eef3271260e528&#34;&gt;Install Golang 1.8 on Raspberry Pi&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Site.js and Pi</title>
      <link>https://ar.al/2019/10/18/site.js-and-pi/</link>
      <pubDate>Fri, 18 Oct 2019 11:47:08 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/18/site.js-and-pi/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/18/site.js-and-pi/site-js-chat-on-raspberry-pi-1.jpeg&#34;
         alt=&#34;Screenshot of the Site.js basic chat example running on a Raspberry Pi 4B.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Chatting about Pi, on a Pi, with a chat server running on Site.js on the same Pi.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Yesterday, I released &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; 12.8.0 which brings initial ARM support for Linux.&lt;/p&gt;
&lt;p&gt;What that means is that it’s now easier than ever to get a static or dynamic (Node.js) web server up and running on a Raspberry Pi.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/18/site.js-and-pi/site-js-chat-on-raspberry-pi-2.jpeg&#34;
         alt=&#34;Screenshot of Terminal on the same Raspberry Pi 4B as before, showing Site.js serving the basic chat app.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Site.js on my Raspberry Pi, serving the basic chat app in the first screenshot.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;have-a-play&#34;&gt;Have a play!&lt;/h2&gt;
&lt;p&gt;Do you have a Raspberry Pi 3B+ or 4B lying around?&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; Well then, grab it, fire up a terminal window, and get your first static site up and running in the next 30 seconds:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install Site.js.&lt;/span&gt;
wget -qO- https://sitejs.org/install | bash

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the most basic web site ever.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;&#34;&gt;&amp;#39;&lt;/span&gt;Hello, world! &amp;gt; index.html

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Run Site.js&lt;/span&gt;
site&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now launch a browser&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; on your Raspberry Pi and hit &lt;code&gt;https://localhost&lt;/code&gt; in it.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/10/18/site.js-and-pi/pi-4b.jpeg&#34;
         alt=&#34;Photo of the Raspberry Pi 4B running the chat example in the first screenshot.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My Raspberry Pi 4B, caught red-handed running the chat app in the first screenshot.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;please-sir-may-i-have-some-more&#34;&gt;Please sir, may I have some more?&lt;/h2&gt;
&lt;p&gt;But, of course!&lt;/p&gt;
&lt;p&gt;If this has whet your appetite, so to speak, head over to the &lt;a href=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/&#34;&gt;Build a simple chat app with Site.js&lt;/a&gt; tutorial where you will learn lots more about making dynamic web sites with Site.js using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#dotjs&#34;&gt;DotJS&lt;/a&gt; (Node.js made simple, ala PHP) as you create the WebSocket-based chat example you see in the first screenshot.&lt;/p&gt;
&lt;p&gt;Have fun and &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;do let me know&lt;/a&gt; how you get on.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Those are the only ones I tested with so far. It might work on other ones too. Do &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;let me know&lt;/a&gt; if you try it out. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For example, type &lt;code&gt;epiphany&lt;/code&gt; in a terminal window to launch the Epiphany browser – aka GNOME Web – that comes with Raspbian but isn’t available from the raspberry menu under Internet for some inexplicable reason. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Build a simple chat app with Site.js</title>
      <link>https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/</link>
      <pubDate>Fri, 11 Oct 2019 17:59:42 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/</guid>
      <description>&lt;div id=&#39;final-version&#39;&gt;
  &lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
    &lt;div class=&#39;chat-interface&#39;&gt;
      &lt;h1&gt;Chat room&lt;/h1&gt;
      &lt;p&gt;Status: &lt;span id=&#39;final-version-status&#39; style=&#34;color: red;&#34;&gt;Offline&lt;/span&gt;&lt;/p&gt;
      &lt;form id=&#39;final-version-message-form&#39;&gt;
        &lt;label for=&#39;final-version-nickname&#39;&gt;Nickname:&lt;/label&gt;
        &lt;input id=&#39;final-version-nickname&#39; name=&#39;nickname&#39; value=&#39;Anonymous&#39;&gt;
        &lt;label for=&#39;final-version-message&#39;&gt;Message:&lt;/label&gt;
        &lt;input id=&#39;final-version-message&#39; name=&#39;message&#39; value=&#39;&#39;&gt;
        &lt;button id=&#39;final-version-send-button&#39; type=&#39;submit&#39;&gt;Send&lt;/button&gt;
      &lt;/form&gt;
      &lt;h2&gt;Messages&lt;/h2&gt;
      &lt;ul class=&#39;messages&#39; id=&#39;final-version-messages&#39;&gt;&lt;/ul&gt;
    &lt;/div&gt;
    &lt;script&gt;
      // Shorthand for basic DOM lookup via CSS selectors.
      const element = document.querySelector.bind(document)

      // Display a message in the messages list and ensure
      // that the list always shows the latest messages.
      function finalVersionDisplayMessage (message) {
        const nickname = `&lt;strong&gt;${message.nickname}:&lt;/strong&gt;`
        const text = message.text
        const messageHTML = `&lt;li&gt;${nickname} ${text}&lt;/li&gt;`

        // Update the message list.
        const messageList = element(&#39;#final-version-messages&#39;)
        messageList.innerHTML += messageHTML
        messageList.scrollTop = messageList.scrollHeight
      }

      // Is the passed object a valid string?
      function finalVersionIsValidString(s) {
        return Boolean(s)                // Not null, undefined, &#39;&#39;, or 0
          &amp;&amp; typeof s === &#39;string&#39;       // and is the correct type
          &amp;&amp; s.replace(/\s/g, &#39;&#39;) !== &#39;&#39; // and is not just whitespace.
      }

      // Disables the Send button if the form isn’t valid.
      function finalVersionValidateForm () {
        const nicknameIsValid = finalVersionIsValidString(element(&#39;#final-version-nickname&#39;).value)
        const messageIsValid = finalVersionIsValidString(element(&#39;#final-version-message&#39;).value)
        const formIsValid = nicknameIsValid &amp;&amp; messageIsValid

        element(&#39;#final-version-send-button&#39;).disabled = !formIsValid
      }

      // Initialise web socket.
      const finalVersionSocket = new WebSocket(
        `wss://${window.location.hostname}/chat-final-version`
      )

      // Display the state of the connection.
      finalVersionSocket.onopen = _ =&gt; {
        element(&#39;#final-version-status&#39;).innerHTML = &#39;&lt;span style=&#34;color: green&#34;&gt;Online&lt;/span&gt;&#39;
      }

      finalVersionSocket.onclose = _ =&gt; {
        element(&#39;#final-version-status&#39;).innerHTML = &#39;Offline&#39;
      }

      // Validate the form whenever the nickname or message changes.
      element(&#39;#final-version-nickname&#39;).addEventListener(&#39;input&#39;, finalVersionValidateForm)
      element(&#39;#final-version-message&#39;).addEventListener(&#39;input&#39;, finalVersionValidateForm)

      // Set initial focus and selection.
      element(&#39;#final-version-nickname&#39;).focus()
      element(&#39;#final-version-nickname&#39;).select()

      // Validate the form when the app first loads.
      finalVersionValidateForm()

      // Handle message sending.
      element(&#39;#final-version-message-form&#39;).addEventListener(&#39;submit&#39;, event =&gt; {
        // Prevent the form from being submitted.
        event.preventDefault()

        // Get the nickname and text.
        const nickname = element(&#39;#final-version-nickname&#39;).value
        const text = element(&#39;#final-version-message&#39;).value

        // Clear the message text field.
        element(&#39;#final-version-message&#39;).value = &#39;&#39;

        // Focus the message text field.
        element(&#39;#final-version-message&#39;).focus()

        // Validate the form.
        finalVersionValidateForm()

        // Create a message object, serialise it as JSON &amp; send it.
        const message = { nickname, text }
        finalVersionSocket.send(JSON.stringify(message))

        // Update the local display
        finalVersionDisplayMessage(message)
      })

      // Handle incoming messages.
      finalVersionSocket.onmessage = message =&gt; {
        // Deserialise the message string and display it.
        message = JSON.parse(message.data)
        finalVersionDisplayMessage(message)
      }
    &lt;/script&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;The chat app we’re going to build together. (It’s live… open another browser window or hit this page from a different device to see it in action!)&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;/div&gt;
&lt;h2 id=&#34;we-need-to-talk-about-sitejs&#34;&gt;We need to talk about Site.js&lt;/h2&gt;
&lt;p&gt;This weekend, I released &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; version 12.7.0 with improvements to &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#websocket-wss-routes&#34;&gt;its WebSocket interface&lt;/a&gt;. Today, I want to take you step-by-step through building and running a basic chat app using Site.js.&lt;/p&gt;
&lt;p&gt;It’s much easier than you think, so fire up a terminal window, grab your code editor, and let’s get started!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h2 id=&#34;install-sitejs&#34;&gt;Install Site.js&lt;/h2&gt;
&lt;p&gt;If you don’t already have Site.js, you first need to install it.&lt;/p&gt;

&lt;p&gt;Copy and paste the following command into &lt;span id=&#39;terminal-copy&#39;&gt;your terminal&lt;/span&gt;. &lt;strong&gt;Before you pipe any script into your computer, always &lt;span id=&#39;view-installation-script-source&#39;&gt;view the source code (&lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install&#34;&gt;Linux and macOS&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install.txt&#34;&gt;Windows&lt;/a&gt;)&lt;/span&gt; and make sure you understand what it does.&lt;/strong&gt;&lt;/p&gt;

&lt;section id=&#39;install-linux&#39; class=&#39;installation-instructions&#39;&gt;
  &lt;h3&gt;Linux&lt;/h3&gt;
  &lt;div class=&#39;code-with-copy-button&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;wget -qO- https://sitejs.org/install | bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;button hidden class=&#39;copy-to-clipboard&#39; onclick=&#39;copyInstallationInstructionsToClipboardFor(&#34;linux&#34;)&#39;&gt;Copy&lt;/button&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;section id=&#39;install-mac&#39; class=&#39;installation-instructions&#39;&gt;
  &lt;h3&gt;macOS&lt;/h3&gt;
  &lt;div class=&#39;code-with-copy-button&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;curl -s https://sitejs.org/install | bash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;button hidden class=&#39;copy-to-clipboard&#39; onclick=&#39;copyInstallationInstructionsToClipboardFor(&#34;mac&#34;)&#39;&gt;Copy&lt;/button&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;section id=&#39;install-windows&#39; class=&#39;installation-instructions&#39;&gt;
  &lt;h3&gt;Windows 10&lt;/h3&gt;
  &lt;div class=&#39;code-with-copy-button&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PowerShell&#34; data-lang=&#34;PowerShell&#34;&gt;iex(&lt;span style=&#34;color:#007020&#34;&gt;iwr &lt;/span&gt;-UseBasicParsing https&lt;span style=&#34;&#34;&gt;:&lt;/span&gt;//sitejs.org/install.txt).Content&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;button hidden class=&#39;copy-to-clipboard&#39; onclick=&#39;copyInstallationInstructionsToClipboardFor(&#34;windows&#34;)&#39;&gt;Copy&lt;/button&gt;
  &lt;/div&gt;
&lt;/section&gt;

&lt;p id=&#39;links-to-instructions-for-other-platforms&#39; hidden&gt;
  &lt;strong&gt;Instructions for other platforms:&lt;/strong&gt;
  &lt;span id=&#39;link-linux&#39;&gt;&lt;a onclick=&#39;displayInstallationInstructionsFor(&#34;linux&#34;)&#39;&gt;Linux&lt;/a&gt; | &lt;/span&gt;
  &lt;span id=&#39;link-mac&#39;&gt;&lt;a onclick=&#39;displayInstallationInstructionsFor(&#34;mac&#34;)&#39;&gt;macOS&lt;/a&gt;&lt;span id=&#39;pipe-before-windows&#39;&gt; | &lt;/span&gt;&lt;/span&gt;
  &lt;a id=&#39;link-windows&#39; onclick=&#39;displayInstallationInstructionsFor(&#34;windows&#34;)&#39;&gt;Windows&lt;/a&gt;
&lt;/p&gt;


&lt;script&gt;
  
  
  const userAgent = navigator.userAgent.toLowerCase()
  let currentPlatform = &#39;unknown&#39;
  if (userAgent.includes(&#39;linux&#39;)) { currentPlatform = &#39;linux&#39; }
  if (userAgent.includes(&#39;mac os x&#39;)) { currentPlatform = &#39;mac&#39; }
  if (userAgent.includes(&#39;windows&#39;)) { currentPlatform = &#39;windows&#39;}

  displayInstallationInstructionsFor(currentPlatform)

  function displayInstallationInstructionsFor(currentPlatform) {
    if (currentPlatform !== &#39;unknown&#39;) {
      
      [&#39;linux&#39;, &#39;mac&#39;, &#39;windows&#39;].forEach(platform =&gt; {
        document.querySelector(`#install-${platform}`).hidden = !(platform === currentPlatform)
        document.querySelector(`#link-${platform}`).hidden = (platform === currentPlatform)
      })

      
      document.querySelector(&#39;#pipe-before-windows&#39;).hidden = (currentPlatform === &#39;windows&#39;)

      
      document.querySelector(&#39;#links-to-instructions-for-other-platforms&#39;).hidden = false

      
      document.querySelectorAll(&#39;.copy-to-clipboard&#39;).forEach(button =&gt; button.hidden = false)

      
      
      document.querySelector(&#39;#view-installation-script-source&#39;).innerHTML = (currentPlatform === &#39;windows&#39;) ? &#39;&lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install.txt&#34;&gt;view the source code&lt;/a&gt;&#39; : &#39;&lt;a href=&#34;https://source.ind.ie/site.js/site/blob/master/installation-scripts/install&#34;&gt;view the source code&lt;/a&gt;&#39;

      
      
      document.querySelector(&#39;#terminal-copy&#39;).innerHTML = (currentPlatform === &#39;windows&#39;) ? &#39;a PowerShell session running under &lt;a href=&#34;https://github.com/Microsoft/Terminal&#34;&gt;Windows Terminal&lt;/a&gt;&#39; : &#39;your terminal&#39;

      
      document.querySelectorAll(&#39;.windows-only&#39;).forEach(node =&gt; node.hidden = !(currentPlatform === &#39;windows&#39;))
    }
  }

  function copyInstallationInstructionsToClipboardFor(platform) {
    const installationCommand = document.querySelector(`section#install-${platform} code`)

    
    
    window.getSelection().removeAllRanges()

    const selectedCode = document.createRange()
    selectedCode.selectNode(installationCommand)
    window.getSelection().addRange(selectedCode)

    try {
      const success = document.execCommand(&#39;copy&#39;)
      if (!success) console.log(&#39;Failed to copy installation command.&#39;)
    } catch(error) {
      console.log(&#39;Copy command threw an error&#39;, error)
    }

    
    
    window.getSelection().removeRange(selectedCode)
  }
&lt;/script&gt;

&lt;p&gt;The download and installation should take about 10-15 seconds depending on the speed of your Internet connection.&lt;/p&gt;
&lt;p&gt;Unless otherwise stated, the remaining instructions should work verbatim across Linux, macOS, and Windows 10.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;test-it&#34;&gt;Test it&lt;/h2&gt;
&lt;p&gt;In your terminal&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, create a simple static “Hello, world!” web page and serve it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a folder to work in and switch to it.&lt;/span&gt;
mkdir demo
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; demo

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the simplest possible “web page”.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello, world&amp;#39;&lt;/span&gt; &amp;gt; index.html

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Run Site.js to serve the site.&lt;/span&gt;
site
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To test that it’s working, fire up your browser of choice and visit &lt;code&gt;https://localhost&lt;/code&gt;&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost/&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;p&gt;Hello, world!&lt;/p&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;A very simple static page.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;A couple of things to note:&lt;/p&gt;
&lt;h3 id=&#34;you-didnt-have-to-configure-anything-it-just-worked&#34;&gt;You didn’t have to configure anything, it just worked.&lt;/h3&gt;
&lt;p&gt;Site.js is zero-configuration. It favours intelligent defaults over silly ones that leave you to do all the work.&lt;/p&gt;
&lt;h3 id=&#34;you-didnt-get-a-certificate-warning-in-the-browser&#34;&gt;You didn’t get a certificate warning in the browser.&lt;/h3&gt;
&lt;p&gt;Site.js uses the excellent &lt;a href=&#34;https://github.com/FiloSottile/mkcert&#34;&gt;mkcert&lt;/a&gt; tool to seamlessly create a certificate authority on your local machine and issue you a valid TLS certificate the first time you run a server at localhost.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&#34;you-have-access-to-very-basic-ephemeral-statistics&#34;&gt;You have access to very basic, ephemeral statistics.&lt;/h3&gt;
&lt;p&gt;If you look at the terminal window, you will see an address you can hit in your browser to see basic statistics about your site.&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;terminal&#34; style=&#34;display: block; position: relative;&#34; data-window-title=&#34;Terminal: ~/demo&#34;&gt;
  

  &lt;div class=&#34;terminal-content&#34;&gt;
    &lt;pre&gt;&lt;code&gt;📊 For statistics, see https://localhost/909b721d634e89c44754cc036fb379e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;Your statistics URL is cryptographically secure.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;This is &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/lib/Stats.js#L48&#34;&gt;a cryptographically secure address&lt;/a&gt; that others cannot guess. So your statistics are initially private. If you want to share them with the world, just share the URL.&lt;/p&gt;
&lt;p&gt;Also, these statistics are not stored anywhere and will reset when you restart the server (but your statistics URL will stay the same). They are there for you to discover how your site is being used so you can improve it and to see if there are any errors like missing pages, not to let you spy on people.&lt;/p&gt;
&lt;p&gt;Once you’re done testing your shiny new site, press &lt;kbd&gt;Ctrl&lt;/kbd&gt; &lt;kbd&gt;C&lt;/kbd&gt; to stop the Site.js server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;cha-cha-cha-changes&#34;&gt;Cha-cha-cha changes!&lt;/h2&gt;
&lt;p&gt;Static sites are all well and good but you were promised a chat app and you can’t build that with a fully static site. So let’s take a quick look at how we can create dynamic apps with Site.js.&lt;/p&gt;
&lt;p&gt;I mentioned earlier that Site.js is zero-configuration. This means that it has certain conventions that it expects you to adhere to. For example, if you want to create dynamic routes in your web app, you must place them in a folder called &lt;code&gt;.dynamic&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Before we move onto creating the chat functionality, let’s create the equivalent of our static “hello, world!” example but with some very basic dynamic functionality to display the current date and time.&lt;/p&gt;
&lt;h3 id=&#34;a-timely-example&#34;&gt;A timely example&lt;/h3&gt;
&lt;p&gt;First, create a folder called &lt;code&gt;.dynamic&lt;/code&gt; within your &lt;code&gt;demo&lt;/code&gt; folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;mkdir .dynamic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, open your code editor of choice and create a file called &lt;code&gt;date.js&lt;/code&gt; in the &lt;code&gt;.dynamic&lt;/code&gt; folder. Once you’re done, your project folder hierarchy should look like this:&lt;/p&gt;
 &lt;div class=&#39;directory-hierarchy&#39;&gt;
 &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;demo/
    ├ .index.html    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# static route&lt;/span&gt;
    └ .dynamic
          └ date.js  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# dynamic route&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;p&gt;In the &lt;code&gt;date.js&lt;/code&gt; file, enter the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (request, response) =&amp;gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; now &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;().toString()
  response
    .type(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;html&amp;#39;&lt;/span&gt;)
    .end(now)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, run the &lt;code&gt;site&lt;/code&gt; command in the &lt;code&gt;demo&lt;/code&gt; folder and visit &lt;code&gt;https://localhost/date&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost/date&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;p&gt;&lt;script&gt;document.write(new Date().toString())&lt;/script&gt;&lt;/p&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;A dynamic DotJS route. Refresh the page to see it update.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;You should see the current date.&lt;/p&gt;
&lt;p&gt;Refresh and you should see the date update. Congratulations, you just created your first dynamic web site using Site.js.&lt;/p&gt;
&lt;p&gt;(Seriously.)&lt;/p&gt;
&lt;h3 id=&#34;what-magic-is-this&#34;&gt;What magic is this?&lt;/h3&gt;
&lt;p&gt;I call it &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#dotjs&#34;&gt;DotJS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;DotJS maps JavaScript modules defined in &lt;code&gt;.js&lt;/code&gt; files (see what I did there?) to web routes on your web site in a manner that will be familiar to anyone who has ever used PHP.&lt;/p&gt;
&lt;p&gt;In our example, DotJS knows that we want the file defined at &lt;code&gt;.dynamic/date.js&lt;/code&gt; to be served at the address &lt;code&gt;https://localhost/date&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we’d wanted our dynamic page to be available from &lt;code&gt;https://localhost/date-and-time&lt;/code&gt; instead, we would have defined our route in &lt;code&gt;.dynamic/date-and-time.js&lt;/code&gt;.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Using DotJS, all you have to do is write the logic for your web app. Everything else, including creating a secure HTTPS and WebSocket server for you and registering your routes, etc., is handled for you by Site.js.&lt;/p&gt;
&lt;p&gt;Furthermore, there is no magic here. Under the hood, these are simply plain old tried-and-tested &lt;a href=&#34;https://expressjs.com/&#34;&gt;Express&lt;/a&gt; routes.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; Site.js contains &lt;a href=&#34;https://nodejs.org&#34;&gt;Node.js&lt;/a&gt; so you can do anything in your dynamic routes that you can do with Node.js – including &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#using-node-modules&#34;&gt;using node modules&lt;/a&gt; – without installing Node.js.&lt;/p&gt;
&lt;p&gt;When you’re ready to move on, press &lt;kbd&gt;Ctrl&lt;/kbd&gt; &lt;kbd&gt;C&lt;/kbd&gt; to stop the Site.js server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;did-someone-mention-a-chat-app&#34;&gt;Did someone mention a chat app?&lt;/h2&gt;
&lt;p&gt;OK, OK, I know I’m taking the scenic route but I wanted to introduce you to the basic concepts of Site.js before getting to the chat app so you have a solid foundation to build on.&lt;/p&gt;
&lt;p&gt;Site.js, as you might have guessed, is not limited to HTTPS routes. You can also create secure WebSocket routes. And you can use DotJS with WebSockets also.&lt;/p&gt;
&lt;p&gt;Let’s start by creating and testing the back-end of the chat app and when we’re happy with it, we can cobble together a basic web interface for it.&lt;/p&gt;
&lt;h3 id=&#34;the-chat-server&#34;&gt;The chat server&lt;/h3&gt;
&lt;p&gt;In your &lt;code&gt;demo/.dynamic&lt;/code&gt; folder, create a new folder called &lt;code&gt;.wss&lt;/code&gt;. This is the folder Site.js expects you to place WebSocket routes in.&lt;/p&gt;
&lt;p&gt;Then, in your &lt;code&gt;.wss&lt;/code&gt; folder, create a file called &lt;code&gt;chat.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you’re done, your directory structure should look like this:&lt;/p&gt;
 &lt;div class=&#39;directory-hierarchy&#39;&gt;
 &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;demo/
    ├ .index.html
    └ .dynamic
          ├ date.js
          └ .wss
              └ chat.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;p&gt;Now type the following code into that file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (client, request) {
  client.room &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.setRoom(request)
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`New client connected to &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;client.room&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The function you just wrote will be called any time a new client connects to our chat server at the &lt;code&gt;/chat&lt;/code&gt; path (which will be available locally at &lt;code&gt;wss://localhost/chat&lt;/code&gt;)&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;. In chat app parlance, what we’ve created is known as a “room.” So let’s try and join it and see what happens.&lt;/p&gt;
&lt;h3 id=&#34;room-for-improvement&#34;&gt;Room for improvement&lt;/h3&gt;
&lt;p&gt;To test our new room, run &lt;code&gt;site&lt;/code&gt; in your &lt;code&gt;demo&lt;/code&gt; folder and, once the server is running, open up a JavaScript console in your browser of choice and enter the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://localhost/chat&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now look in your terminal, you should see:&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;terminal&#34; style=&#34;display: block; position: relative;&#34; data-window-title=&#34;Terminal: ~/demo&#34;&gt;
  

  &lt;div class=&#34;terminal-content&#34;&gt;
    &lt;pre&gt;&lt;code&gt;New client connected to /chat&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;We’ve connected… now what?&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;So we’ve just made a successful connection to the &lt;code&gt;/chat&lt;/code&gt; room. Now we need the server to listen for messages sent from connected clients and broadcast them to every other client in the same room.&lt;/p&gt;
&lt;p&gt;When you’re ready to move on, press &lt;kbd&gt;Ctrl&lt;/kbd&gt; &lt;kbd&gt;C&lt;/kbd&gt; to stop the Site.js server.&lt;/p&gt;
&lt;h3 id=&#34;broadly-speaking&#34;&gt;Broadly speaking&lt;/h3&gt;
&lt;p&gt;Modify the code in &lt;code&gt;chat.js&lt;/code&gt; so that it matches the listing, below. The changed section is highlighted:&lt;/p&gt;
 &lt;div id=&#39;broadly-speaking-example&#39;&gt;
 &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;      module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (client, request) {
        client.room &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.setRoom(request)
        console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`New client connected to &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;client.room&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        client.on(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;, message =&amp;gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.broadcast(client, message)
        })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;      }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;p&gt;What we’re doing here is creating an event handler that listens for &lt;code&gt;message&lt;/code&gt; events and then uses the &lt;code&gt;broadcast&lt;/code&gt; method that all DotJS WebSocket routes have to fan the message out to the other clients connected to the same room.&lt;/p&gt;
&lt;p&gt;An important thing to note is that you should always use an anonymous function expression instead of an arrow function expression when creating your WebSocket routes to ensure that you can access methods like &lt;code&gt;broadcast()&lt;/code&gt; using the &lt;code&gt;this&lt;/code&gt; reference&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;can-you-hear-me-now&#34;&gt;Can you hear me now?&lt;/h2&gt;
&lt;p&gt;Our chat server is complete but does it work? Start the chat server again by running Site.js and let’s return to our JavaScript console and test it. This time, open two browser windows and let’s try and hold a conversation.&lt;/p&gt;
&lt;p&gt;In the JavaScript console of the first browser window, enter the following, one line at a time, skipping the comments:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create the socket connection
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://localhost/chat&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create the message handler to display incoming messages.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket.onmessage &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; console.log(message.data)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the JavaScript console of the second browser window, enter the following, one line at a time, skipping the comments:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create the socket connection
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://localhost/chat&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create the message handler to display incoming messages.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket.onmessage &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; console.log(message.data)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Send a message
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket.send(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Can you hear me now?&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Glance at the JavaScript console of the first browser window and you should see the message you just sent appear. You can reply from the first window using the same &lt;code&gt;socket.send()&lt;/code&gt; method as before.&lt;/p&gt;
&lt;p&gt;Functionally, our chat server is complete but let’s modify the code one last time just to add some logging and to document our work to make it easier to remember what we’re doing.&lt;/p&gt;
&lt;p&gt;Before we move on, replace the code in &lt;code&gt;chat.js&lt;/code&gt; with the following listing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (client, request) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// New client connection: persist client’s “room”
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// based on request path.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  client.room &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.setRoom(request)

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Log the connection.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`New client connected to &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;client.room&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)

  client.on(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;, message =&amp;gt; {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// New message received: broadcast it to all
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// other clients in the same room.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; numberOfRecipients &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.broadcast(client, message)

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Log the number of recipients message was sent to
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and make sure we pluralise the log message properly.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;client.room&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; message broadcast to `&lt;/span&gt;
      &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;numberOfRecipients&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; recipient`&lt;/span&gt;
      &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;numberOfRecipients &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
  })
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Restart Site.js so that it serves this latest version of your chat server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;some-housekeeping&#34;&gt;Some housekeeping&lt;/h2&gt;
&lt;p&gt;If you remember, towards the start of this tutorial we created a dynamic HTTPS route that shows the current date and time. With Site.js serving the &lt;code&gt;demo&lt;/code&gt; folder, try to access the &lt;code&gt;/date&lt;/code&gt; route now.&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost/date&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;div id=&#39;not-found-error&#39;&gt;
        &lt;h1&gt;4🤭4&lt;/h1&gt;
        &lt;p&gt;Could not find &lt;span class=&#39;not-found-path&#39;&gt;/date&lt;/span&gt;&lt;/p&gt;
      &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;We don’t talk about my emoji problem…&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;Oops, you get the default Site.js 404 page&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Site.js can’t file the file. Why?&lt;/p&gt;
&lt;p&gt;Turns out ever since we created the &lt;code&gt;.wss&lt;/code&gt; folder, Site.js has been ignoring our &lt;code&gt;.dynamic/date.js&lt;/code&gt; route due to &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#routing-precedence&#34;&gt;routing precedence&lt;/a&gt; rules.&lt;/p&gt;
&lt;p&gt;That’s a fancy way of saying that if we want to use HTTPS and WebSocket DotJS routes together in the same web app, we must put our HTTPS routes in a folder called &lt;code&gt;.https&lt;/code&gt; just like we put the WebSocket routes in a folder called &lt;code&gt;.wss&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So create a folder called &lt;code&gt;.https&lt;/code&gt; inside the &lt;code&gt;.dynamic&lt;/code&gt; folder and move the &lt;code&gt;date.js&lt;/code&gt; file into it.&lt;/p&gt;
&lt;p&gt;When you’re done, your directory structure should look like this:&lt;/p&gt;
 &lt;div class=&#39;directory-hierarchy&#39;&gt;
 &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;demo/
    ├ .index.html
    └ .dynamic
          ├ .https
          │   └ date.js
          └ .wss
              └ chat.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;p&gt;Restart your Site.js server and hit &lt;code&gt;https://localhost/date&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The route should now load correctly.&lt;/p&gt;
&lt;p&gt;If you look at your terminal output, you will see that Site.js tells you exactly which routes it loads when it launches:&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;terminal&#34; style=&#34;display: block; position: relative;&#34; data-window-title=&#34;Terminal: ~/demo&#34;&gt;
  

  &lt;div class=&#34;terminal-content&#34;&gt;
    &lt;pre&gt;&lt;code&gt;🐁 Found .https/.wss folders. Will load dynamic routes from there.
  🐁 Adding HTTPS GET route: /date
  🐁 Adding WebSocket (WSS) route: /chat&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;The console output from Site.js contains important details.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;room-with-a-view&#34;&gt;Room with a view&lt;/h2&gt;
&lt;p&gt;So our chat server works but it doesn’t have a web interface yet.&lt;/p&gt;
&lt;p&gt;Let’s fix that!&lt;/p&gt;
&lt;p&gt;Open up the static &lt;code&gt;index.html&lt;/code&gt; we created in the very first exercise with the “Hello, world!” message in it and replace the contents of that file with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;lang&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;Basic chat app with Site.js&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;Chat room&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&amp;lt;!-- Code from the next step goes here. --&amp;gt;&lt;/span&gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With Site.js serving the &lt;code&gt;demo&lt;/code&gt; folder, hit &lt;code&gt;https://localhost&lt;/code&gt; in your browser and confirm that you see it say “Chat room”.&lt;/p&gt;
&lt;h3 id=&#34;create-the-interface&#34;&gt;Create the interface&lt;/h3&gt;
&lt;p&gt;Our simple web interface is going to contain three main components: a connection status widget to show you whether you are connected to the server or not, a message form where you can identify yourself, compose messages, and send them, and, finally, a message display area where we can display both your sent messages and any incoming messages received from others.&lt;/p&gt;
&lt;p&gt;Add the interface components to your web page by pasting the code below under the heading in the body of your page.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Status: &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;status&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;color: red;&amp;#34;&lt;/span&gt;&amp;gt;Offline&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message-form&amp;#39;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt;&amp;gt;Nickname:&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;value&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Anonymous&amp;#39;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&amp;gt;Message:&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;value&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;send-button&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&amp;gt;Send&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;Messages&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;messages&amp;#39;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let’s also add some very basic styling to make our app a little easier on the eyes. Just before the end of the &lt;code&gt;head&lt;/code&gt; tag, add a &lt;code&gt;style&lt;/code&gt; tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Code from the next step goes here. */&lt;/span&gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, let’s style the major elements. Add the following to your &lt;code&gt;style&lt;/code&gt; tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;/* Make CSS behave (as much as that’s possible) ;) */&lt;/span&gt;
  &lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;box-sizing&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;border-box&lt;/span&gt;; }

  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-family&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;sans-serif&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  }

  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;margin-top&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, let’s style the form to make it display in a responsive two-column layout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#eee&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;display&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-template-columns&lt;/span&gt;: [labels] &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;auto&lt;/span&gt; [controls] &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;fr;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;align-items&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;center&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-row-gap&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-column-gap&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  }

  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-column&lt;/span&gt;: labels; }

  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;input&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-column&lt;/span&gt;: controls;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;min-width&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;max-width&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;300&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let’s also style the button so that it’s green when enabled, gray when disabled, and transitions smoothly between those states:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;text-align&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;center&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;cursor&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;pointer&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;:&lt;span style=&#34;color:#40a070&#34;&gt;16&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;white&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;border-radius&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;:&lt;span style=&#34;color:#40a070&#34;&gt;#466B6A&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;border&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;none&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding-top&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.25&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding-bottom&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.25&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;transition&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;s&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;transition&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;s&lt;/span&gt;;
  }

  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt;:&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;hover&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;black&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#92AAA4&lt;/span&gt;;
  }

  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt;:&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;disabled&lt;/span&gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#999&lt;/span&gt;;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#ccc&lt;/span&gt;;
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, let’s hide the bullet points and add a background to the message list. We also want to give it a definite height so that it scrolls instead of growing forever:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;#&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;messages&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;overflow-y&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;scroll&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#eee&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;list-style&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;none&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, when you visit &lt;code&gt;https://localhost&lt;/code&gt; in your browser, you should see that your (currently non-functional) chat interface resembles the one below:&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;div class=&#39;chat-interface&#39;&gt;
        &lt;h1&gt;Chat room&lt;/h1&gt;
        &lt;p&gt;Status: &lt;span id=&#39;non-functional-status&#39; style=&#34;color: red;&#34;&gt;Offline&lt;/span&gt;&lt;/p&gt;
        &lt;!-- Note: added code to prevent send button from reloading the tutorial --&gt;
        &lt;form id=&#39;non-functional-message-form&#39; onsubmit=&#39;return false&#39;&gt;
          &lt;label for=&#39;nickname&#39;&gt;Nickname:&lt;/label&gt;
          &lt;input id=&#39;nickname&#39; type=&#39;text&#39; name=&#39;nickname&#39; value=&#39;Anonymous&#39;&gt;
          &lt;label for=&#39;message&#39;&gt;Message:&lt;/label&gt;
          &lt;input id=&#39;message&#39; type=&#39;text&#39; name=&#39;message&#39; value=&#39;&#39;&gt;
          &lt;button id=&#39;send-button&#39; type=&#39;submit&#39;&gt;Send&lt;/button&gt;
        &lt;/form&gt;
        &lt;h2&gt;Messages&lt;/h2&gt;
        &lt;ul class=&#39;messages&#39; id=&#39;non-functional-messages&#39;&gt;&lt;/ul&gt;
      &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;The web interface (non-functional).&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;h3 id=&#34;make-the-connection&#34;&gt;Make the connection&lt;/h3&gt;
&lt;p&gt;OK, so let’s add some life to our room, shall we?&lt;/p&gt;
&lt;p&gt;Under your interface code, right before the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag, add a &lt;code&gt;script&lt;/code&gt; tag and let’s get our status indicator working by making a WebSocket connection to our server, listening for the relevant events, and updating the interface accordingly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt;script&lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt;
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Shorthand for basic DOM lookup via CSS selectors.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; element &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;.querySelector.bind(&lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;)

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Initialise web socket.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(
    &lt;span style=&#34;color:#4070a0&#34;&gt;`wss://&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;window&lt;/span&gt;.location.hostname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/chat`&lt;/span&gt;
  )

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Display the state of the connection.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  socket.onopen &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; _ =&amp;gt; {
    element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#status&amp;#39;&lt;/span&gt;).innerHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;
      &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;span style=&amp;#34;color: green&amp;#34;&amp;gt;Online&amp;lt;/span&amp;gt;&amp;#39;&lt;/span&gt;
  }

  socket.onclose &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; _ =&amp;gt; {
    element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#status&amp;#39;&lt;/span&gt;).innerHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Offline&amp;#39;&lt;/span&gt;
  }

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Code from the next step goes here.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;&#34;&gt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Restart the Site.js server&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt; and you should now see the status indicator read &lt;span style=&#34;color: green&#34;&gt;Online&lt;/span&gt; when you reload the page:&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;div class=&#39;chat-interface&#39;&gt;
        &lt;h1&gt;Chat room&lt;/h1&gt;
        &lt;p&gt;Status: &lt;span id=&#39;connection-only-status&#39; style=&#34;color: red;&#34;&gt;Offline&lt;/span&gt;&lt;/p&gt;
        &lt;!-- Note: added code to prevent send button from reloading the tutorial --&gt;
        &lt;form id=&#39;connection-only-message-form&#39; onsubmit=&#39;return false&#39;&gt;
          &lt;label for=&#39;connection-only-nickname&#39;&gt;Nickname:&lt;/label&gt;
          &lt;input id=&#39;connection-only-nickname&#39; type=&#39;text&#39; name=&#39;nickname&#39; value=&#39;Anonymous&#39;&gt;
          &lt;label for=&#39;connection-only-message&#39;&gt;Message:&lt;/label&gt;
          &lt;input id=&#39;connection-only-message&#39; type=&#39;text&#39; name=&#39;message&#39; value=&#39;&#39;&gt;
          &lt;button id=&#39;connection-only-send-button&#39; type=&#39;submit&#39;&gt;Send&lt;/button&gt;
        &lt;/form&gt;
        &lt;h2&gt;Messages&lt;/h2&gt;
        &lt;ul class=&#39;messages&#39; id=&#39;messages&#39;&gt;&lt;/ul&gt;
      &lt;/div&gt;
      &lt;script&gt;
        // Initialise web socket.
        const connectionOnlySocket = new WebSocket(
          `wss://${window.location.hostname}/chat`
        )

        // Display the state of the connection.
        connectionOnlySocket.onopen = _ =&gt; {
          element(&#39;#connection-only-status&#39;).innerHTML = &#39;&lt;span style=&#34;color: green&#34;&gt;Online&lt;/span&gt;&#39;
        }

        connectionOnlySocket.onclose = _ =&gt; {
          element(&#39;#connection-only-status&#39;).innerHTML = &#39;Offline&#39;
        }
      &lt;/script&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;Live example, connected to wss://ar.al/chat.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;Note that when making the WebSocket connection, we didn’t hardcode the URL like before. Instead, we used:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;`wss://&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;window&lt;/span&gt;.location.hostname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/chat`&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This ensures that the app will work regardless of which domain it is served from.&lt;/p&gt;
&lt;p&gt;During development, &lt;code&gt;windows.location.hostname&lt;/code&gt; will resolve to &lt;code&gt;localhost&lt;/code&gt;, as before. When running in production – as it is here on my blog – it will resolve to the domain name of the site&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;handle-message-sending&#34;&gt;Handle message sending&lt;/h3&gt;
&lt;p&gt;Now that our app can connect to the chat server and display its connection status, let’s implement the ability to send messages.&lt;/p&gt;
&lt;p&gt;When we send a message, we won’t receive it back ourselves, so one of the things we must do is to add it to our local message list manually. Since we’ll also have to do this when we receive a message from someone else, let’s first create a function we can use for both these purposes.&lt;/p&gt;
&lt;p&gt;Add the following code to the end of your current script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Helper: display a message object.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; displayMessage (message) {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Prepare the message HTML.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nickname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;:&amp;lt;/strong&amp;gt;`&lt;/span&gt;
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; text &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message.text
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;text&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Update the message list.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#messages&amp;#39;&lt;/span&gt;).innerHTML &lt;span style=&#34;color:#666&#34;&gt;+=&lt;/span&gt; messageHTML
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, let’s create the handler that will be called when your message form is submitted by pressing the &lt;em&gt;Send&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;Add the following code to the end of your script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Handle message sending.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message-form&amp;#39;&lt;/span&gt;).addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;, event =&amp;gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Prevent the form from being submitted.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  event.preventDefault()

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Get the nickname and text.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nickname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).value
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; text &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).value

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Clear the message
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).value &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create a message object, serialise it as JSON, and send it.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; message &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { nickname, text }
  socket.send(JSON.stringify(message))

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Update the local display
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  displayMessage(message)
})

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Code from the next step goes here.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In our handler, we construct a message object that contains your nickname and the text of the message you want to send, serialise it into a JSON-formatted string, and then send it to our chat server.&lt;/p&gt;
&lt;p&gt;Additionally, we clear the message box to make it easier for you to type your next message and we use our new &lt;code&gt;displayMesssage()&lt;/code&gt; function to display the message we’ve sent locally so you can have a full timeline of messages, including your own.&lt;/p&gt;
&lt;p&gt;Reload the page and you should now we able to send messages. Note that updates to static routes like our &lt;em&gt;index.html&lt;/em&gt; do not require a server restart. Only changes to dynamic routes do.&lt;/p&gt;
&lt;p&gt;To test that it is working, take a look at the Site.js console output in your terminal window and you should see the following message:&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;terminal&#34; style=&#34;display: block; position: relative;&#34; data-window-title=&#34;Terminal: ~/demo&#34;&gt;
  

  &lt;div class=&#34;terminal-content&#34;&gt;
    &lt;pre&gt;&lt;code&gt;/chat message broadcast to 0 recipients&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;A message to no one.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;So our message is being sent but no one is receiving it. This makes sense since the &lt;code&gt;broadcast()&lt;/code&gt; method in our chat server does not send a copy of the message to the client it received it from and we don’t have any other clients connected.&lt;/p&gt;
&lt;p&gt;Now open up a second browser window and load a copy of &lt;code&gt;https://localhost&lt;/code&gt; in it and try sending another message.&lt;/p&gt;
&lt;p&gt;This time, you should see the following console output in your terminal:&lt;/p&gt;
&lt;figure&gt;

  
  &lt;div class=&#34;terminal&#34; style=&#34;display: block; position: relative;&#34; data-window-title=&#34;Terminal: ~/demo&#34;&gt;
  

  &lt;div class=&#34;terminal-content&#34;&gt;
    &lt;pre&gt;&lt;code&gt;/chat message broadcast to 1 recipient&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;A message to someone.&lt;/figcaption&gt;
  

&lt;/figure&gt;

&lt;p&gt;Well that’s progress. So our messages are being broadcast successfully but we’re not doing anything to process them on the web interface yet.&lt;/p&gt;
&lt;p&gt;Let’s implement that next.&lt;/p&gt;
&lt;h3 id=&#34;handle-incoming-messages&#34;&gt;Handle incoming messages&lt;/h3&gt;
&lt;p&gt;When a message is received on the socket, the &lt;code&gt;onmessage&lt;/code&gt; event handler is invoked. We need to define this handler to parse the received JSON string and add it to the unordered list in our interface (remember, we serialise message objects in JSON format before sending them so we need to deserialise them when we receive them).&lt;/p&gt;
&lt;p&gt;Add the following code to the end of your script, just before the closing &lt;code&gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Handle incoming messages.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket.onmessage &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Deserialise the message string and display it.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  message &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSON.parse(message.data)
  displayMessage(message)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when you test your app using two browser windows, you should be able to both send and receive messages.&lt;/p&gt;
&lt;p&gt;You can test out what we have so far using the two browser windows below. They’re both running the code above:&lt;/p&gt;
 &lt;!-- First chat window --&gt;
 &lt;div id=&#39;first-chat-window&#39;&gt;
 &lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;div class=&#39;chat-interface&#39;&gt;
        &lt;h1&gt;Chat room&lt;/h1&gt;
        &lt;p&gt;Status: &lt;span id=&#39;first-chat-window-status&#39; style=&#34;color: red;&#34;&gt;Offline&lt;/span&gt;&lt;/p&gt;
        &lt;!-- Note: added code to prevent send button from reloading the tutorial --&gt;
        &lt;form id=&#39;first-chat-window-message-form&#39; onsubmit=&#39;return false&#39;&gt;
          &lt;label for=&#39;first-chat-window-nickname&#39;&gt;Nickname:&lt;/label&gt;
          &lt;input id=&#39;first-chat-window-nickname&#39; type=&#39;text&#39; name=&#39;nickname&#39; value=&#39;Anonymous&#39;&gt;
          &lt;label for=&#39;first-chat-window-message&#39;&gt;Message:&lt;/label&gt;
          &lt;input id=&#39;first-chat-window-message&#39; type=&#39;text&#39; name=&#39;message&#39; value=&#39;&#39;&gt;
          &lt;button id=&#39;first-chat-window-send-button&#39; type=&#39;submit&#39;&gt;Send&lt;/button&gt;
        &lt;/form&gt;
        &lt;h2&gt;Messages&lt;/h2&gt;
        &lt;ul class=&#39;messages&#39; id=&#39;first-chat-window-messages&#39;&gt;&lt;/ul&gt;
      &lt;/div&gt;
      &lt;script&gt;
        // Initialise web socket.
        const firstChatWindowSocket = new WebSocket(
          `wss://${window.location.hostname}/chat`
        )

        // Display the state of the connection.
        firstChatWindowSocket.onopen = _ =&gt; {
          element(&#39;#first-chat-window-status&#39;).innerHTML = &#39;&lt;span style=&#34;color: green&#34;&gt;Online&lt;/span&gt;&#39;
        }

        firstChatWindowSocket.onclose = _ =&gt; {
          element(&#39;#first-chat-window-status&#39;).innerHTML = &#39;Offline&#39;
        }

        function firstChatWindowDisplayMessage (message) {
          element(&#39;#first-chat-window-messages&#39;).innerHTML += `&lt;li&gt;&lt;strong&gt;${message.nickname}: &lt;/strong&gt;${message.text}&lt;/li&gt;`
        }

        // Handle message sending.
        element(&#39;#first-chat-window-message-form&#39;).addEventListener(&#39;submit&#39;, event =&gt; {
          // Prevent the form from being submitted.
          event.preventDefault()

          // Get the nickname and text.
          const nickname = element(&#39;#first-chat-window-nickname&#39;).value
          const text = element(&#39;#first-chat-window-message&#39;).value

          // Clear the message
          element(&#39;#first-chat-window-message&#39;).value = &#39;&#39;

          // Create a message object, serialise it as JSON, and send it.
          const message = { nickname, text }
          firstChatWindowSocket.send(JSON.stringify(message))

          // Update the local display
          firstChatWindowDisplayMessage(message)
        })

        // Handle incoming messages.
        firstChatWindowSocket.onmessage = message =&gt; {
          // Deserialise the message string.
          message = JSON.parse(message.data)

          // Display the message in the messages list.
          firstChatWindowDisplayMessage(message)
        }
      &lt;/script&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;First chat window.&lt;/figcaption&gt;
  

&lt;/figure&gt;

 &lt;/div&gt;
 &lt;!-- Second chat window --&gt;
 &lt;div id=&#39;second-chat-window&#39;&gt;
 &lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://localhost&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;div class=&#39;chat-interface&#39;&gt;
        &lt;h1&gt;Chat room&lt;/h1&gt;
        &lt;p&gt;Status: &lt;span id=&#39;second-chat-window-status&#39; style=&#34;color: red;&#34;&gt;Offline&lt;/span&gt;&lt;/p&gt;
        &lt;!-- Note: added code to prevent send button from reloading the tutorial --&gt;
        &lt;form id=&#39;second-chat-window-message-form&#39; onsubmit=&#39;return false&#39;&gt;
          &lt;label for=&#39;second-chat-window-nickname&#39;&gt;Nickname:&lt;/label&gt;
          &lt;input id=&#39;second-chat-window-nickname&#39; type=&#39;text&#39; name=&#39;nickname&#39; value=&#39;Anonymous&#39;&gt;
          &lt;label for=&#39;second-chat-window-message&#39;&gt;Message:&lt;/label&gt;
          &lt;input id=&#39;second-chat-window-message&#39; type=&#39;text&#39; name=&#39;message&#39; value=&#39;&#39;&gt;
          &lt;button id=&#39;second-chat-window-send-button&#39; type=&#39;submit&#39;&gt;Send&lt;/button&gt;
        &lt;/form&gt;
        &lt;h2&gt;Messages&lt;/h2&gt;
        &lt;ul class=&#39;messages&#39; id=&#39;second-chat-window-messages&#39;&gt;&lt;/ul&gt;
      &lt;/div&gt;
      &lt;script&gt;
        // Initialise web socket.
        const secondChatWindowSocket = new WebSocket(
          `wss://${window.location.hostname}/chat`
        )

        // Display the state of the connection.
        secondChatWindowSocket.onopen = _ =&gt; {
          element(&#39;#second-chat-window-status&#39;).innerHTML = &#39;&lt;span style=&#34;color: green&#34;&gt;Online&lt;/span&gt;&#39;
        }

        secondChatWindowSocket.onclose = _ =&gt; {
          element(&#39;#second-chat-window-status&#39;).innerHTML = &#39;Offline&#39;
        }

        function secondChatWindowDisplayMessage (message) {
          element(&#39;#second-chat-window-messages&#39;).innerHTML += `&lt;li&gt;&lt;strong&gt;${message.nickname}: &lt;/strong&gt;${message.text}&lt;/li&gt;`
        }

        // Handle message sending.
        element(&#39;#second-chat-window-message-form&#39;).addEventListener(&#39;submit&#39;, event =&gt; {
          // Prevent the form from being submitted.
          event.preventDefault()

          // Get the nickname and text.
          const nickname = element(&#39;#second-chat-window-nickname&#39;).value
          const text = element(&#39;#second-chat-window-message&#39;).value

          // Clear the message
          element(&#39;#second-chat-window-message&#39;).value = &#39;&#39;

          // Create a message object, serialise it as JSON, and send it.
          const message = { nickname, text }
          secondChatWindowSocket.send(JSON.stringify(message))

          // Update the local display
          secondChatWindowDisplayMessage(message)
        })

        // Handle incoming messages.
        secondChatWindowSocket.onmessage = message =&gt; {
          // Deserialise the message string.
          message = JSON.parse(message.data)

          // Display the message in the messages list.
          secondChatWindowDisplayMessage(message)
        }
      &lt;/script&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;Second chat window.&lt;/figcaption&gt;
  

&lt;/figure&gt;

 &lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h2 id=&#34;spit-and-shine&#34;&gt;Spit and shine&lt;/h2&gt;
&lt;p&gt;So our chat room works but it’s not as elegant as it could be. Without going overboard (this is a basic tutorial, after all), there are a couple of little touches we can add that would improve its usability considerably.&lt;/p&gt;
&lt;h3 id=&#34;set-initial-focus-dont-make-me-click&#34;&gt;Set initial focus (“don’t make me click”)&lt;/h3&gt;
&lt;p&gt;To start, when the app first loads, the first thing the person will most likely want to do is replace &lt;em&gt;Anonymous&lt;/em&gt; with their own nickname. So let’s make it easier for them by focusing that field and selecting the text in it so all they have to do to get started is to start typing their nickname.&lt;/p&gt;
&lt;p&gt;Right after the definition of the &lt;code&gt;displayMessage()&lt;/code&gt; function, add the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).focus()
element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).select()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;manage-focus&#34;&gt;Manage focus&lt;/h3&gt;
&lt;p&gt;While on the topic of focus, if the person types a message and presses &lt;kbd&gt;Return&lt;/kbd&gt; to send it, the message text field maintains its focus. This is good as it means that they can send another message without doing any more work. However, if they use the &lt;em&gt;Send&lt;/em&gt; button to send the message, the message text field loses focus. So it’s up to us to set its focus manually.&lt;/p&gt;
&lt;p&gt;Under the &lt;code&gt;element(&#39;#message&#39;).value = &#39;&#39;&lt;/code&gt; line in the form &lt;code&gt;submit&lt;/code&gt; event handler, let’s set the focus after we’ve cleared the field:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).focus()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;auto-scroll-the-messages-list&#34;&gt;Auto-scroll the messages list&lt;/h3&gt;
&lt;p&gt;If you send more messages than will fit in the message list, the latest messages scroll off the screen.&lt;/p&gt;
 &lt;div id=&#39;auto-scroll&#39;&gt;
 &lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/#first-chat-window&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
      &lt;div class=&#39;chat-interface&#39;&gt;
      &lt;h2&gt;Messages&lt;/h2&gt;
      &lt;ul class=&#34;messages&#34;&gt;
        &lt;li&gt;&lt;strong&gt;Aral: &lt;/strong&gt;Hey, so I have a joke for you…&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Laura: &lt;/strong&gt;Oh, yeah?&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Aral: &lt;/strong&gt;Knock, knock!&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Laura: &lt;/strong&gt;Who’s there?&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Aral: &lt;/strong&gt;Endangered species&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Laura: &lt;/strong&gt;Endangered species, who?&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Aral: &lt;/strong&gt;&lt;em&gt;*silence due to extinction of species between delivery of lines*&lt;/em&gt;&lt;/li&gt;
      &lt;/ul&gt;
      &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  
  &lt;figcaption&gt;Lack of auto scroll means we miss the punchline of the joke (and our species is going to be the punchline of the joke if we don’t get our act together).&lt;/figcaption&gt;
  

&lt;/figure&gt;

 &lt;/div&gt;
&lt;p&gt;This is less than ideal.&lt;/p&gt;
&lt;p&gt;Instead, we want the message list to automatically scroll to the end every time a new message arrives so that you can read it without additional effort.&lt;/p&gt;
&lt;p&gt;That’s easy enough to achieve by updating the &lt;code&gt;displayMessage()&lt;/code&gt; function we wrote earlier to the following one. The changed section is highlighted, below:&lt;/p&gt;
 &lt;div id=&#39;display-message-update&#39;&gt;
 &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; displayMessage (message) {
        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Prepare the message HTML.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nickname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;:&amp;lt;/strong&amp;gt;`&lt;/span&gt;
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; text &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message.text
        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;text&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Update the message list.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageList &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#messages&amp;#39;&lt;/span&gt;)
        messageList.innerHTML &lt;span style=&#34;color:#666&#34;&gt;+=&lt;/span&gt; messageHTML
        messageList.scrollTop &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; messageList.scrollHeight
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;      }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;h3 id=&#34;client-side-validation&#34;&gt;Client-side validation&lt;/h3&gt;
&lt;p&gt;Things are feeling a bit nicer now but the elephant in the room (his name is George!) is that we’re not performing any input validation. Someone could easily submit a message with no nickname and no message text and we would dutifully fan it out to the other people in the room leading to undue heartache and pain.&lt;/p&gt;
&lt;p&gt;Try it out for yourself using &lt;a href=&#34;#first-chat-window&#34;&gt;the live example&lt;/a&gt;, above.&lt;/p&gt;
&lt;p&gt;Let’s fix this by adding some client-side validation to our example. There is a listing of all of the code so far, including the instructions below, at the end of the section. Refer to that if you get lost in where to add the various code snippets below.&lt;/p&gt;
&lt;p&gt;First, let’s create a &lt;code&gt;validateForm()&lt;/code&gt; function we can call to ensure that the form is valid. That, in turn, will make use of a function called &lt;code&gt;isValidString()&lt;/code&gt; that validates strings. If the form’s not valid, we’ll disable the &lt;em&gt;Send&lt;/em&gt; button:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is the passed object a valid string?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; isValidString(s) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Boolean&lt;/span&gt;(s)                &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Not null, undefined, &amp;#39;&amp;#39;, or 0
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; s &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;string&amp;#39;&lt;/span&gt;       &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is the correct type
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; s.replace(&lt;span style=&#34;color:#235388&#34;&gt;/\s/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is not just whitespace.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Disables the Send button if the form isn’t valid.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; validateForm () {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nicknameIsValid &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; isValidString(element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).value)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageIsValid &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; isValidString(element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).value)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; formIsValid &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; nicknameIsValid &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; messageIsValid

  element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#send-button&amp;#39;&lt;/span&gt;).disabled &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;formIsValid
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we need to call our &lt;code&gt;validateForm()&lt;/code&gt; function at certain times. First, we must call it when the page first loads so that the form is initially validated. Since there is no text in the message field, our interface will thus start out with the &lt;em&gt;Send&lt;/em&gt; button disabled. This is what we want.&lt;/p&gt;
&lt;p&gt;Next, we should validate the form after a message is sent. Why? Because we clear the old message field and so we want the &lt;em&gt;Send&lt;/em&gt; button to be disabled again.&lt;/p&gt;
&lt;p&gt;Finally, we must validate the form every time the text in the &lt;code&gt;nickname&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; text fields changes so that we can enable the &lt;em&gt;Send&lt;/em&gt; button when there’s text in both of them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;input&amp;#39;&lt;/span&gt;, validateForm)
element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;input&amp;#39;&lt;/span&gt;, validateForm)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the bare minimum of validation that we can get away with. Here’s all the front-end code we wrote together with the above improvements highlighted:&lt;/p&gt;
 &lt;div class=&#39;final-code-listing&#39;&gt;
 &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;lang&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;Basic chat app with Site.js&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
        &lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;box-sizing&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;border-box&lt;/span&gt;; }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-family&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;sans-serif&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
        }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;margin-top&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;; }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#eee&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;display&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-template-columns&lt;/span&gt;: [labels] &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;auto&lt;/span&gt; [controls] &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;fr;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;align-items&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;center&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-row-gap&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-column-gap&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
        }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-column&lt;/span&gt;: labels; }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;input&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-column&lt;/span&gt;: controls;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;min-width&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;6&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;max-width&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;300&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
        }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;text-align&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;center&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;cursor&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;pointer&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;font-size&lt;/span&gt;:&lt;span style=&#34;color:#40a070&#34;&gt;16&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;white&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;border-radius&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;:&lt;span style=&#34;color:#40a070&#34;&gt;#466B6A&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;border&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;none&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding-top&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.25&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding-bottom&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.25&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;transition&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;s&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;transition&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;s&lt;/span&gt;;
        }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt;:&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;hover&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;black&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#92AAA4&lt;/span&gt;;
        }

        &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt;:&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;disabled&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#999&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#ccc&lt;/span&gt;;
        }

        #&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;messages&lt;/span&gt; {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;overflow-y&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;scroll&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;#eee&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;0.75&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;em&lt;/span&gt;;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;list-style&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;none&lt;/span&gt;;
        }
      &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
    &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;Chat room&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Status: &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;status&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;color: red;&amp;#34;&lt;/span&gt;&amp;gt;Offline&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message-form&amp;#39;&lt;/span&gt;&amp;gt;
        &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt;&amp;gt;Nickname:&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt;&amp;gt;
        &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nickname&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;value&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Anonymous&amp;#39;&lt;/span&gt;&amp;gt;
        &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&amp;gt;Message:&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;label&lt;/span&gt;&amp;gt;
        &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;input&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;value&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&amp;gt;
        &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;send-button&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&amp;gt;Send&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;button&lt;/span&gt;&amp;gt;
      &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;form&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;Messages&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;messages&amp;#39;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
  &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Shorthand for basic DOM lookup via CSS selectors.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; element &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;.querySelector.bind(&lt;span style=&#34;color:#007020&#34;&gt;document&lt;/span&gt;)

        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Helper: display a message object.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; displayMessage (message) {
          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Prepare the message HTML.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nickname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;message.nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;:&amp;lt;/strong&amp;gt;`&lt;/span&gt;
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; text &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message.text
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;text&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;

          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Update the message list.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageList &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#messages&amp;#39;&lt;/span&gt;)
          messageList.innerHTML &lt;span style=&#34;color:#666&#34;&gt;+=&lt;/span&gt; messageHTML
          messageList.scrollTop &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; messageList.scrollHeight
        }
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is the passed object a valid string?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; isValidString(s) {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Boolean&lt;/span&gt;(s)                &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Not null, undefined, &amp;#39;&amp;#39;, or 0
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; s &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;string&amp;#39;&lt;/span&gt;       &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is the correct type
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; s.replace(&lt;span style=&#34;color:#235388&#34;&gt;/\s/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is not just whitespace.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        }

        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Disables the Send button if the form isn’t valid.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; validateForm () {
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nicknameIsValid &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; isValidString(element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).value)
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; messageIsValid &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; isValidString(element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).value)
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; formIsValid &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; nicknameIsValid &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; messageIsValid

          element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#send-button&amp;#39;&lt;/span&gt;).disabled &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;formIsValid
        }
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Initialise web socket.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(
          &lt;span style=&#34;color:#4070a0&#34;&gt;`wss://&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;window&lt;/span&gt;.location.hostname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/chat`&lt;/span&gt;
        )

        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Display the state of the connection.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        socket.onopen &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; _ =&amp;gt; {
          element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#status&amp;#39;&lt;/span&gt;).innerHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;
            &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;span style=&amp;#34;color: green&amp;#34;&amp;gt;Online&amp;lt;/span&amp;gt;&amp;#39;&lt;/span&gt;
        }

        socket.onclose &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; _ =&amp;gt; {
          element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#status&amp;#39;&lt;/span&gt;).innerHTML &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Offline&amp;#39;&lt;/span&gt;
        }
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Validate the form whenever the nickname or message changes.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;input&amp;#39;&lt;/span&gt;, validateForm)
        element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;input&amp;#39;&lt;/span&gt;, validateForm)

        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Set initial focus and selection.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).focus()
        element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).select()

        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Validate the form when the app first loads.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        validateForm()
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Handle message sending.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message-form&amp;#39;&lt;/span&gt;).addEventListener(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;, event =&amp;gt; {
          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Prevent the form from being submitted.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          event.preventDefault()

          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Get the nickname and text.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nickname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#nickname&amp;#39;&lt;/span&gt;).value
          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; text &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).value

          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Clear the message text field.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).value &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;

  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Focus the message text field.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          element(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;#message&amp;#39;&lt;/span&gt;).focus()

          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Validate the form.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          validateForm()
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Create a message object, serialise it as JSON &amp;amp; send it.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; message &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; { nickname, text }
          socket.send(JSON.stringify(message))

          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Update the local display
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          displayMessage(message)
        })

        &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Handle incoming messages.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;        socket.onmessage &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; message =&amp;gt; {
          &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Deserialise the message string and display it.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;          message &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSON.parse(message.data)
          displayMessage(message)
        }
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;      &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
    &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
  &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
  &lt;/div&gt;
&lt;h3 id=&#34;server-side-validation&#34;&gt;Server-side validation&lt;/h3&gt;
&lt;p&gt;We just implemented front-end validation but that’s only half the story.&lt;/p&gt;
&lt;p&gt;In a perfect world, everyone will use our lovely web page front-end and our front-end validation will catch all the issues, and no one will ever hit our back-end directly.&lt;/p&gt;
&lt;p&gt;In the real world, watch as I fire up a browser and send you an empty message using the JavaScript console of my browser and your chat server dutifully delivers it to everyone in the room:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// All your front-end validation are belong to us.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://ar.al/chat&amp;#39;&lt;/span&gt;)
socket.send(JSON.stringify({nickname&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, text&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;}))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Enter the above code into the JavaScript console of your browser, one line at a time, and check out &lt;a href=&#34;#first-chat-window&#34;&gt;the side-by-side browsers&lt;/a&gt; above. You should see your empty message display there:&lt;/p&gt;
  &lt;div id=&#39;server-side-validation-failure&#39;&gt;
  &lt;figure&gt;

  
  &lt;div class=&#34;browser facade-minimal&#34; data-url=&#34;https://ar.al/2019/10/11/build-a-simple-chat-app-with-site.js/#first-chat-window&#34;&gt;
  

    &lt;div class=&#34;refresh-button&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;lock&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;browser-content&#34;&gt;
  &lt;div class=&#39;chat-interface&#39;&gt;
  &lt;h2&gt;Messages&lt;/h2&gt;
  &lt;ul class=&#34;messages&#34;&gt;
    &lt;li&gt;&lt;strong&gt;:&lt;/strong&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;/div&gt;
  &lt;/div&gt;
  &lt;/div&gt;

  

&lt;/figure&gt;

  &lt;/div&gt;
&lt;p&gt;Oops! That’s not good.&lt;/p&gt;
&lt;p&gt;Front-end validation is a usability feature; back-end validation is a security feature.&lt;/p&gt;
&lt;p&gt;In a real-world chat app we would have to implement a host of features to prevent abuse (like rate limiting, blacklists, blocking, etc.). That’s outside the scope of this basic tutorial but let’s at least add some basic validation to our chat server.&lt;/p&gt;
&lt;p&gt;Add the following helper functions&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt; to your chat server at the bottom of your &lt;code&gt;chat.js&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is the passed object a valid string?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; isValidString(s) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Boolean&lt;/span&gt;(s)                &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Not null, undefined, &amp;#39;&amp;#39;, or 0
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; s &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;string&amp;#39;&lt;/span&gt;       &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is the correct type
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; s.replace(&lt;span style=&#34;color:#235388&#34;&gt;/\s/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is not just whitespace.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is the passed message object valid?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; isValidMessage(m) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; isValidString(m.nickname) &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isValidString(m.text)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, at the top of the &lt;code&gt;message&lt;/code&gt; handler, let’s call the &lt;code&gt;isValidMessage()&lt;/code&gt; function with a parsed instance of the serialised message and abort broadcasting it if the message isn’t valid:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Perform some basic validation.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;isValidMessage(JSON.parse(message))) {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`Message is invalid; not broadcasting.`&lt;/span&gt;)
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;#final-version&#34;&gt;The live example at the very top of this tutorial&lt;/a&gt; connects to the final version of the chat server that contains our server-side validation.&lt;/p&gt;
&lt;p&gt;You can test that it works by entering the following code into the JavaScript console in your browser, one line at a time, and verifying that your message does not show up:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// And I would have gotten away with it too,
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// if it weren&amp;#39;t for you meddling kids…
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;socket &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; WebSocket(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://ar.al/chat-final-version&amp;#39;&lt;/span&gt;)
socket.send(JSON.stringify({nickname&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;, text&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;}))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here’s the final listing of the chat server, with this new feature highlighted:&lt;/p&gt;
  &lt;div class=&#39;final-code-listing&#39;&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (client, request) {
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// New client connection: persist client’s “room”
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// based on request path.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  client.room &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.setRoom(request)

  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Log the connection.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`New client connected to &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;client.room&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)

  client.on(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;message&amp;#39;&lt;/span&gt;, message =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// New message received: broadcast it to all other clients
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// in the same room after performing basic validation.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;isValidMessage(JSON.parse(message))) {
      console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`Message is invalid; not broadcasting.`&lt;/span&gt;)
      &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; numberOfRecipients &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;.broadcast(client, message)

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Log the number of recipients message was sent to
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and make sure we pluralise the log message properly.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;client.room&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; message broadcast to `&lt;/span&gt;
      &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;numberOfRecipients&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; recipient`&lt;/span&gt;
      &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;numberOfRecipients &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#39;emphasised&#39;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;  })
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is the passed object a valid string?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; isValidString(s) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Boolean&lt;/span&gt;(s)                &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Not null, undefined, &amp;#39;&amp;#39;, or 0
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;typeof&lt;/span&gt; s &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;string&amp;#39;&lt;/span&gt;       &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is the correct type
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; s.replace(&lt;span style=&#34;color:#235388&#34;&gt;/\s/g&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;!==&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// and is not just whitespace.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Is the passed message object valid?
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; isValidMessage(m) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; isValidString(m.nickname) &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; isValidString(m.text)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Remember that you have to restart Site.js for server-side changes to dynamic routes to take effect.&lt;/p&gt;
&lt;h3 id=&#34;congratulations-you-did-it&#34;&gt;Congratulations, you did it!&lt;/h3&gt;
&lt;p&gt;Well, you were promised a basic chat app in Site.js and that’s exactly what we’ve just built.&lt;/p&gt;
&lt;p&gt;Along the way, you also learned the basics of Site.js and how to use it to develop and serve not just &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#websocket-wss-routes&#34;&gt;WebSocket routes&lt;/a&gt; but &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#get-only-simplest-approach&#34;&gt;regular HTTPS routes&lt;/a&gt; also using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#dotjs&#34;&gt;DotJS&lt;/a&gt;.&lt;/p&gt;
&lt;ol start=&#34;9&#34;&gt;
&lt;li&gt;
&lt;h2 id=&#34;going-further&#34;&gt;Going further&lt;/h2&gt;
&lt;p&gt;This tutorial just scratches the tip of the iceberg when it comes to what you can do with Site.js. As I mentioned earlier, anything you can do with Node.js, you can do with Site.js. What you get in addition is a zero-configuration secure static and dynamic web server.&lt;/p&gt;
&lt;p&gt;So exactly what else can you do? Here are some ideas to explore on your own:&lt;/p&gt;
&lt;h3 id=&#34;test-your-app-from-any-device&#34;&gt;Test your app from any device&lt;/h3&gt;
&lt;p&gt;So far, you’ve only run servers at &lt;code&gt;https://localhost&lt;/code&gt; and, behind the scenes, Site.js ensured that you didn’t get certificate warnings when you did. But what if you want to test your chat app from your phone or to have others test it with you? You could, of course, deploy it to a public Virtual Private Server, which is the topic of &lt;a href=&#34;#deploy-to-production&#34;&gt;the next section&lt;/a&gt;, but you can also run your server at your hostname using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#testing-servers-hostname&#34;&gt;the @hostname option&lt;/a&gt;&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt; and provide outside access to your development machine using a tool like &lt;a href=&#34;https://ngrok.com/&#34;&gt;ngrok&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Start Site.js at your hostname instead of at localhost.&lt;/span&gt;
site @hostname

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (In a separate terminal tab/window, start ngrok)&lt;/span&gt;
ngrok start --all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Having done this, you can then hit &lt;code&gt;https://dev.my-domain.org&lt;/code&gt; from any device anywhere and access your site from your development machine&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;. And, again, Site.js will work to seamless provision Let’s Encrypt certificates for you so you will not get any certificate errors.&lt;/p&gt;
&lt;h3 id=&#34;deploy-to-production&#34;&gt;Deploy to production&lt;/h3&gt;
&lt;p&gt;All you need to deploy to production is a Virtual Private Server running a flavour of Linux that has systemd and has a domain name pointing to it.&lt;/p&gt;
&lt;p&gt;Once that’s set, if you ssh to your production server, you can install Site.js just like you did at the start of this tutorial and then, provided you’re in the directory that you want to serve, deploy a production server with the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;site &lt;span style=&#34;color:#007020&#34;&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That will set up your server as a service that automatically restarts should it crash or should you reboot the server.&lt;/p&gt;
&lt;p&gt;And, just like before, Site.js will automatically provision your Let’s Encrypt certificates the first time you hit your site via your domain name. All you have to do ensure is that your &lt;code&gt;hostname&lt;/code&gt; is set properly on your server. To check and set your hostname, use the &lt;code&gt;hostnamectl&lt;/code&gt; command.&lt;/p&gt;
&lt;h3 id=&#34;sync&#34;&gt;Sync&lt;/h3&gt;
&lt;p&gt;So you have (a) your local copy of your site and (b) you’ve deployed a live production server. How do you get your site from A to B as you continue to work on it?&lt;/p&gt;
&lt;p&gt;Simple!&lt;/p&gt;
&lt;p&gt;On Linux and macOS&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt;, you can use Site.js’s &lt;code&gt;sync-to&lt;/code&gt; command to deploy your site like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;site --sync-to&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;my-domain.org
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above command will work with no other information necessary if the account name on your development machine and on your production machine is the same and you’re in your site’s directory. Otherwise, check the docs for on how to specify any details you need to.&lt;/p&gt;
&lt;p&gt;On your production server, to ensure that it supports sync, it’s a good idea to launch your server with the &lt;code&gt;--ensure-can-sync&lt;/code&gt; option, which will install &lt;a href=&#34;https://en.wikipedia.org/wiki/Rsync&#34;&gt;rsync&lt;/a&gt; – the tool Site.js uses behind the scenes to sync your files – if it doesn’t already exist.&lt;/p&gt;
&lt;h3 id=&#34;and-theres-more&#34;&gt;And there’s more…&lt;/h3&gt;
&lt;p&gt;Check out &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md&#34;&gt;the Site.js documentation&lt;/a&gt; for some of the other nifty things you can do with Site.js like not breaking links and contributing to an &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#native-support-for-an-evergreen-web&#34;&gt;evergreen web&lt;/a&gt; by taking advantage of Site.js’s native support for &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#native-cascading-archives-support&#34;&gt;cascading archives&lt;/a&gt; and the &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#native-404-302-support&#34;&gt;404-to-302 method&lt;/a&gt; as well as little niceties like &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#custom-error-pages&#34;&gt;custom error pages&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I hope this tutorial has whet your appetite for Site.js and given you some ideas of what you can do with it.&lt;/p&gt;
&lt;h2 id=&#34;sitejs-and-the-single-tenant-web&#34;&gt;Site.js and the single-tenant web&lt;/h2&gt;
&lt;p&gt;Remember that Site.js is a web tool for human beings, not startups or enterprises.&lt;/p&gt;
&lt;p&gt;Site.js is for building single-tenant web sites and web apps.&lt;/p&gt;
&lt;h3 id=&#34;what-is-a-single-tenant-web-app-or-site&#34;&gt;What is a single-tenant web app (or site?)&lt;/h3&gt;
&lt;p&gt;A single-tenant web app is a personal web app. It’s an app (or site) that you own and control. It’s a step towards building &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;a peer web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That’s quite a radical concept for the web, which has so far been centralised.&lt;/p&gt;
&lt;p&gt;We’re used to sites where you sign up for accounts with huge corporations. Turns out, when we do that, these corporations end up owning those accounts and they end up tracking and profiling us and attempting to influence our behaviour to make a quick buck (or billion).&lt;/p&gt;
&lt;p&gt;When we examine &lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;the social impacts of this toxic business model&lt;/a&gt;, we find that they erode our personhood and threaten our democracies. (Wow, that escalated quickly!)&lt;/p&gt;
&lt;h3 id=&#34;flipping-the-web-upside-down&#34;&gt;Flipping the web upside down&lt;/h3&gt;
&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/808318439.jpg?mw=1100&amp;mh=619&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/355368347.hd.mp4?s=3e60da74579b368cd489b5493065f354db762d1a&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/355368347.m3u8?s=8596a70d2996cb80ea16879c016e04af1d7841af&#39; type=&#39;video/mp4&#39;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a href=&#34;//player.vimeo.com/external/355368347.hd.mp4?s=3e60da74579b368cd489b5493065f354db762d1a&#34;&gt;download this video directly&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Watch Laura and me talk about moving beyond surveillance capitalism and flipping the web upside down with small technology.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If we want to own and control the digital aspects of ourselves that form part of who we are as people, we must turn the web on its head. We must flip it upside down.&lt;/p&gt;
&lt;p&gt;Ultimately, we must build &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;a web where each one of us has their own place&lt;/a&gt;). That’s &lt;a href=&#34;https://small-tech.org/research-and-development/&#34;&gt;what we’re working towards&lt;/a&gt; at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt; and Site.js is the foundations of our efforts.&lt;/p&gt;
&lt;p&gt;I hope you find Site.js useful – even if you don’t care about its philosophical or ethical underpinnings – simply on the merits of what you, as a developer, can do with it and how easy it is to use.&lt;/p&gt;
&lt;h3 id=&#34;comments-questions-yes-please&#34;&gt;Comments, questions? Yes, please!&lt;/h3&gt;
&lt;p&gt;If you have any questions about this tutorial or Site.js in general, please feel free to contact me &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;via my mastodon&lt;/a&gt; or by &lt;a href=&#34;mailto:mail@ar.al&#34;&gt;email&lt;/a&gt;.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;!-- Footnotes --&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you don’t want to use the terminal, you can open up your graphical file browser and create the &lt;code&gt;demo&lt;/code&gt; folder using that and then use your graphical code editor to create an &lt;code&gt;index.html&lt;/code&gt; file in that folder. You will, however, need to run the &lt;code&gt;site&lt;/code&gt; command from a terminal session with its current working directory set to the &lt;code&gt;demo&lt;/code&gt; folder… there’s no getting away from that. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Site.js can also function as a proxy server, so you can test &lt;em&gt;any&lt;/em&gt; server locally over a secure connection. For example, if you are running a &lt;a href=&#34;https://hugo.io&#34;&gt;Hugo&lt;/a&gt; server at its default port (1313), you can test it from &lt;code&gt;https://localhost&lt;/code&gt; by running Site.js like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Proxy the server at port 1313 at&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# https://localhost (port 443).&lt;/span&gt;
site :1313
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that if you want live reload to work, you should add the &lt;code&gt;--liveReloadPort=443&lt;/code&gt; option to your Hugo server command. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;DotJS’s auto routing does not limit you to a flat structure. In our example, we could also have placed our code in a file located at &lt;code&gt;.dynamic/date/time&lt;/code&gt; which would have made it available at the address &lt;code&gt;https://localhost/date/time&lt;/code&gt;. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you want full control over your routing, including the ability to use regular expressions in your route names and accessing global state, etc., you can do everything you can in Express using the &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#advanced-routing-routesjs-file&#34;&gt;advanced routing&lt;/a&gt; feature by declaring a &lt;code&gt;routes.js&lt;/code&gt; file in your &lt;code&gt;.dynamic&lt;/code&gt; fodler. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Note that the URL scheme is no longer &lt;code&gt;https&lt;/code&gt; but &lt;code&gt;wss&lt;/code&gt; since we’re talking about secure WebSocket routes now. If I had a penny for every time I wrote &lt;code&gt;https://&lt;/code&gt; when I meant to write &lt;code&gt;wss://&lt;/code&gt;… &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You might have noticed that we use an anonymous function expression in the &lt;code&gt;module.exports&lt;/code&gt; line whereas we used an arrow function expression in the previous (HTTPS) examples and even though we use an arrow function expression to define the event handler. This is not by accident; it has to do with scope. If you want to have access to the &lt;code&gt;this&lt;/code&gt; reference in your DotJS routes and access methods like &lt;code&gt;broadcast()&lt;/code&gt;, you cannot use an arrow function expression to define your module, you must use the &lt;code&gt;function&lt;/code&gt; keyword. Inside of your module, you are free to use arrow function expressions to your heart’s desire. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can easily &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#custom-error-pages&#34;&gt;replace the default error pages with your own custom ones&lt;/a&gt;. And as far as 404 errors go, you can reduce the number of them on the web in general and contribute towards &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#native-support-for-an-evergreen-web&#34;&gt;an evergreen web&lt;/a&gt; by making use of the native &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#native-cascading-archives-support&#34;&gt;cascading archives&lt;/a&gt; and &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#native-404-302-support&#34;&gt;404-to-302&lt;/a&gt; support in Site.js. &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Site.js does not have LiveReload and does not automatically restart the server when dynamic routes change at the moment. When working with static content, this means that you have to manually refresh the browser and when working with dynamic content you have to manually restart the server whenever you make code changes. I realise this is less than ideal and both of these are high on &lt;a href=&#34;https://source.ind.ie/site.js/app/issues&#34;&gt;my list of issues&lt;/a&gt; to address. &lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The examples on this page are live and connect to the instances of the app I have running at &lt;a href=&#34;https://ar.al/chat&#34;&gt;https://ar.al/chat&lt;/a&gt; (corresponding to the initial example, without server-side validation) and &lt;a href=&#34;https://ar.al/chat-final-version&#34;&gt;https://ar.al/chat-final-version&lt;/a&gt;. Needless to say, this site is served by Site.js. &lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You might have noticed that the &lt;code&gt;isValidString()&lt;/code&gt; function is the same as the one we used to implement the validation on our front-end. Since both our client-side and server-side code in Site.js is JavaScript (remember, it uses Node.js under the hood), we can pull that function into its own module and re-use it in both places. &lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On Windows (because Windows), you have to add quotes around the &lt;code&gt;@hostname&lt;/code&gt; option, so the command becomes &lt;code&gt;site &amp;quot;@hostname&amp;quot;&lt;/code&gt;. Blame Bill Gates. &lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;To use ngrok with your own domain name, you will to subscribe for &lt;a href=&#34;https://ngrok.com/pricing&#34;&gt;a Pro account&lt;/a&gt; from ngrok (at least this is what Laura and I use at &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;) and &lt;a href=&#34;https://ngrok.com/docs#http-custom-domains&#34;&gt;set up a subdomain on your own domain name per their instructions&lt;/a&gt;. &lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:13&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On Windows (because Windows), the sync function is not available as there’s currently no free and open rsync implementation that we can use like we can on Linux and macOS. &lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Site.js, now also on Windows 10</title>
      <link>https://ar.al/2019/10/03/site.js-now-also-on-windows-10/</link>
      <pubDate>Thu, 03 Oct 2019 11:30:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/10/03/site.js-now-also-on-windows-10/</guid>
      <description>&lt;style&gt;
  /* Windows can’t even take screenshots properly. Fix the borders. 🤦 */
  h2 + p &gt; img {
    outline: 1px solid black;
    outline-offset: -1px;
  }
&lt;/style&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2019/10/03/site.js-now-also-on-windows-10/site.js-thinkpad.jpeg&#34; alt=&#34;Photo of ThinkPad 440p. On screen: (left half) PowerShell session under Windows Terminal showing the installation of Site.js, the creation of the basic static site as explained in this post, and running Site.js. (right half) https://localhost in Microsoft Edge browser showing the words “Hello, world!”&#34;&gt;&lt;/p&gt;
&lt;p&gt;Last week, I bought a refurbished 7-year-old ThinkPad 440p&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; so I could test &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; under Windows.&lt;/p&gt;
&lt;p&gt;Long story short, Windows is still shit&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Plus, it’s now also a cesspit of surveillance&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. And, over the weekend, I ended up adding native Windows 10 support&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; to Site.js&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So if you’re on Windows 10 – out of necessity or preference – follow along for a quick look at how you can create your own static and dynamic web site using Site.js in a minute or two.&lt;/p&gt;
&lt;h2 id=&#34;1-install-sitejs-15-seconds&#34;&gt;1. Install Site.js (~15 seconds)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2019/10/03/site.js-now-also-on-windows-10/installation.png&#34; alt=&#34;Screenshot of the installation instructions on the Site.js web site&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://sitejs.org/install.txt&#34;&gt;Skim the source code for the installer&lt;/a&gt; and, when you’re happy it’s not doing anything nefarious, copy and paste the following command&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; into a PowerShell session running under &lt;a href=&#34;https://github.com/microsoft/terminal&#34;&gt;Windows Terminal&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PowerShell&#34; data-lang=&#34;PowerShell&#34;&gt;iex(&lt;span style=&#34;color:#007020&#34;&gt;iwr &lt;/span&gt;-UseBasicParsing https&lt;span style=&#34;&#34;&gt;:&lt;/span&gt;//sitejs.org/install.txt).Content&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;2-create-and-test-your-first-static-site--30-seconds&#34;&gt;2. Create and test your first static site (~ 30 seconds)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2019/10/03/site.js-now-also-on-windows-10/hello-world.png&#34; alt=&#34;Screenshot of the static site displaying its “Hello, world!” message in a web browser&#34;&gt;&lt;/p&gt;
&lt;p&gt;Enter the following three commands into your terminal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PowerShell&#34; data-lang=&#34;PowerShell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a demo folder to work in and switch to it.&lt;/span&gt;
mkdir demo; &lt;span style=&#34;color:#007020&#34;&gt;cd &lt;/span&gt;demo

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a static index route.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;Set-Content&lt;/span&gt; index.html &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello, world!&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Start serving your site with Site.js.&lt;/span&gt;
site&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Visit your new static site at &lt;code&gt;https://localhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you’re ready to move on, press &lt;code&gt;Ctrl+C&lt;/code&gt; to stop Site.js.&lt;/p&gt;
&lt;h2 id=&#34;3-create-and-test-your-first-dynamic-site-1-minute&#34;&gt;3. Create and test your first dynamic site (~1 minute)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2019/10/03/site.js-now-also-on-windows-10/date.png&#34; alt=&#34;Screenshot of the dynamic route displaying the current date and time in a web browser&#34;&gt;&lt;/p&gt;
&lt;p&gt;We’re going to use &lt;a href=&#34;https://sitejs.org/#dynamic&#34;&gt;DotJS&lt;/a&gt; to create a dynamic route. In Site.js, dynamic routes are placed in a folder called &lt;code&gt;.dynamic&lt;/code&gt; in your site’s root.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PowerShell&#34; data-lang=&#34;PowerShell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the .dynamic folder.&lt;/span&gt;
mkdir .dynamic

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create a dynamic route that displays the current time/date.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;Set-Content&lt;/span&gt; ./.dynamic/date.js &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;module.exports = (_, res)=&amp;gt;{res.end(`&amp;lt;pre&amp;gt;${new Date().toString()}&amp;lt;/pre&amp;gt;`)}&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Start Site.js.&lt;/span&gt;
site&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Visit your new dynamic route at &lt;code&gt;https://localhost/date&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Refresh to see that the date changes.&lt;/p&gt;
&lt;p&gt;In the example above, I wrote the route in a rather obfuscated manner for brevity. Here is the same route, re-written for clarity and better compatibility:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (request, response) =&amp;gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; now &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;().toString()
  response
    .type(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;html&amp;#39;&lt;/span&gt;)
    .end(&lt;span style=&#34;color:#4070a0&#34;&gt;`&amp;lt;pre&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;now&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;lt;/pre&amp;gt;`&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;theres-no-magic&#34;&gt;There’s no magic…&lt;/h2&gt;
&lt;p&gt;DotJS simply maps regular &lt;code&gt;.js&lt;/code&gt; files (see what I did there) to &lt;a href=&#34;https://expressjs.com/&#34;&gt;Express&lt;/a&gt; routes. All you have to do is write the logic for your routes. Site.js takes care of all the plumbing.&lt;/p&gt;
&lt;p&gt;Using DotJS, you can specify &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#get-and-post-routes&#34;&gt;HTTPS GET and POST routes&lt;/a&gt; as well as &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#websocket-wss-routes&#34;&gt;secure WebSocket routes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you need full flexibility in your routing, you can do anything you can with Express and &lt;a href=&#34;https://github.com/HenningM/express-ws&#34;&gt;Express-WS&lt;/a&gt; using &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#advanced-routing-routesjs-file&#34;&gt;advanced routing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope you enjoy using Site.js on Windows 10 and remember that you can use everything you’ve just learned on Linux and macOS too. In fact, there are a couple of things you can do on those platforms that you cannot on Windows 10.&lt;/p&gt;
&lt;h2 id=&#34;windows-10-limitations&#34;&gt;Windows 10 Limitations&lt;/h2&gt;
&lt;p&gt;On macOS and Linux, you can &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md#deployment-live-and-one-time-sync&#34;&gt;sync your local site to your remote server&lt;/a&gt;. This is not possible on Windows 10 as there isn’t a free and open &lt;a href=&#34;https://en.wikipedia.org/wiki/Rsync&#34;&gt;rsync&lt;/a&gt; implementation that we can use.&lt;/p&gt;
&lt;p&gt;Also, on Linux distributions with &lt;a href=&#34;https://freedesktop.org/wiki/Software/systemd/&#34;&gt;systemd&lt;/a&gt; (e.g., Ubuntu 18.04 LTS), you can run production servers. In fact, that’s how this post is being served to you right now.&lt;/p&gt;
&lt;p&gt;This blog, &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura’s site&lt;/a&gt;, and the &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation web site&lt;/a&gt; are all powered by Site.js.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;To learn more about Site.js, read through the &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js web site&lt;/a&gt; (and try out the interactive demo in the header), &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md&#34;&gt;read the Site.js documentation&lt;/a&gt;, and feel free to &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;hit me up on my Mastodon&lt;/a&gt; if you have any questions.&lt;/p&gt;
&lt;div id=&#39;fund-us&#39;&gt;
  &lt;h2&gt;Like this? Fund us!&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&#39;https://small-tech.org&#39;&gt;Small Technology Foundation&lt;/a&gt; is a tiny, independent not-for-profit.&lt;/p&gt;
  &lt;p&gt;We exist in part thanks to patronage by people like you. If you share &lt;a href=&#39;https://small-tech.org/about#small-technology&#39;&gt;our vision&lt;/a&gt; and want to support our work, please &lt;a href=&#39;https://small-tech.org/fund-us&#39;&gt;become a patron or donate to us&lt;/a&gt; today and help us continue to exist.&lt;/p&gt;
&lt;/div&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It was the cheapest way I could test under Windows, given that a license of Windows 10 Pro costs basically what I paid for it. And, I have to say, it runs like a champ. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Part of “having been around” is having started out on DOS at age 7 and stubbornly spent twenty odd years on DOS/Windows before overcoming my Stockholm syndrome. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;But that’s a story for a different post. And yes, I have the screenshots. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Note that Windows Subsystem for Linux (WSL) is not supported. Site.js uses &lt;a href=&#34;https://github.com/FiloSottile/mkcert&#34;&gt;mkcert&lt;/a&gt; to seamlessly provision development-time TLS certificates. The way mkcert does this is to unceremoniously create a certificate authority on your local machine. The problem is that under WSL, the certificate authority gets created under Linux, not Windows. And then you go and run your browser of choice under Windows and, oops, you get a certificate error. This is not a trivial issue to fix. If the mkcert folks add support for WSL in the future, Site.js should also benefit. Until then, Site.js won’t be supporting WSL. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Now, you might be asking yourself: “Aral, if you hate Windows so much, why did you spend time supporting it?” And my answer to that is when you’re building a bridge to a better place, you can’t have both its abutments on the shore you want to get to. You have to plant one of them on the shore you want to get away from.&lt;/p&gt;
&lt;p&gt;Case in point: I wouldn’t have been able &lt;a href=&#34;https://ar.al/681/&#34;&gt;to switch to Mac in 2006&lt;/a&gt; if it hadn’t been for cross-platform free and open source tools making the transition easier and Parallels enabling me to run Windows alongside OS X until I was ready to cut the cord entirely.&lt;/p&gt;
&lt;p&gt;This is also why I talk about Tincan being a bridge from the centralised web to the peer web.&lt;/p&gt;
&lt;p&gt;It’s no use building islands of utopia if the only people who can reach them are master swimmers. Our biggest challenge is building bridges. And that sometimes means spending time in places we would rather not. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On Linux and macOS, you can install Site.js with a single-line installation command that you copy from the web site. I wanted the Windows version to be as easy.&lt;/p&gt;
&lt;p&gt;This does mean the installer does a couple of “clever” things out of necessity like re-downloading itself and then launching the saved script in an elevated process to carry out the installation with administrator privileges. As far as the experience is concerned, it should be seamless. Please do let me know if it isn’t for you. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Small Technology Foundation, Site.js, and Tincan</title>
      <link>https://ar.al/2019/08/26/introducing-small-technology-foundation/</link>
      <pubDate>Tue, 27 Aug 2019 12:05:00 +1000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/08/26/introducing-small-technology-foundation/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/small-technology-foundation-header.jpeg&#34;
         alt=&#34;Laura and me with tin-can telephones in the Small Technology Foundation web site header.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Say “hello” to Small Technology Foundation.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Today, &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I want to introduce you to &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt;, where we will be continuing the work we started at &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; five years ago.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://small-tech.org/about/#history&#34;&gt;In those five years&lt;/a&gt;, we’ve developed a strong understanding of the problem (&lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;surveillance capitalism&lt;/a&gt;) and we’ve been &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;iterating on solutions&lt;/a&gt; to it.&lt;/p&gt;
&lt;p&gt;Our work led us to &lt;a href=&#34;https://ar.al/notes/so-long-and-thanks-for-all-the-fish/&#34;&gt;leave the UK&lt;/a&gt;, move to Sweden, and finally, last year, &lt;a href=&#34;https://laurakalbag.com/move-to-ireland/&#34;&gt;to settle in Ireland&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As part of our relocating to Ireland, we are in the process of shutting down our not-for-profit in the UK, Ind.ie (Article 12), and we’ve set up a new not-for-profit in Ireland.&lt;/p&gt;
&lt;p&gt;We’ve also refined how we articulate both the problem and the solution we are exploring. The result is Small Technology Foundation and the concept of &lt;a href=&#34;https://small-tech.org/about/#small-tech&#34;&gt;Small Tech&lt;/a&gt; as the polar opposite of – and &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;the antidote to&lt;/a&gt; – Big Tech and Silicon Valley’s church of startups and exponential growth.&lt;/p&gt;
&lt;h2 id=&#34;what-is-small-technology&#34;&gt;What is Small Technology?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/small-technology-tenets.jpeg&#34;
         alt=&#34;The tenets of Small Technology: easy to use, personal, private by default, share alike, peer to peer, interoperable, zero knowledge, non-commercial, non-colonial, inclusive&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The tenets of Small Technology.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://small-tech.org/about#small-technology&#34;&gt;Small Technology&lt;/a&gt; are everyday tools for everyday people designed to increase human welfare, not corporate profits.&lt;/p&gt;
&lt;h2 id=&#34;what-are-you-working-on&#34;&gt;What are you working on?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/site-js-screenshot.jpeg&#34;
         alt=&#34;Screenshot of SiteJS.org&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Site.js: what would a web development tool look like if it was designed for individuals, not startups or enterprises?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve personally spent the last six months creating &lt;a href=&#34;https://site.js&#34;&gt;Site.js&lt;/a&gt; – which I’m excited to announce here alongside the setup of our foundation. Site.js is the first step in &lt;a href=&#34;https://small-tech.org/research-and-development#the-plan&#34;&gt;our plan&lt;/a&gt; to evolve a &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;peer web&lt;/a&gt; based on the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Site.js makes it simple to set up your own single-tenant, secure personal web site (static and dynamic). How simple? Here’s how you’d deploy a “Hello, world!” site on your production server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install Site.js&lt;/span&gt;
wget -qO- https://sitejs.org/install | bash

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Create the site&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello, world&amp;#39;&lt;/span&gt; &amp;gt; index.html

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Enable a secure production server&lt;/span&gt;
site enable&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And what if you wanted a dynamic site?&lt;/p&gt;
&lt;p&gt;OK, create a file called &lt;code&gt;counter&lt;/code&gt; in &lt;code&gt;.dynamic/counter.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; counter &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;

module.exports &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (request, response) =&amp;gt; {
  response.type(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;html&amp;#39;&lt;/span&gt;).end(&lt;span style=&#34;color:#4070a0&#34;&gt;`You’ve visited this page &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt;counter&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; time&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;counter &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;s&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;site&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And hit &lt;code&gt;https://localhost/counter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Refresh to see the counter update.&lt;/p&gt;
&lt;p&gt;I call this &lt;a href=&#34;https://source.ind.ie/site.js/app#file-system-routing&#34;&gt;DotJS&lt;/a&gt; (think PHP for JavaScript) and it enables you to create secure, dynamic JavaScript servers with the full power of &lt;a href=&#34;https://nodejs.org&#34;&gt;Node.js&lt;/a&gt; and &lt;a href=&#34;https://expressjs.com/&#34;&gt;Express&lt;/a&gt; without having to build any of the plumbing.&lt;/p&gt;
&lt;p&gt;And it’s as easy to use &lt;a href=&#34;https://source.ind.ie/site.js/app/tree/master/examples/wss-basic-chat&#34;&gt;WebSockets&lt;/a&gt; with it too.&lt;/p&gt;
&lt;p&gt;Oh, and Site.js already powers this site, &lt;a href=&#34;https://small-tech.org&#34;&gt;the Small Technology Foundation site&lt;/a&gt; and &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura’s site&lt;/a&gt;. So we’re eating our own hamster food.&lt;/p&gt;
&lt;p&gt;The Small Technology Foundation web site is also an example of how to extend a static site (in this case, one that uses Hugo), with dynamic functionality. Check out the &lt;a href=&#34;https://small-tech.org/fund-us&#34;&gt;Fund Us&lt;/a&gt; page for &lt;a href=&#34;https://source.ind.ie/small-tech.org/site/tree/master/themes/small-tech/static/.dynamic&#34;&gt;an example of the WebSocket functionality&lt;/a&gt; powering the patronage pages (hint, hint) ;)&lt;/p&gt;
&lt;p&gt;To see more, &lt;a href=&#34;https://sitejs.org&#34;&gt;run the demo on SiteJS.org&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/site.js/app/tree/master/examples&#34;&gt;try some other examples&lt;/a&gt;, and &lt;a href=&#34;https://source.ind.ie/site.js/app/blob/master/README.md&#34;&gt;read through the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;img id=&#39;site-js-logo&#39; src=&#39;site-js-logo.svg&#39; alt=&#39;Site.js logo&#39;&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What’s next?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/tincan-architecture-single-person.svg&#34;
         alt=&#34;The architecture of Tincan, showing the always on web node as an untrusted node in a peer-to-peer network&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Tincan: bridges a peer-to-peer network with the centralised web.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The next step is to continue developing Site.js and to build on top of it a peer-to-peer network that bridges to the centralised web. We call this &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;Tincan&lt;/a&gt; (the latest evolution of &lt;a href=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2/&#34;&gt;Hypha&lt;/a&gt;, Indienet, and &lt;a href=&#34;https://2017.ind.ie/heartbeat/&#34;&gt;Heartbeat&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/08/26/introducing-small-technology-foundation/tincan-architecture-two-people.svg&#34;
         alt=&#34;The architecture of Tincan, showing the devices and untrusted web node of two people interacting&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Tincan: two people communicating.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;There are no big reveals here, we are not Silicon Valley (we’re the opposite of Silicon Valley). We’re in this for the long haul. We’re not a startup, we’re a &lt;em&gt;stayup&lt;/em&gt;. As such, we will continue to iterate and build on our work from the past five years.&lt;/p&gt;
&lt;img id=&#39;tincan-logo&#39; src=&#39;tincan-logo.svg&#39; alt=&#39;Tincan logo&#39;&gt;
&lt;h2 id=&#34;happy-we-exist-enable-us-to-continue-to-exist&#34;&gt;Happy we exist? Enable us to continue to exist.&lt;/h2&gt;
&lt;p&gt;In the past five years, &lt;a href=&#34;https://small-tech.org/fund-us/#bank-of-mom-dad&#34;&gt;my family has invested three homes to enable our work&lt;/a&gt;. We don’t have any more homes to invest.&lt;/p&gt;
&lt;p&gt;If you’re happy we exist, &lt;a href=&#34;https://small-tech.org/fund-us&#34;&gt;please become a patron and support us&lt;/a&gt; to ensure that you can still be happy we exist in the next five years.&lt;/p&gt;
&lt;p&gt;We are going to be moving more things  over from Ind.ie to Small Technology Foundation in the coming days (like our source code repository, etc.) and we’ll let you know when we do. And we’re going to continue to polish things up on the web site and improve Site.js. Just like everything else we do, this is yet another iteration.&lt;/p&gt;
&lt;p&gt;We will also be cancelling all patronages on Ind.ie so if you want to continue supporting us, please &lt;a href=&#34;https://small-tech.org/fund-us&#34;&gt;become a patron of Small Technology Foundation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;partner-with-us&#34;&gt;Partner with us&lt;/h2&gt;
&lt;p&gt;We are also looking for &lt;a href=&#34;https://small-tech.org/partners&#34;&gt;VPS hosts and domain name registrars to partner with&lt;/a&gt;. If you share our social mission of a kinder Internet built for people, not corporations and governments, please &lt;a href=&#34;https://small-tech.org/contact-us&#34;&gt;contact us&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;theres-a-turkish-saying-that-goes-from-drops-are-lakes-formed&#34;&gt;There’s a Turkish saying that goes “from drops, are lakes formed…”&lt;/h2&gt;
&lt;p&gt;After five years of working on this problem, I am more excited than ever with where we are and the progress we are making. I hope you will join us, as allies, on this journey and enable us to realise our dream of creating technology that works for people, not startups or enterprises; technology that protects human rights and democracy instead of eroding them.&lt;/p&gt;
&lt;p&gt;Visit &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt; today and &lt;a href=&#34;https://small-tech.org/contact-us&#34;&gt;let us know&lt;/a&gt; how you can help.&lt;/p&gt;
&lt;style&gt;
h2#what-s-next + figure &gt; img, #tincan-logo, #site-js-logo {
  display: block;
  width: 50%;
  margin-left: auto;
  margin-right: auto;
}

#site-js-logo {
  width: 15%;
}
&lt;/style&gt;
</description>
    </item>
    
    <item>
      <title>Small Technology at the Interactive Future Exhibition in Darmstadt</title>
      <link>https://ar.al/2019/07/10/small-technology-at-the-interactive-future-exhibition-in-darmstadt/</link>
      <pubDate>Wed, 10 Jul 2019 11:19:57 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/07/10/small-technology-at-the-interactive-future-exhibition-in-darmstadt/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/07/10/small-technology-at-the-interactive-future-exhibition-in-darmstadt/small-technology-talk-opening-slide.jpeg&#34;
         alt=&#34;Photo of my opening slide: Small Technology, written in string, joins two tin cans to create a tin can telephone. On one side, I’m speaking into it and, on the other, Laura is listening.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Small Technology: my opening slide.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’m about to take off for Frankfurt via Zurich to talk about Small Technology at the &lt;a href=&#34;https://imd.mediencampus.h-da.de/if19/&#34;&gt;Interactive Future Exhibition&lt;/a&gt; in Darmstadt tomorrow. This is the graduation event of students on the Interactive Media Design programme at the &lt;a href=&#34;https://h-da.de/&#34;&gt;Hochschule Darmstady University of Applied Sciences&lt;/a&gt;. It’s an honour to have been invited by the students to speak at their graduation event and I’m very much looking forward to seeing their projects and speaking with them.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>ar.al is now running on a new server</title>
      <link>https://ar.al/2019/07/06/ar.al-is-now-running-on-a-new-server/</link>
      <pubDate>Sat, 06 Jul 2019 17:11:22 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/07/06/ar.al-is-now-running-on-a-new-server/</guid>
      <description>&lt;p&gt;Just a quick note that this site is now running on a new (as of yet unreleased) server. Most of the functionality should be the same as before with the caveat that the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; version is temporarily unavailable. I’ll be re-enabling it once I’ve added native DAT support to the server itself.&lt;/p&gt;
&lt;p&gt;More info soon.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Have you heard about Silicon Valley’s unpaid research and development department? It’s called the EU.</title>
      <link>https://ar.al/2019/06/22/have-you-heard-about-silicon-valleys-unpaid-research-and-development-department-its-called-the-eu/</link>
      <pubDate>Sat, 22 Jun 2019 06:42:04 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/06/22/have-you-heard-about-silicon-valleys-unpaid-research-and-development-department-its-called-the-eu/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/06/22/have-you-heard-about-silicon-valleys-unpaid-research-and-development-department-its-called-the-eu/spiderman-pointing-meme.jpg&#34;
         alt=&#34;Spiderman pointing at Spiderman meme.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;You… yes you.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Who should you thank for Facebook’s Libra?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.wired.co.uk/article/facebook-libra-startup-privacy-analysis&#34;&gt;“One of the UK’s leading privacy researchers”&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.wired.co.uk/article/facebook-libra-startup-privacy-analysis&#34;&gt;University College London&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.wired.co.uk/article/facebook-libra-startup-privacy-analysis&#34;&gt;The DECODE project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And, if you’re an EU citizen who pays their taxes,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Surprised? Don’t be.&lt;/p&gt;
&lt;h2 id=&#34;none-of-this-was-unforeseen&#34;&gt;None of this was unforeseen&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Today, the EU acts like an unpaid research and development department for Silicon Valley. We fund startups, which, if they’re successful, get sold to companies in Silicon Valley. If they fail, the European taxpayer foots the bill. This is madness.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;Slavery 2.0 and how to avoid it: a practical guide for cyborgs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;so-what-should-we-do-instead&#34;&gt;So what should we do instead?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Let’s instead invest in many small and independent not-for-profit organisations and task them with building the ethical alternatives. Let’s get them to compete with each other while doing so. Let’s take what we know works from Silicon Valley (small organisations working iteratively, competing, and failing fast) and remove what is toxic: venture capital, exponential growth, and exits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead of startups, lets build stayups in Europe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead of disposable businesses that either fail fast or become malignant tumours, let’s fund organisations that either fail fast or become sustainable providers of social good.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The EC must stop funding startups and invest in stayups instead. Invest €5M in ten stayups in each area where we want ethical alternatives. Unlike a startup, when stayups are successful, they don’t exit. They can’t get bought by Google or Facebook. They remain sustainable European not-for-profits working to deliver technology as a social good.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Furthermore, funding for a stayup must come with a strict specification of the character of the technology it will build. Goods built using public funds must be public goods. Free Software Foundation Europe is currently raising awareness along these lines with their “public money, public code” campaign. However we must go beyond “open source” to stipulate that technology created by stayups must be not only public but also impossible to enclose. For software and hardware, this means using licenses that are copyleft. A copyleft license ensures that if you build on public technology, you must share alike. Share-alike licenses are essential so that our efforts do not become a euphemism for privatisation and to avoid a tragedy of the commons. Corporations with deep pockets must not be able to take what we create with public funds, invest their own millions on top, and not share back the value they’ve added.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;Slavery 2.0 and how to avoid it: a practical guide for cyborgs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;sounds-great-wish-youd-told-someone-about-it&#34;&gt;Sounds great, wish you’d told someone about it…&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;When I mentioned this plan several years ago at the European Parliament, my words fell on deaf ears. It’s still not too late to try. But every day that we delay, surveillance capitalism becomes ever more entrenched within the fabric of our lives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;Slavery 2.0 and how to avoid it: a practical guide for cyborgs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;but-sometimes-you-have-to-repeat-things-before-people-take-notice&#34;&gt;But sometimes you have to repeat things before people take notice…&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;We must also start to fund ethical, decentralised, free and open alternatives from the commons for the common good. We must ensure that these organisations have social missions ingrained in their very existence that cannot be circumvented. We must make sure that these organisations cannot be bought by surveillance capitalists. Today, we are funding startups and acting as an unofficial and unpaid research and development arm for Silicon Valley. We fund startups and, if they’re successful, they get bought by the Googles and Facebooks. If they’re unsuccessful, the EU taxpayer foots the bill. It’s time for the European Commission and the EU to stop being useful idiots for Silicon Valley, and for us to fund and support our own ethical technological infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Given how much information Silicon Valley, and thus the US government, has on EU citizens, this is not just a matter of our human rights and the future of our democracy, but also the national security of European countries and that of the EU. This isn’t to say that we must wall ourselves off or create a European silo. On the contrary, we must ensure that the technological infrastructure we fund and build is free and open, decentralised, and interoperable so that anyone, anywhere in the world can use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.nesta.org.uk/blog/aral-balkan-and-laura-kalbag-were-not-sleepwalking-dystopian-future-were-there-today/&#34;&gt;Aral Balkan and Laura Kalbag: We&amp;rsquo;re not sleepwalking into a dystopian future, we&amp;rsquo;re there today&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;if-only-we-knew-how-to-fix-this&#34;&gt;If only we knew how to fix this…&lt;/h2&gt;
&lt;p&gt;If only there was an &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;Ethical Design Manifesto&lt;/a&gt; that initiatives like DECODE could have adopted that would have prevented this.&lt;/p&gt;
&lt;p&gt;If only we knew &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;the general shape of the solution&lt;/a&gt; so we could invest EU taxpayer funds in projects that fit it.&lt;/p&gt;
&lt;p&gt;If only there were &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Technology&lt;/a&gt; principles that clearly stated how to fund and build technology that Facebook would never want to buy.&lt;/p&gt;
&lt;p&gt;Oh, wait, we did know but we were so busy being arrogant…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Small tech” cannot afford to be so small-minded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.theguardian.com/commentisfree/2019/may/11/big-tech-progressive-vision-silicon-valley?ref=hvper.com&#34;&gt;Evgeny Morozov&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;… that we ended up being the useful idiots that helped out Facebook.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A key component in Libra comes from an EU-funded academic startup recently acquired by Facebook. (It was apart of the Decode Project, on whose advisory board I sit). Ask yourself how Europe can compete with US/China when Facebook can simply snatch such R&amp;amp;D&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/evgenymorozov/status/1141743293509840896&#34;&gt;Evgeny Morozov&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oops!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Rerun cloud-init on multipass</title>
      <link>https://ar.al/2019/06/15/rerun-cloud-init-on-multipass/</link>
      <pubDate>Sat, 15 Jun 2019 22:17:58 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/06/15/rerun-cloud-init-on-multipass/</guid>
      <description>&lt;p&gt;Today, I had the need to experiment with rerunning &lt;a href=&#34;https://cloud-init.io/&#34;&gt;cloud-init&lt;/a&gt; on a virtual machine created with &lt;a href=&#34;https://multipass.run/&#34;&gt;multipass&lt;/a&gt;. You can &lt;a href=&#34;https://blog.ubuntu.com/2018/04/02/using-cloud-init-with-multipass&#34;&gt;use cloud-init with multipass&lt;/a&gt; by specifying a cloud-init.yaml file when creating your instance. e.g,&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;multipass launch --name my-instance --cloud-init ./cloud-init.yaml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is all well and good and works as you would expect.&lt;/p&gt;
&lt;p&gt;However, today, I wanted to experiment with running cloud-init on an already-provisioned instance. My use case is that the lovely folks at &lt;a href=&#34;https://eclips.is&#34;&gt;Eclips.is&lt;/a&gt; (a project by &lt;a href=&#34;https://greenhost.net&#34;&gt;Greenhost&lt;/a&gt; and the &lt;a href=&#34;https://www.opentech.fund/&#34;&gt;Open Technology Fund&lt;/a&gt;) who have given &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; a generous amount of free hosting do not yet support cloud-init when provisioning a server. So I wanted to see if I can run my cloud-init script after I provisioned a server and I wanted to test that out locally using multipass.&lt;/p&gt;
&lt;h2 id=&#34;how-to-rerun-cloud-init&#34;&gt;How to rerun cloud-init&lt;/h2&gt;
&lt;p&gt;Ordinarily, it is actually quite simply to rerun cloud-init after a server has been provisioned.&lt;/p&gt;
&lt;p&gt;First, you use the &lt;code&gt;clean&lt;/code&gt; command to remove the current build artifacts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo cloud-init clean&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, you specify your &lt;em&gt;meta-data&lt;/em&gt; and &lt;em&gt;user-data&lt;/em&gt; files in the &lt;em&gt;/var/lib/cloud/nocloud-net/&lt;/em&gt; directory, which you must create:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo mkdir /var/lib/cloud/nocloud-net/
sudo touch /var/lib/cloud/nocloud-net/meta-data
sudo nano /var/lib/cloud/nocloud-net/user-data&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, paste your cloud-config file into the editor and save (in nano, Ctrl-o + Ctrl-x).&lt;/p&gt;
&lt;p&gt;Finally, run the &lt;code&gt;init&lt;/code&gt; command to re-initialise your instance using cloud-init:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo cloud-init init&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, in theory, this should work.&lt;/p&gt;
&lt;p&gt;In practice, however, we end up with a &lt;em&gt;/var/lib/cloud/instance/user-data.txt&lt;/em&gt; file that only contains the vendor-data provided by multipass, not our user-data.&lt;/p&gt;
&lt;h2 id=&#34;multipass&#34;&gt;Multipass&lt;/h2&gt;
&lt;p&gt;Say you have created a multipass instance without specifying a &lt;em&gt;cloud-init.yaml&lt;/em&gt; file, like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;multipass launch --name my-instance&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To run your cloud-config file, you first have to SSH into the box. You can either do this by running &lt;code&gt;multipass ls&lt;/code&gt; and finding the IP address and then using the account and key that multipass automatically creates to ssh to it (e.g., &lt;code&gt;ssh multipass@&amp;lt;ENTER THE IP-ADDRESS OF YOUR INSTANCE HERE&amp;gt; -i $(locate multipass | grep .*id_rsa)&lt;/code&gt;) or you can use the handy shortcut that multipass provides:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;multipass shell &amp;lt;NAME OF YOUR INSTANCE&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, to find the culprit, run the &lt;code&gt;status&lt;/code&gt; command in verbose mode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo cloud-init status --long&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which should give you something along the lines of:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;status: running
time: Sat, &lt;span style=&#34;color:#40a070&#34;&gt;15&lt;/span&gt; Jun &lt;span style=&#34;color:#40a070&#34;&gt;2019&lt;/span&gt; 21:11:55 +0000
detail:
DataSourceNoCloudNet &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;seed&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;/var/lib/cloud/seed/nocloud-net,/dev/sr0&lt;span style=&#34;color:#666&#34;&gt;][&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;dsmode&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;net&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first seed source you see is the one you defined. The second one is the one that multipass passes in. You can cat it to see what it has:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo cat /dev/sr0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should see the vendor-data specified and, at the end of that, you should see an empty cloud-init section:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#cloud-init&lt;/span&gt;
&lt;span style=&#34;color:#666&#34;&gt;{}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is also what I saw at the end of &lt;em&gt;/var/lib/cloud/instance/user-data.txt&lt;/em&gt; and that led to me to think that perhaps our datasource was being ignored or overwritten.&lt;/p&gt;
&lt;p&gt;You can find the data sources defined in the file &lt;em&gt;/etc/cloud/cloud.cfg.d/90_dpkg.cfg&lt;/em&gt;. To see its contents:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;cat /etc/cloud/cloud.cfg.d/90_dpkg.cfg&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And you should find a long list similar to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;datasource_list: &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt; NoCloud, ConfigDrive, OpenNebula, DigitalOcean, Azure, AltCloud, OVF, MAAS, GCE, OpenStack, CloudSigma, SmartOS, Bigstep, Scaleway, AliYun, Ec2, CloudStack, Hetzner, IBMCloud, None &lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I had a suspicion that the &lt;code&gt;ConfigDrive&lt;/code&gt; data source might have been overriding my user-data, so I edited that file and reduced the list down to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;datasource_list: &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt; NoCloud, None &lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, after running &lt;code&gt;sudo cloud-init clean&lt;/code&gt; and &lt;code&gt;sudo cloud-init init&lt;/code&gt; again, I had multipass successfully using my cloud-config from &lt;em&gt;/var/lib/cloud/nocloud-net/user-data&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Information about cloud-init seems hard to come by so while I’m mostly documenting this for my own sake, I hope it also ends up helping someone else out in the future.&lt;/p&gt;
&lt;h2 id=&#34;useful-resources&#34;&gt;Useful resources&lt;/h2&gt;
&lt;h3 id=&#34;from-cloud-init-docs&#34;&gt;From Cloud-init docs&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/&#34;&gt;Cloud-init docs&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/capabilities.html&#34;&gt;Capabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/datasources.html#no-cloud&#34;&gt;Datasources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html#instance-metadata&#34;&gt;Instance Metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/format.html&#34;&gt;User-Data Formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/vendordata.html&#34;&gt;Vendor Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html&#34;&gt;NoCloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/dir_layout.html&#34;&gt;Directory layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/merging.html&#34;&gt;Merging User-Data Sections&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;other&#34;&gt;Other&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.whiteboardcoder.com/2016/04/install-cloud-init-on-ubuntu-and-use.html&#34;&gt;Install cloud-init on Ubuntu and use locally… NoCloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.archlinux.org/index.php/Cloud-init#Systemd_integration&#34;&gt;Cloud-init page ArchLinux wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blackboxsw.github.io/category/cloud-init.html&#34;&gt;Cloud-init v.18.2: CLI subcommands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://powersj.io/post/cloud-init-multipass/&#34;&gt;Using cloud-init with Multipass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;How to re-run cloud-init without reboot: &lt;a href=&#34;https://stackoverflow.com/a/50911376&#34;&gt;answer with new cloud-init syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://askubuntu.com/questions/153486/how-do-i-boot-ubuntu-cloud-images-in-vmware&#34;&gt;How do I boot Ubuntu Cloud images in vmware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-use-cloud-config-for-your-initial-server-setup&#34;&gt;How to use cloud-config for your intial server setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.digitalocean.com/community/tutorials/an-introduction-to-cloud-config-scripting&#34;&gt;An introduction to cloud-config scripting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Export/import issues with GitLab CE</title>
      <link>https://ar.al/2019/06/08/export-import-issues-with-gitlab-ce/</link>
      <pubDate>Sat, 08 Jun 2019 16:04:16 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/06/08/export-import-issues-with-gitlab-ce/</guid>
      <description>&lt;p&gt;GitLab CE (the free/open source version of GitLab) has an import issues feature but doesn’t have an export issues feature (because, not enterprise, apparently).&lt;/p&gt;
&lt;p&gt;So if you fork a project and want to transfer the issues also, you’re out of luck. Unless you use the API, that is.&lt;/p&gt;
&lt;p&gt;So I &lt;a href=&#34;https://duckduckgo.com&#34;&gt;ducked around&lt;/a&gt; and found that a kind soul by the name of Joseph Heenan had created &lt;a href=&#34;https://gitlab.com/emobix/get-all-gitlab-issues-as-csv&#34;&gt;a Perl script to export your GitLab issues in CSV format&lt;/a&gt;. &lt;strong&gt;Spoiler:&lt;/strong&gt; &lt;strong&gt;do not run this as-is&lt;/strong&gt; and import the resulting CSV into GitLab CE as you will get corrupted issues. Because apparently GitLab CE has its own, incompatible CSV format compared to GitLab EE (because, not enterprise, apparently). So keep reading…&lt;/p&gt;
&lt;h2 id=&#34;perl-like-its-1999&#34;&gt;Perl like it’s 1999&lt;/h2&gt;
&lt;p&gt;Of course, since I’ve never gotten anything written in Perl ever to run first time in the last three decades of working with computers, I first had to fix the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;Can&lt;span style=&#34;&#34;&gt;&amp;#39;&lt;/span&gt;t locate Text/CSV_XS.pm in @INC&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which I did by installing the Text::CVS_XS module:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;/usr/bin/perl -MCPAN -e&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;install Text::CSV_XS&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a good point to grab a coffee while Perl installs half the known Internet onto your computer.&lt;/p&gt;
&lt;h2 id=&#34;i-say-ce-you-say-ee&#34;&gt;I say CE, you say EE&lt;/h2&gt;
&lt;p&gt;Now, if you &lt;a href=&#34;https://gitlab.com/emobix/get-all-gitlab-issues-as-csv/blob/master/get-all-project-issues.pl#L12&#34;&gt;update the script&lt;/a&gt; with your GitLab installation, repository, and access information and run it, it should work but &lt;strong&gt;you cannot import this export into your GitLab CE instance&lt;/strong&gt;. When I did, I ended up with a bunch of issues that the URL of the issue on the original project for the title and the body. Because apparently GitLab CE’s import CSV format isn’t compatible with GitLab EE’s export format. So, instead, I had to hack the script to leave just the two fields documented in the GitLab CE import CSV alert box (title and description). Honestly, this is all I wanted anyway.&lt;/p&gt;
&lt;h2 id=&#34;finally&#34;&gt;Finally&lt;/h2&gt;
&lt;p&gt;&lt;strike&gt;Here’s the updated script that should work. It creates exports a CSV file from your GitLab CE instance called &lt;em&gt;issues.csv&lt;/em&gt; in the current directory that you can import back into GitLab CE to get the titles and bodies of issues transferred to your fork.&lt;/strike&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; FFS, I just realised that it imports all issues as open. Seriously, GitLab, can you please put both the fucking import and export feature into CE and stop making people jump through hoops for such basic functionality? Thankfully, I only have a handful of closed issues so I’m going to close them manually. I don’t have more time to waste on this right now but I hope this helps you tweak it further if you need to.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#!/usr/bin/perl -w&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;strict&lt;/span&gt;;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;LWP::UserAgent&lt;/span&gt;;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;Text::CSV_XS&lt;/span&gt; &lt;span style=&#34;color:#c65d09&#34;&gt;qw( csv )&lt;/span&gt;;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;JSON::PP&lt;/span&gt; &lt;span style=&#34;color:#c65d09&#34;&gt;qw(decode_json)&lt;/span&gt;;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Uncomment these for debugging&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# use LWP::ConsoleLogger::Easy qw( debug_ua );&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# use Data::Dumper;&lt;/span&gt;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$PROJECT_ID&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&amp;lt;project-id&amp;gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# numeric project id, can be found in project -&amp;gt; general settings&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$GITLAB_API_PRIVATE_TOKEN&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;&amp;lt;your-token&amp;gt;&amp;#39;&lt;/span&gt;; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# obtained from https://gitlab.com/profile/personal_access_tokens&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$baseurl&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&amp;lt;your-gitlab-domain&amp;gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# or https://gitlab.com if using Gitlab.com&lt;/span&gt;

&lt;span style=&#34;color:#bb60d5&#34;&gt;$baseurl&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;api/v4/&amp;#34;&lt;/span&gt;;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$issuesurl&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$baseurl&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;projects/&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$PROJECT_ID&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;/issues&amp;#34;&lt;/span&gt;;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;@issues&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; ();
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$page&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$totalpages&lt;/span&gt;;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;do&lt;/span&gt;
{
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;%query_hash&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; (
      &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;per_page&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;,
      &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;page&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$page&lt;/span&gt;
  );

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Fetching page $page&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;(&lt;span style=&#34;color:#007020&#34;&gt;defined&lt;/span&gt;(&lt;span style=&#34;color:#bb60d5&#34;&gt;$totalpages&lt;/span&gt;)?&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34; (of $totalpages)&amp;#34;&lt;/span&gt;:&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;;

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$ua&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;LWP::UserAgent&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt;();
  &lt;span style=&#34;color:#bb60d5&#34;&gt;$ua&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;default_header(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;PRIVATE-TOKEN&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$GITLAB_API_PRIVATE_TOKEN&lt;/span&gt;);

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$uri&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;URI&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt;(&lt;span style=&#34;color:#bb60d5&#34;&gt;$issuesurl&lt;/span&gt;);
  &lt;span style=&#34;color:#bb60d5&#34;&gt;$uri&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;query_form(&lt;span style=&#34;color:#bb60d5&#34;&gt;%query_hash&lt;/span&gt;);
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$resp&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$ua&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;get(&lt;span style=&#34;color:#bb60d5&#34;&gt;$uri&lt;/span&gt;);
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$resp&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;is_success) {
      &lt;span style=&#34;color:#007020&#34;&gt;die&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$resp&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;status_line;
  }
  &lt;span style=&#34;color:#bb60d5&#34;&gt;$totalpages&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;int&lt;/span&gt;(&lt;span style=&#34;color:#bb60d5&#34;&gt;$resp&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;header(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;X-Total-Pages&amp;#34;&lt;/span&gt;));

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$resptext&lt;/span&gt;;
  &lt;span style=&#34;color:#bb60d5&#34;&gt;$resptext&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$resp&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;decoded_content;

  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$issuedata&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; decode_json(&lt;span style=&#34;color:#bb60d5&#34;&gt;$resptext&lt;/span&gt;);

  &lt;span style=&#34;color:#007020&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#bb60d5&#34;&gt;@issues&lt;/span&gt;, &lt;span style=&#34;color:#bb60d5&#34;&gt;@&lt;/span&gt;{&lt;span style=&#34;color:#bb60d5&#34;&gt;$issuedata&lt;/span&gt;});
}
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#bb60d5&#34;&gt;$page&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;++&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$totalpages&lt;/span&gt;);

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$outputfname&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;issues.csv&amp;#34;&lt;/span&gt;;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$csv&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;Text::CSV_XS&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; ({ binary &lt;span style=&#34;color:#666&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;, eol &lt;span style=&#34;color:#666&#34;&gt;=&amp;gt;&lt;/span&gt; $/ });
&lt;span style=&#34;color:#007020&#34;&gt;open&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$fh&lt;/span&gt;, &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&amp;gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#bb60d5&#34;&gt;$outputfname&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;die&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;$outputfname: $!&amp;#34;&lt;/span&gt;;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;@headings&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [
  &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Title&amp;#34;&lt;/span&gt;,
  &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Description&amp;#34;&lt;/span&gt;,
];
&lt;span style=&#34;color:#bb60d5&#34;&gt;$csv&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;print&lt;/span&gt; (&lt;span style=&#34;color:#bb60d5&#34;&gt;$fh&lt;/span&gt;, &lt;span style=&#34;color:#bb60d5&#34;&gt;@headings&lt;/span&gt;) &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$csv&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;error_diag;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;foreach&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$i&lt;/span&gt; (&lt;span style=&#34;color:#bb60d5&#34;&gt;@issues&lt;/span&gt;)
{
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;@values&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; [
      &lt;span style=&#34;color:#bb60d5&#34;&gt;$i&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;},
      &lt;span style=&#34;color:#bb60d5&#34;&gt;$i&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;{&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;description&amp;#39;&lt;/span&gt;},
  ];
  &lt;span style=&#34;color:#bb60d5&#34;&gt;$csv&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;print&lt;/span&gt; (&lt;span style=&#34;color:#bb60d5&#34;&gt;$fh&lt;/span&gt;, &lt;span style=&#34;color:#bb60d5&#34;&gt;@values&lt;/span&gt;) &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$csv&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;-&amp;gt;&lt;/span&gt;error_diag;
}

&lt;span style=&#34;color:#007020&#34;&gt;close&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$fh&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;die&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;$outputfname: $!&amp;#34;&lt;/span&gt;;

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Issues saved to $outputfname\n&amp;#34;&lt;/span&gt;;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>The Do’s and Don’ts of Tech Regulation</title>
      <link>https://ar.al/2019/05/11/the-dos-and-donts-of-tech-regulation/</link>
      <pubDate>Sat, 11 May 2019 09:19:36 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/05/11/the-dos-and-donts-of-tech-regulation/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/782174241.jpg?mw=1920&amp;mh=1080&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/335559425.m3u8?s=652f9a54268f3ab7f427765c79f27091c67ba008&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/335559425.hd.mp4?s=9f537f2e402ef85bbdc9d11e29fda655151aab1f&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;The Al Jazeera segment from yesterday. &lt;a href=&#39;https://www.aljazeera.com/news/2019/05/facebook-zuckerberg-discusses-hate-speech-macron-190510144412745.html&#39;&gt;Source&lt;/a&gt; (&lt;strong&gt;Warning:&lt;/strong&gt; The video is embedded here without trackers. If you watch it on Al Jazeera you will be tracked, including by Facebook).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Yesterday, I was interviewed on Al Jazeera news about &lt;a href=&#34;https://www.aljazeera.com/news/2019/05/facebook-zuckerberg-discusses-hate-speech-macron-190510144412745.html&#34;&gt;the Macron-Zuckerberg meeting&lt;/a&gt; &lt;strong&gt;(Warning: the linked page on Al Jazeera includes privacy-eroding trackers, including Facebook.)&lt;/strong&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; in which they apparently decided on a framework of “co-regulation.”&lt;/p&gt;
&lt;p&gt;This is not what I mean when I talk about the need to regulate surveillance capitalists.&lt;/p&gt;
&lt;h2 id=&#34;co-regulation-is-bullshit&#34;&gt;Co-regulation is bullshit&lt;/h2&gt;
&lt;p&gt;Co-regulation is basically saying “hey, Big Corp, come, sit at the table and let’s decide together how to regulate you”. Alongside lobbying, revolving doors, and public-private partnerships, it is part and parcel of institutional corruption.&lt;/p&gt;
&lt;p&gt;It is not the job of democratically-elected governments to elevate unelected corporate bodies to their own level to help write the rules of their own regulation. It is the job of such governments, in line with their democratic mandate, to regulate corporations in the public interest to reduce their harms.&lt;/p&gt;
&lt;p&gt;But what else do we expect of Macron exactly? He is a neoliberal who is on the record for wanting to make France “a start-up nation.”&lt;/p&gt;
&lt;p&gt;Furthermore, we exist within a climate where our democratic governments are so clueless, corrupt, or powerless (or all of the above) that instead of protecting the interests of their citizens, they are &lt;a href=&#34;https://www.politico.eu/article/denmark-silicon-valley-tech-ambassador-casper-klynge/&#34;&gt;sending ambassadors to Silicon Valley corporations&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;piece-of-the-pie-regulation&#34;&gt;Piece Of The Pie Regulation&lt;/h2&gt;
&lt;p&gt;When governments do manage to find the will to regulate it is usually for one of two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Government surveillance and censorship&lt;/li&gt;
&lt;li&gt;Business concerns (antitrust and competition)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, for almost entirely&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; the wrong reasons.&lt;/p&gt;
&lt;p&gt;From now on, I’m going to call this type of regulation &lt;em&gt;Piece of The Pie Regulation&lt;/em&gt; because it essentially boils down to either governments or the greater business community wanting a piece of the pie while ignoring the human rights concerns at the heart of surveillance capitalism and &lt;a href=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/&#34;&gt;the business model of people farming&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;government-surveillance-and-censorship&#34;&gt;Government surveillance and censorship&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;“The NSA didn&amp;rsquo;t wake up one morning and say let&amp;rsquo;s spy on everybody. They woke up one morning and said all these companies are spying on everybody, let&amp;rsquo;s get ourselves a copy.”&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.schneier.com/news/archives/2014/06/schneier_most_of_the.html&#34;&gt;Bruce Schneier&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first form of Piece of the Pie Regulation is where governments regulate tech companies because they want access to their data, algorithms, and platforms for their own surveillance, policing, and censorship desires.&lt;/p&gt;
&lt;p&gt;We can see this aspect feature in yesterday’s Macron-Zuckerberg deal that sees the government of France gain unspecified insider access to Facebook’s data and platform.&lt;/p&gt;
&lt;p&gt;While ideally Big Tech would love to have zero government interference in its affairs (this is a power struggle, after all), it is not fundamentally opposed to this sort of regulation. As Mark Zuckerberg said after yesterday’s meeting: “I’m encouraged and optimistic about the regulatory framework that will be put in place.” If a tech CEO is enthusiastic about a piece of government regulation, the one thing you can be sure of is that that regulation is not in &lt;em&gt;your&lt;/em&gt; interests as an individual.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I wake up every morning and I fight regulation, it’s what I do, it’s my job.” — &lt;a href=&#34;https://www.newyorker.com/tech/annals-of-technology/mark-zuckerberg-elizabeth-warren-and-the-case-for-regulating-big-tech&#34;&gt;Eric Schmidt, then-CEO of Google&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The last thing we want are regulations that make corporations like Facebook better censors, better filters of our reality, and the authority on what is and isn’t acceptable and who is and isn’t a terrorist. And the only thing worse than a corporation with that sort of power is a government with access to it when it has a police force and and army attached.&lt;/p&gt;
&lt;p&gt;We live in a world where &lt;a href=&#34;https://2018.ar.al/notes/my-speech-at-general-assembly-in-berlin-november-2017/&#34;&gt;autocrats like Turkish president Erdoğan&lt;/a&gt; maintain a stranglehold on the media and imprison people who criticise them as terrorists. How long do you think it will take for the Turkish government to knock on Facebook’s door demanding the same sort of access that the government of France now has?&lt;/p&gt;
&lt;h3 id=&#34;antitrust-and-competition&#34;&gt;Antitrust and competition&lt;/h3&gt;
&lt;p&gt;In monetary terms, antitrust regulation has netted surveillance capitalists the largest losses. Especially in Europe, regulators like Margrethe Vestager have levied fines ranging from millions to &lt;a href=&#34;https://www.bloomberg.com/news/articles/2019-03-20/google-fined-1-7-billion-by-eu-over-advertising-contracts&#34;&gt;billions of euros&lt;/a&gt; on companies like Google and Facebook.&lt;/p&gt;
&lt;p&gt;This is good. Yes, fine them. Yes, tax the shit out of them&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. Yes, &lt;a href=&#34;https://www.theguardian.com/technology/2019/may/09/facebook-chris-hughes-break-up-company-zuckerberg-power&#34;&gt;break them up&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But we must not limit our regulatory frame to antitrust and competition law. Because there is also a darker desire by regulators that approach the issue solely from this angle: they want all businesses ­– especially new startups – to have a piece of the tasty data pie held by companies like Google and Facebook.&lt;/p&gt;
&lt;p&gt;Instead of calling for companies like Facebook to be broken up – &lt;a href=&#34;https://www.newyorker.com/tech/annals-of-technology/mark-zuckerberg-elizabeth-warren-and-the-case-for-regulating-big-tech&#34;&gt;like Elizabeth Warren in the US is doing&lt;/a&gt; – Vestager, for example, advocates that “we should get access to data from tech companies to push back and get into the market.” Which market? The surveillance capitalism market, where it is taken for granted that companies created centralised services that collect and monetise your data. No, this market &lt;em&gt;is&lt;/em&gt; the problem.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/ronstedt/status/1126076816580595712&#34;&gt;Vestager’s call&lt;/a&gt; mirrors those of &lt;a href=&#34;https://techcrunch.com/2019/03/13/competition-policy-must-change-to-help-startups-fight-winner-takes-all-platforms-says-uk-report/&#34;&gt;a recent government-commissioned report in the UK&lt;/a&gt; that suggests we “force the largest tech companies to open up to smaller firms by providing access to key data sets.” In other words, smaller firms should have a piece of the pie.&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;The pie is toxic.&lt;/p&gt;
&lt;p&gt;We need to bin the pie and bake a different, non-toxic pie.&lt;/p&gt;
&lt;p&gt;Giving more people the ability to sell toxic pies does not solve the problem of toxic pies. If anything, it perpetuates the myth that there is no alternative to surveillance capitalism. Such a failure of imagination is what is currently starving ethical alternatives from the attention and investment they need to exist.&lt;/p&gt;
&lt;h2 id=&#34;regulation-dos&#34;&gt;Regulation Do’s&lt;/h2&gt;
&lt;p&gt;So proper regulation is one half of tackling the problem of surveillance capitalism (the other half being investment in ethical alternatives). But what would proper regulation look like?&lt;/p&gt;
&lt;p&gt;In order of effectiveness:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Algorithmic transparency:&lt;/strong&gt; we should be calling for surveillance capitalists to open up their algorithms so we know exactly what they are doing with our data. Politicians like Marietje Schaake &lt;a href=&#34;https://marietjeschaake.eu/en/opinion-measles-outbreak-is-a-reminder-of-the-power-of-viral-information&#34;&gt;have recently started to flirt with the idea&lt;/a&gt; even if they’re not calling it by name yet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data minimisation:&lt;/strong&gt; If we’re serious about regulating surveillance capitalists, this is the route we must take. See my proposal for a &lt;a href=&#34;https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/&#34;&gt;General Data Minimisation Regulation&lt;/a&gt; for an example that could kill surveillance capitalism tomorrow (good luck finding the political will, backbone, or imagination to implement that today).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;We need regulation that limits the ability of surveillance capitalists to gather data, profile us, and grow. We must tax the shit out of them, break them up, and limit the data they can collect. What we must not do is open up their ill-begotten data to other companies and governments or implement wide-scale government surveillance and censorship alongside the existing corporate one.&lt;/p&gt;
&lt;p&gt;We must go beyond Piece Of The Pie Regulation and beyond the “terrorists and pedophiles” (fear/security) and “business, business, business” frames to a human rights frame for regulation.&lt;/p&gt;
&lt;p&gt;As long as we judge success based on what’s good for a “Digital Single Market”, we face an impossible challenge in getting policymakers to consider human rights, human welfare, and the health of our democracies as success criteria. Just as with the climate crisis, whether or not we can effectively regulate Big Tech depends on whether or not we can move beyond judging success based solely on economic indicators.&lt;/p&gt;
&lt;p&gt;This is why I was happy to be part of the &lt;a href=&#34;http://www.commonsnetwork.org/news/reframing-digital-europe/&#34;&gt;Reframing Digital Europe&lt;/a&gt; initiative  alongside the Commons Network. Our work there has resulted in an alternative vision and policy frame for technology in the EU that we call &lt;a href=&#34;http://www.commonsnetwork.org/news/new-a-vision-for-a-shared-digital-europe/&#34;&gt;Shared Digital Europe&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Surveillance capitalism cannot be reformed. No amount of regulation will magically transform a factory farm for human beings – which is what Facebook, Google, etc., are – into an animal sanctuary. What effective regulation can do is limit their harms and give ethical alternatives a chance to get off the ground.&lt;/p&gt;
&lt;p&gt;So regulation is one half of the solution. The other half is incentivising the ethical alternatives.&lt;/p&gt;
&lt;h2 id=&#34;beyond-regulation-beyond-surveillance-capitalism&#34;&gt;Beyond regulation; beyond surveillance capitalism&lt;/h2&gt;
&lt;p&gt;An ethical alternative to surveillance capitalism in which we own and control our own data will not arise magically out of business as usual. It will not be funded by the same institutions that fund surveillance capitalism. It will not have the same success criteria. It will require investment from the commons in creating &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;a peer web&lt;/a&gt; and &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Technology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to understand just what’s at stake (personhood and democracy), how things are at the moment (not good), what alternatives and stopgaps exist today (a few, even with zero funding), and the shape of the alternative ethical technological infrastructure we must invest in and build, &lt;a href=&#34;https://vimeo.com/333198966&#34;&gt;watch this bootleg recording of my recent Bucerius Lab Lecture on Small Technology in Hamburg&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can use a tracker blocker like our own &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; or &lt;a href=&#34;https://github.com/gorhill/uBlock/&#34;&gt;uBlock Origin&lt;/a&gt; to protect yourself. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I have nothing against antitrust regulation as long as it is not the only frame by which we approach regulating surveillance capitalists. Breaking up monopolies is great. Let’s do that. Let’s fine them for anticompetitive behaviour, of course. But we must move beyond an competition frame to a human rights frame in order to effectively regulate surveillance capitalists. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Given its de facto violation of human rights and toxic ramifications on our democracies, it’s time to start talking about taxing Big Data like Big Tobacco. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Slavery 2.0 and how to avoid it: a practical guide for cyborgs</title>
      <link>https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/</link>
      <pubDate>Thu, 02 May 2019 15:09:33 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/magazine-cover.jpeg&#34;
         alt=&#34;Magazine on wooden table; cover.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The cover of Issue 32 of the Kulturstiftung des Bundes magazine.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;small&gt;This is the original English version of an article that I wrote for &lt;a href=&#34;https://www.kulturstiftung-des-bundes.de/de/magazin/magazin_32.html&#34;&gt;Issue 32&lt;/a&gt; of the &lt;a href=&#34;https://www.kulturstiftung-des-bundes.de/&#34;&gt;Kulturstiftung des Bundes&lt;/a&gt; (The German Federal Cultural Foundation) &lt;a href=&#34;https://www.kulturstiftung-des-bundes.de/de/magazin/&#34;&gt;magazine&lt;/a&gt;. You can also read &lt;a href=&#34;https://www.kulturstiftung-des-bundes.de/de/magazin/magazin_32/sklaverei_20_und_wie_man_sie_vermeiden_kann_eine_praktische_anleitung_fuer_cyborgs.html&#34;&gt;the German version&lt;/a&gt;. Thanks to &lt;a href=&#34;https://www.mauromorales.com/&#34;&gt;Mauro Morales&lt;/a&gt;, there is now also a &lt;a href=&#34;https://www.mauromorales.com/esclavitud-2-0-y-como-evitarla.html&#34;&gt;Spanish translation&lt;/a&gt;.&lt;/small&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You are most likely a cyborg and you don’t even know it.&lt;/p&gt;
&lt;p&gt;Do you have a smartphone?&lt;/p&gt;
&lt;p&gt;You’re a cyborg.&lt;/p&gt;
&lt;p&gt;Do you use a computer? Or the web?&lt;/p&gt;
&lt;p&gt;Cyborg!&lt;/p&gt;
&lt;p&gt;More generally, if you use digital and networked technology today, you are a cyborg. You don’t have to implant yourself with microchips. You don’t have to resemble Robocop. You are a cyborg because you extend your biological abilities using technology.&lt;/p&gt;
&lt;p&gt;Reading that definition, you might take pause: “But wait, human beings have been doing that for far longer than digital technology has existed”. And you’d be right.&lt;/p&gt;
&lt;p&gt;We were cyborgs long before the first bug crawled into the first vacuum tube of the first mainframe computer.&lt;/p&gt;
&lt;p&gt;The caveman brandishing a spear and lighting a fire was the original cyborg. Galileo gazing into the heavens with his telescope was both a Renaissance man and a cyborg. When you pop in your contact lenses in the morning, you’re a cyborg.&lt;/p&gt;
&lt;p&gt;Throughout our history as a species, technology has enhanced our senses. It has afforded us greater mastery and control of both our own lives and the world around us. Equally, technology has been used to oppress and exploit us – as anyone who has ever stared down the barrel of their oppressor’s rifle will readily attest.&lt;/p&gt;
&lt;p&gt;‘Technology,’ according to Melvin Kranzberg’s first law of technology, ‘is neither good nor bad; nor is it neutral.’&lt;/p&gt;
&lt;p&gt;So what decides whether technology contributes to our welfare, human rights, and democracy or erodes them? What separates good technology from bad technology? And, while we’re at it, what separates Galileo’s telescope and your contact lenses from Google and Facebook? And why does it matter whether we see ourselves as cyborgs or not?&lt;/p&gt;
&lt;p&gt;We must all try to understand the answers to these questions. The price of not doing so may be very high indeed. These are not merely questions about technology. They are questions that cut to the heart of what it means to be human in the digital and networked age. How we choose to answer these questions holds fundamental consequences for our welfare, both personally and as a society. The answers we choose will determine the character of our societies and, in the long term, possibly even impact the survival of our species.&lt;/p&gt;
&lt;h2 id=&#34;ownership-and-control-of-the-self-in-the-digital-and-networked-age&#34;&gt;Ownership and control of the self in the digital and networked age&lt;/h2&gt;
&lt;p&gt;Imagine a world where you’re assigned a device at birth that watches and listens and follows you from that moment onwards. It can also read your mind.&lt;/p&gt;
&lt;p&gt;Over the years, this device records your every thought, every word, every movement, and every interaction. It sends all this information about you to a powerful mainframe computer owned by a multinational corporation. There, these aspects of your self are collated using algorithms to create a &lt;em&gt;simulation&lt;/em&gt; of you. The corporation uses your simulation as a &lt;em&gt;digital proxy&lt;/em&gt; to manipulate your behaviour.&lt;/p&gt;
&lt;p&gt;Your digital proxy is invaluable. It is everything that makes you who you are (apart from your physical body). The corporation understands that it does not have to own your physical body to own you. Detractors call the system Slavery 2.0.&lt;/p&gt;
&lt;p&gt;All day long, the corporation subjects your simulation to tests. What do you like? What makes you happy? What makes you sad? What do you fear? Who do you love? What are you going to do this afternoon? It uses the insights it derives from these tests to get you to do what it wants. Perhaps to buy a new dress or vote for a certain politician.&lt;/p&gt;
&lt;p&gt;The corporation is political. It must continue to survive, grow, and thrive. It cannot be hindered by regulations. So it must influence political discourse. Thankfully, everyone who is a politician today was also assigned the same device you were at birth. So the corporation owns their digital proxies too. This makes it much easier for the corporation to get its way.&lt;/p&gt;
&lt;p&gt;All this said, the corporation is not omniscient. It can still make mistakes. It might infer incorrectly – based on your thoughts, words, and actions – that you are a terrorist when you are not. When the corporation gets it right, your digital proxy is an invaluable tool for manipulating your behaviour. When it gets it wrong, it might get you sent to prison. Either way, you lose.&lt;/p&gt;
&lt;p&gt;Sounds like a cyberpunk science-fiction dystopia, doesn’t it?&lt;/p&gt;
&lt;p&gt;Replace ‘the corporation’ with ‘Silicon Valley.’ Replace powerful mainframe computer with ‘The Cloud.’ Replace ‘the device’ with ‘your smart phone and your smart home assistant and your smart city and your smart this, that, and other.’&lt;/p&gt;
&lt;p&gt;Welcome to Earth, circa present day.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/05/02/slavery-2.0-and-how-to-avoid-it-a-practical-guide-for-cyborgs/magazine-article.jpeg&#34;
         alt=&#34;Magazine on wooden table, open, showing two-page article spread.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The German version of the article in Kulturstiftung des Bundes magazine.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;surveillance-capitalism&#34;&gt;Surveillance Capitalism&lt;/h2&gt;
&lt;p&gt;We live in a world where a handful of multinational corporations have unfettered streaming access to the most intimate details of our lives. Their devices watch and listen and track us on our persons, in our homes, on the Web, and (increasingly) on our sidewalks and on our streets. These are not tools we own and control. They are the eyes and ears of the socio-techno-economic system that Shoshana Zuboff calls ‘surveillance capitalism’.&lt;/p&gt;
&lt;p&gt;Just as in our fictional cyberpunk dystopia, the robber barons of Silicon Valley are not merely content with watching and listening. For example, Facebook announced at their developer conference in 2017 that they had 60 engineers working on literally reading your mind&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Earlier, I asked what it is that separates Galileo’s telescope and your contact lenses from the wares of Facebook, Google, and other surveillance capitalists. Understanding the answer to that question is crucial to understanding the extent to which the very concept of personhood in under threat by surveillance capitalism.&lt;/p&gt;
&lt;p&gt;When Galileo used his telescope, only he saw what he was seeing and only he knew what he was looking at. The same is true for when you wear your lenses. If Galileo had bought his telescope from Facebook, Facebook, Inc., would have recorded everything he saw. Similarly, if you get your contacts from Google, they will come with embedded cameras and Alphabet, Inc., will see what you’re seeing. (Google doesn’t make those lenses yet, but they have a patent for it&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. In the meanwhile, if you can’t wait, Snapchat makes spectacles with cameras in them.)&lt;/p&gt;
&lt;p&gt;When you use a pencil to write in your diary, neither the pencil nor your diary know what you’ve written. When you write your thoughts into a Google document, Google knows every word.&lt;/p&gt;
&lt;p&gt;When you send a letter to a friend by regular post, the post office does not know what you’ve written. It’s a crime for anyone else to open your envelope. When you send your friend an instant message on Facebook Messenger, Facebook reads every word.&lt;/p&gt;
&lt;p&gt;When you sign into Google Play Services on an Android phone, your every move and interaction is meticulously logged, sent to Google, stored forever, analysed, and used against you in the court of surveillance capitalism.&lt;/p&gt;
&lt;p&gt;It used to be that we read newspapers. Today, newspapers read us. As you watch YouTube, so does YouTube watch you.&lt;/p&gt;
&lt;p&gt;You get the idea.&lt;/p&gt;
&lt;p&gt;Unless we (as individuals) own and control our technology, ‘smart’ is a euphemism for ‘surveillance.’ A smart phone is a tracking device, a smart home is an interrogation cell, and a smart city is a panopticon.&lt;/p&gt;
&lt;p&gt;Google, Facebook, and other surveillance capitalist are factory farms for human beings. They make their billions by farming you for your data and exploiting that intimate insight into your life to manipulate your behaviour.&lt;/p&gt;
&lt;p&gt;They are scanners for human beings. They exist to digitise you, to own that digital copy, and to use it as a proxy to grow even larger and more powerful.&lt;/p&gt;
&lt;p&gt;We must understand that these corporations are not anomalies. They are the norm. They are the mainstream. The mainstream of technology today is a toxic spill of American crony capitalism that threatens to engulf the entire planet. We are hardly immune to its fumes here in Europe.&lt;/p&gt;
&lt;p&gt;Our politicians are quickly entranced by the millions these corporations spend in the lobbies of Brussels. They are beguiled by the wisdom of Singularity University (not a university). Meanwhile, our schools stock Chromebooks for our children. Our taxes are lowered, so that surveillance capitalists are not unduly burdened should they want to order another Guinness. And our institutionally-corrupt policymakers are too busy organising data protection conferences keynoted by Google and Facebook to protect our interests. I know, because I spoke at one last year. The speaker from Facebook was fresh out of his job at the French data protection office, renowned for the beauty and efficiency of its revolving doors.&lt;/p&gt;
&lt;p&gt;Something has to change.&lt;/p&gt;
&lt;p&gt;And I’m increasingly convinced that if that change is to come at all, it must come from Europe.&lt;/p&gt;
&lt;p&gt;Silicon Valley is not going to solve the problem it created. Mainly because companies like Google and Facebook do not see billions in profit as a problem. Surveillance capitalism isn’t broken by its own success criteria. It works perfectly for companies like Google and Facebook. They are laughing all the way to bank while laughing in the faces of regulators whose comical fines barely exceed a couple of days of revenue. It’s been said that “punishable by fine” means “legal for rich people”&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. That’s doubly true when it comes to regulating trillion-dollar multinational corporations.&lt;/p&gt;
&lt;p&gt;Similarly, venture capital is not going to invest in the solutions that would destroy the immensely lucrative business model it helped fund.&lt;/p&gt;
&lt;p&gt;So when you see initiatives like the so-called Centre for Humane Technology with venture capitalists and ex-Google employees on board, ask some questions. And maybe spare a few more questions for organisations that purport to be creating ethical alternatives while being funded by surveillance capitalists. Mozilla, for example, takes hundreds of millions of dollars from Google every year&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. It has taken more than a billion dollars from them in total. Are you happy to entrust them with building the ethical alternatives?&lt;/p&gt;
&lt;p&gt;If we want to chart a different path forward in Europe, we must fund and build technology differently. We must have the courage to diverge from our friends across the pond. We must have the self-confidence to tell Silicon Valley and their lobbyists that we’re not buying what they’re selling.&lt;/p&gt;
&lt;p&gt;And we must base all this on a solid foundation of human rights law. Did I say ‘human rights?’ I meant cyborg rights.&lt;/p&gt;
&lt;h2 id=&#34;cyborg-rights-are-human-rights&#34;&gt;Cyborg rights are human rights&lt;/h2&gt;
&lt;p&gt;We are faced with a crisis in human rights law that goes all the way back to what we mean by ‘human.’&lt;/p&gt;
&lt;p&gt;Traditionally, we draw the boundaries of the human self at our biological boundaries. Furthermore, we have a system of laws and justice that aims to protect the integrity of those boundaries and thus the dignity of the self. We call this system human rights law.&lt;/p&gt;
&lt;p&gt;Sadly, this definition of the self is no longer adequate to fully protect us in the digital and networked age.&lt;/p&gt;
&lt;p&gt;In this new age, we extend our biological abilities using digital and networked technologies. We extend our minds and our selves using modern technology. Therefore, we must extend our understanding of the boundaries of the self to include the technologies by which we extend our selves. By extending the definition of the self, we can ensure that human rights law covers and thus protects the entirety of the self in the digital and networked age.&lt;/p&gt;
&lt;p&gt;As cyborgs, we are &lt;em&gt;sharded beings&lt;/em&gt;. Parts of us live on our phones, parts on a server somewhere, parts on a laptop. The integrity of the self in the digital and networked age is the sum total of the integrity of those shards.&lt;/p&gt;
&lt;p&gt;So cyborg rights are human rights as applied to the cyborg self. What we do not need are a separate set of – probably lesser – ‘digital rights.’ This is why The Universal Declaration of Cyborg Rights&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; is not a self-contained document but an addendum to The Universal Declaration of Human Rights.&lt;/p&gt;
&lt;p&gt;While constitutional protection of cyborg rights is a necessary long term goal, we do not have to wait for constitutional change before acting. We can and must start protecting ourselves by creating ethical alternatives to mainstream technology.&lt;/p&gt;
&lt;h2 id=&#34;ethical-technology&#34;&gt;Ethical technology&lt;/h2&gt;
&lt;p&gt;An ethical technology&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; is a tool that you own and control. It is a tool designed to make your life kinder and easier. It is a tool that enhances your abilities and improves your life. It is a tool that only acts for – and never against – your interests.&lt;/p&gt;
&lt;p&gt;Conversely, an unethical technology is a tool owned and controlled by corporations and governments. It furthers their interests at the expense of yours. It is a shiny trap designed to capture your attention, addict you, track your every move, and profile you. It is a factory farm disguised as a playground.&lt;/p&gt;
&lt;p&gt;Unethical technology is toxic for our human rights, welfare, and democracy.&lt;/p&gt;
&lt;h2 id=&#34;planting-better-seeds&#34;&gt;Planting better seeds&lt;/h2&gt;
&lt;p&gt;Ethical technology does not grow on trees; you have to fund it. How you fund it matters.&lt;/p&gt;
&lt;p&gt;Unethical technology is funded by venture capital. Venture capital doesn’t invest in a business, it invests in the sale of the business. It also invests in very risky businesses. A venture capitalist in Silicon Valley will invest, say, $5 million in 10 different startups, knowing that 9 of them will fail. So he (he’s usually a he), needs that 10th one to be a billion-dollar “unicorn” so he can get five to ten times his money back. (It’s not even his money, it belongs to his clients.) The only business model we know in technology that delivers that sort of growth is people farming. Slavery paid well. Slavery 2.0 pays well too.&lt;/p&gt;
&lt;p&gt;We should not be surprised that a system that values cancer-like growth above all else has resulted in tumours like Google and Facebook. What is astounding is that we seem to be celebrating the tumours instead of treating the patient. And more perplexing still, we appear doggedly determined to infect ourselves with the same disease here in Europe.&lt;/p&gt;
&lt;p&gt;Let’s not.&lt;/p&gt;
&lt;p&gt;Let’s fund ethical alternatives.&lt;/p&gt;
&lt;p&gt;From the commons.&lt;/p&gt;
&lt;p&gt;For the common good.&lt;/p&gt;
&lt;p&gt;Yes, that means with our taxes. It’s kind of what they’re for (to build shared infrastructure for the common good that advances the welfare of our people and our societies). If the word “tax” scares you or sounds too old-fashioned, just replace it with “mandatory crowdfunding” or “democratised philanthropy”.&lt;/p&gt;
&lt;p&gt;Funding ethical technology from the commons doesn’t mean that we get governments to build, own, or control our technologies. Neither does it mean that we nationalise companies like Google and Facebook. Break them up! Sure. Regulate them! Of course. Do anything and everything that limits their abuses as much as possible. But the only thing worse than a corporate panopticon is a state one (not that these two things are mutually exclusive).&lt;/p&gt;
&lt;p&gt;Let’s not replace one big brother with another.&lt;/p&gt;
&lt;p&gt;Let’s instead invest in many small and independent not-for-profit organisations and task them with building the ethical alternatives. Let’s get them to compete with each other while doing so. Let’s take what we know works from Silicon Valley (small organisations working iteratively, competing, and failing fast) and remove what is toxic: venture capital, exponential growth, and exits.&lt;/p&gt;
&lt;p&gt;Instead of startups, lets build &lt;em&gt;stayups&lt;/em&gt; in Europe.&lt;/p&gt;
&lt;p&gt;Instead of disposable businesses that either fail fast or become malignant tumours, let’s fund organisations that either fail fast or become sustainable providers of social good.&lt;/p&gt;
&lt;p&gt;When I mentioned this plan several years ago at the European Parliament, my words fell on deaf ears. It’s still not too late to try. But every day that we delay, surveillance capitalism becomes ever more entrenched within the fabric of our lives.&lt;/p&gt;
&lt;p&gt;We must overcome this failure of imagination and base our technological infrastructure on those principles that are the best of humanity: human rights, social justice, and democracy.&lt;/p&gt;
&lt;p&gt;Today, the EU acts like an unpaid research and development department for Silicon Valley. We fund startups, which, if they’re successful, get sold to companies in Silicon Valley. If they fail, the European taxpayer foots the bill. This is madness.&lt;/p&gt;
&lt;p&gt;The EC must stop funding startups and invest in stayups instead. Invest €5M in ten stayups in each area where we want ethical alternatives. Unlike a startup, when stayups are successful, they don’t exit. They can’t get bought by Google or Facebook. They remain sustainable European not-for-profits working to deliver technology as a social good.&lt;/p&gt;
&lt;p&gt;Furthermore, funding for a stayup must come with a strict specification of the character of the technology it will build. Goods built using public funds must be public goods. Free Software Foundation Europe is currently raising awareness along these lines with their “public money, public code” campaign. However we must go beyond “open source” to stipulate that technology created by stayups must be not only public but also impossible to enclose. For software and hardware, this means using licenses that are &lt;em&gt;copyleft&lt;/em&gt;. A copyleft license ensures that if you build on public technology, you must &lt;em&gt;share alike&lt;/em&gt;. Share-alike licenses are essential so that our efforts do not become a euphemism for privatisation and to avoid a tragedy of the commons. Corporations with deep pockets must not be able to take what we create with public funds, invest their own millions on top, and not share back the value they’ve added.&lt;/p&gt;
&lt;p&gt;Finally, we must stipulate that the technologies built by stayups are peer to peer. Your data must remain on devices that you own and control. And when you communicate, you must communicate directly (without a “man in the middle” like Google or Facebook). Where this is absolutely technically infeasible, any private data controlled by a third party (for example a web host) must be end-to-end encrypted and you must hold the only key.&lt;/p&gt;
&lt;p&gt;Even without any statistically-relevant investment in ethical technology, there are already tiny groups working on alternatives. Mastodon&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;, a federated ethical alternative to Twitter was created by one person in their early twenties. A couple of people got together to create a project called Dat&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt; that could be the basis of a decentralised web. For over ten years, volunteers have been running an alternative non-commercial domain name system called OpenNIC&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; that could empower everyone with their own place on the Web…&lt;/p&gt;
&lt;p&gt;If even without any support these seeds are beginning to sprout, imagine what we could achieve if we actually started watering them and planting new ones. By investing in stayups, we can start a fundamental shift towards ethical technology in Europe. We can start to build a bridge from where we are to where we want to be. From a world in which corporations own us by proxy to a world in which we own ourselves. From a world in which we are again becoming property to a world in which we remain as people. From surveillance capitalism to a &lt;em&gt;peerocracy&lt;/em&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.theguardian.com/technology/2017/apr/19/facebook-mind-reading-technology-f8&#34;&gt;Facebook has 60 people working on how to read your mind&lt;/a&gt; (The Guardian) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.cnet.com/news/after-google-glass-google-developing-contact-lens-camera/&#34;&gt;After Google Glass, Google developing contact lens camera&lt;/a&gt; (c|net) &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://theconcealedweapon.tumblr.com/post/172134093705/an-action-being-punishable-by-a-fine-basically&#34;&gt;theconcealedweapon.tumblr.com&lt;/a&gt; &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://techcrunch.com/2017/11/14/mozilla-terminates-its-deal-with-yahoo-and-makes-google-the-default-in-firefox-again/&#34;&gt;Mozilla terminates its deal with Yahoo and makes Google the default in Firefox again&lt;/a&gt; (Techcrunch) &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://cyborgrights.eu&#34;&gt;Universal Declaration of Cyborg Rights&lt;/a&gt; &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;Ethical Design Manifesto&lt;/a&gt; &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://joinmastodon.org&#34;&gt;Join Mastodon&lt;/a&gt; &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://datproject.org&#34;&gt;Dat project&lt;/a&gt; &lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://opennic.org&#34;&gt;OpenNIC&lt;/a&gt; &lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server 9.1.0: Better error handling</title>
      <link>https://ar.al/2019/04/30/indie-web-server-9.1.0-better-error-handling/</link>
      <pubDate>Tue, 30 Apr 2019 13:16:29 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/30/indie-web-server-9.1.0-better-error-handling/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server&lt;/a&gt; version 9.1.0 is a minor release that handles the following two, related (and common), errors more gracefully:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trying to enable a web server daemon when one is already enabled&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Previously, this would succeed even though the earlier server would continue to be served. Now, it gives an error and instructs you to disable the existing server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trying to enable a web server daemon when some other service was using port 443&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Previously, this would succeed but your server would only start running once (and if) the service blocking port 443 exited. Now, it gives an error and exits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trying to start a regular web server process when some other service is using port 443&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Previously, this would fail but show a stack trace. Now, it shows a friendly error and no longer shows the stack trace.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server 9.0.0: Housekeeping</title>
      <link>https://ar.al/2019/04/29/indie-web-server-9.0.0-housekeeping/</link>
      <pubDate>Mon, 29 Apr 2019 19:59:24 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/29/indie-web-server-9.0.0-housekeeping/</guid>
      <description>&lt;p&gt;I just released &lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server&lt;/a&gt; version 9.0.0.&lt;/p&gt;
&lt;p&gt;This is mostly a housekeeping release and nearly all the changes are under the hood.&lt;/p&gt;
&lt;h2 id=&#34;maintainability&#34;&gt;Maintainability&lt;/h2&gt;
&lt;p&gt;I refactored the command-line application quite extensively to pull out all the commands into their own modules and remove any redundancy in the command-line argument parsing.&lt;/p&gt;
&lt;p&gt;The whole thing is far more robust now and ready for the two new features I’ve been itching to add.&lt;/p&gt;
&lt;h2 id=&#34;breaking-changes&#34;&gt;Breaking changes&lt;/h2&gt;
&lt;p&gt;There is a major version bump, however, and that’s due to two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The syntax of the local proxy option has changed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You now have to specify the &lt;code&gt;proxy&lt;/code&gt; command explicitly (providing a URL instead of a path is no longer automatically interpreted as a request to create a proxy server):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;web-server proxy http://localhost:1313&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is to ensure that there is no ambiguity when I implement one the new features I’m planning that will also take a URL as its argument.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I’ve dropped Windows support.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Indie Web Server should continue to work under Node.js in Windows but it will no longer be supported and could break in the future.&lt;/p&gt;
&lt;p&gt;If you want to use Indie Web Server on Windows, I highly recommend that you use &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/wsl/install-win10&#34;&gt;Windows Subsystem for Linux&lt;/a&gt; to do so (with, for example, &lt;a href=&#34;https://www.microsoft.com/en-ie/p/ubuntu/9nblggh4msv6?rtc=1&#34;&gt;Ubuntu&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that the codebase is much more maintainable, I look forward to implementing two new features in the coming days. I’ll announce them once they’re ready.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server 8.2.0: Cascading archives for an evergreen web</title>
      <link>https://ar.al/2019/04/20/indie-web-server-8.2.0-cascading-archives-for-an-evergreen-web/</link>
      <pubDate>Sat, 20 Apr 2019 16:52:58 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/20/indie-web-server-8.2.0-cascading-archives-for-an-evergreen-web/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/04/20/indie-web-server-8.2.0-cascading-archives-for-an-evergreen-web/evergreen-web.jpeg&#34;
         alt=&#34;Terminal screenshot showing Indie Web Server serving an archive using the new cascading archives feature.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Cascading archives for an evergreen web.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I just released version 8.2.0 of &lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server&lt;/a&gt;. This version brings with it a cascading archives feature to make it easier than ever for you to support an evergreen web and not break existing links as you evolve your sites.&lt;/p&gt;
&lt;h3 id=&#34;easier-than-the-404-to-302-technique-for-static-archives&#34;&gt;Easier than the 404 to 302 technique for static archives&lt;/h3&gt;
&lt;p&gt;Indie Web Server already had native support for &lt;a href=&#34;https://4042302.org&#34;&gt;the 404 → 302 technique&lt;/a&gt; where you convert 404s into 302 redirects to earlier versions of your site, thereby preserving those links. However, that does require that you keep the older version of your site online separately. If the older version of your site is a dynamic one, that makes sense. But what if it is a static site or what if you can take a static backup of it? It would be so much simpler if Indie Web Server could serve that static archive for you automatically as a fallback if a path cannot be found on the latest version of your site.&lt;/p&gt;
&lt;p&gt;This is exactly what the new cascading archives feature does.&lt;/p&gt;
&lt;p&gt;And, just like everything else with Indie Web Server, it requires zero configuration, relying instead on a naming convention.&lt;/p&gt;
&lt;h3 id=&#34;how-it-works&#34;&gt;How it works&lt;/h3&gt;
&lt;p&gt;Let’s say you are serving your site from the &lt;code&gt;my-site&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;To use the cascading archives feature, just put the archive of your site into a folder named &lt;code&gt;my-site-archive-1&lt;/code&gt; so that the directory tree looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;|- my-site
|- my-site-archive-1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;p&gt;If a path cannot be found in &lt;code&gt;my-site&lt;/code&gt;, it will be served from &lt;code&gt;my-site-archive-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You’re not limited to a single archive either (and hence the “cascading” bit in the name of the feature). As you have multiple older versions of your site, just add them to new folders and increment the archive index in the name. e.g., &lt;code&gt;my-site-archive-2&lt;/code&gt;, &lt;code&gt;my-site-archive-3&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Paths in &lt;code&gt;my-site&lt;/code&gt; will override those in &lt;code&gt;my-site-archive-3&lt;/code&gt; and those in &lt;code&gt;my-site-archive-3&lt;/code&gt; will, similarly, override those in &lt;code&gt;my-site-archive-2&lt;/code&gt; and so on.&lt;/p&gt;
&lt;p&gt;Your old links will never die but if you do replace them with never content in never versions, those will take precedence.&lt;/p&gt;
&lt;p&gt;I hope you enjoy the new cascading archives feature of Indie Web Server and I hope it makes it easier and more economical for you to contribute to an evergreen web where we try not to break links unless we absolutely have to.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Fixing the icon regression in Pop!_OS 19.04</title>
      <link>https://ar.al/2019/04/20/fixing-the-icon-regression-in-pop_os-19.04/</link>
      <pubDate>Sat, 20 Apr 2019 14:05:12 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/20/fixing-the-icon-regression-in-pop_os-19.04/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/04/20/fixing-the-icon-regression-in-pop_os-19.04/consistent-icons.jpeg&#34;
         alt=&#34;Screenshot of some consistent Pop!_OS icons from Pop!_OS 18.10 and earlier.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Ah, consistent icons.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After installing Pop!_OS 19.04 yesterday, my desktop experience became an eyesore as the previously consistent icon set was replaced with what I can only describe as &lt;a href=&#34;https://mastodon.ar.al/@aral/101958586404597562&#34;&gt;icon vomit soup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to get your pre-19.04 consistent icon set back, do this:&lt;/p&gt;
&lt;h3 id=&#34;method-1-update&#34;&gt;Method 1 (update)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://pop-planet.info/forums/projects/pop-classic.3/&#34;&gt;Pop!_Planet has released a “Pop Classic” icon theme&lt;/a&gt; that you can download and install.&lt;/p&gt;
&lt;p&gt;You should use this method.&lt;/p&gt;
&lt;h3 id=&#34;method-2&#34;&gt;Method 2&lt;/h3&gt;
&lt;p&gt;Update: This was how I originally got the icons back. The Pop Classic method, above, is better as you can switch back and forth more easily. I’m now using that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Backup the current icons and get them out of the way.&lt;/span&gt;
sudo mv /usr/share/icons/Pop /usr/share/icons/Pop-19.04

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Clone the older branch and install it.&lt;/span&gt;
git clone --single-branch --branch&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;master_cosmic --depth&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; https://github.com/pop-os/icon-theme.git
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; icon-theme
sudo make install
sudo make post-install&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the post-install step failed for me but the consistent icons are back and my eyes are happier.&lt;/p&gt;
&lt;h3 id=&#34;consistency-is-the-1-problem-with-desktop-linux&#34;&gt;Consistency is the #1 problem with desktop Linux&lt;/h3&gt;
&lt;p&gt;Here’s hoping that Linux distributions understand that the biggest problem with desktop Linux today isn’t lack of apps, functionality, or performance… it’s consistency.&lt;/p&gt;
&lt;p&gt;Distributions must enforce consistency. System76 had the right idea originally. It’s sad to see this regression in their approach.&lt;/p&gt;
&lt;p&gt;Remember that the right of millions of people who use your operating system to a pleasing functional and aesthetic experience far outweighs the vanities of developers to subject people to their proud app icon concoctions in Gimp. We’re developers. We make things that we and other people to use. It’s not about us. It’s about them.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server 8.1.1: Reverse proxy (local mode)</title>
      <link>https://ar.al/2019/04/18/indie-web-server-8.1.1-reverse-proxy-local-mode/</link>
      <pubDate>Thu, 18 Apr 2019 15:25:42 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/18/indie-web-server-8.1.1-reverse-proxy-local-mode/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/04/18/indie-web-server-8.1.1-reverse-proxy-local-mode/reverse-proxy.jpeg&#34;
         alt=&#34;Screenshot of Indie Web Server running as a reverse proxy. Messages include: Proxy created -&amp;gt; ws://localhost:1313, Proxy created -&amp;gt; http://localhost:1313, Upgrading to WebSocket, rewriting hugo livereload URL to use WebSocket proxy.&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;I just released &lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server&lt;/a&gt; version 8.1.1 which introduces a reverse proxy feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s Indie Web Server? &lt;a href=&#34;https://ar.al/2019/04/16/set-up-a-live-static-personal-web-site-in-seconds-with-indie-web-server-8.0.0/&#34;&gt;Watch this quick 2-minute demo video&lt;/a&gt; to find out.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;reverse-proxy&#34;&gt;Reverse proxy&lt;/h3&gt;
&lt;p&gt;In local mode (for development use), if you run the server with an HTTP URL instead of a path to serve, Indie Web Server will start as a reverse proxy.&lt;/p&gt;
&lt;p&gt;It proxies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP ↔ HTTPS&lt;/li&gt;
&lt;li&gt;WS ↔ WSS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I added this feature as we use Hugo quite a bit at Ind.ie for generating our static sites (e.g., this blog and the Ind.ie web site) and, by default, Hugo runs its server over HTTP on 1313. This is fine for development but if you run anything on &lt;code&gt;localhost&lt;/code&gt; over HTTPS, most browsers have trouble letting you run anything else over HTTP again.&lt;/p&gt;
&lt;p&gt;So if you’re going to test anything over HTTPS on localhost, it’s worth testing everything over HTTPS. Heck, with how easy Indie Web Server makes it, you really have no reason not to. Especially now with its reverse proxy feature.&lt;/p&gt;
&lt;h3 id=&#34;hugo-cooking-on-httpslocalhost&#34;&gt;Hugo cooking on https://localhost&lt;/h3&gt;
&lt;p&gt;So, for example, if you want to test Hugo over HTTPS on localhost, just run Hugo’s built-in web server as usual. e.g., for this blog:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;hugo server --buildDrafts --renderToDisk --baseURL&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;https://ar.al --appendPort&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;false&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then install Indie Web Server 8.1.1 and start it as a reverse proxy using:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install it&lt;/span&gt;
wget -qO- https://ind.ie/web-server/install.sh | bash

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Run it&lt;/span&gt;
web-server http://localhost:1313&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that the reverse proxy feature currently only works in local mode (not with the &lt;code&gt;global&lt;/code&gt; or &lt;code&gt;enable&lt;/code&gt; commands).&lt;/p&gt;
&lt;p&gt;Enjoy! :)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>This site now runs on Indie Web Server</title>
      <link>https://ar.al/2019/04/17/this-site-now-runs-on-indie-web-server/</link>
      <pubDate>Wed, 17 Apr 2019 17:38:18 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/17/this-site-now-runs-on-indie-web-server/</guid>
      <description>&lt;p&gt;In the interests of eating my own hamster food&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, I just switched this site from nginx to &lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The only complication in the process was that I had to update the hostname of the server to match the domain name.&lt;/p&gt;
&lt;p&gt;Otherwise, the whole process was basically as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Disable nginx.&lt;/span&gt;
sudo systemctl disable nginx

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Copy the web directory into a more human-sounding place&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (not necessary but I like it better, so there!)&lt;/span&gt;
cp -R /var/www/live.ar.al/html/ ~/site

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Update the hostname to match the domain.&lt;/span&gt;
sudo hostnamectl set-hostname ar.al

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install Indie Web Server.&lt;/span&gt;
wget -qO- https://ind.ie/web-server/install.sh | bash

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Stop nginx.&lt;/span&gt;
sudo systemctl stop nginx

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Launch Indie Web Server as a startup daemon.&lt;/span&gt;
web-server enable&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I also had to update the path in &lt;a href=&#34;https://source.ind.ie/ar.al/sync&#34;&gt;my rsync-based deployment script&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; since I’d changed the path of the web site files on the server.&lt;/p&gt;
&lt;p&gt;And that was it! Within seconds, the site was up and running again on Indie Web Server.&lt;/p&gt;
&lt;p&gt;Mmm, hamster food is yum!&lt;/p&gt;
&lt;p&gt;🐹🥜&lt;/p&gt;
&lt;p&gt;PS. &lt;a href=&#34;https://ar.al/2019/04/16/set-up-a-live-static-personal-web-site-in-seconds-with-indie-web-server-8.0.0/&#34;&gt;Watch this two-minute video&lt;/a&gt; to see just how easy it is to install and run Indie Web Server from scratch.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;What, dogs have a monopoly on the concept or something? &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Part of &lt;a href=&#34;https://ar.al/2018/06/26/web+&#34;&gt;my Web+ system&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Set up a live static personal web site in seconds with Indie Web Server 8.0.0</title>
      <link>https://ar.al/2019/04/16/set-up-a-live-static-personal-web-site-in-seconds-with-indie-web-server-8.0.0/</link>
      <pubDate>Tue, 16 Apr 2019 20:20:20 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/16/set-up-a-live-static-personal-web-site-in-seconds-with-indie-web-server-8.0.0/</guid>
      <description>&lt;figure&gt;
    &lt;video controls poster=&#39;https://i.vimeocdn.com/video/776052529.jpg?mw=1920&amp;mh=1080&amp;q=70&#39;&gt;
      &lt;source src=&#39;https://player.vimeo.com/external/330849017.m3u8?s=984a660057188b8536a949e6665ffa1794bec54c&#39; type=&#39;video/mp4&#39;&gt;
      &lt;source src=&#39;https://player.vimeo.com/external/330849017.hd.mp4?s=e553455c7bedb6be3c714a826ff8d3070a953294&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
    &lt;/video&gt;
    &lt;figcaption&gt;Watch as I set up a secure static personal web site in seconds from installation to live.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On April 1st (no joke), &lt;a href=&#34;https://ar.al/2019/04/01/indie-web-server-7.1.0-launch-a-live-secure-static-site-with-a-single-command/&#34;&gt;Indie Web Server 7.1.0&lt;/a&gt; introduced the ability to set up a live static web site in seconds on any server that had Node.js installed.&lt;/p&gt;
&lt;p&gt;Two weeks of hitting my head on a wall and multiple rewrites later, &lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server&lt;/a&gt; 8.0.0 now lets you do that &lt;em&gt;without Node.js&lt;/em&gt; thanks to native binaries for Linux.&lt;/p&gt;
&lt;p&gt;You can now literally go from installing the server to serving a fire and forget secure personal web site in seconds:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Make a web page&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello, world.&amp;#39;&lt;/span&gt; &amp;gt; index.html

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install Indie Web Server&lt;/span&gt;
wget -qO- https://ind.ie/web-server/install.sh | bash

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Start your secure web site&lt;/span&gt;
web-server enable&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Version 8.0.0 also introduces native binaries for macOS although you can only run regular processes, not daemons on that platform. (Linux systems with systemd are currently the only ones supported for production use.) And those of you on Windows can still install Indie Web Server via npm and use it as a development server with locally-trusted certificates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;npm i -g @ind.ie/web-server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I hope Indie Web Server makes your life easier. It is just one of the earliest modules to come out of &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;the Hyhpa research and development project&lt;/a&gt;. It’s what I’ll be using to power the untrusted web node in Hypha.&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&#34;https://ind.ie/web-server&#34;&gt;Indie Web Server homepage&lt;/a&gt; and &lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server&#34;&gt;the source code repository and documentation&lt;/a&gt; for more details, including how you can use it as the basis of your own dynamic servers with Node.js.&lt;/p&gt;
&lt;p&gt;As always, if you like and want to support the ongoing work of our tiny two-person not-for-profit, please &lt;a href=&#34;https://ind.ie/fund&#34;&gt;fund us&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server 7.1.0: Launch a live secure static site with a single command</title>
      <link>https://ar.al/2019/04/01/indie-web-server-7.1.0-launch-a-live-secure-static-site-with-a-single-command/</link>
      <pubDate>Mon, 01 Apr 2019 11:10:51 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/04/01/indie-web-server-7.1.0-launch-a-live-secure-static-site-with-a-single-command/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/04/01/indie-web-server-7.1.0-launch-a-live-secure-static-site-with-a-single-command/indie-web-server-live.png&#34;
         alt=&#34;Screenshot of terminal: ~/ind.ie/hypha/web-server   master  web-server --live test/site 💖 Indie Web Server v7.1.0 😈 Launched as daemon on https://aral2.hypha.dev 😈 Installed for auto-launch at startup.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Deploying a secure static web server now takes just one command and a few seconds.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;You have:&lt;/strong&gt; a VPS with a domain name pointing to it and Node.js installed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want:&lt;/strong&gt; to deploy a secure, live static site.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You do:&lt;/strong&gt; &lt;code&gt;npm i -g @ind.ie/web-server &amp;amp;&amp;amp; web-server --live&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Hit your domain name in a browser.&lt;/p&gt;
&lt;p&gt;The first time you do it, it will take a few seconds to load as your &lt;a href=&#34;https://letsencrypt.org&#34;&gt;Let’s Encrypt&lt;/a&gt; certificates are being provisioned for you by &lt;a href=&#34;https://source.ind.ie/hypha/tools/acme-tls&#34;&gt;ACME TLS&lt;/a&gt;. Then you’re up and running (with an A on the &lt;a href=&#34;https://www.ssllabs.com/ssltest&#34;&gt;SSL Labs SSL Server Test&lt;/a&gt;), serving a static site from the folder you were in when you issued the command.&lt;/p&gt;
&lt;p&gt;Your web server is running as a daemon thanks to the seamlessly integrated &lt;a href=&#34;https://pm2.io/runtime/&#34;&gt;pm2&lt;/a&gt; process manager. If you restart the server, your web site will be launched automatically. Ditto if it should crash, etc.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/04/01/indie-web-server-7.1.0-launch-a-live-secure-static-site-with-a-single-command/indie-web-server-process-monitor.png&#34;
         alt=&#34;Screenshot of the pm2 process monitor showing the web-server running&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Monitor your live server via the seamlessly integrated pm2 process manager.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To monitor it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;web-server --monitor&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To view the logs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;web-server --logs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To take it offline and stop it from launching at startup:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;web-server --offline&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For full details, please see &lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server#live&#34;&gt;the relevant section in the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Where’s all this heading? &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;Read this.&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server: now with native 404 to 302 support for an evergreen web</title>
      <link>https://ar.al/2019/03/31/indie-web-server-now-with-native-404-to-302-support-for-an-evergreen-web/</link>
      <pubDate>Sun, 31 Mar 2019 12:31:06 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/31/indie-web-server-now-with-native-404-to-302-support-for-an-evergreen-web/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/31/indie-web-server-now-with-native-404-to-302-support-for-an-evergreen-web/4042302.png&#34;
         alt=&#34;404 → 302: A simple gesture for an evergreen Web.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Seamlessly handle links to earlier versions of your sites.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What if links never died? What if &lt;a href=&#34;https://www.w3.org/Provider/Style/URI&#34;&gt;we never broke the Web?&lt;/a&gt; What if it didn’t involve any extra work? It’s possible. And easy. Just make your 404s into 302s.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server&#34;&gt;Indie Web Server&lt;/a&gt; now has native support for 404 to 302.&lt;/p&gt;
&lt;p&gt;Learn more at &lt;a href=&#34;https://4042302.org&#34;&gt;4042302.org&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Custom error pages for Indie Web Server</title>
      <link>https://ar.al/2019/03/30/custom-error-pages-for-indie-web-server/</link>
      <pubDate>Sat, 30 Mar 2019 20:12:45 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/30/custom-error-pages-for-indie-web-server/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/30/custom-error-pages-for-indie-web-server/custom-404.png&#34;
         alt=&#34;A green monster, deep in thought. The text reads: “Hmm… Sorry, I can’t find /that-which-cannot-be-found&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A custom 404 message.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I just released &lt;a href=&#34;https://ar.al/2019/03/14/introducing-indie-web-server-video/&#34;&gt;Indie Web Server&lt;/a&gt; version 6.3.0 with new default 404 and 500 error pages and support for custom ones.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/30/custom-error-pages-for-indie-web-server/custom-500.png&#34;
         alt=&#34;A pink monster with tears welling up in its eyes. The text reads: “Sniff… There was a server error: Bad things have happened.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A custom 500 message. Poor little baby monster is really taking it badly.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To create a custom error page for your static site, just create a folder at &lt;code&gt;/404&lt;/code&gt; or &lt;code&gt;/500&lt;/code&gt; in your web content and add, at a minimum, an &lt;code&gt;index.html&lt;/code&gt; file in it.&lt;/p&gt;
&lt;p&gt;Any assets you put in those folders can be addressed using standard relative links from the &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;On your custom 404 error page, you can use the template variable &lt;code&gt;THE_PATH&lt;/code&gt; to include the missing path that the person tried to access and on your custom 500 error page, you can use the template variable &lt;code&gt;THE_ERROR&lt;/code&gt; to include the body of the error message.&lt;/p&gt;
&lt;p&gt;For example, here’s an excerpt from &lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server/blob/master/test/site/404/index.html&#34;&gt;the sample custom 404 error page&lt;/a&gt; that is used in the unit tests&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;Hmm…&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;h1&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;hmm-monster.svg&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Green monster, thinking.&amp;#34;&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;strong&lt;/span&gt;&amp;gt;Sorry, I can’t find&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;strong&lt;/span&gt;&amp;gt; THE_PATH&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Your error pages will be served at the URL of the error itself and using the correct error codes (not, for example, using redirects).&lt;/p&gt;
&lt;h2 id=&#34;new-default-error-pages&#34;&gt;New default error pages&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/30/custom-error-pages-for-indie-web-server/default-404.png&#34;
         alt=&#34;The default 404 page. Reads: 4🤭4 Could not find /that-which-cannot-be-found&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The default 404 page.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I also added a tiny bit of life to the default error pages.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/30/custom-error-pages-for-indie-web-server/default-500.png&#34;
         alt=&#34;The default 500 page. Reads: 5🔥😱 Internal Server Error: Bad things have happened.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The default 500 page showing the test page at /test-500-error&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Hope you enjoy them! :)&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can find both of the sample custom error pages pictured here in the &lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server/tree/master/test/site&#34;&gt;test/site&lt;/a&gt; directory used by the unit tests. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Upcoming talks: April</title>
      <link>https://ar.al/2019/03/29/upcoming-talks-april/</link>
      <pubDate>Fri, 29 Mar 2019 12:22:34 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/29/upcoming-talks-april/</guid>
      <description>&lt;p&gt;In April, I will be speaking at two events, starting with &lt;a href=&#34;https://www.straitstimes.com/tags/st-education-forum-2019&#34;&gt;The Straits Times Education Forum&lt;/a&gt; debate on entrepreneurship to be held at &lt;a href=&#34;https://www.smu.edu.sg/&#34;&gt;Singapore Management University&lt;/a&gt; on April 6th, 2019.&lt;/p&gt;
&lt;h2 id=&#34;st-education-forum&#34;&gt;ST Education Forum&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/29/upcoming-talks-april/st-education-forum-2019.jpg&#34;
         alt=&#34;Photos of some of the participants at the ST Education Forum 2019. Clockwise from top-left: Aral Balkan, cyborg rights activist, software developer and co-founder of Ind.ie, Sumitra Pasupathy, Country Directory, Ashoka Singapore and Malaysia, Chua Mui Hoong, Opinion Editor, The Straits Times, Associate Professor Reddi Kotha, Lee Kong Chian School of Business, Associate Professor of Strategic Management, Academic Director, Master of Science in Innovation, Professor Timothy Clark, SMU Provost-Designate&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Some of the participants at this year’s ST Education Forum.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://www.straitstimes.com/global&#34;&gt;The Straits Times&lt;/a&gt; (ST) is Singapore’s most popular paper. I remember reading its Malaysian counterpart&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; as a kid in Kuala Lumpur and eagerly awaiting its tech supplement every Thursday to find out ­– among other things – exactly how many floppy disks the next version of &lt;a href=&#34;https://en.wikipedia.org/wiki/King&#39;s_Quest_I&#34;&gt;King’s Quest&lt;/a&gt; would require.&lt;/p&gt;
&lt;p&gt;At this year’s ST Education Forum, I will be joined by &lt;a href=&#34;https://www.ashoka.org/en-US/people/sumitra-pasupathy&#34;&gt;Sumitra Pasupathy&lt;/a&gt;, country director of &lt;a href=&#34;https://www.ashoka.org&#34;&gt;Ashoka&lt;/a&gt; Singapore and Malaysia, to argue for the motion that “Entrepreneurs today do more harm than good.” We will be debating Professor Timothy Clark, SMU’s new Provost and Associate Professor Reddi Kotha, Academic Director of Master of Science in Innovation at SMU, who will be arguing against the motion. The debate will be chaired by Chua Mui Hoong, Opinion Editor at ST.&lt;/p&gt;
&lt;p&gt;ST recently ran &lt;a href=&#34;https://www.straitstimes.com/singapore/education/st-education-forum-to-debate-modern-day-entrepreneurship&#34;&gt;two&lt;/a&gt; &lt;a href=&#34;https://www.straitstimes.com/singapore/st-education-forum-to-debate-entrepreneurship&#34;&gt;articles&lt;/a&gt; on the event&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Here’s some background on what I will be presenting:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Silicon Valley model of entrepreneurship is often celebrated.&lt;/p&gt;
&lt;p&gt;However, Mr Aral Balkan takes issue with the business model underpinning tech-based start-ups, termed &amp;ldquo;surveillance capitalism&amp;rdquo;, in which technology companies monetise the data captured through monitoring their users&#39; online behaviour.&lt;/p&gt;
&lt;p&gt;The co-founder of Indie, a social enterprise striving for social justice in the digital age, says that supporting sustainable businesses is in the common interest - arguing that the world needs &amp;ldquo;stay-ups&amp;rdquo;, instead of Silicon Valley&amp;rsquo;s disposable and surveillance-based &amp;ldquo;start-ups&amp;rdquo;. He also maintains that the antidote to surveillance-based Big Tech is &amp;ldquo;Small Tech&amp;rdquo; made by humans for humans.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;europacamp-react-act-democracy&#34;&gt;EuropaCamp: React. Act. Democracy!&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/29/upcoming-talks-april/europacamp.jpeg&#34;
         alt=&#34;Screenshot of the EuropaCamp web site showing two people talking at the venue. The slogan for the event – React. Act. Democracy! – is displayed alongside the name: EuropaCamp der ZEIT-Stiftung.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;EuropaCamp: React. Act. Democracy! in Hamburg.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;On April 26th, I will be speaking on &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Technology&lt;/a&gt; and the &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;peer web&lt;/a&gt; at &lt;a href=&#34;https://europacamp.zeit-stiftung.de/&#34;&gt;EuropaCamp&lt;/a&gt;, organised by &lt;a href=&#34;https://www.zeit-stiftung.de/&#34;&gt;ZEIT-Stiftung&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This will be the first time I’ve been back in Hamburg since I was there for some &lt;a href=&#34;https://2018.ar.al/notes/constructive-disobedience/&#34;&gt;constructive disobedience&lt;/a&gt; with &lt;a href=&#34;https://2018.ar.al/notes/farewell-not-goodbye/&#34;&gt;Diem25&lt;/a&gt; during the G20 in 2017 and it follows &lt;a href=&#34;https://vimeo.com/157297541&#34;&gt;my Bucerius Lab Lecture on Digital Emancipation&lt;/a&gt; that I gave at the same venue in 2016.&lt;/p&gt;
&lt;p&gt;My talk is titled &lt;a href=&#34;https://europacamp.zeit-stiftung.de/#Programm&#34;&gt;Small Technology: the antidote to surveillance capitalism for protecting human rights and democracy in Europe and beyond&lt;/a&gt; and here’s the description:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Big Tech, with its billion-dollar unicorns, has robbed us of the potential of the Internet. Fueled by the extreme shortsightedness and greed of venture capital and startups, the utopic vision of a decentralised and democratic commons has morphed into the dystopic autocracy of Silicon Valley panopticons that we call surveillance capitalism. This status quo violates our human rights, threatens our democracies, and casts doubt on the integrity of personhood itself.&lt;/p&gt;
&lt;p&gt;Aral Balkan is a designer and programmer who has been making things with computers for the past 35 years. He’s spent the last five of those working on the problem of technologically regulating the abuses of surveillance capitalism as well as designing freedom-respecting alternatives to it. In this talk, he presents his latest thinking on how we can create alternatives to surveillance capitalism that cannot be co-opted. His suggestion is simple:&lt;/p&gt;
&lt;p&gt;Think small.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here’s to hopefully seeing some of you in person this month.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The New Straits Times. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The &lt;a href=&#34;https://www.straitstimes.com/singapore/st-education-forum-to-debate-entrepreneurship&#34;&gt;second one&lt;/a&gt; includes a video of some of the local participants and students talking about the issue, which I’d highly recommend you watch if you have a few minutes. It’s awesome to see the next generation aware of and caring about the issues. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to trim an MP4 video without re-encoding it</title>
      <link>https://ar.al/2019/03/16/how-to-trim-an-mp4-video-without-reencoding-it/</link>
      <pubDate>Sat, 16 Mar 2019 14:36:02 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/16/how-to-trim-an-mp4-video-without-reencoding-it/</guid>
      <description>&lt;p&gt;You have an MP4 file and all you need to do is trim a few seconds off from either the start or the end (or both). And you don’t want to spend time cutting it in a non-linear editor and re-exporting it and waiting for it to encode again.&lt;/p&gt;
&lt;p&gt;If you do not need frame-level precision&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, there is a near-instant way you can do it using ffmpeg&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;For example, I trimmed the last few seconds off &lt;a href=&#34;https://ar.al/2019/03/16/open-broadcaster-software-studio-is-amazing/&#34;&gt;my previous video&lt;/a&gt; – which I’d saved as &lt;em&gt;obs-demo.mp4&lt;/em&gt; ­– using the following command to take everything from the very beginning until the 2 minute 13 second mark and to create a new file called &lt;em&gt;obs-demo-trimmed.mp4&lt;/em&gt; with it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;ffmpeg -ss 00:00:00 -i obs-demo.mp4 -to 00:02:13 -c copy obs-demo-trimmed.mp4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hope this saves you some time.&lt;/p&gt;
&lt;h2 id=&#34;source&#34;&gt;Source&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.georgechalhoub.com/2017/03/trimming-videos-via-ffmpeg.html&#34;&gt;George Chalhoub&lt;/a&gt; via &lt;a href=&#34;https://stackoverflow.com/a/42827058&#34;&gt;StackOverflow&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This technique has keyframe-level precision. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you don’t have it, you can install it on Debian-esque systems with:&lt;/p&gt;
&lt;p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt install ffmpeg&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Using the Advanced Scene Switcher Plugin with manual overrides in OBS Studio</title>
      <link>https://ar.al/2019/03/16/using-the-advanced-scene-switcher-plugin-with-manual-overrides-in-obs-studio/</link>
      <pubDate>Sat, 16 Mar 2019 14:26:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/16/using-the-advanced-scene-switcher-plugin-with-manual-overrides-in-obs-studio/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/16/using-the-advanced-scene-switcher-plugin-with-manual-overrides-in-obs-studio/advanced-scene-switcher-1.jpeg&#34;
         alt=&#34;Screenshot of Advanced Scene Switcher plugin dialog on the General tab showing the order of the different switching methods.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Advanced Scene Switcher plugin: General Tab.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/03/16/open-broadcaster-software-studio-is-amazing/&#34;&gt;Open Broadcaster Software Studio&lt;/a&gt; comes with a built-in Automatic Scene Switcher that can switch between different scenes&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; based on the title of the active window&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Sadly, at least on my Linux laptop running Pop_OS! 18.10, Chromium is not detected. As I’m going to be running Chromium to display my slides during my talk next week in Lille, this is a problem.&lt;/p&gt;
&lt;p&gt;Enter the &lt;a href=&#34;https://obsproject.com/forum/resources/advanced-scene-switcher.395/&#34;&gt;Advanced Scene Switcher OBS plugin&lt;/a&gt;, which can detect Chromium by window title and also offers a boatload of other criteria for automatically switching scenes. It does, however, require a tiny bit of configuration if you want to retain the ability to manually override the currently-displayed scene using the &lt;em&gt;Scenes&lt;/em&gt; list in OBS and/or the multiview window.&lt;/p&gt;
&lt;h2 id=&#34;install-advanced-scene-switcher&#34;&gt;Install Advanced Scene Switcher&lt;/h2&gt;
&lt;p&gt;To install the plugin on Linux, &lt;a href=&#34;https://obsproject.com/forum/resources/advanced-scene-switcher.395/download&#34;&gt;download&lt;/a&gt; and unzip it. Then, copy the &lt;em&gt;advanced-scene-switcher.so&lt;/em&gt; file from the &lt;em&gt;SceneSwitcher/Linux&lt;/em&gt; directory to your OBS Studio plugins directory. For me, that was:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;cp SceneSwitcher/Linux/advanced-scene-switcher.so  /usr/lib/obs-plugins/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Instructions for other platforms are available on the plugin’s web site.&lt;/p&gt;
&lt;h2 id=&#34;disable-automatic-scene-switcher&#34;&gt;Disable Automatic Scene Switcher&lt;/h2&gt;
&lt;p&gt;Make sure you disable the built-in Automatic Scene Switcher (&lt;em&gt;Tools → Automatic Scene Switcher → Stop&lt;/em&gt;) and restart OBS Studio.&lt;/p&gt;
&lt;h2 id=&#34;set-up-your-window-title-to-scene-mappings&#34;&gt;Set up your window-title-to-scene mappings&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/16/using-the-advanced-scene-switcher-plugin-with-manual-overrides-in-obs-studio/advanced-scene-switcher-2.jpeg&#34;
         alt=&#34;Screenshot of Advanced Scene Switcher plugin dialog on the Window Title tab showing the regular expressions for detecting Chromium (.*- Chromium), Tilix (^Tilix.*), and Firefox (.* - Mozilla Firefox)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Applications are detected using regular expressions and the Window Title.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Use the &lt;em&gt;Window Title&lt;/em&gt; tab to add regular expressions to match and map them to the scenes you want them to trigger.&lt;/p&gt;
&lt;p&gt;For example, when Scene Switcher detects that a window with a title that starts with “Tilix&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;” gains focus, it switches to the scene called Tilix.&lt;/p&gt;
&lt;h2 id=&#34;enable-manual-override&#34;&gt;Enable manual override&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/16/using-the-advanced-scene-switcher-plugin-with-manual-overrides-in-obs-studio/advanced-scene-switcher-3.jpeg&#34;
         alt=&#34;Screenshot of Advanced Scene Switcher plugin dialog on the Pause tab showing That Multiview.* and OBS.* are added to the Pause the Scene Switcher when … is in focus list.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;To enable manual overrides, you must tell the Scene Switcher to pause when OBS and the Multiview window are in focus.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This bit is important and it tripped me up initially. If you want to retain your ability to manually override the shown scene using the &lt;em&gt;Scenes&lt;/em&gt; list or the multiview window in OBS, you must pause Automatic Scene Switcher for these windows. To do so, switch to the Pause tab in the plugin’s dialogue window and add the following two entries to the &lt;em&gt;Pause the Scene Switcher when … is in focus&lt;/em&gt; list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-regexp&#34; data-lang=&#34;regexp&#34;&gt;Multiview.*
OBS.*&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. You should now have the Advanced Scene Switcher plugin properly set up to detect and switch to the corresponding scene for you when you switch applications and you should still be able to manually switch to you scene of choice using the &lt;em&gt;Scenes&lt;/em&gt; list and the multiview window in OBS Studio.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Scenes are combinations of sources (inputs). For example, if I want a screen that has a full-screen recording of my web browser with a picture-in-picture overlay of my camera, I would make a scene with two sources: one, a Window Capture source set to capture video from my web browser’s window and the other a Video Capture Device set to use my webcam. Then, I can scale and position the webcam video over the browser. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;More specifically, you can match a window title using regular expressions. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In other words, a window with a title that matches the regular expression &lt;code&gt;^Tilix.*&lt;/code&gt;, which reads “starts with (&lt;code&gt;^&lt;/code&gt;) the literal string Tilix (&lt;code&gt;Tilix&lt;/code&gt;) and followed by zero or more characters of any type (&lt;code&gt;.*&lt;/code&gt;). &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Open Broadcaster Software Studio is amazing!</title>
      <link>https://ar.al/2019/03/16/open-broadcaster-software-studio-is-amazing/</link>
      <pubDate>Sat, 16 Mar 2019 12:58:33 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/16/open-broadcaster-software-studio-is-amazing/</guid>
      <description>&lt;figure&gt;
    &lt;video controls poster=&#39;https://i.vimeocdn.com/video/768082747.jpg?mw=2100&amp;mh=1112&amp;q=70&#39;&gt;
      &lt;source src=&#39;https://player.vimeo.com/external/324596330.m3u8?s=4baf2708816ab7e82cbe874890f13aacdd2a8f1c&#39; type=&#39;video/mp4&#39;&gt;
      &lt;source src=&#39;https://player.vimeo.com/external/324596330.hd.mp4?s=7a42340ef1d83dc4c78b92d354d8fcece6ae2052&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
    &lt;/video&gt;
    &lt;figcaption&gt;Can I be any more excited about discovering OBS Studio? (Hint: no.)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://obsproject.com/&#34;&gt;Open Broadcaster Software Studio&lt;/a&gt; (OBS Studio&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;) is an amazing free software realtime switcher for live streaming and recording that works across all major platforms including Linux.&lt;/p&gt;
&lt;p&gt;I used it to record my &lt;a href=&#34;https://ar.al/2019/03/14/introducing-indie-web-server-video/&#34;&gt;Introducing Indie Web Server video&lt;/a&gt; this week.&lt;/p&gt;
&lt;p&gt;Right after that, I also recorded the second short video above to show you my setup&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;OBS, alongside &lt;a href=&#34;https://slides.com/&#34;&gt;slides.com&lt;/a&gt; and &lt;a href=&#34;https://revealjs.com/#/&#34;&gt;reveal.js&lt;/a&gt; is also what I’m using to prepare for my opening keynote next week at &lt;a href=&#34;https://www.legrandbarouf.fr/&#34;&gt;Le Grand Barouf Numérique&lt;/a&gt; organised by The European Metropolis of Lille&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. I’ll be documenting that setup as I go.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Which, I just realised, I was erroneously referring to as Open Broadcasting Studio until now. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On these first two videos, I set the size of my output based on the size of the maximized app windows I was recording, which means that the aspect ratio is a little messed up (it’s not exactly 16:9). That’s why the video has black bars on the sides. I’ve now updated my setup and record the apps in fullscreen so that this will no longer be an issue in future videos. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The theme is “Who runs The world?” The event aims to be “a two-day debate about our digital empowerment.” Find out more about &lt;a href=&#34;https://www.ar.al/2019/03/07/upcoming-talks-march/&#34;&gt;my upcoming talks in Lille and Copenhagen in March&lt;/a&gt;. I’ll also post details on my upcoming talks in Singapore and Hamburg in April soon. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Indie Web Server (video)</title>
      <link>https://ar.al/2019/03/14/introducing-indie-web-server-video/</link>
      <pubDate>Thu, 14 Mar 2019 18:29:11 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/14/introducing-indie-web-server-video/</guid>
      <description>&lt;figure&gt;
    &lt;video controls poster=&#39;https://i.vimeocdn.com/video/767083323.jpg?mw=2100&amp;mh=1112&amp;q=70&#39;&gt;
      &lt;source src=&#39;https://player.vimeo.com/external/323840063.m3u8?s=28bf6d1780b9f0d4f8af15585b41b9020066b408&#39; type=&#39;video/mp4&#39;&gt;
      &lt;source src=&#39;https://player.vimeo.com/external/323840063.hd.mp4?s=0e03f3f4984322c0740ca461b837d30b0149e712&amp;profile_id=169&#39; type=&#39;video/mp4&#39;&gt;
    &lt;/video&gt;
    &lt;figcaption&gt;Watch as I set up a secure development and production web server with a single command using Indie Web Server.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I just recorded a short video demonstrating just how simple and seamless &lt;a href=&#34;https://ar.al/2019/03/10/indie-web-server/&#34;&gt;Indie Web Server&lt;/a&gt; really is.&lt;/p&gt;
&lt;p&gt;To install and run a secure local development web server and serve the current directory without certificate warnings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm i -g @ind.ie/web-server
web-server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To install and run a secure production web server and serve the current directory without certificate warnings, on the production machine (e.g., VPS server) that you have already pointed a domain name to, do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm i -g @ind.ie/web-server
web-server --global&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then hit your site at your domain name. The first time you hit the site, it will take a few seconds to load as Let’s Encrypt certificates are seamlessly provisioned for you and used. That’s it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you want to run your production server so that it restarts should it crash or should the computer restart,you should use a process manager like &lt;a href=&#34;http://pm2.keymetrics.io/&#34;&gt;pm2&lt;/a&gt;. Using pm2, the number of commands you need to set up a fire and forget personal web server balloon to a whopping four:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install pm2.&lt;/span&gt;
npm i -g pm2

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Set pm2 to run itself and your servers at startup.&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# (This will output a command and ask you to copy and paste it. Do that.)&lt;/span&gt;
pm2 startup

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install Indie Web Server.&lt;/span&gt;
npm i -g @ind.ie/web-server

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Run Indie Web Server using pm2.&lt;/span&gt;
pm2 start web-server -- --global&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it! You now have a production web server that restarts should it crash and is automatically launched on server restarts. It has never been this easy to run your own secure personal web server. Enjoy and please do let me know you get on with it.&lt;/p&gt;
&lt;p&gt;For full details of the command-line syntax (and the API), please &lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server/blob/master/README.md&#34;&gt;see the documentation&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Reclaiming your backtick/tilde key with a UK Macintosh key layout on an ANSI US Keyboard in GNOME</title>
      <link>https://ar.al/2019/03/12/reclaiming-your-tilde-and-backtick-with-mac-uk-layout-on-an-ansi-us-keyboard/</link>
      <pubDate>Tue, 12 Mar 2019 01:36:46 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/12/reclaiming-your-tilde-and-backtick-with-mac-uk-layout-on-an-ansi-us-keyboard/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/12/reclaiming-your-tilde-and-backtick-with-mac-uk-layout-on-an-ansi-us-keyboard/topre-realforce-87uw55g.jpeg&#34;
         alt=&#34;A Topre Realforce 87UW 55g ANSI US layout keyboard with white and grey keys and a Filco wooden wrist rest.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Thonk, thonk, thonk!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Let me begin by acknowledging that this is most likely a niche use case. I am documenting this for my own future reference as much as anything else. That said, the technique can be used to remap or swap or alter your keyboard’s layout to your heart’s content.&lt;/p&gt;
&lt;p&gt;As a recent owner of a Topre Realforce 87UW 55g keyboard&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; who uses UK Macintosh layout on his Linux box (🎶sosumi🎶), my one pain point is missing my beloved backtick&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;/tilde key: &lt;kbd&gt;`~&lt;/kbd&gt;.&lt;/p&gt;
&lt;p&gt;Now, of course, I hear you mutter, “but, Aral, you could just hold down your handy &lt;kbd&gt;AltGR&lt;/kbd&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; key and tap the &lt;kbd&gt;]&lt;/kbd&gt; key &lt;em&gt;twice&lt;/em&gt; to create a tilde by abusing the diacritic&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;” Goes without saying really – but that’s no fun when you’re running a Unix-based system&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; and you’re a programmer who loves &lt;a href=&#34;http://tc39wiki.calculist.org/es6/template-strings/&#34;&gt;template strings in JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, instead, here’s how to set the key marked as the &lt;kbd&gt;`~&lt;/kbd&gt; key on an ANSI US keyboard to actually be that key by assigning it the same values as the &lt;kbd&gt;`~&lt;/kbd&gt; key that falls between the &lt;kbd&gt;Shift&lt;/kbd&gt; and &lt;kbd&gt;Z&lt;/kbd&gt; keys on a UK Macintosh layout and which doesn’t exist on an ANSI US keyboard.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Backup your current keyboard mapping&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;xmodmap -pke &amp;gt; ~/xmodmap_original&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the keycodes you need by running the following command and pressing the keys you’re interested in. You are going to override the values of the key you want to set with the non-existent key (in my case, I had to press the non-existent key on my laptop’s ISO UK keyboard):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;xev -event keyboard&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file in your home directory (e.g., called &lt;em&gt;my-keyboard-customisations&lt;/em&gt;) with your Xmodmap setting:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;! Make the useless section key in the Mac/UK layout
! into the priceless backtick-shift-tilde key.
keycode  &lt;span style=&#34;color:#bb60d5&#34;&gt;49&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; grave asciitilde less greater bar brokenbar bar brokenbar grave asciitilde bar brokenbar backslash bar bar brokenbar&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make a simple script that will run &lt;a href=&#34;https://linux.die.net/man/1/xmodmap&#34;&gt;xmodmap&lt;/a&gt; with your customisations file (e.g., in &lt;em&gt;~/bin/modmap&lt;/em&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;xmodmap ~/my-keyboard-customisations&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remember to make the script file executable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;chmod +x ~/bin/modmap&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a GNOME Desktop file to run at startup (e.g., _~/.config/autostart/xmodmap.desktop) with the following contents:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;Desktop Entry&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;Application
&lt;span style=&#34;color:#bb60d5&#34;&gt;Exec&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;/home/&lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;REPLACE-WITH-YOUR-ACCOUNT-NAME&lt;span style=&#34;color:#666&#34;&gt;}&lt;/span&gt;/bin/modmap
&lt;span style=&#34;color:#bb60d5&#34;&gt;Hidden&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;false&lt;/span&gt;
X-GNOME-Autostart-enabled&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;true&lt;/span&gt;
&lt;span style=&#34;color:#bb60d5&#34;&gt;Name&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;xmodmap
&lt;span style=&#34;color:#bb60d5&#34;&gt;Comment&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;xmodmap script&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now log out and log back in again and your new settings should take effect.&lt;/p&gt;
&lt;p&gt;And voilà&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;, just like that, I have my backtick-shift-tilde key back and can enjoy my beloved UK Macintosh layout but with extra thonk-thonk-thonks! 🤓&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~~~ fin ~~~&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://xahlee.info/linux/linux_xmodmap_tutorial.html&#34;&gt;http://xahlee.info/linux/linux_xmodmap_tutorial.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://askubuntu.com/a/1088394&#34;&gt;https://askubuntu.com/a/1088394&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Oh my goodness these keys are amazing to type on! &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Alt Graph (Right Alt). &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or the &lt;em&gt;accent grave&lt;/em&gt;, if you will. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And similarly, you can create a backtick by layering two &lt;em&gt;accent grave&lt;/em&gt; diacritics like this:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;AltGr&lt;/kbd&gt; &lt;kbd&gt;|&lt;/kbd&gt; &lt;kbd&gt;|&lt;/kbd&gt; &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There’s no place like ~. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can return to your original mapping at any time using: &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;xmodmap ~/xmodmap_original&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In case you’re wondering what the &lt;em&gt;accent grave&lt;/em&gt; diacritic is useful for, I just used it to write the &lt;em&gt;a-grave&lt;/em&gt; in voilà:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;AltGr&lt;/kbd&gt; &lt;kbd&gt;|&lt;/kbd&gt; &lt;kbd&gt;a&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;Of course, now that I have my backtick key back, &lt;a href=&#34;https://ar.al/2018/07/18/typographical-typing-habits-for-linux/&#34;&gt;I can use my Compose Key&lt;/a&gt; instead:&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;Compose&lt;/kbd&gt; &lt;kbd&gt;`&lt;/kbd&gt; &lt;kbd&gt;a&lt;/kbd&gt; &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Setting multiple key bindings for the same action in GNOME</title>
      <link>https://ar.al/2019/03/11/setting-multiple-key-bindings-for-the-same-action-in-gnome/</link>
      <pubDate>Mon, 11 Mar 2019 21:44:37 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/11/setting-multiple-key-bindings-for-the-same-action-in-gnome/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/11/setting-multiple-key-bindings-for-the-same-action-in-gnome/dconf-editor.jpeg&#34;
         alt=&#34;Screenshot of dconf-editor showing the keybindings for switching the windows of an applicaiton.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Editing keybindings in dconf Editor.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In GNOME, you can only set one key binding for a given action using the Settings app (under Devices → Keyboard) even though the settings data structure itself accepts an array.&lt;/p&gt;
&lt;p&gt;You can, however, set multiple key bindings per action by installing the dconf Editor app or through the command-line using the &lt;code&gt;gsettings&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;To install dconf Editor:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt install dconf-editor&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To change key bindings via the command-line (e.g. for switching the windows of an application):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;gsettings &lt;span style=&#34;color:#007020&#34;&gt;set&lt;/span&gt; org.gnome.desktop.wm.keybindings switch-group &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;[&amp;#39;&amp;lt;Alt&amp;gt;Above_Tab&amp;#39;, &amp;#39;&amp;lt;Super&amp;gt;Above_Tab&amp;#39;]&amp;#34;&lt;/span&gt;
gsettings &lt;span style=&#34;color:#007020&#34;&gt;set&lt;/span&gt; org.gnome.desktop.wm.keybindings switch-group-backward &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;[&amp;#39;&amp;lt;Shift&amp;gt;&amp;lt;Alt&amp;gt;Above_Tab&amp;#39;, &amp;#39;&amp;lt;Shift&amp;gt;&amp;lt;Super&amp;gt;Above_Tab&amp;#39;]&amp;#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Indie Web Server</title>
      <link>https://ar.al/2019/03/10/indie-web-server/</link>
      <pubDate>Sun, 10 Mar 2019 13:15:20 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/10/indie-web-server/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/10/indie-web-server/indie-web-server.jpeg&#34;
         alt=&#34;Screenshot of a web browser and two terminal windows. The URL of the web browser is set to https://aral2.hypha.dev and it shows a copy of my blog. The first terminal window is running Indie Web Server and the second one is running ngrok with HTTP and HTTPS tunnels to my machine. Excerpt of first terminal window: ~/ar.al/live: web-server --global 💖 Indie Web Server v6.0.1 🌍 [Indie Web Server] Using globally-trusted certificates. 👉 [Indie Web Server] HTTP → HTTPS redirection active. 🎉 Serving . on https://aral2.hypha.dev GET / 200 51166 - 4.602 ms (followed by more server log output).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Indie Web Server serving my blog over a TLS tunnel via ngrok.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Indie Web Server&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; is a secure and seamless &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Tech&lt;/a&gt; personal web server.&lt;/p&gt;
&lt;p&gt;Use it to seamlessly serve your personal static web site in development and production or build your own dynamic web app on top of it using JavaScript and Node.js.&lt;/p&gt;
&lt;p&gt;Indie Web Server is as easy as it gets.&lt;/p&gt;
&lt;h2 id=&#34;features&#34;&gt;Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Zero-configuration – It Just Works 🤞™.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Develop and test with seamlessly-provisioned locally-trusted &lt;a href=&#34;https://github.com/FiloSottile/mkcert&#34;&gt;mkcert&lt;/a&gt; TLS certificates via &lt;a href=&#34;https://source.ind.ie/hypha/tools/nodecert&#34;&gt;Nodecert&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stage and deploy with automatically-provisioned globally-trusted &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt; TLS certificates via &lt;a href=&#34;https://source.ind.ie/hypha/tools/acme-tls&#34;&gt;ACME TLS&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;$ npm i -g @ind.ie/web-server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;use&#34;&gt;Use&lt;/h2&gt;
&lt;h3 id=&#34;command-line&#34;&gt;Command-line&lt;/h3&gt;
&lt;p&gt;Start serving the current directory at https://localhost:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;$ web-server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Start serving the &lt;em&gt;site&lt;/em&gt; directory at your hostname:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;$ web-server site --global&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example, if you run the command on a connected server that has the ar.al domain pointing to it and &lt;code&gt;ar.al&lt;/code&gt; set in &lt;em&gt;/etc/hostname&lt;/em&gt; (on Unix/Linux/macOS), you will be able to access the site at &lt;a href=&#34;https://ar.al&#34;&gt;https://ar.al&lt;/a&gt;. The first time you hit it, it will take a little longer to load as your Let’s Encrypt certificates are being automatically provisioned by ACME TLS.&lt;/p&gt;
&lt;h3 id=&#34;api&#34;&gt;API&lt;/h3&gt;
&lt;p&gt;You can also use Indie Web Server programatically as the basis of you own web applications.&lt;/p&gt;
&lt;h4 id=&#34;examples&#34;&gt;Examples&lt;/h4&gt;
&lt;p&gt;Serve the current directory at https://localhost using locally-trusted TLS certificates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; webServer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@ind.ie/web-server&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; server &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; webServer.serve()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Serve the current directory at your hostname using globally-trusted Let’s Encrypt TLS certificates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; webServer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@ind.ie/web-server&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; server &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; webServer.serve({global&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create a custom server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; webServer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;@ind.ie/web-server&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; express &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;express&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; app &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; express()
app.use(express.&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;))

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; options &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {} &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// to use globally-trusted certificates instead, set this to {global: true}
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; server &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; webServer.createServer(options, app).listen(&lt;span style=&#34;color:#40a070&#34;&gt;443&lt;/span&gt;, () =&amp;gt; {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;` 🎉 Serving on https://localhost&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;n`&lt;/span&gt;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For full details of the command-line syntax and API, please &lt;a href=&#34;https://source.ind.ie/hypha/tools/web-server/blob/master/README.md&#34;&gt;see the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Previously known as &lt;a href=&#34;https://ar.al/2019/03/07/https-server/&#34;&gt;HTTPS Server&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Receives an A on the &lt;a href=&#34;https://www.ssllabs.com/ssltest&#34;&gt;SSL Labs SSL Server Test&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>HTTPS Server: now with seamless Let’s Encrypt support</title>
      <link>https://ar.al/2019/03/08/https-server-now-with-seamless-lets-encrypt-support/</link>
      <pubDate>Fri, 08 Mar 2019 10:19:57 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/08/https-server-now-with-seamless-lets-encrypt-support/</guid>
      <description>&lt;div style=&#39;background-color: yellow; margin-top: 1em; padding: 1em;&#39;&gt;&lt;strong style=&#39;border-bottom: 3px double;&#39;&gt;Note:&lt;/strong&gt; HTTP Server is now &lt;a href=&#39;https://ar.al/2019/03/10/indie-web-server&#39;&gt;Indie Web Server&lt;/a&gt;.&lt;/div&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/08/https-server-now-with-seamless-lets-encrypt-support/https-server-lets-encrypt.png&#34;
         alt=&#34;Screenshot of my terminal showing ~/ar.al: https-server live --global aral@ind.ie 📜 [nodecert] Local development TLS certificate exists. 🌍 [https-server] Using globally-trusted certificates. 👉 [https-server] (Globally-trusted TLS) HTTP → HTTPS redirection active. 🎉 Serving live on https://aral.hypha.dev&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;You can now also use globally-trusted Let’s Encrypt TLS certificates.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/03/07/https-server/&#34;&gt;Yesterday&lt;/a&gt;, I introduced &lt;a href=&#34;https://source.ind.ie/hypha/tools/https-server&#34;&gt;HTTPS Server&lt;/a&gt; as a development server. What a difference a day makes! Today, with seamless &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt; support via the excellent &lt;a href=&#34;https://git.coolaj86.com/coolaj86/greenlock.js.git&#34;&gt;Greenlock.js&lt;/a&gt; module&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, HTTPS Server is ready for use as a secure &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Tech&lt;/a&gt; personal web server for development and deployment. It is a human-scale tool for creating and hosting single-tenant/personal web applications using Node.js.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With HTTPS Server you can:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Develop using seamlessly-provisioned locally-trusted certificates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Develop/stage using seamlessly-provisioned globally-trusted certificates via a secure tunnel (e.g., with a pro or business plan at &lt;a href=&#34;https://ngrok.com/&#34;&gt;ngrok&lt;/a&gt; with &lt;a href=&#34;https://ngrok.com/docs#tls-cert-warnings&#34;&gt;custom domains&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy using seamlessly-provisioned and renewed globally-trusted certificates (e.g., via &lt;a href=&#34;http://pm2.keymetrics.io/&#34;&gt;pm2&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I plan to expand on each of the above use cases in future posts.&lt;/p&gt;
&lt;p&gt;In the meanwhile, we have one more small piece of the puzzle in our fledgling effort to build a bridge from &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalism&lt;/a&gt; to &lt;a href=&#34;https://peertube.fr/videos/watch/3875e4f2-500d-4c21-94f6-0692a67b87f1&#34;&gt;peerocracy&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Although, what’s up with &lt;a href=&#34;https://git.coolaj86.com/coolaj86/greenlock.js/src/branch/master/lib/community.js&#34;&gt;telemetry and tracking&lt;/a&gt; in a Node module? These “features” are turned off in HTTPS Server. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Upcoming talks: March</title>
      <link>https://ar.al/2019/03/07/upcoming-talks-march/</link>
      <pubDate>Thu, 07 Mar 2019 19:27:01 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/07/upcoming-talks-march/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/07/upcoming-talks-march/who-runs-the-world.jpeg&#34;
         alt=&#34;Illustration: three hands with index fingers pointing upwards. Text: 3rd edition: Who runs the world? A two day debate about our digital empowerment.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Who runs the world? The topic of Le Grand Barouf Numérique.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’m giving two talks this month; at &lt;a href=&#34;https://www.legrandbarouf.fr/&#34;&gt;Le Grand Barouf Numérique&lt;/a&gt; in Lille, France on the 20th and at the &lt;a href=&#34;https://cphdox.dk/en/art-technology-change-2019/&#34;&gt;Art, Technology, and Change conference&lt;/a&gt; at &lt;a href=&#34;https://cphdox.dk/en/&#34;&gt;CPH:DOX&lt;/a&gt;, the Copenhagen International Documentary Film Festival in Copenhagen, Denmark on the 26th.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/07/upcoming-talks-march/cph-dox-session-description.jpeg&#34;
         alt=&#34;Screenshot of my session description from the Art, Technology, and Change conference at CPH:DOX. Title: The Antidote to Big Tech. The body text is the same as the opening paragraph of my Small Tech post that’s linked to in the body of the page.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Antidote to Big Tech (is Small Tech) – my session description at CPH:DOX.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;At both events, I will be talking about how the bridge from &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalism&lt;/a&gt; to &lt;a href=&#34;https://peertube.fr/videos/watch/3875e4f2-500d-4c21-94f6-0692a67b87f1&#34;&gt;peerocracy&lt;/a&gt; will be paved by &lt;a href=&#34;https://ar.al/2019/03/04/small-technology/&#34;&gt;Small Technology&lt;/a&gt; and &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;the peer web&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>HTTPS Server</title>
      <link>https://ar.al/2019/03/07/https-server/</link>
      <pubDate>Thu, 07 Mar 2019 14:16:15 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/07/https-server/</guid>
      <description>&lt;div style=&#39;background-color: yellow; margin-top: 1em; padding: 1em;&#39;&gt;&lt;strong style=&#39;border-bottom: 3px double;&#39;&gt;Note:&lt;/strong&gt; HTTP Server is now &lt;a href=&#39;https://ar.al/2019/03/10/indie-web-server&#39;&gt;Indie Web Server&lt;/a&gt;.&lt;/div&gt;
&lt;p&gt;HTTPS Server (&lt;a href=&#34;https://source.ind.ie/hypha/tools/https-server&#34;&gt;source&lt;/a&gt;, &lt;a href=&#34;https://github.com/indie-mirror/https-server&#34;&gt;GitHub mirror&lt;/a&gt;, &lt;a href=&#34;https://www.npmjs.com/package/@ind.ie/https-server&#34;&gt;npm&lt;/a&gt;) is a development server that uses nodecert to automatically provision and use locally-trusted TLS certificates.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm i -g @ind.ie/https-server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;use&#34;&gt;Use&lt;/h2&gt;
&lt;h3 id=&#34;command-line&#34;&gt;Command-line:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;https-server &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;folder-to-serve&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;--port N&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All arguments are optional. By default, a secure HTTPS server will be created to serve the current folder over port 443.&lt;/p&gt;
&lt;p&gt;If you do not already have TLS certificates, they will be created for you automatically using nodecert.&lt;/p&gt;
&lt;p&gt;All dependencies will be installed automatically for you if they do not exist if you have apt, pacman, or yum (untested) on Linux or if you have Homebrew or MacPorts (untested) on macOS.&lt;/p&gt;
&lt;h3 id=&#34;api&#34;&gt;API&lt;/h3&gt;
&lt;p&gt;HTTP Server can also be used programmatically (e.g., with &lt;a href=&#34;http://expressjs.com/&#34;&gt;Express&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;example&#34;&gt;Example&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; httpsServer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https-server&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; express &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;express&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; app &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; express()
app.use(express.&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;static&lt;/span&gt;(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;))

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; options &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; {} &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// (optional) customise your server
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; server &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; httpsServer.createServer(options, app).listen(&lt;span style=&#34;color:#40a070&#34;&gt;443&lt;/span&gt;, () =&amp;gt; {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;` 🎉 Serving on https://localhost&lt;/span&gt;&lt;span style=&#34;&#34;&gt;\&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;n`&lt;/span&gt;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For more details on the API, &lt;a href=&#34;https://github.com/indie-mirror/https-server/blob/master/README.md&#34;&gt;please see the readme&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;help-wanted&#34;&gt;Help wanted&lt;/h2&gt;
&lt;p&gt;If you get a chance to test HTTPS Server with the following setups, &lt;a href=&#34;https://github.com/indie-mirror/https-server/issues&#34;&gt;please let me know by opening an issue on the GitHub mirror&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux with yum&lt;/li&gt;
&lt;li&gt;macOS with MacPorts&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Nodecert</title>
      <link>https://ar.al/2019/03/07/nodecert/</link>
      <pubDate>Thu, 07 Mar 2019 14:01:37 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/07/nodecert/</guid>
      <description>&lt;p&gt;Nodecert (&lt;a href=&#34;https://source.ind.ie/hypha/tools/nodecert&#34;&gt;source&lt;/a&gt;, &lt;a href=&#34;https://github.com/indie-mirror/nodecert&#34;&gt;GitHub mirror&lt;/a&gt;, &lt;a href=&#34;https://www.npmjs.com/package/@ind.ie/nodecert&#34;&gt;npm&lt;/a&gt;) is a Node.js module and command-line tool that automatically provisions locally-trusted TLS certificates for your development environment using &lt;a href=&#34;https://github.com/FiloSottile/mkcert/&#34;&gt;mkcert&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm i -g @ind.ie/nodecert&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;use&#34;&gt;Use&lt;/h2&gt;
&lt;h3 id=&#34;command-line&#34;&gt;Command-line:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;nodecert&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;javascript-nodejs&#34;&gt;JavaScript (Node.js):&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;nodecert&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(Synchronous.)&lt;/p&gt;
&lt;h2 id=&#34;certificates&#34;&gt;Certificates&lt;/h2&gt;
&lt;p&gt;The generated certificates are placed in the &lt;em&gt;~/.nodecert&lt;/em&gt; directory.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Small Technology</title>
      <link>https://ar.al/2019/03/04/small-technology/</link>
      <pubDate>Mon, 04 Mar 2019 10:19:01 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/03/04/small-technology/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/03/04/small-technology/2954872088_57405cb369_o.jpg&#34;
         alt=&#34;A tiny plant sprouting from within the crack in a pavement with a person’s sneaker towering above it.&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;The antidote to Big Tech is Small Tech.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Big Tech, with its billion-dollar unicorns, has robbed us of the potential of the Internet. Fueled by the extreme shortsightedness and greed of venture capital and startups, the utopic vision of a decentralised and democratic commons has morphed into the dystopic autocracy of Silicon Valley panopticons that we call &lt;a href=&#34;https://www.bbc.com/ideas/videos/surveillance-capitalism-has-led-us-into-a-dystopia/p06p0tdy&#34;&gt;surveillance capitalism&lt;/a&gt;. This status quo threatens not just our democracies but the very integrity of our personhood in the digital and networked age&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;While &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical design&lt;/a&gt; unambiguously describes the criteria and characteristics of ethical alternatives to surveillance capitalism, “ethics” itself is &lt;a href=&#34;https://librarianshipwreck.wordpress.com/2018/02/13/be-wary-of-silicon-valleys-guilty-conscience-on-the-center-for-humane-technology/comment-page-1/&#34;&gt;being co-opted by Big Tech&lt;/a&gt; in &lt;a href=&#34;https://humanetech.com/&#34;&gt;public relations initiatives&lt;/a&gt; that misdirect from the core systemic issues&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to highlight superficial symptoms&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We need an antidote to surveillance capitalism that is so anathema to the interests of Big Tech that it cannot possibly be co-opted by them.&lt;/strong&gt; It must have clear and simple characteristics and goals that are impossible to misinterpret. And it must provide a viable, practical alternative to Silicon Valley’s strangehold on mainstream technology and society.&lt;/p&gt;
&lt;p&gt;That antidote is Small Tech.&lt;/p&gt;
&lt;h2 id=&#34;small-tech&#34;&gt;Small Tech&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Is built by humans for humans&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is not for profit&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is built by individuals and organisations without equity capital&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is not sponsored by Big Tech/surveillance capitalists&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is private by default&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is peer-to-peer&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is &lt;a href=&#34;https://www.gnu.org/copyleft/&#34;&gt;copyleft&lt;/a&gt;&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Favours small over big, simple over complex, and modular over monolithic.&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Respects human rights, effort, and experience&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is human scale&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;These criteria mean that Small Tech:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Is owned and controlled by individuals, not corporations or governments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Respects, protects, and reinforces the integrity of personhood, human rights, social justice, and democracy in the digital and networked age.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Encourages hitherto impractical non-hierarchial political organisation and agency at scale.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nurtures a healthy commons.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is sustainable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will one day be funded from the commons, for the common good.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will never make anyone a billion dollars.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Background reading: &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;The nature of the self in the digitial age&lt;/a&gt;, &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;Encouraging individual sovereignty and a healthy commons&lt;/a&gt;, and &lt;a href=&#34;https://2018.ar.al/notes/we-didnt-lose-control-it-was-stolen/&#34;&gt;We didn’t lose control, it was stolen&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;We have a system in which 99.99999% of investment goes into funding surveillance-based businesses tasked with growing exponentially by violating the privacy of the general population. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;“Attention” and “addiction”. While it is true that surveillance capitalists crave to capture our attention and addict us to their products, they do this not as an end in and of itself but because the more we use their products, the more they can farm us for our data. Companies like Google and Facebook are factory farms for human beings. Their products are the farming machinery. They must provide a shiny façade to keep our attention and addict us so that we – the livestock – will willingly allow ourselves to be farmed. These institutions cannot be reformed. Big Tech can only be regulated in the same way we regulate Big Tobacco to reduce its harms on society. And we can – and should – invest in the ethical alternative: Small Tech. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Small Technology is human-to-human (H2H) in nature. Specifically, it is &lt;em&gt;not&lt;/em&gt; built by for-profit corporations to exploit individuals – what we call business-to-consumer (B2C) technology. It is also not technology built by corporations for other corporations – what we call “enterprise” or “business-to-business” (B2B) technology. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;We build Small Tech primarily for the common good, not to make a profit. This does not mean we disregard the economic system we find ourselves mired in currently or the fact that the alternatives we build must be sustainable. While we hope that one day Small Tech will be funded from the commons, for the common good, we cannot wait for our politicians and policymakers to wake up and implement such social change. While we find ourselves having to survive within capitalism, we can sell and make a profit from Small Tech. But that is not our primary purpose. Our organisations are primarily concerned with sustainable methods for creating tools that empower people without exploiting them, not with making a profit. Small Tech is not charity but it is not-for-profit. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Organisations with equity capital are owned and thus can be sold. Contrast this to organisations without equity capital (for example, companies limited by guarantee in Ireland and the United Kingdom) that cannot be sold. Furthermore, if an organisation has venture capital, we can consider that it has already been sold at investment-time as, if it doesn’t fail, it must exit (be bought by a larger corporation or by the public in general in an IPO). The exit is what venture capitalists invest their clients’ money in. The exit is how those investors make their return on investment. We avoid this toxic poison pill in Small Tech by creating organisations without equity capital that cannot be sold. Silicon Valley has disposible businesses they call Startups. We have sustainable organisations working for the common good that we call &lt;em&gt;Stayups&lt;/em&gt;. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The revolution will not be sponsored by those we are revolting against. Small Tech rejects sponsorship by surveillance capitalists. We will not allow our efforts to be used as public relations to legitimise and whitewash the toxic business model of Big Tech and help them stave off effective regulations to curb their abuses and give the ethical alternatives a chance to flourish. &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Privacy is having the right to decide what you keep to yourself and what you share with others. Therefore, private-by-default is the only definition of privacy that matters. What this means is that we design Small Tech so that people’s data stays on their devices. If there is a legitimate reason why this is not possible (for example, we need an always-on node in a peer-to-peer system to guarantee findability and availability), we ensure that data is end-to-end encrypted and the individual who owns the tool has sole possession of the keys to their private information and sole control over who the “ends” are (to avoid the spectre of &lt;a href=&#34;https://www.zdnet.com/article/tech-giants-and-civil-liberty-groups-call-out-ghost-cops-and-source-code-demands-under-australian-encryption-laws/&#34;&gt;Ghosting&lt;/a&gt;). &lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The core topology of our technology is peer to peer – a system without centres in which every node is equal. Nodes that individuals do not have direct control over (e.g., the always-on node in the peer-to-peer system mentioned in the previous footnote) are untrusted and unprivileged dumb relay nodes that never have the keys to people’s private information. &lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In order to ensure a healthy commons, we must protect the commons from exploitation and enclosure. Small Tech uses copyleft licenses to ensure that if you benefit from the commons, you must give back to the commons. This also stops Big Tech from embracing and extending our work &lt;a href=&#34;https://en.wikipedia.org/wiki/Embrace,_extend,_and_extinguish&#34;&gt;only to eventually lock us out of it&lt;/a&gt; using their vast concentration of wealth and power. &lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Small Tech is influenced in no small part by the wealth of existing work by those inspirational designers and developers in the JavaScript community that birthed the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; and &lt;a href=&#34;https://www.scuttlebutt.nz/&#34;&gt;Scuttlebutt&lt;/a&gt; communities. Their philosophy of creating pragmatic, modular, minimalist, human-scale components results in technology that is accessible to, maintable by, and at the benefit of individuals. Their approach, and ours, in turn, is based on &lt;a href=&#34;https://en.wikipedia.org/wiki/Unix_philosophy&#34;&gt;the UNIX philosophy&lt;/a&gt;. &lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Small Tech adheres to &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;The Ethical Design Manifesto&lt;/a&gt;. &lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:13&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Small Tech is built by humans, for humans; it is staunchly non-colonial in approach. It is not built by smarter humans for dumber humans (e.g., by “developers” for “users” – we do not use the &lt;a href=&#34;http://www.otheringandbelonging.org/the-problem-of-othering/&#34;&gt;othering&lt;/a&gt; term “user” in Small Tech. We call people, &lt;em&gt;people&lt;/em&gt;.) We build our tools as simply as possible so that they can be understood and maintained and improved upon by the greatest number of people. &lt;a href=&#34;https://vimeo.com/70030549&#34;&gt;We do not arrogantly expect people to put in undue effort to learn our tools. We invest undue effort ourselves in making them intuitive and easy to use.&lt;/a&gt; We implement beautiful defaults and layer the seams. Remember: complexity happens, simplicity you have to strive for. In Small Tech, “too smart” is a euphemism for dumb. In the words of Brian Kernighan: “Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” We take the spirit of Brian’s quote and apply it across the board – from funding, to organisational structure, to the design of the product, to its development, deployment, and beyond.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Photo credit:&lt;/strong&gt; &lt;a href=&#34;https://www.flickr.com/photos/smanography/2954872088&#34;&gt;Small Things, Big Things&lt;/a&gt; by &lt;a href=&#34;https://www.flickr.com/photos/smanography/&#34;&gt;Sherman Geronimo-Tan&lt;/a&gt;. Released under &lt;a href=&#34;https://creativecommons.org/licenses/by/2.0/&#34;&gt;Creative Commons Attribution&lt;/a&gt;. &lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Hypha: Glossary</title>
      <link>https://ar.al/2019/02/18/hypha-glossary/</link>
      <pubDate>Mon, 18 Feb 2019 10:05:48 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/02/18/hypha-glossary/</guid>
      <description>&lt;p&gt;&lt;strong&gt;This is a work-in-progress glossary of terms for Hypha.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hypha&lt;/td&gt;
&lt;td&gt;A &lt;strong&gt;small technology&lt;/strong&gt; &lt;a href=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/&#34;&gt;peer web&lt;/a&gt; platform (work-in-progress) that provides individual sovereignty over those aspects of your self that you choose to express digitally and a means of communicating with others on your own terms without third-party mediators or filters. Hypha is free software and peer to peer. It aims to create a bridge from the centralised web to the peer web. Hypha is based on &lt;a href=&#34;https://datproject.org&#34;&gt;the Dat ecosystem&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small Technology&lt;/td&gt;
&lt;td&gt;Technology created by organisations for which exponential growth and making a billion dollars or “becoming a unicorn” is a failure state. Technology designed for individuals that respects, protects, and strengthens the integrity and dignity of the person and thus provides the basis for a healthy commons. Technology designed to further social justice.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A hypha&lt;/td&gt;
&lt;td&gt;A single database that replicates according to the protocols of the Hypha platform. A hypha is addressed on the centralised Web by a domain name and on the peer Web by its &lt;em&gt;hyphalink&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;A node within a hypha is any location that replicates that particular hypha.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initialised node&lt;/td&gt;
&lt;td&gt;A node that has a local copy of a hypha set up (even if it has not replicated yet).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uninitialised node&lt;/td&gt;
&lt;td&gt;A node that does not have a local copy of a hypha set up yet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passphrase&lt;/td&gt;
&lt;td&gt;A secret Diceware phrase generated with a process that has at least 100 bits of entropy. All key material used in a hypha, including the hyphalink, are generated from this passphrase. The passphrase is never stored anywhere (people are highly encouraged to store it in their password managers).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;A centralised ICAAN domain that points to the Relay Node.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Forever Node ∞&lt;/td&gt;
&lt;td&gt;An &lt;em&gt;unprivileged&lt;/em&gt; &lt;strong&gt;always-on&lt;/strong&gt; node that can either be hosted first-party or (more commonly) by an untrusted third-party. The Forever Node: &lt;strong&gt;a)&lt;/strong&gt; provides distribution of the latest Hypha source to browser nodes (and perhaps, later, native nodes), &lt;strong&gt;b)&lt;/strong&gt; acts as a bridge between the centralised web and the peer web by &lt;strong&gt;i)&lt;/strong&gt; providing findability, &lt;strong&gt;ii)&lt;/strong&gt; bridging replication between browser and native nodes, &lt;strong&gt;c)&lt;/strong&gt; provides signalling etc. (e.g., for WebRTC), and &lt;strong&gt;d)&lt;/strong&gt; provides availability. The domain points to the relay node.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyphalink&lt;/td&gt;
&lt;td&gt;An Ed25519 public key that uniquely identifies a Hypha. A discovery key generated from this key is used with &lt;a href=&#34;https://github.com/hyperswarm/network&#34;&gt;Hyperswarm&lt;/a&gt; to provide peer-to-peer findability for hyphas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorised node&lt;/td&gt;
&lt;td&gt;An initialised node that has write privileges for a hypha.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unauthorised node&lt;/td&gt;
&lt;td&gt;An initialised node that has read-only access for a hypha.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signed-in node&lt;/td&gt;
&lt;td&gt;An authorised node with an unlocked local writer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signed-out node&lt;/td&gt;
&lt;td&gt;An authorised node with a locked local writer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Origin node&lt;/td&gt;
&lt;td&gt;The initial database that’s created for a hypha. The only purpose of the origin node is the authorise the first writer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writer&lt;/td&gt;
&lt;td&gt;Any node with write privileges. Synonymous with authorised node.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reader&lt;/td&gt;
&lt;td&gt;Any node with real-only privileges. Synonymous with unauthorised node.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</description>
    </item>
    
    <item>
      <title>Privacy is not a science, it is a human right</title>
      <link>https://ar.al/2019/02/14/privacy-is-not-a-science-it-is-a-human-right/</link>
      <pubDate>Thu, 14 Feb 2019 12:17:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/02/14/privacy-is-not-a-science-it-is-a-human-right/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/14/privacy-is-not-a-science-it-is-a-human-right/wolf-in-sheeps-clothing.jpeg&#34;
         alt=&#34;A doctored image of a wolf in sheep’s clothing amid a group of sheep.&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;Given the levels of institutional corruption in academia and in the regulatory bodies and advocacy institutions that should be protecting our privacy, very few things shock me these days. So hats off to &lt;a href=&#34;https://bartvandersloot.com/&#34;&gt;Bart van der Sloot&lt;/a&gt; for managing the impossible and finding a new low by framing institutional corruption as scientific neutrality in his article &lt;a href=&#34;https://www.netkwesties.nl/1321/dubbele-petten-in-de-privacywetenschap.htm&#34;&gt;Dubbele petten in de privacywetenschap&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The gist of Mr. van der Sloot’s argument can be summarised with this doozy of a quote from his article&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Should privacy science be pro-privacy, or is it an undermining of the neutrality of privacy science? If privacy science should be neutral, why is there so much commotion about the sponsorship by commercial parties like Google, Facebook and Palantir and are there few words wasted on sponsorship by activist civil rights organizations such as the Electronic Privacy Information Center (EPiC), Privacy First and Bits of Freedom, which are outspoken pro-privacy? Does this not indicate that the criticism of sponsorship by commercial parties comes from persons who are not themselves neutral and objective, but actually pursue a pro-privacy agenda?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where does one begin to dissect such a juicy turd?&lt;/p&gt;
&lt;h3 id=&#34;privacy-science-is-not-a-thing&#34;&gt;“Privacy science” is not a thing&lt;/h3&gt;
&lt;p&gt;First off, let’s get this straight: privacy is not a science, it is a human right. There is no such thing as “privacy science”. There never was. There never will be. It’s not a science any more than human rights is a science. I can see what you’re trying to do, Mr. van der Sloot, and it’s duplicitous as all hell.&lt;/p&gt;
&lt;p&gt;Let me tell you why privacy is not a science: because there is no scientific reason for us to have privacy any more than there is a scientific reason why we should not be slaves. Mr. van der Sloot purposefully conflates ethics, which asks “what is good?” and “how should we live?” with evolutionary biology, and perhaps sociology, which study the way things are and how they came to be the way they are and maybe even extrapolate to how they might be in the future given a certain set of constraints. The latter do not make value judgements. The former is all about value judgements. To the extent that studies in the latter follow the scientific method, we call them sciences. The former is not science, it is philosophy.&lt;/p&gt;
&lt;p&gt;It is science that tells us how a projectile can be propelled from a hand-held device at such velocity as to cause terminal damage to another human being and exactly how it causes that damage. It is ethics that tells us we should not shoot people. Only people interested in providing some sort of pseudoscientific justification for their desire to shoot people conflate the two.&lt;/p&gt;
&lt;p&gt;If you need Mr. van der Sloot to make my case himself, this next snippet of utter depravity should do the trick:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…the question is whether this does not make the image of the privacy scientist too one-sided and whether the name of the science is not being used or abused to take a personal standpoint. An example is the petition from January 2014 entitled &amp;lsquo;Academics against mass surveillance&amp;rsquo;, following the Snowden disclosures, in which it seems unequivocally suggested that mass surveillance is unlawful and unethical.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But are these statements sufficiently scientifically substantiated, or are they rather one-sided, emotional views? From a legal point of view, the assessment of these kinds of programmes is not so simple, which also applies, for example, to an ethical evaluation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Thus, the name (&amp;lsquo;Academics against mass surveillance&amp;rsquo;) of science is used to add lustre to a personal view, so the scientists who refrain from signing such petitions believe. They also ask the question whether &amp;lsquo;right-wing&amp;rsquo; scientists should then start their own petition: Other Academics for Mass Surveillance. Moreover, in the area of privacy, you can continue to sign petitions. So here too, there are a number of question marks to be placed: why should one petition sign and another not?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In case reading the above paragraphs didn’t make your blood boil as it did mine, re-read them and replace “mass surveillance” with “genocide”. And remember that science is “neutral” on that subject too.&lt;/p&gt;
&lt;p&gt;Heck, even Mr. van der Sloot’s core postulate on science being neutral doesn’t stand up to scrutiny. Science isn’t neutral, it is entirely biased in favour of facts. It is the specific purpose of the scientific method to separate fact from fiction. However, what it does not then do is to assign a subjective value to those facts. That, once again, is the purview of philosophy and ethics.&lt;/p&gt;
&lt;h2 id=&#34;but-wait-a-minute-why-are-organisations-that-purport-to-defend-your-privacy-silent-on-this-topic&#34;&gt;But wait a minute, why are organisations that purport to defend your privacy silent on this topic?&lt;/h2&gt;
&lt;p&gt;Having dismissed the kindergarten philosophy underpinning Mr. van der Sloot’s argument, let’s turn to a more practical matter because there is one question that Mr. van der Sloot raises that I would also like an answer to:&lt;/p&gt;
&lt;p&gt;Why are, in Mr. van der Sloot’s words, so “few words wasted on sponsorship by activist civil rights organizations such as the Electronic Privacy Information Center (EPiC) [sic], Privacy First and Bits of Freedom, which are outspoken pro-privacy?”&lt;/p&gt;
&lt;p&gt;Is it because these organisations are, as Mr. van der Sloot would like them to be, neutral when it comes to privacy? Are they objective bystanders, taking notes? If so, it would be helpful for us to know so we do not ascribe to them any sort of ethical or moral position on the subject of privacy so that we are not disappointed later. If they do, however, hold a position on the matter, now would be a good time for them to find their voices and their spines and tell us exactly what that position is.&lt;/p&gt;
&lt;p&gt;I look forward to reading your responses to Mr. van der Sloot, &lt;a href=&#34;https://epic.org/&#34;&gt;EPIC&lt;/a&gt;, &lt;a href=&#34;https://www.privacyfirst.eu/&#34;&gt;Privacy First&lt;/a&gt;, and &lt;a href=&#34;https://www.bitsoffreedom.nl/english/&#34;&gt;Bits of Freedom&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;a-dangerous-new-attempt-at-reframing-that-we-must-nip-in-the-bud&#34;&gt;A dangerous new attempt at reframing that we must nip in the bud&lt;/h2&gt;
&lt;p&gt;When I first became aware of the problem of &lt;a href=&#34;https://www.bbc.com/ideas/videos/surveillance-capitalism-has-led-us-into-a-dystopia/p06p0tdy&#34;&gt;surveillance capitalism&lt;/a&gt; and &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;the threat it poses to personhood in the digital/networked age&lt;/a&gt;, I admit that I did naïvely believe that people who called themselves “privacy professionals” were there to protect our privacy, just like I like to believe that doctors are there to protect our health. In truth, the key word in that label isn’t privacy, it’s &lt;em&gt;professional&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Privacy professionals are just people who get paid to work on the topic of privacy. Whether they work to protect or erode your privacy depends on who pays them. Given who holds the money and what their business model is (big tech and people farming), it is not surprising that most people who call themselves “privacy professionals” today work to erode, not protect, your privacy.&lt;/p&gt;
&lt;p&gt;Even in such a corrupt status quo, the suggestions Mr. van der Sloot provides in his conclusion (“rules needed”) manage to convey a thirst for an even newer low:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What to do. Privacy science can connect to existing regulations of for example the KNAW and sector-specific regulations, such as in the medical sector. Or with the Dutch Code of Conduct for Scientific Practice of the VSNU, which states that a scientist must avoid personal relationships &amp;lsquo;that could raise reasonable doubt about the objectivity of his decisions&amp;rsquo;, that research must be carried out &amp;lsquo;scientifically soundly&amp;rsquo; and methodologically correct on commission and that scientists must be transparent about their relationships, financial flows and any agreements with third parties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;All this is not enough, because it is too general and noncommittal. It would therefore be advisable to draw up further standards and guidelines for the integrity and independence of privacy science, but perhaps also more widely. Privacy is often found in ethical environments and privacy science permits ethical judgements more than once; this is precisely why it must conduct the ethical discussion about its own position and actions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What Mr. van der Sloot is suggesting here is dangerous. It is a brazen attempt to further institutionalise the corruption in an already highly-corrupt field while shaming those already few who actually stand up for human rights and privacy. It is an attempt to get ethical privacy advocates de-platformed and potentially fired for being “unscientific”&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and biased.&lt;/p&gt;
&lt;p&gt;Mr. van der Sloot wants people working in privacy to be neutral. In the words of Desmond Tutu, “If you are neutral in situations of injustice, you have chosen the side of the oppressor. If an elephant has its foot on the tail of a mouse and you say that you are neutral, the mouse will not appreciate your neutrality.”&lt;/p&gt;
&lt;p&gt;By this point, you might be wondering, “who is this Mr. van der Sloot you are talking about?” Who is this man who wants us to think privacy is a science (it’s not) and punish those privacy scientists (not a thing) who aren’t ‘neutral?’ What does he do? Well, he is the &lt;a href=&#34;https://bartvandersloot.com/About/index.html&#34;&gt;chief organiser&lt;/a&gt; of Amsterdam Privacy Conference &lt;a href=&#34;https://apc2018.com/sponsors/index.html&#34;&gt;sponsored by Google and Palantir&lt;/a&gt;. Oh, and he is also &lt;a href=&#34;https://www.tilburguniversity.edu/webwijs/show/b.vdrsloot/&#34;&gt;a docent at the Tilburg University on “Privacy and Big Data.”&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;enough-bullshit&#34;&gt;Enough bullshit&lt;/h3&gt;
&lt;p&gt;In many ways, and without such intention, Mr. van der Sloot’s article shines an impossible-to-ignore spotlight onto the outstanding level of rot and corruption in academia and non-governmental organisations today. It also lays bare a new and dangerous effort to further cement such corruption institutionally by those that benefit financially from it.&lt;/p&gt;
&lt;p&gt;Perhaps such a spectacular display of disdain of ethics, privacy, and human rights will be enough to overcome even the herculean efforts of suspension of disbelief practiced by some in our field to deny that we have a gigantic problem on our hands.&lt;/p&gt;
&lt;p&gt;Or, to put it more simply, perhaps Mr. van der Sloot has produced a turd of such magnificence that even those who usually remain silent might see it fit to stand and comment.&lt;/p&gt;
&lt;p&gt;I sure hope so.&lt;/p&gt;
&lt;p&gt;Because the silence has been deafening as of late.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;All quotes are translated from the original with &lt;a href=&#34;www.DeepL.com/Translator&#34;&gt;DeepL&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Not surprisingly, this newfound love for “science” is the same tactic used by the US factory farming industry in their attempts to push lower food standards on the EU, and most recently, &lt;a href=&#34;https://twitter.com/GeorgeMonbiot/status/1095614578690527232&#34;&gt;on post-Brexit UK&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>On the General Architecture of the Peer Web (and the placement of the PC 2.0 era within the timeline of general computing and the greater socioeconomic context)</title>
      <link>https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/</link>
      <pubDate>Wed, 13 Feb 2019 14:30:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/eras-of-computing.jpeg&#34;
         alt=&#34;An image with four equal-spaced columns, zebra-striped with light and dark grey. In the first is a photo of a mainframe computer. A woman sits at the input device and a man examines a panel. The second is a photo of the first Apple computer. It has a hand-made wooden case with Apple Computer carved out of it in uneven lettering. The third column has the image of a data center with a grid of servers; the room is lit in blue. The last column is empty.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;There have been three eras of general computing… the fourth remains to be written.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;a-highly-compressed-overview-of-the-history-of-general-computing&#34;&gt;A highly-compressed overview of the history of general computing&lt;/h3&gt;
&lt;p&gt;The first era of general computing was the mainframe era and it was centralised. The second, the personal computing (PC 1.0) era, was decentralised. The third is the Web era (let’s call it Mainframe 2.0). And it is, once again, centralised. Today, we find ourselves at the end of the Mainframe 2.0 era and on the precipice of a new chapter in the history of general computing: the Peer Web. Let’s call it the Personal Computing 2.0 (PC 2.0) era.&lt;/p&gt;
&lt;h3 id=&#34;on-taxonomic-criteria-and-the-greater-context&#34;&gt;On taxonomic criteria and the greater context&lt;/h3&gt;
&lt;p&gt;The primary characteristics that determine our taxonomy of the four eras of computing are ownership and control, reach, and network topology. However, these neither exist in a vacuum nor do they emerge spontaneously.&lt;/p&gt;
&lt;p&gt;Technology is an amplifier. What technology amplifies – in other words, the character of our technologies – is determined by the social, economical, and ideological context that funds and produces it to realise its objectives. Any taxonomy of technological artifacts not grounded in socioeconomics is akin to a classification of life devoid of evolutionary theory. Without understanding the social, economical, and ideological forces that forge our technologies, we cannot understand their true purpose beyond that which is stated by the marketing departments of the organisations that produce them.&lt;/p&gt;
&lt;p&gt;Furthermore, given that technology is a multiplier, a crucial question for the future of humanity is what exactly is it multiplying? Given that the rate of change in technological progress is exponential, and given that this translates to an exponential increase in our ability to impact our own habitat, it is essential that we are able to answer this question correctly. And if we find that the feedback loop between our ideologies and their realisations (as enabled by the technologies they sponsor) are leading to outcomes that range from undesirable to catastrophic – from radically reduced freedom and human welfare at the optimistic end of the scale to extinction of our species at the other – then we must alter our ideologies and socioeconomic success criteria and ensure that technological infrastructure exists to amplify those more sustainable alternatives.&lt;/p&gt;
&lt;p&gt;I must also state that I am not optimistic about our chances of effecting the change we need to stave off the worst outcomes. I am, at most, cautiously hopeful. This is part of a far more fundamental battle that rages inside of each of us: the struggle between our base and higher-order evolutionary traits (both of which have made us who we are today). Our base evolutionary traits once facilitated our survival in times when resources were scarce and our agency within our environment was limited. Yet, today, left unrestrained, they lead us to obesity and self-harm at the species scale. And yet we also possess higher-order evolutionary traits that manifest as our ability to forgo instant gratification. We have been known to reject the next readily available source of glucose and dopamine and engage in acts of enlightened self interest. These have translated into our forming societies, caring for others, and the creation of social safety nets. These higher-order evolutionary traits have had as much (if not perhaps a far greater in later times) impact on the success of humanity as a social species. If we care about our own freedom and welfare as individuals and as a society at the lower end of the scale and the survival of the species at the higher end, we must shun the former and embrace the latter traits. It remains to be seen if we are capable of doing so or whether the current levels of inequality within our systems have already crossed a tipping point beyond which they self perpetuate &lt;em&gt;ad perniciem&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Finally, any analysis that overlooks the irrationality woven into the very fabric of our society cannot hope to make sense of the nonsense that shapes much of our current ideology, and, thus, gets translated into policy. These nonsensical aspects are, ironically perhaps, as scientifically grounded in the same evolutionary process I mentioned earlier. Irrationality simply wasn’t a negative evolutionary trait when our technological capabilities limited its potential fallout to a statistically-insignificant percentage of our habitat or our species. Back when, we could at most destroy cities and, at our most barbaric, kill some millions. Even with our relatively modest technology during the colonial era, however, it turns out that &lt;a href=&#34;https://www.commondreams.org/news/2019/02/01/european-colonizers-genocide-56-million-native-americans-so-devastating-it-literally&#34;&gt;we managed genocide on such a scale that it led to a period of global cooling&lt;/a&gt;. Today, we have the technological capability to destroy our habitat and wipe out our species several times over. And it would be folly to assume that the ideologies that shape our policies and determine our success criteria, which fund our technologies, which realise our ideologies are in any way based on reason, enlightened self interest, or even a fundamental desire to survive or thrive in anything but the extreme present. Our current feedback loop incentivises, rewards, breeds, and elevates to great power and wealth the most sociopathic/psychopathic, shortsighted, and self-destructive of behaviours and the people that embody them. To break such a loop, we must look not only at which rational levers to adjust to alter our socioeconomic success criteria but also which irrational clutches prevent us from doing so. To put it simply, we cannot avoid catastrophe as long as we believe that limitless, exponential growth is possible in a closed system with scarce resources. And we cannot impart the responsibility necessary to alter our behaviour as long as we believe that we do not actually possess catastrophic agency over our habitat because we ultimately attribute agency at such scale to a magical sky fairy.&lt;/p&gt;
&lt;p&gt;All that said, all we can do is to concentrate on the change that we can effect. Blissful ingnorance will not get us anywhere but neither will abject hopelessness. We must compose a rational understanding of the problem and concoct sustainable alternatives that can be adopted should the will arise. Whether or not our alternatives are adopted or used is unknowable and, to a great extent, irrelevant. Because what we do know is that unless the alternatives exist – even in some basic prototypical form – it is guaranteed that they will not be adopted or used and that disaster is inevitable. All we can do is to make our case as clearly as we can, attempt to the best of our abilities to inform and educate, build the tools that can fix the problem, and generally be the change we want to see in the world. This does not require optimism, only realism with imagination. This is not philantrophy. It’s humanity’s struggle to survive past its a painful adolescence and emerge as a responsible adult. This is about getting us from celebrating sociopathically-extreme shortsightedness and greed as success to embracing enlightened self interest.&lt;/p&gt;
&lt;p&gt;Now put all that aside (but always keep it in your little finger) and let’s return to our taxonomy of technology.&lt;/p&gt;
&lt;h3 id=&#34;mainframe-10&#34;&gt;Mainframe 1.0&lt;/h3&gt;
&lt;p&gt;In the original mainframe era, ownership and control of computers was centralised to a handful of wealthy organisations (military, academic, and corporate). The direct reach of the network was similarly limited to the people in those organisations. That is not to say, of course, that the impacts of mainframe computing did not extend beyond the walls of those organisations. One need look no further than &lt;a href=&#34;https://archive.nytimes.com/www.nytimes.com/books/first/b/black-ibm.html&#34;&gt;IBM’s partnership with the Third Reich&lt;/a&gt; for evidence of its worst ramifications. (If IBM could enable the Holocaust using punch-card mainframes, imagine what Alphabet, Inc. could do today with the technology it has.)&lt;/p&gt;
&lt;h3 id=&#34;pc-10&#34;&gt;PC 1.0&lt;/h3&gt;
&lt;p&gt;The first personal computing era began &lt;a href=&#34;https://en.wikipedia.org/wiki/Apple_I&#34;&gt;circa 1976 with the Apple I&lt;/a&gt;. It was the first time that individuals got to own and control computers. It was also the last decentralised era of technology. While the primary reach of computing expanded far beyond institutional walls and entered our homes, what was noticeably lacking (beyond the rudimentary bulletin board systems – BBSes – that were the purview of a strict subset of enthusiasts) was a network to tie all these personal computers together and enable them (and us) to communicate. Information in the PC 1.0 era was exchanged the old-fashioned way… hand-to-hand using floppy disks. In the PC 1.0 era, individuals had ownership and control of their machines. If, for example, you installed an app and it used your 9600 baud modem to phone home and report on your behaviour, we called it a Trojan – and we understood it to be malware. Today, we call a far more sophisticated and ubiquitous version of this the extremely lucrative business model of Silicon Valley.&lt;/p&gt;
&lt;h3 id=&#34;centralised-web-mainframe-20&#34;&gt;Centralised Web (Mainframe 2.0)&lt;/h3&gt;
&lt;p&gt;There is a common misconception (perhaps owing to &lt;a href=&#34;https://www.w3.org/DesignIssues/Architecture.html&#34;&gt;its stated aims&lt;/a&gt; or a conflation with the nature of some of the fundamental protocols of the Internet) that the World Wide Web is decentralised. It is not and never has been.&lt;/p&gt;
&lt;p&gt;The fundamental architecture of the Web has always been client/server. In other words, centralised. The Centralised Web era was, in effect, a return to mainframe computing. The only difference is that the mainframes are now global in reach. Our new mainframes are household names like Google, Facebook, and Snapchat (and lesser-known data brokers and surveillance companies that lurk behind the scenes like Acxiom and Palantir).&lt;/p&gt;
&lt;p&gt;The Web today is an oligopoly of multinational corporations with business models based on surveillance and ownership of people by proxy. It was the Centralised Web that ushered in the socioeconomic system we call &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalism&lt;/a&gt;. Capitalism, of course, has always relied on some level of surveillance and ownership of people, all the way back to &lt;a href=&#34;https://www.thenation.com/article/capitalism-and-slavery/&#34;&gt;its basis in slavery&lt;/a&gt;. What is different today is the nature and scope of the surveillance and ownership.&lt;/p&gt;
&lt;p&gt;It is crucial that we understand that even in the early days, the Web was centralised. The centres (the servers) were just closer to each other in size. That changed when the Web was commercialised. The injection of venture capital with its expectation of exponential returns for Vegas-style high-risk betting provided the enzymatic pool that incentivised the centres to grow in tumour-like fashion until we got the monopolies of Google, Facebook, and their ilk. A new era of people farming was born. And while we diagnosed the tumours early, instead of recognising them as a threat, we started celebrating them and paving the way for a new type of slavery by proxy to sneak in through this digital and networked backdoor. This new slavery, let’s call it Slavery 2.0, is not as crude as Slavery 1.0. In Slavery 2.0, we no longer need physical possession of your body. We can own you by proxy by obtaining and owning a digital copy of you.&lt;/p&gt;
&lt;p&gt;Here’s how it works: The so-called consumer technologies of today, with few exceptions, have two facets. There is the face you see: the addictive, potentially useful one that you interact with as “the user” and the one you don’t see. The one that’s watching you and taking notes and analysing your behaviour so that it can use the intimate insight it gleams from this constantly-evolving profile of you as a digital proxy with which to manipulate and exploit you. In retrospect, the World Wide Web is a most fitting name for the construct that enabled this. It’s a web with a giant spider in the middle. The spider goes by many names… Google, Facebook, Snapchat…&lt;/p&gt;
&lt;p&gt;It’s also no coincidence that the centralised Web evolved alongside a period of unprecedented global concentration of wealth and power within the hands of a tiny group of billionaires. Surveillance capitalism, after all, is the feedback loop between capitalism (accumulation of wealth) and surveillance (accumulation of information). Surveillance capitalism is what you get when those with accumulated wealth invest that wealth in systems that result in the accumulation of information within the same hands which they then exploit to accrue further wealth.&lt;/p&gt;
&lt;p&gt;The power differential between the haves and the have nots in surveillance capitalism is compounded not just by a widening gap in the wealth of the former versus the latter but also by the information the former has on the latter. To put it simply, if I know everything about you and you know nothing about me, I essentially own you by proxy. If, further, I dictate the tools you use to experience the world around you, I get to filter (and thus create) your reality. In the film The Matrix, people’s minds inhabit a virtual reality while their bodies are farmed in physical space. On Earth, circa 2019, we inhabit a physical space while our minds are farmed from a virtual reality. But, as in any good science fiction story, there is hope that a band of plucky rebels might just turn the tide in the face of overwhelming odds… and that brings us to the present day where we find ourselves witnessing and helping shape the next era of technology: the Personal Computing 2.0 era.&lt;/p&gt;
&lt;h3 id=&#34;peer-web-pc-20&#34;&gt;Peer Web (PC 2.0)&lt;/h3&gt;
&lt;p&gt;Where the Centralised Web is client/server, the Peer Web is peer to peer. Unlike traditional peer to peer, however, the Peer Web makes use of unprivileged always-on nodes to solve two of the usability problems that have plagued traditional peer to peer systems: findability and availability.&lt;/p&gt;
&lt;p&gt;In order to bootstrap the Peer Web, we must make the provision of personal always-on nodes at a universally-reachable and human-readable address a seamless process. In other words, we must make it trivial for anyone to sign up for and own a node (hosted either by a third party or themselves) that guarantees universal findability via a domain name as well as close-to-constant availability. These two requirements are non-negotiable as they form the bare minimum necessary to compete with the usability of centralised systems. On top of this, however, we can layer those aspects made possible by a peer-to-peer and end-to-end encrypted architecture: privacy and censorship resistance.&lt;/p&gt;
&lt;h4 id=&#34;inverting-the-web&#34;&gt;Inverting the web&lt;/h4&gt;
&lt;p&gt;On the Peer Web, the always-on node, likely hosted by a third party, must be an &lt;em&gt;unprivileged&lt;/em&gt; node. What I mean by this is that it must not know/have the paraphrase (and the secret keys derived from it) that the nodes that are physically under your control do. The always-on node acts as a signaling service, as a dumb relay (for example between browser-based nodes and native nodes) and as a software delivery mechanism for browser apps.&lt;/p&gt;
&lt;p&gt;That last point requires clarification as its implications run contrary to the traditional best practices of web development. In traditional web development, sites are rendered on the server and delivered to the client. In the earliest days, web sites were entirely static. Then, we started adding functionality through JavaScript. This functionality was implemented as enhancements to the page (“progressive enhancement”). While this traditional model has been upended in recent years with the evolution of so-called single page apps (SPAs) that are rendered on the client using JavaScript, traditional best practice for the Centralised Web still holds that if your site doesn’t work without JavaScript, it is broken. For the Peer Web, we invert that rule:&lt;/p&gt;
&lt;p&gt;On the Peer Web, if your always on node works without JavaScript, it is centralised (and thus broken).&lt;/p&gt;
&lt;h4 id=&#34;trust-but-verify&#34;&gt;Trust but verify&lt;/h4&gt;
&lt;p&gt;An always-on node on the Peer Web is an unprivileged and untrusted node. Thus, when used to deliver an application to a browser, we must be sure that it is delivering exactly what we expect from it and nothing malicious. The way we do this is to make sure that we can verify the content that it serves. And that means that that content must be predictable. The method we use to verify the content is a combination of &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity&#34;&gt;subresource integrity of the script&lt;/a&gt; and third-party verification of the integrity hashes in the HTML (e.g., via a browser extension). What this precludes is the implementation of any server-side implementation of app functionality. All browser node functionality must be implemented client-side.&lt;/p&gt;
&lt;p&gt;The importance of the above paragraph becomes clear once you realise that aspects of a person’s identity are tied to pieces of information that only they possess and which they enter into their trusted nodes, including browser nodes. These passphrases (and the secret signing keys and encryption keys that are derived from them behind the scenes) must be kept secure as they are what secure the integrity of the person they belong to in the digital/networked age.&lt;/p&gt;
&lt;p&gt;On the Peer Web, a strong passphrase (eg., a randomly-generated &lt;a href=&#34;https://www.rempe.us/diceware/#eff&#34;&gt;Diceware&lt;/a&gt; phrase with &amp;gt;= 100 bits of entropy) is the key to a database that’s associated with a domain name you own as well as an uncensorable identifier (a hash that addresses your database within a distributed hash table).&lt;/p&gt;
&lt;p&gt;In terms of usability, we can (and should) carry forward familiar concepts and flows from the centralised world (such as “sign up” and “sign in” and “authorise device/node”) that abstract away the more confusing technical details of decentralised authentication and replication.&lt;/p&gt;
&lt;h4 id=&#34;building-bridges&#34;&gt;Building bridges&lt;/h4&gt;
&lt;p&gt;I mentioned earlier that the always-on node acts as a “dumb relay.” In this capacity, the always-on node works not just as a means to ensure findability and availability but also as a bridge between the browser and native apps (including, possibly, operating systems).&lt;/p&gt;
&lt;p&gt;In technical terms, the always on node bridges between native and web by replicating with native nodes over TCP and bridging with browser nodes via WebSocket. Native nodes, of course, replicate among themselves via TCP, and browser nodes via WebRTC. The end result, from the perspective of the people who use such systems &lt;a href=&#34;https://vimeo.com/70030549&#34;&gt;as everyday things&lt;/a&gt;, is a seamless and continuous experience across their various devices and between their browser and native apps.&lt;/p&gt;
&lt;p&gt;I have begun to explore the genera architecture outlined here with a series of spikes in a project I’m calling Hypha (&lt;a href=&#34;https://ar.al/tags/hypha/index.xml&#34;&gt;RSS&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/hypha&#34;&gt;Source&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/hypha-logo.jpeg&#34;
         alt=&#34;Hypha logo: several flat spheres arranged on the corners of a light grey isometric wireframe cube. The colours of the spheres match the rainbow colours from the Pride flag.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Hypha: a work-in-progress.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;hypha&#34;&gt;Hypha&lt;/h4&gt;
&lt;p&gt;The core technologies I’m basing Hypha on are the hyper* modules (&lt;a href=&#34;https://github.com/mafintosh/hypercore&#34;&gt;hypercore&lt;/a&gt;, &lt;a href=&#34;https://github.com/mafintosh/hyperdb&#34;&gt;hyperdb&lt;/a&gt;, and &lt;a href=&#34;https://github.com/mafintosh/hyperdrive&#34;&gt;hyperdrive&lt;/a&gt;) from the &lt;a href=&#34;https://datproject.org/&#34;&gt;Dat project&lt;/a&gt;. While Hypha is the logical evolution of &lt;a href=&#34;https://2017.ind.ie/heartbeat/&#34;&gt;Heartbeat&lt;/a&gt;, and the continuation of our work alongside the City of Ghent last year on the Indienet project, it is an evolution of (and differs substantially from) both in many details.&lt;/p&gt;
&lt;p&gt;Heartbeat, while it had the same overall goals, was limited in scope, design, and execution. It had a single privileged centralised node for signaling whereas in Hypha you get your own unprivileged always-on node. Heartbeat was also limited to a single device; &lt;a href=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2/&#34;&gt;Hypha is not&lt;/a&gt;. There are, also, aspects that are very similar. Heartbeat had a &lt;a href=&#34;https://milinda.pathirage.org/kappa-architecture.com/&#34;&gt;kappa architecture&lt;/a&gt; and replicated data using a modified version of &lt;a href=&#34;https://syncthing.net/&#34;&gt;Syncthing&lt;/a&gt;. Hypha will implement a kappa architecture (see &lt;a href=&#34;https://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;this workshop&lt;/a&gt; and the design of &lt;a href=&#34;https://cabal-club.github.io/&#34;&gt;Cabal&lt;/a&gt; for references) and use hyper* for replication. However, unlike Syncthing, which is a file backup tool that I was trying to shoehorn into being the engine of a decentralised web, Dat and the hyper* family are designed to solve the same problem I’m tackling. And, unlike Heartbeat, Hypha is not limited to a single platform and has both the browser and native apps as first-class citizens.&lt;/p&gt;
&lt;p&gt;Hypha is also an iteration of our Indienet project with the City of Ghent last year (here’s &lt;a href=&#34;https://channel.royalcast.com/webcast/stadgent/20180417_1?start=8473837&#34;&gt;a talk I gave at Eurocities describing it&lt;/a&gt; right before preparations for a new right-wing local government meant that our funding was cut for the project). The Ghent experience made me realise a number of things. First, that anything politically-funded is fragile and should be treated as such (as we should remember before putting our trust in city governments while designing systems. Your government may be progressive today and fascist tomorrow. Does the design of your system protect people against the worst-possible government that might ever come to power?) It also made me realise that I must follow a first-principles approach and that I need to take my time to fully grok and implement the fundaments of this system. (Specifically, that I cannot lead a team to think through these aspects for me.) In other words, slow down and (re)start small. That’s the approach I’m taking with Hypha and it is starting to really pay off. What Hypha will become is slowly evolving from a series of iterative &lt;a href=&#34;https://source.ind.ie/hypha/spikes&#34;&gt;spikes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For any interested developers out there, the latest completed spike is &lt;a href=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2/&#34;&gt;Multiwriter-2&lt;/a&gt; (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2&#34;&gt;Source&lt;/a&gt;), where I implemented and integrated &lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2&#34;&gt;an end-to-end encrypted secure ephemeral messaging channel&lt;/a&gt; that is used to provide seamless device/node authentication. The latest work-in-progress spike is &lt;a href=&#34;https://ar.al/2019/02/12/hypha-spike-persistence-1/&#34;&gt;Persistence-1&lt;/a&gt; (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/persistence-1&#34;&gt;Source&lt;/a&gt;), where I am now moving from using &lt;a href=&#34;https://github.com/random-access-storage/random-access-memory&#34;&gt;random access memory&lt;/a&gt; as storage to &lt;a href=&#34;https://github.com/random-access-storage/random-access-idb&#34;&gt;random access IndexedDB&lt;/a&gt; in the browser and &lt;a href=&#34;https://github.com/random-access-storage/random-access-file&#34;&gt;random access file&lt;/a&gt; on native. This iteration is also going to be the first time I implement a flow similar to what will eventually be used.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/13/on-the-general-architecture-of-the-peer-web/../../12/hypha-spike-persistence-1/flow.jpeg&#34;
         alt=&#34;Screenshot of the flow to be implemented in this spike. The details are explained in the text below.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Persistance-1 Spike will get us closer to the actual flow in Hypha proper.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Hypha has no release date, no big reveal, and makes no promises. It’s what I’m calling my current work on as I continue to tackle the general problem I’ve been working on in one shape or other for the past six years. At this point, I’m not looking for collaborators as I’m still working through the basic concepts on my own. But if you’re a developer and you want to start playing with some of the code, please do. Although it would probably be a more useful introduction to the space if you take the &lt;a href=&#34;https://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;Kappa Architecture Workshop&lt;/a&gt; and the &lt;a href=&#34;https://github.com/sodium-friends/learntocrypto&#34;&gt;Learn Crypto Workshop&lt;/a&gt; (&lt;a href=&#34;https://source.ind.ie/aral/learn-to-crypto-workshop&#34;&gt;find my work files here&lt;/a&gt;), start hanging out in &lt;a href=&#34;https://gitter.im/datproject/discussions?source=orgpage&#34;&gt;the Dat chat room&lt;/a&gt;, &lt;a href=&#34;https://datproject.org/&#34;&gt;exploring the Dat project&lt;/a&gt;, &lt;a href=&#34;https://blog.datproject.org/&#34;&gt;checking out the Dat blog&lt;/a&gt; and reading through the excellent &lt;a href=&#34;https://docs.datproject.org/&#34;&gt;documentation&lt;/a&gt; and &lt;a href=&#34;https://datprotocol.github.io/how-dat-works/&#34;&gt;reference material&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’re at the very beginning of the Peer Web (PC 2.0) era. &lt;a href=&#34;https://mastodon.ar.al/web/statuses/101580388258025942&#34;&gt;I saw yesterday&lt;/a&gt; that &lt;a href=&#34;https://ar.al&#34;&gt;my blog&lt;/a&gt;, which is &lt;a href=&#34;dat://ar.al&#34;&gt;also available over Dat&lt;/a&gt; (use &lt;a href=&#34;https://beakerbrowser.com&#34;&gt;Beaker Browser&lt;/a&gt; to view that link), is currently the most popular Dat site on the Internet. Out of the ten that the researcher was able to find by scouring the top 2.4 million domains, that is. And &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie’s web site&lt;/a&gt; (&lt;a href=&#34;dat://ind.ie&#34;&gt;dat link&lt;/a&gt;) is the third most popular. That tells you just how nascent this all is. If you want to help write the next chapter of the Internet, now is the time to pick up your quills and join us in hyperspace.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As always, if you want to support my work, you can &lt;a href=&#34;https://ind.ie/fund&#34;&gt;help fund our not-for-profit, Ind.ie&lt;/a&gt;, or just tell your friends about &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;, the free and open source tracker blocker that &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I develop, maintain, and which we sell on macOS and iOS.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: Persistence 1</title>
      <link>https://ar.al/2019/02/12/hypha-spike-persistence-1/</link>
      <pubDate>Tue, 12 Feb 2019 14:18:04 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/02/12/hypha-spike-persistence-1/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike previous spikes, I’m leaving this spike in a non-functional state and instead starting from scratch on the main project. See &lt;a href=&#34;#post-mortem&#34;&gt;post-mortem&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;source&#34;&gt;Source&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/persistence-1&#34;&gt;source.ind.ie/hypha/spikes/persistence-1&lt;/a&gt; (canonical location)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/indie-mirror/hypha-spike-persistence-1&#34;&gt;Github mirror&lt;/a&gt; (pull requests, issues, etc. welcome)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;scope&#34;&gt;Scope&lt;/h2&gt;
&lt;p&gt;Following on from &lt;a href=&#34;https://ar.al/2019/01/01/hypha-spike-multiwriter-2&#34;&gt;Hypha Spike: Multiwriter 2&lt;/a&gt; this spike aims to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use persistent storage&lt;/li&gt;
&lt;li&gt;Evolve the sign up / sign in processes accordingly&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/12/hypha-spike-persistence-1/flow.jpeg&#34;
         alt=&#34;Screenshot of the flow to be implemented in this spike. The details are explained in the text below.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Implementing persistance and getting closer to the actual flow.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Domain registration + hosting setup (out of scope, although see &lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;Hypha Spike: Deployment 1&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Origin node setup: an uninitialised always-on node is hit: the setup page is delivered. In browser (and, later, also via native app), person chooses a generated Diceware passphrase. The node that the person is using becomes the origin node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Origin node replicates to the unprivileged always-on node via WebSocket. At this point the always-on node is initialised and set to replicate that particular database only.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The person sees themselves as signed in and can post entries. (For the purposes of this spike, simple text-only public posts – just so we have some data and can see things replicate.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On some other node (browser or native), person accesses the domain. They can see the public posts as we set up a read-only local database and replicate them. We know which database to replicate based on a &lt;a href=&#34;https://www.datprotocol.com/deps/0005-dns/&#34;&gt;Dat-DNS&lt;/a&gt; lookup via the &lt;strong&gt;.well-known/dat&lt;/strong&gt; location on the domain of the always-on node. &lt;strong&gt;See security note 1.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They do not see the positing interface. Instead, they see a passphrase field (not shown in above whiteboard sketch) and a sign in button. Entering a passphrase creates the deterministic keys and proceeds to request authorisation via the &lt;a href=&#34;https://source.ind.ie/hypha/secure-ephemeral-messaging-channel&#34;&gt;secure ephemeral messaging channel&lt;/a&gt;. The person is asked to authorise the request from an existing node (e.g., the browser(/native app*) they set up from). _* out of scope for this spike._&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On a previously-authorised node (e.g., the origin node), the person approves the authorisation request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the newly-authorised node, the person sees the posting interface and can now write to the database.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;iteration-plan&#34;&gt;Iteration plan&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/persistence-1/tags/idb&#34;&gt;tag&lt;/a&gt;) Implement &lt;a href=&#34;https://github.com/random-access-storage/random-access-idb&#34;&gt;random-access-idb&lt;/a&gt; in the browser node.&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/persistence-1/tags/file&#34;&gt;tag&lt;/a&gt;)Implement &lt;a href=&#34;https://github.com/random-access-storage/random-access-file&#34;&gt;random-access-file&lt;/a&gt; in the always-on node.&lt;/li&gt;
&lt;li&gt;Update interface and flow according to the design notes above.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;future-plans&#34;&gt;Future plans&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Create a higher level Hypha authentication library with a simpler API that abstracts away the messaging aspect (@hypha/auth)&lt;/li&gt;
&lt;li&gt;Tie in with &lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;Hypha Spike: Deployment 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add options to interface to selectively enable replication over WebSocket or WebRTC or both for testing.&lt;/li&gt;
&lt;li&gt;Clean up the interface and carry out some general housekeeping on the code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;upcoming-spikes&#34;&gt;Upcoming spikes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;multiwriter hyperdrive (carried over from multiwriter-2)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;development-notes&#34;&gt;Development notes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Although the random-access-storage project states that the interfaces of the various random-access-* projects are the same, this is not entirely true for random-access-memory and random-access-idb (IndexedDB). This tripped me up while migrating from one to the other. &lt;a href=&#34;https://github.com/substack/random-access-idb/issues/6&#34;&gt;I noted the discrepancy and suggested that we document it.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you’re testing IndexedDB persistence on Firefox, make sure you are not browsing in private mode as it will fail silently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I’ve decided to use the hostname as the identifier of the browser node database. As browser nodes will have to be served from a domain and as they will have the domain available regardless of whether they are online or, later, offline (PWA support), it feels like the correct identifier to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initial load flow:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/12/hypha-spike-persistence-1/hypha-browser-node-initial-load-flow.jpeg&#34;
         alt=&#34;Flow chart showing the initial load flow.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Initial load flow for browser nodes.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Regarding sign-ins: if a database is lost for any reason, we will need to recreate it. So &lt;a href=&#34;https://github.com/mafintosh/hyperdb/pull/163&#34;&gt;I’ve reopened the reproducible writers pull request I was preparing for hyperdb&lt;/a&gt;. &lt;strong&gt;Update:&lt;/strong&gt; On further thought, having a reproducible unique ID for browser nodes is a hard problem and this feels like a premature optimisation right now. Instead, I realised I can separate the sign in process (the provision of the passphrase) from the database creation (something that also becomes clear once the flow accounts for unauthorised, read-only nodes – i.e., people who are just browsing your public content). Also, we can encrypt the local secret key and persist the encrypted key in the browser. The combination of these should mean that we can get away with sacrificing a writer if the database is lost and create a new one instead. We’ll need to keep an eye on real-world usage down the road but it doesn’t feel like this should be a showstopper. The &lt;a href=&#34;https://github.com/datprotocol/DEPs/blob/master/proposals/0008-multiwriter.md&#34;&gt;Multiwriter DEP&lt;/a&gt;, for example, states that “The design should easily accommodate dozens of writers, and should scale to 1,000 writers without too much additional overhead.” I will write a separate post to document the sign up/sign in and database creation flow.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/12/hypha-spike-persistence-1/hypha-separate-sign-in-from-node-initialisation.jpeg&#34;
         alt=&#34;The separate sign-in versus node initialisation flow mentioned in the bullet point above.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Separating sign in from database initialisation. Legend: rK = read key (public key), wK = write key (secret key), eK = (symmetric) encryption key (secret).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I’m also thinking that &lt;a href=&#34;https://gitter.im/datproject/discussions?at=5c6855d65095f6660c05d807&#34;&gt;the origin database shouldn’t be used for anything else but to create and authorise a secondary writer&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/12/hypha-spike-persistence-1/hypha-initial-setup.jpeg&#34;
         alt=&#34;Hypha initial node setup (showing the origin database being removed locally after creating and authorising the first node)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Setup flow where the origin database is used just to authorise the first writer.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Did some testing on hyperdb/hypercore (writer) recreation in a separate &lt;a href=&#34;https://source.ind.ie/hypha/spikes/recreate-hypercore&#34;&gt;recreate-hypercore spike&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;post-mortem&#34;&gt;Post-mortem&lt;/h3&gt;
&lt;p&gt;Working on this spike has made a couple of things clear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If I don’t want to introduce new concepts into the web experience (I don’t) and keep the flow to the traditional (sign up/sign in), I have to use two hyperdbs per instance: one signed out and one signed in:&lt;/p&gt;
&lt;p&gt;A person is signed out if they haven’t entered their passphrase and/or their write key was not saved (unencrypted) localStorage. The latter will be an option (“keep me signed in”). When an authorised node is signed out, the write key is stored encrypted in local storage and decrypted once the person has entered their passphrase.&lt;/p&gt;
&lt;p&gt;Since we must have the read and write key for a local writer in order to recreate a hyperdb, we cannot recreate a hyperdb when the write key is encrypted and the person has not entered their passphrase yet. In this case, we need a separate hyperdb, with a separate read and write key. This will allow us to implement the familiar (signed in/signed out) experience from the centralised web.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The spikes have morphed (especially during the last few ones) into iterations and have now grown to an unmanageable state. I’ve hit diminishing returns in this spike while trying to alter the core interactions. It’s time to start the project proper from scratch and using more maintainable practices. As such, I am going to leave this spike in its non-functional state and start the main project. I will still be using spikes in the future to explore specific issues but from here on, I will be iterating on the main project and on modules used by it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;security-considerations&#34;&gt;Security considerations&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Regarding design step 5:&lt;/strong&gt; Remember that the always-on node is untrusted and unprivileged. We could easily set it up so that it returns the Dat URL in the rendered source but we won’t be doing that. The unprivileged node will return unaltered source that we will verify using &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity&#34;&gt;subresource integrity&lt;/a&gt; (out of scope for this spike). We will also implement trusted third-party audits of the source (this could, for example, be handled by a browser extension that compares the hashes received as well as the hash of the source code with the trusted hashes from the source code repository).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Other:&lt;/strong&gt; See &lt;a href=&#34;https://ar.al/2019/01/01/hypha-spike-multiwriter-2&#34;&gt;Hypha Spike: Multiwriter 2&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/random-access-storage/random-access-idb&#34;&gt;random-access-idb&lt;/a&gt;: Random-access-compatible indexedDB storage layer.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/random-access-storage/random-access-file&#34;&gt;random-access-file&lt;/a&gt;:  Continuous reading or writing to a file using random offsets and lengths.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/datprotocol/DEPs/blob/master/proposals/0004-hyperdb.md&#34;&gt;DEP-0004: Hyperdb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/datprotocol/DEPs/blob/master/proposals/0008-multiwriter.md&#34;&gt;DEP-0008: Multiwriter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also see &lt;a href=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2#reference&#34;&gt;Hypha Spike: Multiwriter 2 Reference&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: Multiwriter 2</title>
      <link>https://ar.al/2019/02/01/hypha-spike-multiwriter-2/</link>
      <pubDate>Fri, 01 Feb 2019 15:04:03 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/02/01/hypha-spike-multiwriter-2/</guid>
      <description>&lt;h2 id=&#34;source&#34;&gt;Source&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2&#34;&gt;source.ind.ie/hypha/spikes/multiwriter-2&lt;/a&gt; (canonical location)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/indie-mirror/hypha-spike-multiwriter-2&#34;&gt;Github mirror&lt;/a&gt; (pull requests, issues, etc. welcome)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;scope&#34;&gt;Scope&lt;/h2&gt;
&lt;p&gt;Following on from &lt;a href=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1&#34;&gt;Hypha Spike: Multiwriter 1&lt;/a&gt; this spike aims to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplify the node authorisation flow to a simple authorisation alert on already-authorised nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2/authorisation-request.jpeg&#34;
         alt=&#34;Screenshot of an authorisation request. Message: Authorise node Firefox on Ubuntu 64-bit? Button with label: Authorise&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Peer-to-peer node authorisation: early proof of concept.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is the onboarding and new node authorisation (sign up/sign in) flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Person signs up either via a native app or the web. The app or the browser becomes the origin node. The database is only writable on this node at this point.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Person signs in using their password on a second node (either native or web). A database is created on this node and replicates with the origin database but it is read only at creation. The person is asked to sign into an existing node and approve the new node.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/02/01/hypha-spike-multiwriter-2/authorisation-via-ephemeral-message.jpeg&#34;
         alt=&#34;Description is in the caption&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Whiteboard sketch showing sign up (origin node) and sign in (read-only node) and the means available for peer-to-peer authorisation request of nodes via an encrypted ephemeral messaging channel.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The second node uses an encrypted ephemeral messaging channel extension to the Dat protocol to ask for authorisation. This request is sent browser-to-browser via WebRTC, native-to-native via TCP, and browser-to-native and native-to-browser via WebSocket (the always-on node proxies the requests via WebSocket but it cannot see the contents of the messages as it is an unprivileged node and doesn’t have the secret key).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the origin node (or, later, on any other authorised node), the person is prompted to authorise the new node. When they do, the new node becomes able to both read and write to the common database.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;iteration-plan&#34;&gt;Iteration plan&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;del&gt;✔ Refactor to create &lt;a href=&#34;https://github.com/mafintosh/hyperdb/issues/158&#34;&gt;reproducible local writers&lt;/a&gt;&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/manual-authentication-with-reproducible-local-writers&#34;&gt;tag&lt;/a&gt;) Authenticate (manually) using the node name and reproducing the remote node’s local keys&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://github.com/beakerbrowser/dat-ephemeral-ext-msg/pull/1&#34;&gt;pull request&lt;/a&gt;) Update &lt;a href=&#34;https://github.com/beakerbrowser/dat-ephemeral-ext-msg&#34;&gt;DEP-0000: Ephemeral Message (Extension Message)&lt;/a&gt; to support hyperdb.&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/ephemeral-messaging-1&#34;&gt;tag&lt;/a&gt;) Implement an &lt;a href=&#34;https://github.com/beakerbrowser/dat-ephemeral-ext-msg&#34;&gt;ephemeral messaging channel&lt;/a&gt; between nodes and use a JSON request to ask for authorisation of new nodes (WebRTC)&lt;/li&gt;
&lt;li&gt;✔ Also add the ephemeral messaging channel to replication over WebSocket&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/relay-1&#34;&gt;tag&lt;/a&gt;) Also add the ephemeral messaging channel to replication over TCP&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/deduplication&#34;&gt;tag&lt;/a&gt;) Add ephemeral message deduplication to the browser and native clients as messages may be received more than once due to the always-on node relay.&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/secure-ephemeral-messaging-channel&#34;&gt;source&lt;/a&gt;) Implement a secure ephemeral messaging channel as a Dat extension based on &lt;a href=&#34;https://github.com/beakerbrowser/dat-ephemeral-ext-msg&#34;&gt;DEP-0000: Ephemeral Message (Extension Message)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/secure-messages-1&#34;&gt;tag&lt;/a&gt;) Implement the secure ephemeral messaging channel in the spike. (Currently only on WebRTC replication.)&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/secure-ephemeral-messaging-channel/tags/unprivileged-relay-nodes&#34;&gt;tag&lt;/a&gt;) Extend the secure ephemeral messaging channel to support unprivileged relay nodes (the always-on nodes).&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/secure-messages-2&#34;&gt;tag&lt;/a&gt;) Implement secure ephemeral messaging channel on WebSocket connection. (Encrypted messages are now relayed by the always-on node.)&lt;/li&gt;
&lt;li&gt;✔ Update the native app (mock) to accept a secret key and set up the secure ephemeral messaging channel over a TCP connection.&lt;/li&gt;
&lt;li&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-2/tags/secure-messages-3&#34;&gt;tag&lt;/a&gt;) Fix: allow any authorised node to authorise any other node.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;pushed-to-later-spikes&#34;&gt;Pushed to later spikes:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create a higher level Hypha authentication library with a simpler API that abstracts away the messaging aspect (@hypha/auth)&lt;/li&gt;
&lt;li&gt;Add options to interface to selectively enable replication over WebSocket or WebRTC or both for testing.&lt;/li&gt;
&lt;li&gt;Clean up the interface and carry out some general housekeeping on the code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;future-plans&#34;&gt;Future plans&lt;/h2&gt;
&lt;p&gt;Release the authentication functionality as a stand-alone module that can be used across projects – e.g., @hypha/auth&lt;/p&gt;
&lt;h2 id=&#34;upcoming-spikes&#34;&gt;Upcoming spikes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;persistence&lt;/li&gt;
&lt;li&gt;multiwriter hyperdrive&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;security-considerations&#34;&gt;Security considerations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Authentication is stateless and handled via encrypted messages over an encrypted connection. Valid handled messages could be stored in the persistent database and used to protect against replay attacks (this would require a man in the middle attack on an encrypted connection with very little possible gain as the message can only be acted upon from a writeable node. If an adversary has the writeable node already, they would not need to request authorisation. The only scenario I can think of is if they somehow came across a read-only node that had requested authorisation but hadn’t been granted it.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As the messages are sent over an ephemeral messaging channel they cannot be used to denial of service the database as they are not persisted. They also cannot be used to denial of service the person’s experience (e.g., by causing endless alerts) as any messaged not encrypted with the secret key is simply discarded. Other techniques (rate limiting, etc.) can also be employed in a manner similar to how they are used in traditional APIs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hypha is app and browser-based. If a device containing authorised nodes is lost and stolen that is outside of Hypha’s jurisdiction. Proper device security should be employed (i.e., auto lock, password on lock, full-disk encryption). That said, if an unlocked device with an authorised Hypha node is compromised, we could consider having a ‘compromised’ message replicate to the other nodes. If the legitimate owner is still in possession of multiple nodes, they could send a compromised message from all those nodes, strengthening the trust in the message. Regardless, a single compromised message should be cause to not trust the database any longer. Additionally, the always-on node can be updated with a new database backed-up from the old one and Dat DNS could be used to point to the new database to enable the person to continue where they left off.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The secret keys are never persisted on nodes. They are regenerated as necessary from the passphrase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the always-on node is compromised, the adversary cannot write to the person’s database as the always-on node is an unprivileged node and has read-only access. If we decide to use Dat DNS (either host-based or DNS-record based), it could be used by an adversary to spoof the person’s database and replace it with another for anyone relying solely on the lookup. However, clients could check for changes in the read key and consider that a compromise unless the change is, for example, written into the database itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If both a writable node and the always-on-node are compromised that particular aspect of the person should be considered compromised.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;p&gt;In this spike, I added hyperdb support to Paul Frazee’s dat-ephemeral-ext-msg module and created an end-to-end encrypted secure messaging channel for use between nodes owned by the name person: &lt;a href=&#34;https://source.ind.ie/hypha/secure-ephemeral-messaging-channel&#34;&gt;secure-ephemeral-messaging-channel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Any authenticated node can now securely authorise any other node as a writer.&lt;/p&gt;
&lt;p&gt;Up to this point, the spikes have been using random-access-memory as the storage layer. In the next spike, I will expore persistence. My plan is to then look at the auth module refactor once the sign up/sign in process more closely implements real-world behaviour.&lt;/p&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/beakerbrowser/dat-ephemeral-ext-msg&#34;&gt;Dat Ephemeral Message Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdb/blob/master/ARCHITECTURE.md&#34;&gt;Hyperdb architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/lachenmayer/hyperdb-authorization-guide&#34;&gt;Hyperdb authorisation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdb/issues/153&#34;&gt;The definitive replication and authorization guide (github issue)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datprotocol.github.io/how-dat-works&#34;&gt;How DAT works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdb/issues/58&#34;&gt;Hyperlog-style semantics with hypercore/hyperdb?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdrive/pull/204&#34;&gt;Multiwriter hyperdb WIP pull request&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;historic-links-heartbeat--2014&#34;&gt;Historic links (Heartbeat – 2014)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://2017.ind.ie/heartbeat/&#34;&gt;Heartbeat&lt;/a&gt; was the initial precursor to Hypha. We were limited by lack of control over the replication engine we had chosen ­(syncthing). Other limitations were: it was single writer/device, it had a privileged centralised signalling server. That said, it basically used &lt;a href=&#34;http://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;kappa architecture&lt;/a&gt; (although the term was independently being coined at about the same time) and did solve some of the same challenges we need to solve now using the Dat protocol/ecosystem. The design is very close to that of &lt;a href=&#34;https://github.com/cabal-club/cabal&#34;&gt;Cabal&lt;/a&gt; but with the addition of authentication and private messaging.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/heartbeat-pre-alpha-release/740&#34;&gt;Heartbeat pre-alpha release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/pre-alpha-conceptual-design/25&#34;&gt;Heartbeat conceptual design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/pre-alpha-technical-design/42&#34;&gt;Heartbeat technical design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/pre-alpha-core-sequence-diagrams/34&#34;&gt;Heartbeat sequence diagrams&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;interesting-links&#34;&gt;Interesting links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/fsteff/DatFS&#34;&gt;DatFS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/poga/hyperidentity&#34;&gt;Hyperidentity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aral/hypercore-encrypted&#34;&gt;hypercore-encrypted&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/fsteff/hyperdb-encrypted&#34;&gt;hyperdb-encrypted&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Hypercore protocol deep dive</title>
      <link>https://ar.al/2019/01/24/hypercore-protocol-deep-dive/</link>
      <pubDate>Thu, 24 Jan 2019 07:29:23 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/24/hypercore-protocol-deep-dive/</guid>
      <description>&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;2019/01/24: This is a Work In Progress (WIP).&lt;/strong&gt; I will be live-updating this post as I work on the spike. If you want to get streaming updates without having to refresh your browser, &lt;a href=&#34;dat://ar.al/2019/01/24/hypercore-protocol-deep-dive/&#34;&gt;open the DAT version&lt;/a&gt; in &lt;a href=&#34;https://beakerbrowser.com/&#34;&gt;Beaker Browser&lt;/a&gt; and toggle the &lt;em&gt;live reloading&lt;/em&gt; feature. Please feel free to &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;talk to me about this&lt;/a&gt; on the fediverse as I work on it, perhaps via &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Last modified:&lt;/strong&gt; Thu, 24 Jan 2019 07:29:23 UTC&lt;/p&gt;

&lt;hr&gt;
&lt;p&gt;At the heart of &lt;a href=&#34;https://datproject.org&#34;&gt;Dat&lt;/a&gt; is the hypercore protocol. Understanding it is fundamental to grokking how and why the higher-level libraries and tools in the ecosystem work the way they do and have the properties they have.&lt;/p&gt;
&lt;p&gt;This is a documentation of my study of the &lt;a href=&#34;https://github.com/mafintosh/hypercore-protocol&#34;&gt;hypercore-protocol&lt;/a&gt; code. It’s meant to be a personal reference. For introductory material as well as general references, please see the &lt;a href=&#34;#references&#34;&gt;references&lt;/a&gt; section.&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The hypercore protocol is defined using &lt;a href=&#34;https://developers.google.com/protocol-buffers/&#34;&gt;protocol buffers&lt;/a&gt; in &lt;a href=&#34;https://github.com/mafintosh/hypercore-protocol/blob/master/schema.proto&#34;&gt;schema.proto&lt;/a&gt;. From this, &lt;a href=&#34;https://github.com/mafintosh/hypercore-protocol/blob/master/messages.js&#34;&gt;message.js&lt;/a&gt; is generated &lt;a href=&#34;https://github.com/mafintosh/hypercore-protocol/blob/7c79430ac108c758b50586fdda42bf8bfe533406/package.json#L24&#34;&gt;via&lt;/a&gt; &lt;code&gt;npm run protobuf&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initial message sent when establishing a connection is a &lt;a href=&#34;https://github.com/mafintosh/hypercore-protocol/blob/7c79430ac108c758b50586fdda42bf8bfe533406/schema.proto#L5&#34;&gt;Feed message&lt;/a&gt; that includes the discovery key and a nonce&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. If both parties have the read key (public signing key of the hypercore), then they can replicate from the second message onwards &lt;a href=&#34;https://datprotocol.github.io/how-dat-works/#Encryption&#34;&gt;via an encrypted connection that uses XSalsa20&lt;/a&gt; to encrypt the messages with the read key and the nonce.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;limitations&#34;&gt;Limitations&lt;/h2&gt;
&lt;h3 id=&#34;lack-of-general-ephemeral-messaging-channel&#34;&gt;Lack of general ephemeral messaging channel&lt;/h3&gt;
&lt;p&gt;For certain use cases (e.g., ability to share public keys in multi-writer replication/presence), it would be useful to have a generic and ephemeral (non-persisted) messaging channel. There is currently a DEP by Paul Frazee for using the extension message feature in the protocol to implement this (&lt;a href=&#34;https://www.datprotocol.com/deps/0006-session-data-extension/&#34;&gt;DEP-0006: Session Data&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Also see: &lt;a href=&#34;https://github.com/datprotocol/DEPs/pull/28&#34;&gt;DEP: Ephemeral message extension pull request&lt;/a&gt;. This PR was closed but I’m not sure why exactly as – unless I’m missing something – the privacy concerns can be addressed by signing the messages with the Feed key as part of the extension protocol itself (&lt;a href=&#34;https://github.com/datprotocol/DEPs/pull/28#issuecomment-457111761&#34;&gt;comment&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/datprotocol/DEPs/pull/27&#34;&gt;The discussion on the DEP’s pull request&lt;/a&gt; is very valuable and concerns the use cases that we have for Hypha also.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; there are ephemeral messages within the protocol (e.g., &lt;a href=&#34;https://datprotocol.github.io/how-dat-works/#keepalive&#34;&gt;keepalive&lt;/a&gt;) but no general means to send arbitrary ephemeral messages.&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://datprotocol.github.io/how-dat-works/&#34;&gt;How Dat works&lt;/a&gt; (&lt;a href=&#34;https://blog.datproject.org/2019/01/21/how-dat-works/&#34;&gt;blog post&lt;/a&gt;): documentation for the Dat protocol&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.datprotocol.com/&#34;&gt;DAT protocol spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.google.com/protocol-buffers/&#34;&gt;Protocol buffers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Number only used once. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: Multiwriter 1</title>
      <link>https://ar.al/2019/01/22/hypha-spike-multiwriter-1/</link>
      <pubDate>Tue, 22 Jan 2019 13:00:03 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/22/hypha-spike-multiwriter-1/</guid>
      <description>&lt;h2 id=&#34;source&#34;&gt;Source&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-1&#34;&gt;source.ind.ie/hypha/spikes/multiwriter-1&lt;/a&gt; (canonical location)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/indie-mirror/hypha-spike-multiwriter-1&#34;&gt;Github mirror&lt;/a&gt; (pull requests, issues, etc. welcome)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;p&gt;Following on from &lt;a href=&#34;../../15/hypha-spike-webrtc-1&#34;&gt;Hypha Spike: WebRTC 1&lt;/a&gt; and &lt;a href=&#34;https://ar.al/../../14/hypha-spike-dat-1/&#34;&gt;Hypha Spike: DAT 1&lt;/a&gt;, this spike aims to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get multiwriter working with hyperdb.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;flow&#34;&gt;Flow&lt;/h2&gt;
&lt;p&gt;Note: the flow is currently limited by not being able to pass a custom read and write for the local writer to hyperdb. I will be adding this functionality to hyperdb in the next spike. Once that’s in there, I should be able to reduce this flow to two steps: request permission + grant permission (via an interaction familiar to anyone who has ever authorised one device from another). I did not tackle that first as I wanted to get hyperdb and multiwriter working as-is myself before anything else. Taking it a step at a time.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-1.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 1&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Load app in two different browsers. Press the “Sign up” button in left browser.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-2.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 2&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Left browser initialises origin hyperbd and writes to it. Copy its hyphalink to the second browser and press the “Sign In” button.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-3.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 3&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This creates a hyperdb for the same hyphalink in the right browser. It replicates the latest entry from the left browser (see &lt;a href=&#34;#issues&#34;&gt;issues&lt;/a&gt;, below) and writes to its local hypercore but those writes do not replicate as it has not been authorised to write to the main hyperdb by the origin node (left browser).&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-4.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 4&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We write a few more entries using the Write button in the origin (left) browser and note that they replicate as expected to the right browser.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-5.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 5&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We write a couple of new entries into the right browser and note that, as expected, they do not replicate to the origin browser because the node in the right browser has not been authorised yet.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-6.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 6&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We copy the &lt;em&gt;local read key&lt;/em&gt; from the right browser and paste it into the &lt;em&gt;Other node read key&lt;/em&gt; field in the left browser and press the &lt;em&gt;Authorise&lt;/em&gt; button.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-7.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 7&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We note that the right browser is now authorised as its last item replicates to the origin node (left browser). Again, see &lt;a href=&#34;#issues&#34;&gt;issues&lt;/a&gt; (I don’t know yet why only the last-written item is replicating and not the whole hypercore. I might have configured it incorrectly but I haven’t had a chance to look into it yet.)&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-8.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Step 8&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We add a few more items to the right browser and note, as expected, that they now replicate to the origin node from the newly-authorised second writer.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-9.jpeg&#34;
         alt=&#34;Screenshot of the spike running on Firefox (left) and Chromium (right). Details are described in the body of the text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Backend&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We also note that &lt;em&gt;some&lt;/em&gt; of the entries have replicated to the always-on node over WebSocket. But not all (see &lt;a href=&#34;#issues&#34;&gt;issues&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;issues&#34;&gt;Issues&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;To-do:&lt;/strong&gt; investigate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only the last item added before and any items added after replication begins are replicated via WebRTC (the latter requires &lt;code&gt;{live: true}&lt;/code&gt; in the &lt;code&gt;options&lt;/code&gt;.) This does not manifest in replication over WebSocket (although, see below).&lt;/li&gt;
&lt;li&gt;Above also appears to manifest over TCP. Why?&lt;/li&gt;
&lt;li&gt;Also, recently noticed that only some items are replicating over WebSocket. Why?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Signalhub is now integrated into the server. You do not have to run it separately for WebRTC.&lt;/li&gt;
&lt;li&gt;Although we are not creating a persistent database in this iteration (we’re using random-access-ram instead of, say, random-access-i(ndexed)db), we must validate as if we were. The actual app will persist the database so it would never present the option to authorise a node from the same platform/app combination (e.g., Browser X on Platform Y). What this means practically is that you must test with two different browsers when testing on the same machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;iteration-plan&#34;&gt;Iteration plan&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-1/tags/initial&#34;&gt;tag&lt;/a&gt;) Implement multi-writer via &lt;a href=&#34;https://github.com/mafintosh/hyperdb&#34;&gt;hyperdb&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-1/tags/signalhub&#34;&gt;tag&lt;/a&gt;) Integrate Signal Hub*&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;✔ (&lt;a href=&#34;https://source.ind.ie/hypha/spikes/multiwriter-1/tags/multiwriter-working&#34;&gt;tag&lt;/a&gt;) Get multiwriter working with hyperdb automatically generating the local key material and with manual local key copy/paste between web clients&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;✔ Mirror the spike to GitHub&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is about as far as we can come with the current state of hyperdb.&lt;/p&gt;
&lt;p&gt;Once this is working, I have to extend hyperdb so that you can specify your own keys for the local writer so that we can have reproducible keys for writers.&lt;/p&gt;
&lt;p&gt;Once that’s done, I’ll spike:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cross-node authorisation via ephemeral messaging over hyperswarm and public-key authorisation (this can be released as a stand-alone module that can be used across projects – e.g., hyperauth)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Pushed to later iteration:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For ‘sign-in’ feature, generate a hypercore based on the reproducible node id&lt;/li&gt;
&lt;li&gt;Integrate WebRTC client*&lt;/li&gt;
&lt;li&gt;Implement multi-writer via &lt;a href=&#34;https://github.com/noffle/multifeed&#34;&gt;multifeed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;* These are to make it simple for others to clone and run the spike with minimal effort.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General:&lt;/strong&gt; document what’s necessary to implement proper multiwriter (i.e., with ability to both authorise and &lt;em&gt;de-authorise&lt;/em&gt; writer nodes). We need to get the ball rolling on this if it isn’t already.&lt;/p&gt;
&lt;h2 id=&#34;upcoming-spikes&#34;&gt;Upcoming spikes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Extend hyperdb to accept a hypercore factory in its constructor&lt;/li&gt;
&lt;li&gt;hyperdrive&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;limitations&#34;&gt;Limitations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It’s currently not possible to specify your own key material for the root hypercore in hyperdb. This means we cannot use hyperdb out of the box with the keys generated from our diceware passphrase (&lt;a href=&#34;https://github.com/mafintosh/hyperdb/issues/158&#34;&gt;Issue #158&lt;/a&gt;). I will be resolving this issue as the next spike.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;further-thoughts-on-device-authorisation&#34;&gt;Further thoughts on device authorisation&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/22/hypha-spike-multiwriter-1/multiwriter-key-derivation-whiteboard.jpeg&#34;
         alt=&#34;Whiteboard sketch showing two regular nodes and the always-on node. The keys for the regular nodes are derived from the master key.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Whiteboard sketch: thoughts on device keys and authentication in Hypha&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As initially posted in the &lt;a href=&#34;https://gitter.im/datproject/discussions?at=5c484da98318994524359c04&#34;&gt;Datproject discussion room&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Been giving device authorisation in multiwriter some more thought and, contrary to my initial knee-jerk reaction, I think it can be done within the limitations of the current system. It should be possible to handle lost/stolen devices as long as the write key (secret key/private key) is never stored on any device. Since I’m using key derivation from a Diceware passphrase salted with the unique domain in Hypha, this should be easy to support in a usable manner (the passphrase has to be stored in a password manager – or a brain better than mine ­– anyway).&lt;/p&gt;
&lt;p&gt;So I’m thinking that the master passphrase will not be tied to any hypercore and every device’s writeable hypercore will derive its keys from the master key with the name of the device used as the salt. This means that on any node where the person enters the passphrase and the name of the device to be authorised is known, its public (and private) key can be calculated and the device authorised in the local hypercore.&lt;/p&gt;
&lt;p&gt;If a device is lost/stolen, lack of the passphrase will disallow further writing. Of course, this requires that the device is properly secured at other layers in the stack (i.e., auto lock, password on lock, full-disk encryption).&lt;/p&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;See &lt;a href=&#34;#flow&#34;&gt;flow&lt;/a&gt;, &lt;a href=&#34;#issues&#34;&gt;issues&lt;/a&gt;, &lt;a href=&#34;#iteration-plan&#34;&gt;iteration plan&lt;/a&gt;, and &lt;a href=&#34;#upcoming-spikes&#34;&gt;upcoming spikes&lt;/a&gt; for a good summary of how the spike went, what issues I ran into, and how I plan to resolve them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdb/blob/master/ARCHITECTURE.md&#34;&gt;Hyperdb architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/lachenmayer/hyperdb-authorization-guide&#34;&gt;Hyperdb authorisation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdb/issues/153&#34;&gt;The definitive replication and authorization guide (github issue)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datprotocol.github.io/how-dat-works&#34;&gt;How DAT works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdb/issues/58&#34;&gt;Hyperlog-style semantics with hypercore/hyperdb?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mafintosh/hyperdrive/pull/204&#34;&gt;Multiwriter hyperdb WIP pull request&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;historic-links-heartbeat--2014&#34;&gt;Historic links (Heartbeat – 2014)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://2017.ind.ie/heartbeat/&#34;&gt;Heartbeat&lt;/a&gt; was the initial precursor to Hypha. We were limited by lack of control over the replication engine we had chosen ­(syncthing). Other limitations were: it was single writer/device, it had a privileged centralised signalling server. That said, it basically used &lt;a href=&#34;http://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;kappa architecture&lt;/a&gt; (although the term was independently being coined at about the same time) and did solve some of the same challenges we need to solve now using the Dat protocol/ecosystem. The design is very close to that of &lt;a href=&#34;https://github.com/cabal-club/cabal&#34;&gt;Cabal&lt;/a&gt; but with the addition of authentication and private messaging.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/heartbeat-pre-alpha-release/740&#34;&gt;Heartbeat pre-alpha release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/pre-alpha-conceptual-design/25&#34;&gt;Heartbeat conceptual design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/pre-alpha-technical-design/42&#34;&gt;Heartbeat technical design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.ind.ie/t/pre-alpha-core-sequence-diagrams/34&#34;&gt;Heartbeat sequence diagrams&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;interesting-links&#34;&gt;Interesting links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/fsteff/DatFS&#34;&gt;DatFS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/poga/hyperidentity&#34;&gt;Hyperidentity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aral/hypercore-encrypted&#34;&gt;hypercore-encrypted&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/fsteff/hyperdb-encrypted&#34;&gt;hyperdb-encrypted&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: WebRTC 1</title>
      <link>https://ar.al/2019/01/20/hypha-spike-webrtc-1/</link>
      <pubDate>Sun, 20 Jan 2019 14:08:58 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/20/hypha-spike-webrtc-1/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/20/hypha-spike-webrtc-1/webrtc-browser.jpeg&#34;
         alt=&#34;Two browser windows, side-by-side. The left one is running the DAT 1 spike, the right one this one. Data has replicated between them.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A hypercore replicating over WebRTC between two browsers&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/webrtc-1&#34;&gt;Source code&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;p&gt;Following on from (and in conjunction with) the &lt;a href=&#34;../../15/hypha-spike-dat-1&#34;&gt;Hypha Spike: DAT 1&lt;/a&gt;, this spike aims to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replicate a &lt;a href=&#34;https://github.com/mafintosh/hypercore&#34;&gt;hypercore&lt;/a&gt; from browser-to-browser using WebRTC.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;
&lt;h3 id=&#34;iteration-plan&#34;&gt;Iteration plan&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create a simple single-page web application that replicates a passed read key (equivalent to &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/blob/master/native/index.js&#34;&gt;the native replication script&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;usage&#34;&gt;Usage&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/20/hypha-spike-webrtc-1/webrtc-terminal.jpeg&#34;
         alt=&#34;The DAT 1 spike, this spike, and signalhub running in three terminal windows tiled in quarters in Tilix (the fourth terminal window is empty at my site’s directory)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The servers&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run the &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1&#34;&gt;Hypha DAT 1 Spike&lt;/a&gt; (&lt;a href=&#34;../../15/hypha-spike-dat-1&#34;&gt;blog post&lt;/a&gt;) in a separate browser and copy the hyphalink.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run a local instance of &lt;a href=&#34;https://github.com/mafintosh/signalhub&#34;&gt;signalhub&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;npm install -g signalhub
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; &amp;lt;dat-1-spike-directory&amp;gt;/server
signalhub listen -p &lt;span style=&#34;color:#40a070&#34;&gt;445&lt;/span&gt; --key localhost-key.pem --cert localhost.pem&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste the hyphalink into the &lt;em&gt;hyphalink&lt;/em&gt; field and press the &lt;em&gt;Connect&lt;/em&gt; button.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No special notes/gotchas. Worked out of the box.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/joehand/hyperdb-web-example/blob/master/index.js&#34;&gt;Joe Hand’s Hyperdb web example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: Diceware</title>
      <link>https://ar.al/2019/01/15/hypha-spike-diceware/</link>
      <pubDate>Tue, 15 Jan 2019 21:34:17 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/15/hypha-spike-diceware/</guid>
      <description>&lt;p&gt;Pulled out from &lt;a href=&#34;../../14/hypha-spike-dat-1&#34;&gt;Hypha Spike: DAT 1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/diceware&#34;&gt;Source code&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/15/hypha-spike-diceware/spike-screenshot.jpeg&#34;
         alt=&#34;Screenshot of the completed spike in the browser: Domain: ar.al Password: chive cartwheel attire headlamp approach alphabet splendid deceptive. There is a button labeled “Change”. Underneath, the generated public signing key, private signing key, public encryption key, and private encryption key are listed in form fields. The footnote reads that the password generation process has 100 bits of entropy and would take roughly a few hundred million years to brute force with government-level resources. It also says that the generated keyse are Ed25519 (signing) and Curve25519 (encryption)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of the completed spike.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;a href=&#34;https://www.rempe.us/diceware/&#34;&gt;Diceware&lt;/a&gt; for passphrase generation to ensure a high-entropy process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;a href=&#34;https://github.com/mattdesl/budo&#34;&gt;budo&lt;/a&gt; in the spikes to enable lightweight use of bundling/modules/etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;p&gt;Pulled this out into its own Spike so that it doesn’t clutter the DAT 1 spike.&lt;/p&gt;
&lt;p&gt;Starting with the &lt;a href=&#34;../../10/hypha-spike-aspect-setup-1&#34;&gt;Aspect Setup 1 spike&lt;/a&gt;, in this spike I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Refactored to use budo so I can use requires, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Was getting an error in budo due to Chokidar on Linux:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;Error: Cannot find module &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;fsevents&amp;#39;&lt;/span&gt; from &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;…/node_modules/chokidar/lib&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ended up upgrading dependencies for budo (&lt;a href=&#34;https://github.com/mattdesl/budo/pull/240&#34;&gt;pull request&lt;/a&gt;) and also squashing this error as the error is in error (&lt;a href=&#34;https://github.com/mattdesl/budo/pull/241&#34;&gt;pull request&lt;/a&gt;). We will, of course, not be using budo in production but it’s fine for the purposes of this spike.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Started generating passphrases using &lt;a href=&#34;https://www.rempe.us/diceware/&#34;&gt;Diceware&lt;/a&gt; and &lt;a href=&#34;https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases&#34;&gt;EFF’s New Wordlists for Random Passphrases&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://xkcd.com/936/&#34;&gt;xkcd: Password Strength&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://security.stackexchange.com/questions/6095/xkcd-936-short-complex-password-or-long-dictionary-passphrase/6116#6116&#34;&gt;Excellent explanation of above xkcd&lt;/a&gt;. Quote: “Security at the expense of usability comes at the expense of security.” – &lt;a href=&#34;https://security.stackexchange.com/users/33/avid&#34;&gt;AviD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://theintercept.com/2015/03/26/passphrases-can-memorize-attackers-cant-guess/&#34;&gt;Passphrases that you can memorize — but that even the NSA can’t guess&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rempe.us/diceware/&#34;&gt;Diceware&lt;/a&gt; (&lt;a href=&#34;https://github.com/grempe/diceware&#34;&gt;Source code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases&#34;&gt;EFF’s New Wordlists for Random Passphrases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/emilbayes/eff-diceware-passphrase&#34;&gt;eff-diceware-passphrase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: DAT 1</title>
      <link>https://ar.al/2019/01/14/hypha-spike-dat-1/</link>
      <pubDate>Mon, 14 Jan 2019 22:42:02 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/14/hypha-spike-dat-1/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/14/hypha-spike-dat-1/replication.jpeg&#34;
         alt=&#34;See caption (browser is on the right, the always-on node and native client are node apps running under Tilix)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of data replicated between the browser and the always-on node, and between the always-on node and a native client.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1&#34;&gt;Source code&lt;/a&gt; (See &lt;a href=&#34;iteration-plan&#34;&gt;iteration plan&lt;/a&gt; for links to specific tags.)&lt;/p&gt;
&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;p&gt;Following on from &lt;a href=&#34;../../15/hypha-spike-diceware&#34;&gt;Hypha Spike: Diceware&lt;/a&gt;, this spike aims to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating an in-browser DAT data store using the keys generated in the previous spike&lt;/li&gt;
&lt;li&gt;Replicating that datastore over a web socket connection with the always-on node and making it available over UTP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;
&lt;h3 id=&#34;iteration-plan&#34;&gt;Iteration plan&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;✔ &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/tags/create-hypercore&#34;&gt;Create hypercore in browser using the generated public and private signing keys.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;✔ &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/tags/expose-hypercore-state-on-page&#34;&gt;Expose hypercore state and events on the page itself.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;✔ &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/tags/replicating&#34;&gt;Replicate hypercore to server using websocket connection.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;✔ &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/tags/hyperswarm-native-replication&#34;&gt;Join hyperswarm and replicate hypercore from server from a native client.&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; since the original spike, a further iteration has also added &lt;a href=&#34;../../20/hypha-spike-webrtc-1&#34;&gt;WebRTC support&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;in-browser-hypercore-gotcha&#34;&gt;In-browser hypercore gotcha&lt;/h3&gt;
&lt;p&gt;When creating a hypercore in browser by manually specifying the read and write keys, you must convert the keys to Node.js’s Buffer type, you cannot use ArrayBuffer. Just submitted &lt;a href=&#34;https://github.com/mafintosh/hypercore/pull/189&#34;&gt;a pull request&lt;/a&gt; to the hypercore readme to make this explicit as this caught me out initially:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;[key]&lt;/code&gt; and &lt;code&gt;secretKey&lt;/code&gt; are &lt;em&gt;Node.js&lt;/em&gt; buffer instances, not browser-based ArrayBuffer instances. When creating hypercores in browser, if you pass an ArrayBuffer instance, you will get an error similar to &lt;code&gt;key must be at least 16, was given undefined&lt;/code&gt;. Instead, create a Node.js Buffer instance using &lt;a href=&#34;https://github.com/feross&#34;&gt;Feross‘s&lt;/a&gt; &lt;a href=&#34;https://github.com/feross/buffer&#34;&gt;buffer&lt;/a&gt; module (&lt;code&gt;npm install buffer&lt;/code&gt;). e.g.,
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; storage &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; someRandomAccessStorage
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; myPublicKey &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; someUint8Array

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; Buffer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;buffer&amp;#39;&lt;/span&gt;).Buffer
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; hypercorePublicKeyBuffer &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; Buffer.from(myPublicKeyAsUint8Array.buffer)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; hypercore &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; hypercore(storage, hypercorePublicKeyBuffer)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;callback-in-onwrite-hook-gotcha&#34;&gt;Callback in onwrite hook gotcha&lt;/h2&gt;
&lt;p&gt;If you implement the &lt;code&gt;onwrite&lt;/code&gt; hook in the options passed to the hypercore constructor, you must explicitly call the passed callback at the end of your handler (&lt;code&gt;cb()&lt;/code&gt;) or your appends will not work. (&lt;a href=&#34;https://github.com/mafintosh/hypercore/pull/190&#34;&gt;Pull request to update docs.&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&#34;websocketreplication-gotcha-with-budo-and-livereload&#34;&gt;WebSocket/replication gotcha with budo and livereload&lt;/h2&gt;
&lt;p&gt;Initially, I was getting the following errors while trying to replicate over the web socket connection:&lt;/p&gt;
&lt;h3 id=&#34;firefox&#34;&gt;Firefox:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;The connection to wss://localhost/livereload was interrupted &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;while&lt;/span&gt; the page was loading.
The connection to wss://localhost/hypha/f86a223b93b19929eee4e402480ac4d69ad4d8342b2f39b03f3dfd7d0fafbe93 was interrupted &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;while&lt;/span&gt; the page was loading.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;gnome-web&#34;&gt;GNOME web:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;Error&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; WebSocket connection to &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://localhost/livereload&amp;#39;&lt;/span&gt; failed: Compressed bit must be &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; no negotiated deflate-frame extension
&lt;span style=&#34;color:#666&#34;&gt;[&lt;/span&gt;Error&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt; WebSocket connection to &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;wss://localhost/hypha/78575ce623d7e7ef8e55c7d888e36f64c4fcea9404b1073ca517f94cc32b08b4&amp;#39;&lt;/span&gt; failed: Compressed bit must be &lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; no negotiated deflate-frame extension&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The issue is that budo’s web socket server and mine are clashing. It looks like Jim’s had it turned off also in his Shopping List Example (it defaults to off).&lt;/p&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;A strong passphrase is &lt;a href=&#34;https://github.com/emilbayes/eff-diceware-passphrase&#34;&gt;generated via EFF’s Diceware Word List&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;From the passphrase, Ed25519 (signing) and Curve25519 (encryption) key material is derived via &lt;a href=&#34;https://github.com/jo/session25519&#34;&gt;session25519&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The signing keys are used to create a &lt;a href=&#34;https://github.com/mafintosh/hypercore&#34;&gt;hypercore&lt;/a&gt; in the browser.&lt;/li&gt;
&lt;li&gt;The hypercore is replicated via web socket to the unprivileged always-on node (server)&lt;/li&gt;
&lt;li&gt;The always-on node joins the &lt;a href=&#34;https://github.com/hyperswarm&#34;&gt;hyperswarm&lt;/a&gt; and announces the hypercore via its discovery key.&lt;/li&gt;
&lt;li&gt;A native client is run with the &lt;em&gt;read key&lt;/em&gt; of the hypercore. It calculates the &lt;em&gt;discovery key&lt;/em&gt; from the &lt;em&gt;read key&lt;/em&gt; and uses hyperswarm to find and replicate the hypercore from the always-on node that originated in the browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;limitations&#34;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;This spike proves only &lt;em&gt;a subset the absolute basics&lt;/em&gt; of the Hyphanet design. See &lt;a href=&#34;#areas-for-future-study&#34;&gt;areas for future study&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;future-improvements&#34;&gt;Future improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✔ &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/tags/read-key-as-commandline-argument&#34;&gt;Specify read key as a command-line argument on the native node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Integrate &lt;a href=&#34;https://github.com/mafintosh/signalhub&#34;&gt;signalhub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;areas-for-future-study&#34;&gt;Areas for future study&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✔ &lt;a href=&#34;https://source.ind.ie/hypha/spikes/dat-1/tags/webrtc&#34;&gt;Browser-to-browser discovery and replication via WebRTC&lt;/a&gt;. See &lt;a href=&#34;../../20/hypha-spike-webrtc-1&#34;&gt;Hypha Spike WebRTC 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;../../22/hypha-spike-multiwriter-1/&#34;&gt;Multi-writer/multi-feed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CRDT&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jimpick/dat-shopping-list-tokyo&#34;&gt;Jim Pick’s DAT Shopping List demo&lt;/a&gt; (Tokyo version)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://datprotocol.github.io/how-dat-works&#34;&gt;How DAT works&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>I was wrong about Google and Facebook: there’s nothing wrong with them (so say we all)</title>
      <link>https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/</link>
      <pubDate>Fri, 11 Jan 2019 12:17:06 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/</guid>
      <description>&lt;p&gt;It’s always difficult admitting you’re wrong. But sometimes, it’s exactly what you have to do in the face of overwhelming evidence to the contrary. So, today, I admit that I was wrong about Google, Facebook, and &lt;a href=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/&#34;&gt;surveillance capitalism&lt;/a&gt; in general being toxic for our human rights and democracy.&lt;/p&gt;
&lt;p&gt;You see, it simply cannot be true given how they are endorsed by some of the most well-respected groups and organisations in the world. All the evidence points to Google and Facebook being good actors who are not a threats to our privacy.&lt;/p&gt;
&lt;h2 id=&#34;fsf-and-the-software-freedom-conservancy&#34;&gt;FSF and The Software Freedom Conservancy&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/google-microsoft-and-fsf-sponsor-copyleftconf.jpeg&#34;
         alt=&#34;The sponsors page of CopyLeftConf: Google, Microsoft, and FSF are among the sponsors&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;CopyLeftConf 2019 is brought to you by Google, Microsoft, and the FSF&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://www.fsf.org/&#34;&gt;The FSF&lt;/a&gt; is the world’s foremost defender of software freedom. &lt;a href=&#34;https://sfconservancy.org/&#34;&gt;The Software Freedom Conservancy&lt;/a&gt; “is a not-for-profit charity that helps promote, improve, develop, and defend Free, Libre, and Open Source Software (FLOSS) projects.” This month, The Software Freedom Conversancy is organising &lt;a href=&#34;https://2019.copyleftconf.org/&#34;&gt;CopyLeft Conference&lt;/a&gt; sponsored by Google, Microsoft, and the FSF.&lt;/p&gt;
&lt;p&gt;In fact, Google is such a force for good in the world that they are allowed to sponsor a CopyLeft conference &lt;a href=&#34;https://opensource.google.com/docs/using/agpl-policy/&#34;&gt;even though they ban CopyLeft licenses at their company&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If even the FSF doesn’t have a problem with having their logo right next to Google and Microsoft’s, who am I to criticise these corporations?&lt;/p&gt;
&lt;p&gt;How can I blame any policymaker who points to that and says “Aral, you’re being a bit melodramatic about these companies, aren’t you?” They’re right. I must be. What am I trying to do? Out-Stallman Stallman? Madness!&lt;/p&gt;
&lt;h2 id=&#34;mozilla&#34;&gt;Mozilla&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/firefox-default-search-engine.jpeg&#34;
         alt=&#34;Screenshot of Firefox showing the default search engine (Google)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Firefox defends your privacy. That’s why its default search engine is Google.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://mozilla.org&#34;&gt;Mozilla&lt;/a&gt; is &lt;a href=&#34;https://www.mozilla.org/en-US/foundation/moco/&#34;&gt;a not-for-profit for-profit&lt;/a&gt; organisation working to ensure “an Internet that truly puts people first, where individuals can shape their own experience and are empowered, safe and independent.” Mozilla makes Firefox, a browser that is &lt;a href=&#34;https://blog.mozilla.org/internetcitizen/2018/08/13/firefox-privacy-philosophy/&#34;&gt;Fast. Private. Fearless.&lt;/a&gt; That’s why its default search engine is Google&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Mozilla &lt;a href=&#34;https://mastodon.ar.al/@aral/101069104134924159&#34;&gt;doesn’t have a problem with Google&lt;/a&gt;, frequently partners with them, and &lt;a href=&#34;https://github.com/mozilla/addons-frontend/issues/1107#issuecomment-314754318&#34;&gt;even uses Google Analytics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If such an upstanding and ethical not-for-profit for-profit that cares so deeply about protecting our privacy has no problem with &lt;a href=&#34;https://www.cnet.com/news/google-firefox-search-deal-gives-mozilla-more-money-to-push-privacy/&#34;&gt;having Google as its primary search engine&lt;/a&gt; or &lt;a href=&#34;https://www.zdnet.com/article/firefox-hits-the-jackpot-with-almost-billion-dollar-google-deal/&#34;&gt;taking hundreds of millions of dollars from them&lt;/a&gt;, who am I to criticise Google on privacy?&lt;/p&gt;
&lt;h2 id=&#34;apple&#34;&gt;Apple&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/apple-privacy-ad-ces-2019.jpeg&#34;
         alt=&#34;Apple ad on side of building during CES 2019. Reads: ‘What happens on your iPhone, stays on your iPhone.’&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple’s devices protect your privacy. Google is the primary search engine on their browser. Google must also protect your privacy. (Photo: Chris Velazco, Engadget)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://2018.ar.al/notes/apple-vs-google-on-privacy-a-tale-of-absolute-competitive-advantage/&#34;&gt;Apple’s business model is to sell products to people&lt;/a&gt;. They are proud that this separates them from &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;companies that sell people to people&lt;/a&gt;. As &lt;a href=&#34;https://www.engadget.com/2019/01/05/apple-ces-2019-privacy-advertising/&#34;&gt;their building-tall ad at CES&lt;/a&gt; says, “What happens on your iPhone, stays on your iPhone.”&lt;/p&gt;
&lt;p&gt;As they state &lt;a href=&#34;https://apple.com/privacy&#34;&gt;on their privacy page&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At Apple, we believe privacy is a fundamental human right … Every Apple product is designed from the ground up to protect that information. And to empower you to choose what you share and with whom.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apple’s CEO, Tim Cook, &lt;a href=&#34;https://www.recode.net/2018/4/6/17197754/watch-apple-ceo-tim-cook-msnbc&#34;&gt;has personally endorsed this commitment to privacy&lt;/a&gt; and this is why Apple has Google as the default search engine for their browser and why &lt;a href=&#34;https://www.investopedia.com/news/google-spend-12-billion-remain-apple-safaris-2019-default-search-engine-gs/&#34;&gt;they welcome the 12 billion dollars&lt;/a&gt; of revenue that this deal brings in. Because Google is just like Apple and designs their services from the ground up to protect your privacy. Why else would Apple allow them on their phones and jeopardise your privacy?&lt;/p&gt;
&lt;p&gt;If Tim Cook is happy having Google on the iPhone then surely there must be something wrong with me.&lt;/p&gt;
&lt;h2 id=&#34;gnome&#34;&gt;GNOME&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/geary-gmail-vs-other-email-providers.jpeg&#34;
         alt=&#34;Screenshot showing the setup of Gmail (very simple) vs other email providers apart from Yahoo and Outlook.com (twice the height, multiple times the complexity)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;GNOME makes it easy to use Gmail (by advisory board member Google). If you use FastMail, however, you have to do some work.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If Apple is too commercial an example for you, then there’s &lt;a href=&#34;https://www.gnome.org/about/&#34;&gt;GNOME&lt;/a&gt;, led by the non-profit GNOME foundation. They make a popular and usable display environment for UNIX-like systems. It’s what I use on &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;my daily driver&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;GNOME doesn’t see a problem with Google. In fact, &lt;a href=&#34;https://wiki.gnome.org/AdvisoryBoard&#34;&gt;Google is on their advisory board&lt;/a&gt; and apps on GNOME have first-class support for Google’s services.&lt;/p&gt;
&lt;p&gt;Geary, the email application, for example, has Gmail listed in first place in the account setup dropdown and makes it trivial to get started with a simple interface that’s far easier to use than setting up a generic IMAP provider.&lt;/p&gt;
&lt;p&gt;If Gmail was bad for your privacy – if, for example, Google read all your messages and used it to profile you (I know, there I go with the nutty conspiracy theories again) – then GNOME foundation would not promote it in their software.&lt;/p&gt;
&lt;p&gt;If &lt;a href=&#34;https://social.libre.fi/objects/14464dd3-a085-4ef1-af11-bb9353086e8c&#34;&gt;they were forced to support Gmail just because it’s popular&lt;/a&gt; but absolutely hated doing so, they would put up a warning message to protect you. Something like “When you use Gmail, Google, Inc. will use the contents of your messages to profile you. Please only proceed if you understand the dangers.” But they don’t do that. On the contrary, they put it first and make it as easy as possible to set up and use so Gmail must be fine.&lt;/p&gt;
&lt;p&gt;In fact, I think we have to start asking questions about some of these other email providers who say they don’t spy on you. Like &lt;a href=&#34;https://www.fastmail.com&#34;&gt;FastMail&lt;/a&gt;, the service I use.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/fastmail-as-a-first-class-citizen-in-geary.jpeg&#34;
         alt=&#34;My fork of Geary has FastMail as a first-class citizen&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My fork of Geary has FastMail as a first-class citizen. This is because I must be a communist.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When &lt;a href=&#34;https://gitlab.gnome.org/GNOME/geary/merge_requests/26&#34;&gt;I added support for FastMail to Geary&lt;/a&gt;, &lt;a href=&#34;https://gitlab.gnome.org/GNOME/geary/merge_requests/26#note_281362&#34;&gt;my changes were rejected&lt;/a&gt;. If FastMail was an ethical provider of email, &lt;a href=&#34;https://gitlab.gnome.org/aral/geary/issues/1&#34;&gt;I’m sure that this would not have been the case&lt;/a&gt;. No doubt the team would have fallen over themselves to promote an ethical email service over an unethical one that reads people’s emails and profiles them.&lt;/p&gt;
&lt;p&gt;So now I’m worried about what GNOME knows about FastMail that I don’t. What are those sneaky FastMail people up to? Maybe we should all move to Gmail. It’s what GNOME recommends by default, after all.&lt;/p&gt;
&lt;h2 id=&#34;nordic-privacy-arena&#34;&gt;Nordic Privacy Arena&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/11/i-was-wrong-about-google-and-facebook-theres-nothing-wrong-with-them-so-say-we-all/nordic-privacy-arena-facebook-talk.jpeg&#34;
         alt=&#34;A slide from the Facebook keynote at Nordic Privacy Arena. It reads: ‘making an ad on Facebook’&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;We learned how to make Facebook ads at the Facebook keynote at the Nordic Privacy Arena. I was not clever enough to appreciate it.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://dpforum.se/nordic-privacy-arena/&#34;&gt;The Nordic Privacy Arena&lt;/a&gt; is a yearly gathering of &lt;a href=&#34;https://ec.europa.eu/info/departments/data-protection-officer_en&#34;&gt;data protection officers&lt;/a&gt; and &lt;a href=&#34;https://twitter.com/i/web/status/1072171225865248771&#34;&gt;privacy professionals&lt;/a&gt;. At this year’s event, Facebook had a keynote and I was told by the organisers to &lt;a href=&#34;https://mastodon.ar.al/@aral/101058108952903227&#34;&gt;be nice to Facebook and Google&lt;/a&gt;, &lt;a href=&#34;https://mastodon.ar.al/@aral/101058753005011136&#34;&gt;keep my points to my own talk&lt;/a&gt; and not challenge the speaker &lt;a href=&#34;https://mastodon.ar.al/@aral/101058298383358746&#34;&gt;with questions&lt;/a&gt; after &lt;a href=&#34;https://mastodon.ar.al/@aral/101058170623477123&#34;&gt;his talk&lt;/a&gt; like I did at Mozilla’s session.&lt;/p&gt;
&lt;p&gt;They were so right! What was I thinking? Thankfully, &lt;a href=&#34;https://www.youtube.com/watch?v=2oTgp2deLrg&#34;&gt;they cut my question from the video of Mozilla’s talk&lt;/a&gt; so you don’t have to be subjected to my radical conspiracy theory that if you’re getting hundreds of millions of dollars from a corporation you perhaps might not be working against its interests.&lt;/p&gt;
&lt;p&gt;I apologise profusely for being such a silly little pain in the ass. After all, if the data protection officers and privacy professionals organising and attending this event see no problem with Facebook or Google, who am I to disagree? These people are tasked with protecting our privacy. Surely, they have our best interests at heart and understand the basics of their job well enough to know exactly what they’re doing when they &lt;a href=&#34;https://twitter.com/DPforumSwe/status/1062358171165978624&#34;&gt;invite Google to keynote their next event&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Furthermore, the Facebook keynote was by &lt;a href=&#34;https://www.linkedin.com/in/nicolas-de-bouville-57486a4a&#34;&gt;Nicolas de Bouville&lt;/a&gt; whose previous job was at &lt;a href=&#34;https://www.cnil.fr/en/home&#34;&gt;the French data protection office&lt;/a&gt; (CNIL), famous for its magnificient revolving doors. So if Nicolas chose to go work at Facebook after CNIL, surely Facebook can’t be that bad.&lt;/p&gt;
&lt;p&gt;And it’s not just Nordic Privacy Arena. RightsCon by AccessNow &lt;a href=&#34;https://2018.ar.al/notes/rightscon-or-a-right-con/&#34;&gt;has no problem with Google or Facebook&lt;/a&gt;. As they state on their &lt;a href=&#34;https://www.rightscon.org/&#34;&gt;web&lt;/a&gt; &lt;a href=&#34;https://www.rightscon.org/sponsors/&#34;&gt;site&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RightsCon is the world’s leading conference on human rights in the digital age, brought to you by Access Now … RightsCon offers organizations and businesses the perfect opportunity to generate exposure, goodwill, and growth. So when you consider whether to invest in RightsCon, remember sponsorship can positively impact your organization and business by demonstrating leadership, building your brand, engaging your community, and more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Neither does &lt;a href=&#34;https://motherboard.vice.com/en_us/article/aekw4b/people-are-mad-that-facebook-and-google-sponsored-a-privacy-event&#34;&gt;Amsterdam Privacy Week&lt;/a&gt;. Nor &lt;a href=&#34;https://2018.ar.al/notes/why-im-not-speaking-at-cpdp&#34;&gt;CPDP&lt;/a&gt;. Nor &lt;a href=&#34;https://fosdem.org/2019/about/sponsors/&#34;&gt;FOSDEM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;No, it’s clear. It must be me. I must just be deluded. This is the only conclusion that makes sense given the avalanche of evidence.&lt;/p&gt;
&lt;h2 id=&#34;war-is-peace-freedom-is-slavery-surveillance-is-privacy&#34;&gt;War is Peace, Freedom is Slavery, Surveillance is Privacy&lt;/h2&gt;
&lt;p&gt;So, in light of the overwhelming support for surveillance capitalism by generally well-respected organisations that say they work to protect our human rights, privacy, and democracy, I have decided that I must be the one who’s wrong.&lt;/p&gt;
&lt;p&gt;If Google, Facebook, etc., were even half as bad &lt;a href=&#34;https://2018.ar.al/notes/we-didnt-lose-control-it-was-stolen/&#34;&gt;as I make them out to be&lt;/a&gt;, these organisations would not be partnering with or endorsing them.&lt;/p&gt;
&lt;p&gt;I just want to apologise for being such a “&lt;a href=&#34;https://2018.ar.al/notes/a-nancy-writes-back-or-how-i-learned-to-stop-worrying-and-love-digital-imperialism/&#34;&gt;Negative Nancy&lt;/a&gt;” and take this opportunity to thank the FSF, The Free Software Conservancy, Mozilla, Apple, The Nordic Privacy Arena, AccessNow, RightsCon, Amsterdam Privacy Week, CPDP, and GNOME for showing me the error of my ways. I now know, thanks to their moral leadership on this issue, that it’s perfectly fine for people working to protect human rights and democracy to take millions and billions from companies like Google and Facebook and to partner with them.&lt;/p&gt;
&lt;p&gt;I must admit, I feel a little silly. The last five years would have been so much easier if only I’d understood this earlier.&lt;/p&gt;
&lt;p&gt;Here’s wishing you all a world full of freedom sponsored by Google, privacy sponsored by Facebook, and democracy sponsored by Palantir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And, if for some wild reason that’s &lt;em&gt;not&lt;/em&gt; the world you want to live in, then maybe it’s time for &lt;em&gt;some organisations&lt;/em&gt; to take a fucking stand. Start calling surveillance capitalists “surveillance capitalists.” Say no to their money. And stop legitimising these bastards.&lt;/strong&gt;&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Mozilla is now trying to diversify its revenue stream and, in some markets, has different default search engines. For example, it partners with privacy-championing Chinese search engine Baidu in China. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: Aspect Setup 1</title>
      <link>https://ar.al/2019/01/10/hypha-spike-aspect-setup-1/</link>
      <pubDate>Thu, 10 Jan 2019 12:27:36 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/10/hypha-spike-aspect-setup-1/</guid>
      <description>&lt;h2 id=&#34;design&#34;&gt;Design&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/10/hypha-spike-aspect-setup-1/hypha-aspect-setup.jpeg&#34;
         alt=&#34;My Boogie Board notes on the general design on Hypha&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My Boogie Board notes on the general design on Hypha&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;philosophy&#34;&gt;Philosophy&lt;/h2&gt;
&lt;p&gt;Your identity – your &lt;em&gt;self&lt;/em&gt; – is a &lt;em&gt;sharded&lt;/em&gt; aggregate of information&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. For an organism to have integrity it must have ownership and control over the aggregate of these various elemental shards that, combined, constitute its being.&lt;/p&gt;
&lt;p&gt;In Hypha (&lt;a href=&#34;https://ar.al/tags/hypha/index.xml&#34;&gt;subscribe via RSS&lt;/a&gt;), I will call these shards &lt;em&gt;aspects&lt;/em&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;For the purposes of Hypha, an aspect is defined by a secret known only to the person who owns it.&lt;/p&gt;
&lt;p&gt;From this secret, we derive two keys&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A key to obtain and read this aspect (“read key”)&lt;/li&gt;
&lt;li&gt;A key to write to this aspect (“write key”)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Anyone with the &lt;em&gt;read key&lt;/em&gt; can replicate your aspect and read any unencrypted information in it. The root of an aspect is public information. It is how other people find you.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Your &lt;em&gt;write key&lt;/em&gt; is used both to add to your aspect and, for private information, to encrypt it.&lt;/p&gt;
&lt;p&gt;The owner of an aspect can write to it from any device that they own.&lt;/p&gt;
&lt;p&gt;The aspect acts as a root index that links to both public and private &lt;em&gt;collections&lt;/em&gt; of data. These collections may be &lt;em&gt;interactions&lt;/em&gt; and include contributions by multiple people on multiple devices.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;scope&#34;&gt;Scope&lt;/h2&gt;
&lt;p&gt;This spike will focus on the basics of aspect setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enter strong password on web interface&lt;/li&gt;
&lt;li&gt;Generate ED25519 signing keys (read key and write key&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;) from password (via Argon2)&lt;/li&gt;
&lt;li&gt;Generate Curve25519 encryption keys from signing keys&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Generate root aspect DAT archive using the keys generated in Step 2.&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Replicate the aspect from a separate node to test that it works as intended (e.g., command-line DAT running on a native client)&lt;/del&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Steps 4 &amp;amp; 5 moved to the next spike (see &lt;a href=&#34;#postmortem&#34;&gt;postmortem&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;All key generation happens on the client. The untrusted server (unprivileged always-on node) must never have the secret key.&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This spike is related to &lt;a href=&#34;https://source.ind.ie/indienet/spikes/security/publickey-auth-feathers-nuxt-sockets&#34;&gt;the Indienet publickey auth spike from last year&lt;/a&gt;. However, I no longer feel that publickey authentication is necessary for the client. The DAT archives are our source of truth, they’re what we replicate, and they already handle authentication. Visibility of sensitive data does not have to be controlled at the web interface level but handled through end-to-end encryption. I am therefore also leaning towards the web interface being a single-page application.&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&#34;spike-notes&#34;&gt;Spike notes&lt;/h2&gt;
&lt;h3 id=&#34;iteration-1&#34;&gt;Iteration 1&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/aspect-setup-1/tree/iteration-1&#34;&gt;Branch&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Review &lt;a href=&#34;https://source.ind.ie/indienet/spikes/security/publickey-auth-feathers-nuxt-sockets&#34;&gt;what we had with the Indienet project&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;iteration-2&#34;&gt;Iteration 2&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2019/01/10/hypha-spike-aspect-setup-1/hypha-aspect-setup-spike-iteration-2.jpeg&#34;
         alt=&#34;Screenshot of Iteration 2: A domain (ar.al) and a strong password have generated public/private signing and encryption keys.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of Iteration 2&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;(Master branch.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;a href=&#34;https://github.com/jo/session25519&#34;&gt;session25519&lt;/a&gt; to generate the DAT keypair from a strong passphrase and the domain name as salt. Since domain names are &lt;em&gt;globally unique&lt;/em&gt;, this is a strong salt, regardless of the fact that it is not random and could be short (e.g., ar.al).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I’m going with the approach in Iteration 2.&lt;/li&gt;
&lt;li&gt;In the next spike, I will look at generating a DAT using the generated key material and replicating it to the always-on node via web socket.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;also-see&#34;&gt;Also see&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Update: (2020-03-16)&lt;/strong&gt; All Indienet links removed as that project is now over and we no longer own the domain. Our work continues at Small Technology Foundation. See &lt;a href=&#34;https://sitejs.org&#34;&gt;Site.js&lt;/a&gt; and our ongoing &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;research and development&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Indienet general cryptography policy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Indienet security spikes (docs, &lt;a href=&#34;https://source.ind.ie/indienet/spikes/security&#34;&gt;source&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Indienet configuration information docs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;Hypha Spike: Deployment 1&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/&#34;&gt;How to ED25519 keys work?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://crackstation.net/hashing-security.htm&#34;&gt;Salted password hashing: doing it right&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Key generation in &lt;a href=&#34;https://github.com/kaepora/miniLock&#34;&gt;minilock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I include biological aspects in the definition of information because our biology is also, at its fundaments, information like all else. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; terms, an &lt;em&gt;aspect&lt;/em&gt; corresponds roughly to a &lt;a href=&#34;https://www.datprotocol.com/deps/0008-multiwriter/&#34;&gt;multiwriter&lt;/a&gt;/&lt;a href=&#34;https://github.com/noffle/multifeed&#34;&gt;multifeed&lt;/a&gt; hypercore collection. Neither are entirely fit for purpose in their current state although the former is a better fit for implementing a root aspect while the latter is better suited for implementing individual/group interactions (see &lt;a href=&#34;https://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;kappa architecture&lt;/a&gt;). &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is an &lt;a href=&#34;https://en.wikipedia.org/wiki/EdDSA&#34;&gt;ED25519&lt;/a&gt; keypair. The public key is the DAT &lt;a href=&#34;https://github.com/datprotocol/DEPs/pull/5#issuecomment-447495769&#34;&gt;read key&lt;/a&gt;. From the signing keys, we also generate Curve25519 encryption keys. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;On your &lt;a href=&#34;https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/&#34;&gt;always-on node&lt;/a&gt;, it is analogous with your domain name. That said, and crucially, it also exists separately from your domain name (which is still a centralised and commercially-governed identifier). Your read key (more precisely, its &lt;em&gt;hash&lt;/em&gt;, which we call the &lt;em&gt;discovery key&lt;/em&gt;) is the canonical address for your aspect. Even if your domain name changes/goes away, your aspect will remain as long as at least one host is available on the network for it to be found and replicated from. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Each collection/interaction is also a multifeed kappa architecture data store. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;While both the read and write keys are considered secrets for collections/interactions, for the root aspect, the read key considered public. It is possible to create a fully private aspect by not linking it to a domain name and not advertising the read key but that is not an initial use case for Hypha. It would be interested to see how it could be implemented with a native DAT browser (currently, &lt;a href=&#34;https://beakerbrowser.com&#34;&gt;Beaker Browser&lt;/a&gt;). &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s outside the scope of this spike but for future reference: We must protect the system from Evil Host/Evil App Store (or Evil Maid at Good Host, etc.) attacks. Since the setup of the private key happens on the client and must stay on the client unless we are to compromise all security and privacy of the system, we must ensure that the client is not compromised. For native clients, this can be achieved via combination of open source and reproducible builds. For web clients, we can use &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity&#34;&gt;subresource integrity&lt;/a&gt; along with third-party validators/a web of trust. &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is because the always-on node/web interface &lt;a href=&#34;https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/&#34;&gt;must be an unprivileged node&lt;/a&gt;. While the core of that requirement concerns the node not having the person’s secret, it also applies to not privileging it through additional functionality. I often say that we are building a bridge between the centralised web (&lt;a href=&#34;http://localhost:1313/2019/01/09/success-criteria-for-the-pc-2.0-era/#fn:1&#34;&gt;the Mainframe 2.0 era&lt;/a&gt;) but what I’ve so far failed to state is that this is a one-way bridge. The goal isn’t to encourage travel in both direction but travel from Mainframe 2.0 to PC 2.0. So it makes sense to invest as much effort as possible in the forward-looking (native) clients physically owned and controlled by everyday people that we are trying to move them towards instead of the bridge itself. The bridge is essential but it is a means to an end. &lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>The post-Web is single tenant</title>
      <link>https://ar.al/2019/01/09/the-post-web-is-single-tenant/</link>
      <pubDate>Wed, 09 Jan 2019 12:38:52 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/09/the-post-web-is-single-tenant/</guid>
      <description>&lt;p&gt;Hypha (&lt;a href=&#34;https://ar.al/tags/hypha/index.xml&#34;&gt;subcribe to updates via RSS&lt;/a&gt;) is an exploration of what personal technology means in the digital/networked age. The goal is to create a bridge from the Mainframe 2.0 era to the Peer Computing (PC 2.0) era&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. When we talk about scale in peer computing, our focus is on creating systems that are human-scale.&lt;/p&gt;
&lt;p&gt;To ensure that the systems we design are human-scale, we must favour:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Small over big&lt;/li&gt;
&lt;li&gt;Simple over complex&lt;/li&gt;
&lt;li&gt;Clarity over cleverness&lt;/li&gt;
&lt;li&gt;Inexpensive over expensive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not an exhaustive list. But you get the idea.&lt;/p&gt;
&lt;p&gt;For Hypha, it means that the always-on node is a single-tenant application/server.&lt;/p&gt;
&lt;p&gt;This focus means that we can remove a host of complexity from the design and keep things small, manageable, and inexpensive.&lt;/p&gt;
&lt;p&gt;When designing peer technology, we must nurture a profound respect for the limitations of individuals: whether that is time, knowledge, ability, or psychology. Not because of an elitist preconception about ‘the human condition’ that presupposes that some ill-defined ‘majority’ are lacking in any of those areas but because they constitute scarce resources &lt;em&gt;for all of us&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It is no longer permissible to perpetuate silly rights of passage based on the myth that people must work hard to obtain, understand, and therefore &lt;em&gt;deserve&lt;/em&gt; the tools that we create. We must put to rest the toxic myth that those who do not use the overly-complicated contraptions we create do so because of a lack of intelligence, ability, or desire. I do not use your crappy confusing dog’s arse of an application not because I do not care about my privacy or because I lack the necessary technical knowledge but because my name is Amanda and I’m a brain surgeon who works over 60 hours a week and has three kids at home. I simply do not have enough hours in the day to devote to deciphering the diarrhoeic mess you just dumped on my lap to save yourself the effort of thinking about anyone else but yourself while developing it. I think it’s time we laid to rest the stereotype of ‘even your grandmother’ and started designing for Amanda instead.&lt;/p&gt;
&lt;h2 id=&#34;a-system-is-only-as-simple-as-its-most-complicated-part&#34;&gt;A system is only as simple as its most complicated part.&lt;/h2&gt;
&lt;p&gt;Simplicity is our greatest competitive advantage. And a system is only as simple as its most complicated part. This is why we must, at all times, think holistically about the overall simplicity of the system we are designing. Which necessitates that we think about the whole experience from the outset.&lt;/p&gt;
&lt;p&gt;Now, I know from first-hand experience that this can be debilitating and result in developer’s block. So we must also proceed with the caveat of “for what is feasible given the current stage of development.” My goal &lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;at this point&lt;/a&gt; is to make it as simple as possible for other &lt;em&gt;developers&lt;/em&gt; to get up and running with Hypha on their own domain and on their own VPS.&lt;/p&gt;
&lt;p&gt;We will then iterate on making this a seamless process for everyday people who want to use Hypha as an everyday thing.&lt;/p&gt;
&lt;p&gt;My goal is to keep development as modular as possible so that we will, hopefully, get lots of small modules and tools as Hypha progresses.&lt;/p&gt;
&lt;h2 id=&#34;see-also&#34;&gt;See also&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/&#34;&gt;Success criteria for the PC 2.0 era&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/deployment-first-development/&#34;&gt;Deployment-first development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;Hypha deployment 1 spike&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The four eras of digital computing: ① Mainframe (centralised) → ② Personal Computing (PC 1.0; decentralised) → ③ Web/Cloud (Mainframe 2.0; centralised) → ④ Peer Computing (PC 2.0; decentralised) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Success criteria for the PC 2.0 era</title>
      <link>https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/</link>
      <pubDate>Wed, 09 Jan 2019 12:23:52 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/</guid>
      <description>&lt;p&gt;The major success criteria for the &lt;em&gt;Peer Computing&lt;/em&gt; (PC 2.0&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;) era, as I see them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Own and control your own identifiers.&lt;/strong&gt; You and you alone should decide on your identifiers and your identifiers should be accessible as long as at least one of your devices is accessible on the network. For a concrete example of how this would work, see how data is addressed under the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT protocol&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No privileged nodes&lt;/strong&gt;. The network must not have any privileged nodes, especially ones hosted by third-parties. This does not mean it cannot have third-party hosted nodes. In fact, it is a prerequisite for bootstrapping such a system in an accessible manner that we have always-on nodes hosted by third parties (see &lt;em&gt;always-on node&lt;/em&gt;, below). It does mean, however, that such nodes must be less-privileged than nodes that are under the physical control of people. Basically, this means that such nodes must never have the person’s private key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Effortlessly own and control your own always-on node&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; on the Internet.&lt;/strong&gt; For PC 2.0 to be adopted as &lt;em&gt;an everyday thing by everyday people&lt;/em&gt;, it must provide the same (if not better) levels of &lt;em&gt;findability&lt;/em&gt; and &lt;em&gt;availability&lt;/em&gt; as centralised systems. This is simply not possible in a purely peer-to-peer network, at least in its early stages. The always-on node is training wheels for the peer-to-peer network we are building. It is the supports we print when doing 3D printing. We might be able to remove them one day (and we should design it so that it can be removed) but without it, the whole thing would collapse from the start.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can either physically host your always-on node yourself&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; or have it hosted by a third-party&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. The always-on node is part of a personal peer-to-peer network of other more precariously-connected nodes that you have physical ownership of (e.g., your phone, laptop, home assistant, car, etc.)&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;
&lt;p&gt;Have the always-on node, unless it is hosted physically by you, be &lt;em&gt;less&lt;/em&gt; privileged than the other nodes. (You must be &lt;em&gt;the sole holder of the keys&lt;/em&gt; to the system so only devices you physically control will ever have your password/private key).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Have this system be as accessible, usable, and pleasurable as possible in totality. That includes criteria such as affordability&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;, ease of getting started (‘onboarding’), usefulness (functionality and usability), etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;see-also&#34;&gt;See also&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/the-post-web-is-single-tenant/&#34;&gt;The post-Web is single tenant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/deployment-first-development/&#34;&gt;Deployment-first development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;Hypha deployment 1 spike&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The four eras of digital computing: ① Mainframe (centralised) → ② Personal Computing (PC 1.0; decentralised) → ③ Web/Cloud (Mainframe 2.0; centralised) → ④ Peer Computing (PC 2.0; decentralised) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In colloqual usage, we could call it an “own node” or a &lt;em&gt;peer&lt;/em&gt; and/or &lt;em&gt;person cloud&lt;/em&gt; (both of which also fit the PC 2.0 acronym). I was originally thinking that we could call it AoN (pronounced, “own”) but thanks to &lt;a href=&#34;https://lily.network/@millenomi&#34;&gt;Lily&lt;/a&gt; for reminding me that we don’t need &lt;a href=&#34;https://lily.network/@millenomi/101383290368446261&#34;&gt;yet another weird acronym&lt;/a&gt;, I’ve reconsidered that. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This might be, for example, using a single-board computer like a Raspberry Pi or, perhaps even with the computer that comes pre-installed in your new home. (I know that certain new developments in Sweden, for example, are being wired with networked computers at construction-time. The nature of the software on these systems will determine whether your next house is a home or a panopticon.) &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The cost of VPS accounts is now a couple of euros a month and new instances can be spun up almost instantly either using automated scripts or pre-baked server images. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Ideally, the core elements will eventually be &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;provided for from our taxes as a public good&lt;/a&gt;. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Deployment-first development</title>
      <link>https://ar.al/2019/01/09/deployment-first-development/</link>
      <pubDate>Wed, 09 Jan 2019 11:59:46 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/09/deployment-first-development/</guid>
      <description>&lt;p&gt;Independent technology – &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical technology&lt;/a&gt; – must be as accessible as possible for its intended audience at every step of the process. That doesn’t mean it must be accessible as possible to &lt;em&gt;everyone&lt;/em&gt; at every stage in its development but rather it should be accessible as possible for the people that are working on it or with it at any given point.&lt;/p&gt;
&lt;p&gt;Hypha is currently at the start of its development stage and thus must be as accessible as possible to developers who want to follow along with its development, run it themselves, and possibly fork it off and try new things with it.&lt;/p&gt;
&lt;p&gt;One of the greatest barriers to trying out new free and open source web-related projects is the difficulty in deploying them. This is because nearly all web-related projects are designed as multi-tenant services. They are designed to scale to being used by thousands if not millions or billions of people. And that brings with it a whole slew of complexity.&lt;/p&gt;
&lt;p&gt;Case in point: I spent hours and failed the first time I tried to deploy &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. One of my developers on the Ghent project last year gave up after struggling with it for most of a day. While I eventually managed to deploy Mastodon for myself initially via a “serverless” Heroku-ish service and, eventually, from source, today I do not maintain my own Mastodon instances. Instead, I offload that complicated and involved task to the wonderful Hugo who runs &lt;a href=&#34;https://masto.host&#34;&gt;masto.host&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Which is why I’m looking into &lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;deployment-first development with Hypha&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;see-also&#34;&gt;See also&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/the-post-web-is-single-tenant/&#34;&gt;The post-Web is single tenant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/&#34;&gt;Success criteria for the PC 2.0 era&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/05/hypha-spike-deployment-1/&#34;&gt;Hypha deployment 1 spike&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I love Mastodon. &lt;a href=&#34;https://mastodon.ar.al/@ind.ie&#34;&gt;Here’s mine.&lt;/a&gt; &lt;a href=&#34;https://mastodon.ind.ie/@indie&#34;&gt;Here’s Ind.ie’s.&lt;/a&gt; But it’s not personal technology. It is not single tenant. It has the complexity of a system that can host hundreds of thousands of people on a single instance. We are not building that. We are building for instances of one. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Hypha Spike: Deployment 1</title>
      <link>https://ar.al/2019/01/05/hypha-spike-deployment-1/</link>
      <pubDate>Sat, 05 Jan 2019 23:40:29 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/05/hypha-spike-deployment-1/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/deployment-1&#34;&gt;Source code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wait, what, we’re deploying &lt;a href=&#34;https://ar.al/2018/12/07/baby-steps/&#34;&gt;Hypha&lt;/a&gt; (&lt;a href=&#34;https://ar.al/tags/hypha/index.xml&#34;&gt;subscribe via RSS&lt;/a&gt;) – but we haven’t even built it yet?!&lt;/p&gt;
&lt;p&gt;Exactly.&lt;/p&gt;
&lt;h2 id=&#34;philosophy&#34;&gt;Philosophy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/the-post-web-is-single-tenant/&#34;&gt;The post-Web is single tenant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/success-criteria-for-the-pc-2.0-era/&#34;&gt;Success criteria for the PC 2.0 era&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ar.al/2019/01/09/deployment-first-development/&#34;&gt;Deployment-first development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;design-and-scope-limitation-for-the-spike&#34;&gt;Design and scope limitation for the spike&lt;/h2&gt;
&lt;p&gt;There are two interrelated processes to deploying your own instance of Hypha:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Domain registration and/or DNS setup&lt;/li&gt;
&lt;li&gt;VPS server setup&lt;/li&gt;
&lt;li&gt;TLS setup&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is a somewhat cyclic relationship between these three steps as they each depend on the other for certain information.&lt;/p&gt;
&lt;p&gt;The DNS setup requires the IP address of the server and the server needs to know the domain that it will be responding for. To complicate things a little more, the domain name has to propagate before we can obtain a free TLS certificate from &lt;a href=&#34;https://beakerbrowser.com/&#34;&gt;Let’s Encrypt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, steps 1 and 2 have a commercial aspect.&lt;/p&gt;
&lt;p&gt;For the purposes of this spike, I want to concentrate only on Step 2: automating the VPS server setup.&lt;/p&gt;
&lt;h2 id=&#34;cloud-init&#34;&gt;Cloud-init&lt;/h2&gt;
&lt;p&gt;VPS accounts are available for a couple of euros per month these days and many support cloud-config syntax (&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/examples.html#yaml-examples&#34;&gt;examples&lt;/a&gt;) via &lt;a href=&#34;https://cloud-init.io/&#34;&gt;the cloud-init standard&lt;/a&gt; by Canonical as part of the new instance provisioning process via a ‘user data’ field on their online forms or via their APIs. &lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/availability.html&#34;&gt;Supported operating systems&lt;/a&gt; include Ubuntu, Fedora, Debian, RHEL, CentOS, and others.&lt;/p&gt;
&lt;p&gt;In this spike, I’m going to explore using cloud-init to set up a server so that we can automatically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Install the latest Long-Term Support (LTS) version of &lt;a href=&#34;https://nodejs.org/en/&#34;&gt;Node.js&lt;/a&gt; in a manner that would make it easy to perform updates on it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone and run an empty (‘hello world’) version of Hypha.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thankfully, Canonical has a tool called &lt;a href=&#34;https://github.com/CanonicalLtd/multipass&#34;&gt;multipass&lt;/a&gt; that lets you easily spin up Ubuntu instances locally &lt;a href=&#34;https://blog.ubuntu.com/2018/04/02/using-cloud-init-with-multipass&#34;&gt;and pass them a cloud-init file&lt;/a&gt;. I’ll be using that to iterate on the cloud-init script.&lt;/p&gt;
&lt;h3 id=&#34;notes&#34;&gt;Notes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/hypha/spikes/deployment-1&#34;&gt;Spike source code repository&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/&#34;&gt;cloud-init documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cloud-init format supports Gzip compression as &lt;a href=&#34;https://cloudinit.readthedocs.io/en/latest/topics/format.html#gzip-compressed-content&#34;&gt;user-data is limited to ~16,384 bytes&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;add-an-account-so-you-can-ssh-into-the-instance&#34;&gt;Add an account so you can ssh into the instance&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#cloud-config&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;users&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;- &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;INSERT ACCOUNT NAME HERE&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;groups&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;sudo&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;shell&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;/bin/bash&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;sudo&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;ALL=(ALL) NOPASSWD:ALL&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ssh-authorized-keys&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&amp;lt;INSERT SSH PUBLIC KEY HERE&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;&amp;lt;INSERT ACCOUNT NAME HERE&amp;gt;&lt;/code&gt; with the account name you want (e.g., this is the &lt;code&gt;&amp;lt;account name&amp;gt;@&amp;lt;your instance ip&amp;gt;&lt;/code&gt; that you will use to SSH into the instance).&lt;/p&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;lt;INSERT SSH PUBLIC KEY HERE&amp;gt;&lt;/code&gt; with your public SSH key, which you can most likely find in &lt;code&gt;~/.ssh/id_rsa.pub&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, if your account name is &lt;em&gt;indie&lt;/em&gt;, you want the instance to be called &lt;em&gt;hypha&lt;/em&gt;, and you save the above file as &lt;em&gt;cloud-init.yaml&lt;/em&gt;, you can start up a new instance and connect to it over SSH:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create an launch the hypha instance:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;multipass launch --name hypha --cloud-init cloud-init.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List the available instances to find the IP address of the new hypha instance (e.g., 10.83.214.166):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;multipass list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connect via SSH:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;ssh indie@10.83.214.166
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is a good article on &lt;a href=&#34;https://serversforhackers.com/c/permissions-and-user-management&#34;&gt;users and groups&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the final cloud init file, with many more tasks, see &lt;a href=&#34;https://source.ind.ie/hypha/spikes/deployment-1/blob/master/cloud-init.yaml&#34;&gt;cloud-init.yaml&lt;/a&gt; and read the comments.&lt;/p&gt;
&lt;h2 id=&#34;thoughtsto-dosquestions&#34;&gt;Thoughts/to-dos/questions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Since TLS setup with Let’s Encrypt depends on domain name propagation, it is the last thing we must do (and is thus outside the scope of this spike). See &lt;a href=&#34;https://www.aaflalo.me/2017/02/lets-encrypt-with-dehydrated-dns-01/&#34;&gt;dns-01 verification&lt;/a&gt; (&lt;a href=&#34;https://github.com/lukas2511/dehydrated/wiki/Examples-for-DNS-01-hooks&#34;&gt;examples&lt;/a&gt;). Can be used along with &lt;a href=&#34;https://github.com/AnalogJ/lexicon&#34;&gt;Lexicon&lt;/a&gt; for manipulating DNS records in a standardised way across providers. e.g., See &lt;a href=&#34;https://github.com/aral/dehydrated_namecheap_dns_api_hook?organization=aral&amp;amp;organization=aral&#34;&gt;this Dehydrated hook for Namecheap + Let’s Encrypt&lt;/a&gt;. Also interesting, IWantMyName has &lt;a href=&#34;https://iwantmyname.com/developer/domain-dns-api&#34;&gt;a Dynamic DNS interface&lt;/a&gt; which &lt;a href=&#34;https://github.com/hughdavenport/letsencrypt-iwantmyname-hook/issues/1&#34;&gt;could possibly be used for this&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Node.js is perfectly capable as its own server and does not need to be proxied (e.g., by nginx) for single-tenant use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;✓ &lt;del&gt;Add link to spike source code repository&lt;/del&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How long does server setup take?&lt;/p&gt;
&lt;p&gt;About ~2 minutes 30 seconds. 2 minutes of that is our custom initialisation and 30 seconds the generic server setup. That incudes apt update/upgrade, Node.js install, PM2 install, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test the cloud-init script with a number of different hosts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;to-explore-in-future-spikes&#34;&gt;To explore in future spikes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Domain registration and DNS setup via third-party service and API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Native app that handles onboarding (domain registration, DNS setup, VPS setup, TLS setup, and app setup) in a seamless experience.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As above but with the integration of a payment step for the domain registration and hosting.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h2&gt;
&lt;p&gt;We can get a server up and running with a Node.js app in ~ 2 minutes 30 seconds without any optimisations. This could be hugely optimised for everyday use later by having prebuilt server images but it is entirely acceptable as-is for use by developer to deploy their own copy of Hypha. Even when TLS is supported, the longest part of a developer getting up and running with their own node of Hypha will be the DNS propagation.&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;h3 id=&#34;tls&#34;&gt;TLS&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/FiloSottile/mkcert&#34;&gt;mkcert&lt;/a&gt;: a simple zero-config tool to make locally trusted development certificates with any names you&amp;rsquo;d like&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;server-setup&#34;&gt;Server setup&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/CanonicalLtd/multipass&#34;&gt;multipass&lt;/a&gt;: orchestrate virtual Ubuntu instances (supports cloud-init)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cloud-init.io/&#34;&gt;cloud-init&lt;/a&gt;: the standard for customising cloud instances&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/nodesource/distributions&#34;&gt;NodeSource Node.js binary distributions&lt;/a&gt;: &lt;a href=&#34;https://github.com/nodesource/distributions#deb&#34;&gt;for Ubuntu&lt;/a&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/Unitech/pm2&#34;&gt;PM2&lt;/a&gt;: Node.js Production Process Manager with a built-in Load Balancer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;promising-discoveries&#34;&gt;Promising discoveries&lt;/h3&gt;
&lt;p&gt;(Unused in current spike but might be useful in the future.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/Daplie/greenlock-express&#34;&gt;greenlock-express&lt;/a&gt;: Free SSL and managed or automatic HTTPS for node.js with Express…&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/nodenv/nodenv&#34;&gt;nodenv&lt;/a&gt;: for managing node versions in production&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Getting Green Recorder running on Wayland under Gnome on Ubuntu 18.10-based systems</title>
      <link>https://ar.al/2019/01/01/getting-green-recorder-running-on-wayland-under-gnome-on-ubuntu-18.10-based-systems/</link>
      <pubDate>Tue, 01 Jan 2019 20:17:41 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/01/getting-green-recorder-running-on-wayland-under-gnome-on-ubuntu-18.10-based-systems/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/foss-project/green-recorder&#34;&gt;Green Recorder&lt;/a&gt; is an app for recording your screen on Linux which, as far as I know, is the only such app at the moment that works with Wayland. It’s what I used to capture &lt;a href=&#34;../al-jazeera-news-interview-french-tech-tax/&#34;&gt;the video of my segment on Al Jazeera News today&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I had trouble trying to install it on my laptop running &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt; 18.10&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; with Gnome 3.30.&lt;/p&gt;
&lt;p&gt;Here’s a brief summary of the issues I encountered and the workarounds I implemented to get it running.&lt;/p&gt;
&lt;h2 id=&#34;e-unable-to-locate-package-green-recorder&#34;&gt;E: Unable to locate package green-recorder&lt;/h2&gt;
&lt;p&gt;I was unable to install Green Recorder via apt&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;to-fix&#34;&gt;To fix:&lt;/h3&gt;
&lt;p&gt;I installed it from source:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo pip install pydbus
git clone https://github.com/foss-project/green-recorder.git
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; green-recorder
sudo python setup.py install&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The build succeeded but running the &lt;em&gt;green-recorder&lt;/em&gt; binary resulted in a series of errors.&lt;/p&gt;
&lt;h2 id=&#34;failed-to-open-file-usrsharegreen-recorderuiglade-no-such-file-or-directory&#34;&gt;Failed to open file “/usr/share/green-recorder/ui.glade”: No such file or directory&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;Traceback &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;most recent call last&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;:
  File &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;./green-recorder&amp;#34;&lt;/span&gt;, line 262, in &amp;lt;module&amp;gt;
    builder.add_from_file&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;/usr/share/green-recorder/ui.glade&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
gi.repository.GLib.Error: g-file-error-quark: Failed to open file “/usr/share/green-recorder/ui.glade”: No such file or directory &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;4&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;to-fix-1&#34;&gt;To fix:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo mv /usr/local/share/green-recorder /usr/share/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;could-not-load-usrsharepixmapsgreen-recorderpng&#34;&gt;Could not load &amp;lsquo;/usr/share/pixmaps/green-recorder.png&amp;rsquo;&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;green-recorder:12589&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;: Gtk-WARNING **: 15:59:28.105: Could not load image &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/usr/share/pixmaps/green-recorder.png&amp;#39;&lt;/span&gt;: Failed to open file “/usr/share/pixmaps/green-recorder.png”: No such file or directory&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;to-fix-2&#34;&gt;To fix:&lt;/h3&gt;
&lt;p&gt;Move it to &lt;code&gt;/usr/share/pixmaps/&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo mv /usr/local/share/pixmaps/green-recorder.png /usr/share/pixmaps&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;configparsernooptionerror-errors&#34;&gt;ConfigParser.NoOptionError errors&lt;/h2&gt;
&lt;p&gt;You encounter a number of errors as certain app options do not exist:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;ConfigParser.NoOptionError: No option &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;videoswitch&amp;#39;&lt;/span&gt; in section: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Options&amp;#39;&lt;/span&gt;
ConfigParser.NoOptionError: No option &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;audioswitch&amp;#39;&lt;/span&gt; in section: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Options&amp;#39;&lt;/span&gt;
ConfigParser.NoOptionError: No option &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;mouseswitch&amp;#39;&lt;/span&gt; in section: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Options&amp;#39;&lt;/span&gt;
ConfigParser.NoOptionError: No option &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;followmouseswitch&amp;#39;&lt;/span&gt; in section: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Options&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;to-fix-3&#34;&gt;To fix:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;videoswitch = True&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt;  ~/.config/green-recorder/config.ini
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;audioswitch = True&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt;  ~/.config/green-recorder/config.ini
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;mouseswitch = True&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt;  ~/.config/green-recorder/config.ini
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;followmouseswitch = False&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt;  ~/.config/green-recorder/config.ini&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(You can toggle all of these defaults in the interface later but they need to be set in the config file for the app to run properly for the first time.)&lt;/p&gt;
&lt;p&gt;With those fixes, the app ran and I was able to get a fullscreen recording&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;outstanding-issues&#34;&gt;Outstanding issues&lt;/h2&gt;
&lt;p&gt;Although full-screen recording works, the &lt;em&gt;Select a Window&lt;/em&gt; button is disabled.&lt;/p&gt;
&lt;p&gt;Selecting the &lt;em&gt;Select an Area&lt;/em&gt; button results in a selection window but clicking the &lt;em&gt;Apply&lt;/em&gt; button results in the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;xwininfo: error: No window with name &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Area Chooser&amp;#34;&lt;/span&gt; exists!
Traceback &lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;most recent call last&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;:
  File &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;./green-recorder&amp;#34;&lt;/span&gt;, line 453, in areasettings
    &lt;span style=&#34;color:#bb60d5&#34;&gt;output&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; subprocess.check_output&lt;span style=&#34;color:#666&#34;&gt;([&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;xwininfo -name \&amp;#34;Area Chooser\&amp;#34; | grep -e Width -e Height -e Absolute&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;, &lt;span style=&#34;color:#bb60d5&#34;&gt;shell&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;True&lt;span style=&#34;color:#666&#34;&gt;)[&lt;/span&gt;:-1&lt;span style=&#34;color:#666&#34;&gt;]&lt;/span&gt;
  File &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;/usr/lib/python2.7/subprocess.py&amp;#34;&lt;/span&gt;, line 223, in check_output
    raise CalledProcessError&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;retcode, cmd, &lt;span style=&#34;color:#bb60d5&#34;&gt;output&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;output&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;
subprocess.CalledProcessError: Command &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;[&amp;#39;&lt;/span&gt;xwininfo -name &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Area Chooser&amp;#34;&lt;/span&gt; | grep -e Width -e Height -e Absolute&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;]&amp;#39;&lt;/span&gt; returned non-zero &lt;span style=&#34;color:#007020&#34;&gt;exit&lt;/span&gt; status &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I haven’t had a chance to look into those issues yet.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I also used &lt;a href=&#34;http://www.pitivi.org/&#34;&gt;Pitivi&lt;/a&gt; to edit it. It’s hosted on a paid-for Vimeo account and presented on my site via a simple video tag without any tracking. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Based on Ubuntu 18.10. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I also tried via Gnome Software and although it said it was installed, it wouldn’t launch. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The only setting to record computer audio that worked for me was &lt;em&gt;Monitor of Built-in Audio Analogue Stereo&lt;/em&gt;. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Al Jazeera News interview: French “tech tax”</title>
      <link>https://ar.al/2019/01/01/al-jazeera-news-interview-french-tech-tax/</link>
      <pubDate>Tue, 01 Jan 2019 16:55:12 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2019/01/01/al-jazeera-news-interview-french-tech-tax/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/749270318.jpg?mw=2880&amp;mh=1620&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/308993557.m3u8?s=a0de4faa7599ab452d3b88f718d07f6f83f7b3f8&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/308993557.hd.mp4?s=67cfd309affbf0eaab5bb3ebddc6113496cf2c8b&amp;profile_id=175&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Who’s more powerful today? Democractically-elected governments or multinational tech companies? Corporate taxation is just one element of a power play that has fundamental impacts on democracy and human rights.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I did a live interview on &lt;a href=&#34;https://www.aljazeera.com/&#34;&gt;Al Jazeera News&lt;/a&gt; today on &lt;a href=&#34;https://www.theguardian.com/technology/2018/dec/17/france-alone-new-tax-big-tech-companies-gafa&#34;&gt;France pushing forward alone with a new tax on big tech companies&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sometimes you have to stick a screwdriver in it (or how to liberate a Chromebook in ten easy steps)</title>
      <link>https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/</link>
      <pubDate>Mon, 31 Dec 2018 12:38:26 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/screwdriver.jpeg&#34;
         alt=&#34;A chromebook with a red screwdriver sticking out of it.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Google is about as open as a clam.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Over the holidays, I found a Chromebook that Samsung had given me to evaluate about six years ago and which had been gathering dust ever since. Coincidentally, Laura’s sister Annie had just told me that she needed a laptop. Hmm… Well, there was no way I was going to give her a Google spy device, so I decided to liberate the Chromebook from Google’s surveillance-based operating system (ChromeOS) and gift it to her.&lt;/p&gt;
&lt;p&gt;Now, you would think given how people just love to harp on about how damn &lt;em&gt;open&lt;/em&gt; Google is, that this would be easy to do. Just install a lightweight Linux distribution and be done with it, right?&lt;/p&gt;
&lt;p&gt;Oh, you poor, naïve, dear. No, not even close.&lt;/p&gt;
&lt;p&gt;Instead, what you have to do&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; is to physically open up the computer, short a pair of jumpers to disable the write protection and flash the firmware with something that isn’t designed to stop you from protecting yourself from Google’s surveillance machine.&lt;/p&gt;
&lt;h2 id=&#34;google-is-closed&#34;&gt;Google is closed&lt;/h2&gt;
&lt;p&gt;Google is anything but open&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and Chromebooks are not computers; they are corporate surveillance devices. A Chromebook is an inexpensive data milking device and you are the cow.&lt;/p&gt;
&lt;p&gt;It’s no coincidence, for example, that they have tiny hard drives. Why do you need local storage when you can just put all your data on Google’s machines and use Google’s services for everything? And what if you decide to foil Google’s cunning plan and install a larger hard drive? Computer says, “no!” You can’t. Why? Because “security”, of course. &lt;em&gt;Wink, wink!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Similarly, you cannot install a different operating system. And if you have the gall to try and dual boot, you are greeted with a nag screen on every boot. Why? Because “security”, of course. &lt;em&gt;Nudge, nudge!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Giving the owner of a computer control over who can and cannot update the hardware or operating system is a valid security concern. Giving &lt;em&gt;the manufacturer&lt;/em&gt; such control and making it as difficult as possible for the owner isn’t.&lt;/p&gt;
&lt;p&gt;If Google really cared about people’s security, they’d have designed Chromebooks to ship with hardware keys which, when inserted, would enable the hardware or software to be updated. Boom! Problem solved. And they wouldn’t have designed a malicious firmware that tries to get you to revert to factory defaults on every boot once you’ve modifed your own system. This is one of the most owner-hostile features I’ve seen yet in a piece of consumer technology&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. But then again, you never really own a Google Chromebook… it owns you.&lt;/p&gt;
&lt;h2 id=&#34;how-to-liberate-a-chromebook&#34;&gt;How to liberate a Chromebook&lt;/h2&gt;
&lt;p&gt;Here are the instructions for liberating a Samsung Series 5 550 Chromebook (lumpy) and installing &lt;a href=&#34;https://galliumos.org/&#34;&gt;GalliumOS&lt;/a&gt; on it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Push &lt;a href=&#34;https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices/samsung-sandy-bridge#TOC-Entering&#34;&gt;the developer switch&lt;/a&gt; to the right. This is a small switch at the back of the laptop on the right-hand side.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Place the laptop on its lid and disconnect the battery by sticking a paperclip into &lt;a href=&#34;https://a77db9aa-a-7b23c8ea-s-sites.googlegroups.com/a/chromium.org/dev/chromium-os/developer-information-for-chrome-os-devices/samsung-sandy-bridge/lumpy-internals.jpg?attachauth=ANoY7cpUTpaw1vK5ripJL6ue_UMhPvUkaUlYk5hT9276TNXRQ5gWB9JJhihTAIfhpg8SIZ0fBYgUkx66NQcMuEn4akR5mUPfzWAY9esDBelYghbh3mKbvDuAoUZ7ENeYfyZ7UCP4eyLvdZ80C6nUGh-LiSj2hsUNGgZmucYT6K1NPeGcos7sSS66_yd5rCla2qQb4CKQPkvgo4zFvtCMGWl_QZRCqjftNxfkIlCd2qynj7lEkWdeLCGtAveD5jWFXey51mEQ-ScHRhImPDMpOguqNpV6hQowTpZFtbdic9jli7itsklzv8UXp1tmPEjsvMlglrxoOC_E&amp;amp;attredirects=0&#34;&gt;the battery disconnect hole&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove all &lt;em&gt;eight&lt;/em&gt; screws using a Phillips screwdriver. Four of the screws are hidden under the pads for the feet, so you will have to remove those first by gently prying them loose using a small flat-head screwdriver.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pry open the lid by sticking a small flat-head screwdriver in and gently moving it all around the edges. You will hear clicks as the plastic clamps come loose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Locate the &lt;a href=&#34;https://a77db9aa-a-7b23c8ea-s-sites.googlegroups.com/a/chromium.org/dev/chromium-os/developer-information-for-chrome-os-devices/samsung-sandy-bridge/lumpy-internals.jpg?attachauth=ANoY7cpUTpaw1vK5ripJL6ue_UMhPvUkaUlYk5hT9276TNXRQ5gWB9JJhihTAIfhpg8SIZ0fBYgUkx66NQcMuEn4akR5mUPfzWAY9esDBelYghbh3mKbvDuAoUZ7ENeYfyZ7UCP4eyLvdZ80C6nUGh-LiSj2hsUNGgZmucYT6K1NPeGcos7sSS66_yd5rCla2qQb4CKQPkvgo4zFvtCMGWl_QZRCqjftNxfkIlCd2qynj7lEkWdeLCGtAveD5jWFXey51mEQ-ScHRhImPDMpOguqNpV6hQowTpZFtbdic9jli7itsklzv8UXp1tmPEjsvMlglrxoOC_E&amp;amp;attredirects=0&#34;&gt;write protect jumper&lt;/a&gt; and short it. The way I did this was to find a small flat-head screwdriver and stick it into the plastic head of the jumper. I put non-conductive tape under and above the screwdriver to make sure it didn’t short anything else.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/screwdriver-2.jpeg&#34;
         alt=&#34;A red screwdriver shorting a jumper on the motherboard&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screwdrivers for freedom.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Put the cover back on (but don’t clamp it shut) and start up the machine. You will see a scary warning screen. Press &lt;em&gt;CTRL+D&lt;/em&gt;. Once ChromeOS boots, press &lt;em&gt;CTRL+ALT+T&lt;/em&gt; to get to Crosh and type &lt;code&gt;shell&lt;/code&gt; to drop into a terminal running Bash.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter &lt;code&gt;sudo -s&lt;/code&gt;, followed by &lt;code&gt;flashrom --wp-disable&lt;/code&gt;. You should see a success message. If you don’t check that you’ve disconnected the battery and that your screwdriver is properly shorting the right jumper.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/disable-write-protect.jpeg&#34;
         alt=&#34;Terminal: successful result of disabling write protect by entering the commands mentioned in this step.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;One step closer to freedom.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://johnlewis.ie/custom-chromebook-firmware/rom-download/&#34;&gt;SeaBIOS&lt;/a&gt; (Full ROM):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;cd;bash &amp;lt;&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;curl https://johnlewis.ie/flash_cb_fw.sh&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/flash-firmware.jpeg&#34;
         alt=&#34;Terminal: flashing firmware&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Flashing firmware for freedom.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reboot. If you see the SeaBIOS screen, then you can turn the computer off, remove the screwdriver, and close the computer up.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/seabios.jpeg&#34;
         alt=&#34;The SeaBIOS screen&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Freedom is a BIOS, best served cold. Or something.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prepare a USB key with &lt;a href=&#34;https://galliumos.org/&#34;&gt;GalliumOS&lt;/a&gt; on it. Insert it into your USB slot and reboot. You will need to &lt;a href=&#34;https://galliumos.org/download&#34;&gt;download&lt;/a&gt; the Sandy / Ivy Bridge version of Gallium OS. You can burn it to a USB key using a tool like &lt;a href=&#34;https://www.balena.io/etcher/&#34;&gt;Etcher&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/i3.jpeg&#34;
         alt=&#34;i3 window manager running on GalliumOS&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Playing with i3-gaps on GalliumOS. (Gallium comes with xfce and xfwm.)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Your computer should boot into the GalliumOS installer. Follow the instructions to set up your free and open operating system and you should find yourself the proud owner of a fully-liberated &lt;em&gt;Unchromedbook&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Enjoy having the freedom to do what you want with your own machine without being tracked and profiled by Google.&lt;/p&gt;
&lt;p&gt;On the machine I prepared for Annie, I ended up covering up the corporate branding with some colourful unicorn and cloud stickers I found at the local stationary store&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/lid.jpeg&#34;
         alt=&#34;Unicorns, clouds, and shooting starts on the lid of Annie’s new UnchromedBook&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Because fuck you, Silicon Valley, you don’t get to own unicorns and clouds.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;!--


&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/&#34;/&gt; 
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/&#34;/&gt; 
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/&#34;/&gt; 
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/&#34;/&gt; 
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/31/sometimes-you-have-to-stick-a-screwdriver-in-it/&#34;/&gt; 
&lt;/figure&gt;

--&gt;
&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices/samsung-sandy-bridge#TOC-Entering&#34;&gt;Samsung Series 5 550 Chromebook and Series 3 Chromebox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.maketecheasier.com/replace-chromebook-bios/&#34;&gt;Replace Your Chromebook BIOS with SeaBIOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is true for the Samsung Chromebook 550 model, at least. On other models, apparently you can open up the machine and remove a screw from the motherboard, etc. On all models, you have to flash the firmware to stop Google from nagging you on every boot. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;To prove me wrong, please just email me a link to the source code for Google Search, Google Mail, Google Docs, Google Play Services, and all of their proprietary, surveillance-based services as well as the free/open source licenses for all of the above. Thanks! &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://uk.pcmag.com/news-analysis/117795/apples-t2-chip-makes-third-party-mac-repairs-impossible&#34;&gt;Apple is engaged in similar shenanigans&lt;/a&gt; with its latest T2 chip. But then again no one ever accused Apple of being open. Still, at least they make their money by selling products to people instead of selling people to corporations like Google does. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I was originally going to write something like “unchromedbook” or “liberated” on the case using some scrabble letters but ended ruining the tiles when the superglue exploded out of the tube, ruining the tiles and almost sticking my fingers together. That was fun (wasn’t fun). &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Tilingnome</title>
      <link>https://ar.al/2018/12/29/tilingnome/</link>
      <pubDate>Sat, 29 Dec 2018 10:10:58 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/29/tilingnome/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/29/tilingnome/tilingnome.jpeg&#34;
         alt=&#34;My desktop with three windows tiled using Tilingnome. One takes up half the screen on the left and the other two share the other half, tiled vertically. The apps are, clockwise, GNOME Web, Visual Studio Code, and Tilix. The terminal prompt reads ~/ar.al/site/(master) ./new tilingnome&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Tilingnome in action&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Update 2019/01/02:&lt;/strong&gt; I’ve discovered &lt;a href=&#34;https://github.com/timbertson/slinger&#34;&gt;Slinger&lt;/a&gt; and started using that instead. Tilingnome gets in the way too much.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I like the idea of a tiling window manager like &lt;a href=&#34;https://i3wm.org/&#34;&gt;i3&lt;/a&gt; (or &lt;a href=&#34;https://github.com/Airblader/i3&#34;&gt;i3-gaps&lt;/a&gt;, or &lt;a href=&#34;https://swaywm.org/&#34;&gt;sway&lt;/a&gt;) not necessarily because of their lightweight nature when compared to a fully-fledged desktop environment like GNOME but because of their potential organisational and navigational value&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;tiling-up-is-hard-to-do&#34;&gt;Tiling up is hard to do&lt;/h3&gt;
&lt;p&gt;The problem with the current crop of tiling window managers, however, is that they are difficult to set up and configure and they suffer from a host of issues such as no out-of-the-box HiDPI support, screen tearing, etc.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; You are also bereft of some of the everyday things you take for granted with desktop environments, such as having a built-in way to change your desktop image or manage locking on suspend, etc.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;So, yesterday, after getting i3-gaps to work as best as I could on my machine, I went looking to see if anyone had made an extension that gives you the operational aspects of a tiling windowing manager within my desktop environment of choice, GNOME. And lo and behold, I found &lt;a href=&#34;https://github.com/rliang/gnome-shell-extension-tilingnome&#34;&gt;Tilingnome&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;gnome-330-updates&#34;&gt;GNOME 3.30 updates&lt;/h3&gt;
&lt;p&gt;Sadly, it didn’t work on GNOME Shell 3.30, so I first had to update it. &lt;a href=&#34;https://github.com/rliang/gnome-shell-extension-tilingnome/pull/7&#34;&gt;Now it does&lt;/a&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. The joys of free software.&lt;/p&gt;
&lt;p&gt;It was also not clear immediately how it should be used because the keyboard shortcuts were not documented or exposed anywhere. Unless I’m missing something, this seems to be the norm for keybindings in GNOME extensions. The only way I could find of changing the bindings was to alter the source, recompile the schema, and reload the extension. The least I could do was to &lt;a href=&#34;https://github.com/aral/gnome-shell-extension-tilingnome/tree/gnome-shell-3.30&#34;&gt;document the keyboard shortcuts in the &lt;em&gt;readme&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition to the documented keyboard shortcuts, you will probably want to use the full-screen shortcut (on my distribution, F11) to focus on your current task from time to time.&lt;/p&gt;
&lt;h3 id=&#34;to-tile-or-not-to-tile&#34;&gt;To tile or not to tile…&lt;/h3&gt;
&lt;p&gt;Tilingnome has its own idiosyncrasies and gremlins. It fails to resize and position certain apps and can flake out when you leave full-screen mode in an app. I’m not sure if I will keep using as it seems to get in the way more than it improves my workflow at the moment. That said, it remains an option for managing workflow with multiple windows. And if you want to improve it, you can.&lt;/p&gt;
&lt;p&gt;I’d love to hear your thoughts on tiling window managers and Tilingnome so feel free hit me up &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;on my Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is a very personal and subjective measure and not a blanket statement. Depending on how comfortable you are with remembering and using keyboard shortcuts for navigating between windows, you will either love or hate a tiling window manager. Also, GNOME, out of the box, gives you the ability to use your pointing device to snap a window to the left, right, top, or bottom of your screen (or to maximize it). It also has keyboard shortcuts for doing all of the above. For many people, that will probably be enough. In fact, I’m not entirely sold on the benefits of a tiling window manager myself but I’m exploring the experience and their effect on my workflow out of academic interest. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Most of which have workarounds. But again, you have to be willing to invest time in seeking them out and implementing them. As with most such things, we cannot expect everyday people to do this (because they might have two jobs and five kids, not because they are not smart enough) but it is a great learning experience if you want to understand how your computer and operating system works better. If nothing else, it leaves you with a profound respect for the amount of work a desktop environment like GNOME does. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Again, you can add all of these things and more yourself. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or, more precisely, will do when my pull request is merged. Until then, you can &lt;a href=&#34;https://github.com/aral/gnome-shell-extension-tilingnome/tree/gnome-shell-3.30&#34;&gt;use my branch&lt;/a&gt;. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>What does a private communicator look like?</title>
      <link>https://ar.al/2018/12/23/what-does-a-private-communicator-look-like/</link>
      <pubDate>Sun, 23 Dec 2018 19:22:13 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/23/what-does-a-private-communicator-look-like/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/23/what-does-a-private-communicator-look-like/snap-on-air-lora-communicator.jpg&#34;
         alt=&#34;The SnapOnAir Lora communicator circuit board, purple, with buttons and a screen. Unit is off.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The SnapOnAir Lora CommunityCator lets you message without a SIM card.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://ar.al/tags/hypha/index.xml&#34;&gt;Hypha&lt;/a&gt; is not about building a single product. It’s about exploring the possibilities and problem domain of private communication and what it means to have technology that enables privacy (and therefore personhood). At the same time, it isn’t about designing from the inside out. It’s not about building the protocols and waiting for the tools to happen. It’s about experimenting with both. And, in the process, hopefully sparking one or more everyday things that enable people to communicate with privacy.&lt;/p&gt;
&lt;p&gt;So what could some of those everyday things be?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A purely ephemeral private P2P communicator.&lt;/li&gt;
&lt;li&gt;A private communicator with stored/replicated history.&lt;/li&gt;
&lt;li&gt;A personal web “site” that enables public publishing alongside acting as an always-on, always, available node to enable findability, signalling, and availability in an otherwise peer-to-peer system.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given that we have to build a bridge, in every sense, from the centralised world we live in to the peerocratic world we want, the initiatives we embark on have the unenviable requirement of being successful in both worlds.&lt;/p&gt;
&lt;p&gt;They must be economically viable, at least on a small scale, in the current world (so that, at the very least, we can feed ourselves and continue to work on them&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;), and they have to be successful in enabling a new peerocratic infrastucture for society to safeguard personhood and usher in equitable, just, and sustainable peerocratic governance structures. In order to do either, the way people use and experience these platforms must be through convenient, usable, everyday things.&lt;/p&gt;
&lt;p&gt;But what will these things look like?&lt;/p&gt;
&lt;p&gt;Will they be purely virtual and accessed via apps or browsers on mainstream platforms? (Will mainstream platforms allow them to exist should they become successful?)&lt;/p&gt;
&lt;p&gt;Will they be physical and self-contained? Possibly with control over the whole stack? Will they resemble the &lt;a href=&#34;http://www.snaponair.com/&#34;&gt;SnapOnAir Lora communicator&lt;/a&gt; by Philippe Cadic or the prototype communicator shown below by &lt;a href=&#34;https://github.com/arturo182&#34;&gt;arturo182&lt;/a&gt;?&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/748073806.jpg?mw=1900&amp;mh=1900&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/307996441.m3u8?s=b03d35e3e292bd8f34dfc1469b1a4eb10207d758&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/307996441.hd.mp4?s=dddbfefffa2a54732cedfdd41d228b4fab082740&amp;profile_id=174&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;arturo182’s communicator prototype in action&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Will we snap them together ourselves from readily available and/or free and open components, like the little touch-screen device below that I was snapped together this weekend that’s running &lt;a href=&#34;http://source.ind.ie/aral/kappa-chat&#34;&gt;a basic P2P replicated chat app&lt;/a&gt; in Node.js that uses &lt;a href=&#34;https://github.com/kappa-db/kappa-core&#34;&gt;kappa-core&lt;/a&gt;?&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/23/what-does-a-private-communicator-look-like/pi-touchscreen-kappa-chat.jpeg&#34;
         alt=&#34;A Raspberry Pi 3B&amp;#43; with a HyperPixel touch screen running a chat app in the terminal.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A home-made communicator.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Will they be combinations of the above?&lt;/p&gt;
&lt;p&gt;With Hypha, it’s clear what shape our core protocols will have&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. What I’m looking forward to exploring in the coming year is the shapes the tools will take as we explore the problem space to solve specific use cases.&lt;/p&gt;
&lt;p&gt;The protocols, without the convenient everyday things that enable everyday people to utilise them, are simply the theoretical ejaculate of masturbatory academics.&lt;/p&gt;
&lt;p&gt;The tools, without the protocols that ensure privacy are not fit for purpose.&lt;/p&gt;
&lt;p&gt;Here’s what I do know:&lt;/p&gt;
&lt;p&gt;Whatever we build must be…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Small&lt;/li&gt;
&lt;li&gt;Focused&lt;/li&gt;
&lt;li&gt;Verifiable&lt;/li&gt;
&lt;li&gt;Usable&lt;/li&gt;
&lt;li&gt;Convenient&lt;/li&gt;
&lt;li&gt;Useful&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where the centralised mainstream’s advantage is scale, complexity, and secrecy, ours is lack of scale, simplicity, and openness.&lt;/p&gt;
&lt;p&gt;We must think &lt;em&gt;small&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I look forward to exploring the small things in 2019.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And/or we must try to educate policymakers and nurture the political will fund the independent organisations that make these platforms from the commons for the common good and ensure that their products remain in the commons and cannot be enclosed. But I’m not holding my breath on this one. Not least because lobbying, revolving doors, public-private partnerships, and multistakeholderism – collectively, instutional corruption – makes it very difficult to get politicians and policymakers to embrace what is right instead of what is good for their wallets or what they’re exposed to day in, day out from corporate public relations departments. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;Kappa architecture&lt;/a&gt; (append-only logs with streaming views) with end-to-end encrypted content for private messages, replicated via the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; &lt;a href=&#34;https://datprotocol.github.io/book/&#34;&gt;protocol&lt;/a&gt;. Depending on the use case, a CRDT like &lt;a href=&#34;https://hal.archives-ouvertes.fr/hal-00921633/document&#34;&gt;LSEQ&lt;/a&gt; can be used at the content layer. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>A simple Node transform stream with the new Node 10 pipeline() function</title>
      <link>https://ar.al/2018/12/20/a-simple-node-transform-stream-with-the-new-node-10-pipeline-function/</link>
      <pubDate>Thu, 20 Dec 2018 12:54:13 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/20/a-simple-node-transform-stream-with-the-new-node-10-pipeline-function/</guid>
      <description>&lt;p&gt;As part of my research for &lt;a href=&#34;https://ar.al/2018/12/07/baby-steps/&#34;&gt;Hypha&lt;/a&gt; (&lt;a href=&#34;https://ar.al/tags/hypha/index.xml&#34;&gt;RSS&lt;/a&gt;), I just completed the &lt;a href=&#34;https://ar.al/2018/12/15/kappa-architecture-workshop/&#34;&gt;Kappa Architecture Workshop&lt;/a&gt; and I’m continuing to dive deeper down the stack to brush up on the fundamental concepts I need to be comfortable with going forward.&lt;/p&gt;
&lt;p&gt;Next up is &lt;a href=&#34;https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_stream&#34;&gt;Node streams&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Streams are used everywhere in Node and while I’ve made extensive use of them in the past, there’s always been parts that seemed magical&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. I don’t really grok them and I’m working to change that.&lt;/p&gt;
&lt;p&gt;So this morning I whipped up a little custom transform stream and got up to speed with the new &lt;code&gt;pipeline()&lt;/code&gt; function in Node 10 that adds the functionality of Matthias’s &lt;a href=&#34;https://github.com/mafintosh/pump&#34;&gt;pump&lt;/a&gt; module to Node proper&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The simple example takes the raw stream of characters (Buffer objects) you type from &lt;code&gt;stdin&lt;/code&gt;, pipes them through a transform stream that uppercases the lowercase characters (and handles CTRL + C for exit) and then pipes it to &lt;code&gt;stdout&lt;/code&gt; to display in your console.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; { pipeline, Transform } &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;stream&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; { StringDecoder } &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;string_decoder&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Handle the raw output from standard input
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// (characters, not lines, as is the default).
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;process.stdin.setRawMode(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;)
process.stdin.resume()

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;class&lt;/span&gt; UppercaseCharacters &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;extends&lt;/span&gt; Transform {
  constructor(options) {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;super&lt;/span&gt; (options)

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// The stream will have Buffer chunks. The
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// decoder converts these to String instances.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;._decoder &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; StringDecoder(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;)
  }

  _transform (chunk, encoding, callback) {
    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Convert the Buffer chunks to String.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (encoding &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;buffer&amp;#39;&lt;/span&gt;) {
      chunk &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;this&lt;/span&gt;._decoder.write(chunk)
    }

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Exit on CTRL + C.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (chunk &lt;span style=&#34;color:#666&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;\u0003&amp;#39;&lt;/span&gt;) {
      process.exit()
    }

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Uppercase lowercase letters.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (chunk &lt;span style=&#34;color:#666&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;a&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; chunk &lt;span style=&#34;color:#666&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;z&amp;#39;&lt;/span&gt;) {
      chunk &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; chunk.toUpperCase()
    }

    &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Pass the chunk on.
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;    callback(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;null&lt;/span&gt;, chunk)
  }
}

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// pipeline() is new in Node 10
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;pipeline(
  process.stdin,
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; UppercaseCharacters(),
  process.stdout,
  err =&amp;gt; {
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (err) {
      console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Pipeline failed: &amp;#39;&lt;/span&gt;)
    } &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;else&lt;/span&gt; {
      console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Pipeline succeeded.&amp;#39;&lt;/span&gt;)
    }
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can achive the same thing on earlier versions of Node using the &lt;a href=&#34;https://github.com/mafintosh/pump&#34;&gt;pump&lt;/a&gt; module simply by replacing the &lt;code&gt;pipeline&lt;/code&gt; function with the &lt;code&gt;pump&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; pump &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;pump&amp;#39;&lt;/span&gt;)
pump(
  process.stdin,
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; UppercaseCharacters(),
  &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// …
&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Especially duplex/transform streams – &lt;code&gt;a.pipe(b).pipe(a)&lt;/code&gt; is still a royal mindfuck, made a little more sane &lt;a href=&#34;https://source.ind.ie/aral/kappa-chat/blob/master/index.js#L103&#34;&gt;via pump syntax&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;See &lt;a href=&#34;https://gulpjs.org/why-use-pump/&#34;&gt;Why use pump?&lt;/a&gt; from the Gulp documentation for an explainer. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Boogie Board: a beautiful, modern, portable take on the blackboard</title>
      <link>https://ar.al/2018/12/16/boogie-board-a-beautiful-modern-portable-take-on-the-blackboard/</link>
      <pubDate>Sun, 16 Dec 2018 16:00:11 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/16/boogie-board-a-beautiful-modern-portable-take-on-the-blackboard/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/16/boogie-board-a-beautiful-modern-portable-take-on-the-blackboard/boogie-board-blackboard-box.jpeg&#34;
         alt=&#34;Photo of Blackboard in its box.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Boogie Board Blackboard by Kent Displays&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’m always on the look-out for tools that help me think things through with quick sketches and notes. Until recently, nothing really beat a traditional whiteboard and a good paper notebook and pen. So you can imagine that I was suitably excited to discover the &lt;a href=&#34;https://writeonblackboard.com/&#34;&gt;Boogie Board Blackboard&lt;/a&gt; by &lt;a href=&#34;http://kentdisplays.com/&#34;&gt;Kent Displays&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/16/boogie-board-a-beautiful-modern-portable-take-on-the-blackboard/boogie-board-blackboard.jpeg&#34;
         alt=&#34;Photo of the Blackboard on a wooden table with some early Hypha plans on it in green text.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Green on black. Shown with the dotted paper backing template.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Boogie Board Blackboard is a no-frills electronic writing instrument that has a &lt;a href=&#34;http://kentdisplays.com/technology/chlcd&#34;&gt;cholesteric liquid crystal display (ChLCD)&lt;/a&gt;. Or what their marketing department calls Liquid Crytal Paper™.&lt;/p&gt;
&lt;p&gt;In practice, it means that images are created by a mechanical/chemical process when you write on the Boogie Board with a stylus. No electricty required. The only time the device’s watch battery is used is when you want to erase something. This also explains why the battery, which is replacable, is stated to last around five years.&lt;/p&gt;
&lt;p&gt;Prior to this model, erasing the screen was an all or nothing affair for Boogie Boards. That changes with the Blackboard. The included stylus has an eraser side that you can use to rub out portions of your work. And it even looks like you were rubbing out pencil on paper, with slight smudges. Far from being a problem, I feel it adds even more to the analogue feel of the device.&lt;/p&gt;
&lt;p&gt;And feel is what it’s all about.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/16/boogie-board-a-beautiful-modern-portable-take-on-the-blackboard/laura-using-the-blackboard.jpeg&#34;
         alt=&#34;Photo of Laura writing on the Blackboard.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Laura using the Blackboard.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Boogie Board Blackboard feels like you’re writing on an analogue medium. It doesn’t feel exactly like paper or a blackboard. It has its own feel. And it feels good.&lt;/p&gt;
&lt;p&gt;The Blackboard comes in two sizes (note and letter&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;). I got the larger letter-sized one. And I love it.&lt;/p&gt;
&lt;p&gt;Given that this is a no-frills instrument, there is no fancy way to transfer what you’ve created to the digital realm. There is, however, an app you can download that lets you save your work by taking a photo of the screen with your phone. It has the ability to detect the corners of the Blackboard but, in practice, it has never done so correctly for me. Instead, I set the corners manually. It’s not a hassle. The app can then optionally apply an enhancing filter (I make it convert the green on black image to dark gray on white) and store it, catagorizing it by title and date/last update. All in all, this process works perfectly well for me.&lt;/p&gt;
&lt;p&gt;Here is a sample of the saved output. You will probably start to see me post images with the aesthetic as I work on Hypha. So now you know where they come from.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/16/boogie-board-a-beautiful-modern-portable-take-on-the-blackboard/boogie-board-saved-image.jpeg&#34;
         alt=&#34;One of the images captured by the Blackboard app: black on white, Hypha plans.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;An image saved on the Blackboard app.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Blackboard is the first electronic writing instrument I’ve used that just works. It gets out of the way and lets me think through something without worrying about the medium I’m using to do so.&lt;/p&gt;
&lt;p&gt;I couldn’t find this particular model for sale in the EU so I ended up ordering it from the US. It got here in two days for around €50. In case you’re confused, that’s the price of the device and the shipping, not just the shipping. In the US, the smaller version sells for $35 and the larger goes for $45 making this one affordable little electronic writing tablet.&lt;/p&gt;
&lt;p&gt;And did I mention it feels great to write on it?&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Because when have Americans ever used rational measurements? (The note is smilar to A4 and the letter is larger than that.) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Kappa Architecture workshop</title>
      <link>https://ar.al/2018/12/15/kappa-architecture-workshop/</link>
      <pubDate>Sat, 15 Dec 2018 18:04:33 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/15/kappa-architecture-workshop/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://kappa-db.github.io/workshop/build/01.html&#34;&gt;Kappa Architecture Workshop&lt;/a&gt; is an excellent online resource by Stephen Whitmore (of &lt;a href=&#34;https://github.com/cabal-club/cabal&#34;&gt;Cabal&lt;/a&gt; fame), Mathias Buus (one of the cornerstones of &lt;a href=&#34;https://datproject.org&#34;&gt;the DAT Project&lt;/a&gt;), &lt;a href=&#34;https://github.com/mafintosh&#34;&gt;et al.&lt;/a&gt;, that gives you an introduction to &lt;a href=&#34;http://milinda.pathirage.org/kappa-architecture.com/&#34;&gt;Kappa Architecture&lt;/a&gt; using modules from the DAT Node.js ecosystem like &lt;a href=&#34;https://github.com/mafintosh/hypercore&#34;&gt;hypercore&lt;/a&gt;, &lt;a href=&#34;https://github.com/noffle/multifeed&#34;&gt;multifeed&lt;/a&gt;, &lt;a href=&#34;https://github.com/mafintosh/discovery-swarm&#34;&gt;discovery-swarm&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; and &lt;a href=&#34;https://github.com/hyperswarm/discovery&#34;&gt;kappa-core&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The examples take you from the very basics – such as how to make peer to connections and send simple ephemeral chat messages (code below) to &lt;a href=&#34;https://github.com/aral/kappa-architecture-workshop-work-files/blob/master/multi-chat.js&#34;&gt;P2P replicated feeds with multiple writers&lt;/a&gt; and beyond.&lt;/p&gt;
&lt;p&gt;You can follow along with the workshop &lt;a href=&#34;https://kappa-db.github.io/workshop/build/01.html&#34;&gt;online&lt;/a&gt;, view &lt;a href=&#34;https://github.com/aral/kappa-architecture-workshop-work-files&#34;&gt;my working files&lt;/a&gt; as I do, and also submit any issues you may run into or &lt;a href=&#34;https://github.com/kappa-db/workshop/pulls&#34;&gt;improvements you might want to suggest&lt;/a&gt; on &lt;a href=&#34;https://github.com/kappa-db/workshop&#34;&gt;the workshop’s source code repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; discovery &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;discovery-swarm&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; swarm &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; discovery()

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; nickname &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;person&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt;  &lt;span style=&#34;color:#007020&#34;&gt;Math&lt;/span&gt;.floor(&lt;span style=&#34;color:#007020&#34;&gt;Math&lt;/span&gt;.random() &lt;span style=&#34;color:#666&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;42&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;

swarm.join(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;my-very-very-simple-p2p-app&amp;#39;&lt;/span&gt;)

swarm.on(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;connection&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (connection, info) {
  console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`Found a peer: &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;info.host&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;info.port&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)

  process.stdin.on(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;data&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (data) {
    connection.write(JSON.stringify({
      type&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;chat-message&amp;#39;&lt;/span&gt;,
      nickname,
      text&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; data.toString().trim(),
      timestamp&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;Date&lt;/span&gt;().toISOString()
    }))
  })

  connection.on(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;data&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; (data) {
    data &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; JSON.parse(data)
    console.log(&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;data.timestamp&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;data.nickname&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;data.text&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;`&lt;/span&gt;)
  })
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;See the new, improved version called &lt;a href=&#34;https://github.com/hyperswarm/network&#34;&gt;hyperswarm&lt;/a&gt;, which you should be able to replace discovery-swarm with in the examples in the workshop. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For an example of kappa-core in use in a real-world library, see &lt;a href=&#34;https://github.com/cabal-club/cabal-core&#34;&gt;cabal-core&lt;/a&gt;, the – uhum ­– &lt;em&gt;core&lt;/em&gt; of &lt;a href=&#34;https://github.com/cabal-club/cabal&#34;&gt;Cabal&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Remember directory from last session in Tilix with zsh</title>
      <link>https://ar.al/2018/12/15/remember-directory-from-last-session-in-tilix-with-zsh/</link>
      <pubDate>Sat, 15 Dec 2018 17:46:40 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/15/remember-directory-from-last-session-in-tilix-with-zsh/</guid>
      <description>&lt;p&gt;If you’re using the excellent &lt;a href=&#34;https://gnunn1.github.io/tilix-web/&#34;&gt;Tilix&lt;/a&gt; terminal with the wonderful &lt;a href=&#34;http://www.zsh.org/&#34;&gt;zsh&lt;/a&gt; shell (in my case, via &lt;a href=&#34;https://github.com/robbyrussell/oh-my-zsh&#34;&gt;oh-my-zsh&lt;/a&gt;), you might notice that opening a new session (e.g., splitting your current session vertically or horizontally or opening a new window), doesn’t start you at the directory you were in in the previous one as you would expect but rather returns you to your home directory.&lt;/p&gt;
&lt;p&gt;I’ve been getting increasingly fed up with this but not so much that I actually felt bothered enough to do something about it. Until today.&lt;/p&gt;
&lt;p&gt;To fix it, you apparently have to source file called vte.sh in your shell configuration&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Why? To be honest, I couldn’t care less. This is one yak I really do not need to shave right now. Some yaks look better with hair. It’s a fact. Don’t @ me.&lt;/p&gt;
&lt;p&gt;On my machine, I did the following to find the file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;find / -type f -name &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;vte.sh&amp;#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That located several copies – the most promising of which seemed to be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;/var/lib/flatpak/runtime/org.gnome.Platform/x86_64/3.28/6d1d0ebbd72404c61d109307eb2240542b7ad82608bc6428bba6f3eebcfc8bf3/files/etc/profile.d/vte.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, in my &lt;em&gt;.zshrc&lt;/em&gt; file, I added the following line to source it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Make Tilix remember the path from previous sessions.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;source&lt;/span&gt; /var/lib/flatpak/runtime/org.gnome.Platform/x86_64/3.28/6d1d0ebbd72404c61d109307eb2240542b7ad82608bc6428bba6f3eebcfc8bf3/files/etc/profile.d/vte.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I opened a new window, changed into a directory off of home, split the window, and boom, it remembered my path.&lt;/p&gt;
&lt;p&gt;Case closed.&lt;/p&gt;
&lt;p&gt;What lovely hair you have. Good yak! Down boy!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Source: &lt;a href=&#34;https://github.com/gnunn1/tilix/issues/1208&#34;&gt;https://github.com/gnunn1/tilix/issues/1208&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Surveillance Capitalism at the BBC</title>
      <link>https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/</link>
      <pubDate>Mon, 10 Dec 2018 18:41:49 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/</guid>
      <description>&lt;iframe src=&#34;https://www.bbc.com/ideas/videos/surveillance-capitalism-has-led-us-into-a-dystopia/p06p0tdy/player&#34; width=&#34;640&#34; height=&#34;500&#34; scrolling=&#34;no&#34; style=&#34;overflow: hidden&#34; allowfullscreen frameborder=&#34;0&#34;&gt;&lt;/iframe&gt;
&lt;p&gt;Recently, I recorded a video on surveillance capitalism with &lt;a href=&#34;https://www.bbc.com/ideas&#34;&gt;BBC Ideas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&#34;https://www.bbc.com/ideas/videos/surveillance-capitalism-has-led-us-into-a-dystopia/p06p0tdy&#34;&gt;watch the video on the BBC Ideas web site&lt;/a&gt; and you can also embed it on your own site, as shown above. All you need is a tiny bit of code that looks something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;iframe&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;https://www.bbc.com/ideas/videos/surveillance-capitalism-has-led-us-into-a-dystopia/p06p0tdy/player&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;width&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;640&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;height&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;500&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;scrolling&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;no&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;overflow: hidden&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;allowfullscreen&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;frameborder&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;iframe&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But what happens when you add that code to your site?&lt;/p&gt;
&lt;p&gt;Do you just get the video, as you would expect, or do you also get something you didn’t bargain for?&lt;/p&gt;
&lt;p&gt;To find out, I ran our &lt;a href=&#34;https://source.ind.ie/better/inspector&#34;&gt;inspector&lt;/a&gt; tool from &lt;a href=&#34;https://better.fyi&#34;&gt;Better&lt;/a&gt; on this very page to see if any third-party trackers get included with a BBC Ideas video when you include one on your own site.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/better-inspector.png&#34;
         alt=&#34;Screenshot of the Better Inspector command-line app inspecting this page.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better Inspector inspecting this page.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Turns out there is a third-party tracker included with the embed: &lt;a href=&#34;https://better.fyi/trackers/chartbeat.net/&#34;&gt;Chartbeat&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And if you visit the BBC Ideas page to watch the video there, you will also be tracked by &lt;a href=&#34;https://www.atinternet.com/en/&#34;&gt;AT Internet&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, in addition to Chartbeat.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/10/surveillance-capitalism-at-the-bbc/blocked-third-party-hostnames.png&#34;
         alt=&#34;The two third-party tracking domains that Better Inspector found: chartbeat.com and chartbeat.net&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;How’s that for irony?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is not just ironic. It’s unethical.&lt;/p&gt;
&lt;p&gt;When someone embeds a video on their page, they do not expect to expose their visitors to third-party tracking and profiling by some random corporation like Chartbeat that they’ve never heard of.&lt;/p&gt;
&lt;p&gt;So, what is &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalism&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;It’s this.&lt;/p&gt;
&lt;p&gt;It’s the mainstream.&lt;/p&gt;
&lt;p&gt;It’s the Web.&lt;/p&gt;
&lt;p&gt;It’s the BBC exposing you to third-party tracking by two companies – Chartbeat and AT Internet – when you watch a video about surveillance capitalism on their site. It’s people becoming unwitting accomplices to perpetuating the reach of surveillance capitalism on the Web by sharing a video on their own sites to raise awareness about the dangers of surveillance capitalism.&lt;/p&gt;
&lt;p&gt;Here’s hoping that when the folks at the BBC see this, they will do some soul searching and revise their policies. At the very least, please do not include a third-party tracker in video embeds. Innocent people who just want to share videos should not find themselves unknowingly complicit in web tracking and profiling.&lt;/p&gt;
&lt;p&gt;As for all of you looking for ethical alternatives to surveillance-based video services for your own content, check out &lt;a href=&#34;https://joinpeertube.org/en/&#34;&gt;Peertube&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://better.fyi&#34;&gt;Better&lt;/a&gt; already blocks Chartbeat. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;We weren’t aware of AT Internet, &lt;a href=&#34;https://source.ind.ie/better/content/issues/860&#34;&gt;but we are now&lt;/a&gt; and they will be blocked in this week’s Better update. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Baby steps</title>
      <link>https://ar.al/2018/12/07/baby-steps/</link>
      <pubDate>Fri, 07 Dec 2018 12:14:28 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/12/07/baby-steps/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/12/07/baby-steps/first-apple-computer-vs-ipad-pro-1280-720.jpeg&#34;
         alt=&#34;A side-by-side layout of the first Apple computer – its components laid out in a suitcase – and the new iPad Pro.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Apple didn’t start out by making the iPad Pro.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Five years ago, when I decided to devote myself to &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons&#34;&gt;tackling&lt;/a&gt; the problem of &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age&#34;&gt;surveillance capitalism&lt;/a&gt;, it was clear what we needed: convenient and beautiful &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical&lt;/a&gt; &lt;em&gt;everyday things&lt;/em&gt; that provide &lt;a href=&#34;https://vimeo.com/70030549&#34;&gt;seamless experiences&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; on fully free-as-in-freedom stacks.&lt;/p&gt;
&lt;p&gt;This is as true today as it was then and it will remain so. The only way to compete with unethical products built by organisations that have control over hardware + software + services is to create ethical organisations that have control over hardware + software + services and thus have &lt;em&gt;at least the possibility&lt;/em&gt; to craft competitive experiences. We remove our eyes from this goal at our peril.&lt;/p&gt;
&lt;p&gt;That said, it also clear that this is a huge, if not impossible, undertaking for individuals and small, independent organisations. We find ourselves in a world where 99.99999% of all investment goes into funding centralised surveillance-based startups and thus where 99.99999% of the technology infrastructure that exists and that we have to work with – including open source and web standards – has been created to support the needs of centralised, surveillance-based organisations and their products, incentivise their topology, and perpetuate their goals.&lt;/p&gt;
&lt;p&gt;Small, independent groups can and do create great things with next to no resources&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. But we cannot rely on the fluke of individual sacrifice and genius to cobble together alternative ethical mainstream platforms to effect systemic change from whatever open source scraps they manage to scavenge from surveillance capitalism while striving to survive within its incentive structures.&lt;/p&gt;
&lt;p&gt;In other words, the ethical alternatives will not grow on trees. They must be funded. And given that they cannot and will not be funded by the same interests that created the problem to begin with (venture capital), we need alternative, ethical funding to create alternative, ethical infrastructure. The technological infrastructure of our societies must be funded from the commons, for the common good. And that requires political will and a system that’s not institutionally corrupt. Neither of which we have today.&lt;/p&gt;
&lt;p&gt;Given all these givens, it’s no surprise that I often find myself frustrated. But frustration alone doesn’t get us anywhere. Action can.&lt;/p&gt;
&lt;p&gt;So here’s what I’m doing and what we’re doing at &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; currently and into 2019. Our approach can be summarised by the phrase “regulate and replace”.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We cannot get the political will to regulate effectively or fund the alternatives as long as surveillance capitalists like Google and Facebook are deemed good actors; as long as they and their business model are socially acceptable. So we will continue to raise awareness and fight whitewashing attempts by so-called “privacy professionals” and organisations like Mozilla (who get nearly all their money – hundreds of millions of dollars – from Google). We will continue to call out the doctors in the cigarette ads in technology.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will continue to technologically regulate surveillance capitalists and the adtech industry by maintaining &lt;a href=&#34;https://better.fyi/trackers&#34;&gt;our own block list&lt;/a&gt; and continuing to develop &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will continue our work on alternatives by building on what we learned with &lt;a href=&#34;https://2017.ind.ie/heartbeat/&#34;&gt;Heartbeat&lt;/a&gt; and Indienet to start experimenting with a new personal network system based on &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; called Hypha. My goal is to issue small, frequent updates of my experiments and progress.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Expect baby steps.&lt;/p&gt;
&lt;p&gt;If there’s one thing I’ve learned in the last five years, it is that we cannot start out by building the iPad Pro of the peer-to-peer, free and open Internet. We must build the scrappy Apple computers in the suitcases first. And yet, all the while, we must never forget what our ultimate goal is.&lt;/p&gt;
&lt;p&gt;Finally, while I acknowledge that the countless talks I’ve given have probably had some impact, I’ve decided that I can best raise awareness about this issue at this point with a book. It will be called Peerocracy and I’ll be writing it and sharing it publicly, alongside my work on Better and Hypha.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;We can have beautiful, seamless defaults and layer the seams. While proprietary technology can do the former, only we can do the latter with ethical technology that is free as in freedom. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;See &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;. See &lt;a href=&#34;https://elementary.io&#34;&gt;Elementary OS&lt;/a&gt;. See &lt;a href=&#34;https://www.gnome.org/&#34;&gt;Gnome&lt;/a&gt;. See &lt;a href=&#34;http://mntmn.com/&#34;&gt;MNT&lt;/a&gt;. See &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>GDMR: this one simple regulation could end surveillance capitalism in the EU</title>
      <link>https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/</link>
      <pubDate>Thu, 29 Nov 2018 11:18:19 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/11/29/gdmr-this-one-simple-regulation-could-end-surveillance-capitalism-in-the-eu/gdmr.png&#34;
         alt=&#34;The European Union flag with the acronym GDMR printed within the circle of yellow stars.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;GDMR: The regulation EU citizens deserve.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;No, you didn’t misread it and, no, it’s not a typo. GDMR – the &lt;strong&gt;General Data Minimisation Regulation&lt;/strong&gt; – can end &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalism&lt;/a&gt; in the EU.&lt;/p&gt;
&lt;p&gt;The problem is that no such regulation exists.&lt;/p&gt;
&lt;p&gt;So, let’s change that, starting now.&lt;/p&gt;
&lt;p&gt;To be effective, GDMR must be succinct and precise. The essence of it can be expressed in a single article with two paragraphs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In any digital/networked product, if a certain feature &lt;em&gt;can&lt;/em&gt; be built where the algorithms and data are kept exclusively on an individual’s own devices, it &lt;em&gt;must&lt;/em&gt; be built in that manner.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In any such system, if an always-on centralised node hosted by the service provider is required for purposes of findability&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; and availability&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, any private information that is replicated to that centralised node must be end-to-end encrypted and the individual must be the exclusive holder of the private key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;Seriously, that’s it.&lt;/p&gt;
&lt;p&gt;I keep hearing people ask ‘what is effective regulation?’ Effective regulation is not legislation that encourages the Facebooks and Googles to become arbiters of truth, filters of reality, and master censors.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; It’s this. Effective regulation is regulation that directly prevents the toxic core of the business model of people farming&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; while &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;incentivising the creation of ethical alternatives&lt;/a&gt;.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Implement the GDMR as regulation in the EU today and wake up tomorrow to witness the death of surveillance capitalism and the birth of a European alternative that puts human rights and democracy, not corporate profits, first.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For example, so that the service can be discovered/accessed via an easy-to-type domain name. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;That is, so that the service is available even if all the person’s other physical devices are offline. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;All things that they already are and do to a great extent. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Tracking, data collection, profiling, and the use of the resulting intimate insight into our lives to manipulate our behaviour to satisfy the profit and political motives of surveillance capitalists. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;Ethical alternatives&lt;/a&gt; are free (as in freedom), decentralised/peer-to-peer, and interoperable. They will not be funded by the venture capitalists who gave us surveillance capitalism and make their billions from it. This alternative ethical technical infrastructure is essential for safeguarding the future of our human rights and democracy. We must fund it from the commons for the common good and it must be owned and controlled by individuals, not the state. And we must ensure that if the independent organisations building these ethical alternatives are successful, they cannot be bought out by Silicon Valley surveillance capitalists or traditional commercial interests so that our efforts don’t end up amounting to a fancy euphemism for privatisation and so that the EU stops acting like an unpaid (European taxpayer-funded) research and development department for Silicon Valley. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Gnomit 1.0.6</title>
      <link>https://ar.al/2018/11/08/gnomit-1.0.6/</link>
      <pubDate>Thu, 08 Nov 2018 16:23:54 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/11/08/gnomit-1.0.6/</guid>
      <description>&lt;p&gt;I pushed the 1.0.6 release of Gnomit, my little git commit message editor for Linux, to the Flathub GitHub repo about two weeks ago but I’m only writing about it now as &lt;a href=&#34;https://github.com/flathub/flathub/issues/694&#34;&gt;there was a delay with the update appearing on Flathub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;whats-new&#34;&gt;What’s new?&lt;/h2&gt;
&lt;p&gt;In addition to git commit messages and tag messages, Gnomit now also supports &lt;code&gt;git add -p&lt;/code&gt; messages and &lt;code&gt;rebase -i&lt;/code&gt; messages. A big thank-you to Philip Chimento for reporting those issues.&lt;/p&gt;
&lt;h2 id=&#34;get-gnomit&#34;&gt;Get Gnomit&lt;/h2&gt;
&lt;p&gt;You can install Gnomit via &lt;a href=&#34;https://wiki.gnome.org/Apps/Software&#34;&gt;Gnome Software&lt;/a&gt; and &lt;a href=&#34;https://flathub.org/apps/details/ind.ie.Gnomit&#34;&gt;Flathub&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;flatpak install flathub ind.ie.Gnomit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For detailed installation instructions, please see the &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs/blob/master/README.md&#34;&gt;readme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source code is available from &lt;a href=&#34;ttps://source.ind.ie/gnome/gnomit&#34;&gt;source.ind.ie&lt;/a&gt; and there’s also &lt;a href=&#34;https://github.com/indie-mirror/gnomit&#34;&gt;a GitHub mirror&lt;/a&gt; where you can open issues and submit pull requests.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Better Blocker 2018.2 release for macOS and iOS</title>
      <link>https://ar.al/2018/11/05/better-blocker-2018.2-release-for-macos-and-ios/</link>
      <pubDate>Mon, 05 Nov 2018 15:34:28 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/11/05/better-blocker-2018.2-release-for-macos-and-ios/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/11/05/better-blocker-2018.2-release-for-macos-and-ios/better-2018.2-ios-macos.jpg&#34;
         alt=&#34;Photo of Better Blocker 2018.2 running on my iPhone and my Mac.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better running on my iPhone and Mac.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I are happy to announce that Better Blocker version 2018.2 is now available for purchase for iPhone, iPad, and Mac on the App Store and Mac App Store.&lt;/p&gt;
&lt;p&gt;As always, you can also &lt;a href=&#34;https://source.ind.ie/better/app&#34;&gt;grab the source code&lt;/a&gt; and build it for your devices yourself as Better Blocker is freedom software and licensed under GPLv3+. (If you do want to see Better continue to exist, &lt;a href=&#34;https://ind.ie/fund&#34;&gt;please consider funding Ind.ie&lt;/a&gt;, our two-person-and-one-husky not-for-profit.)&lt;/p&gt;
&lt;h2 id=&#34;whats-new&#34;&gt;What’s new?&lt;/h2&gt;
&lt;li&gt;Even simpler interface, consistent across the macOS and iOS versions.&lt;/li&gt;
&lt;li&gt;Displays version and last update information for blocking rules.&lt;/li&gt;
&lt;li&gt;Exceptions is now called the Do Not Block list.&lt;/li&gt;
&lt;li&gt;macOS: Fixes El Capitan crash.&lt;/li&gt;
&lt;li&gt;Comes bundled with the latest blocking rules.&lt;/li&gt;
&lt;p&gt;With this new release, we are also tweaking the App Store pricing slightly. Better Blocker, prior to the 2018.1 release, used to be priced at Tier 5 ($4.99/£4.99/€5.49) on macOS and iOS.&lt;/p&gt;
&lt;p&gt;With the 2018.1 release, we dropped the price to the lowest possible tier on both platforms. While this has resulted in unit sales increasing, it hasn’t translated into higher proceeds for our tiny not-for-profit. &lt;a href=&#34;https://mastodon.ar.al/@aral/101008070422381696&#34;&gt;After seeking feedback from the community&lt;/a&gt;, we’ve decided to move the pricing from Tier 1 to Alternate Tier 1 on iOS ($0.99/£0.99/€1.99) and from Tier 1 to Alternate Tier 2 on macOS ($1.99/£1.99/€2.99). While this is a small change on the individual level and should not negatively impact the number of units sold, it should hopefully mean that the proceeds will increase and that we can afford to keep working on Better in a more sustainable manner.&lt;/p&gt;
&lt;p&gt;On that last note, &lt;a href=&#34;https://better.fyi/news/&#34;&gt;Laura updated the blocking rules again&lt;/a&gt; on November 3rd. The latest rules come bundled with the apps and you can always check for and update to the latest rules from within the apps themselves.&lt;/p&gt;
&lt;h2 id=&#34;please-tell-your-friends&#34;&gt;Please tell your friends&lt;/h2&gt;
&lt;p&gt;If you are enjoying Better, please do tell your friends. We don’t have a marketing budget so word of mouth is the only way people have of finding out about it.&lt;/p&gt;
&lt;p&gt;Finally, a big thank-you, once again, to those of you who leave us wonderful reviews on the App Store and send us lovely messages via email and &lt;a href=&#34;https://joinmastodon.org&#34;&gt;on Mastodon&lt;/a&gt; and elsewhere. It means a lot and really brightens up our days.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gnomit 1.0.5</title>
      <link>https://ar.al/2018/10/26/gnomit-1.0.5/</link>
      <pubDate>Fri, 26 Oct 2018 19:29:00 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/10/26/gnomit-1.0.5/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/10/26/gnomit-1.0.5/gnomit-dark-theme-support.png&#34;
         alt=&#34;Screenshot of Gnomit version 1.0.5 in a dark theme.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Gnomit 1.0.5, wearing Adwaita-dark&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I just released a minor update to Gnomit, my little commit message editor for Linux, that adds dark theme support. Previously, Gnomit did not update the background colour that it uses to highlight the commit message overflow area for dark themes, rendering the overflow area difficult to read.&lt;/p&gt;
&lt;p&gt;Now, it uses a darker shade of the light theme colour when it detects a change to the theme.&lt;/p&gt;
&lt;p&gt;You can install Gnomit via &lt;a href=&#34;https://wiki.gnome.org/Apps/Software&#34;&gt;Gnome Software&lt;/a&gt; and &lt;a href=&#34;https://flathub.org/apps/details/ind.ie.Gnomit&#34;&gt;Flathub&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;flatpak install flathub ind.ie.Gnomit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For detailed installation instructions, please see the &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs/blob/master/README.md&#34;&gt;readme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source code is available from &lt;a href=&#34;ttps://source.ind.ie/gnome/gnomit&#34;&gt;source.ind.ie&lt;/a&gt; and there’s also &lt;a href=&#34;https://github.com/indie-mirror/gnomit&#34;&gt;a GitHub mirror&lt;/a&gt; where you can open issues and submit pull requests.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>With this amazing trick, you can stay an extra night in Paris but only if you use Linux!</title>
      <link>https://ar.al/2018/10/26/with-this-amazing-trick-you-can-stay-an-extra-night-in-paris-but-only-if-you-use-linux/</link>
      <pubDate>Fri, 26 Oct 2018 16:27:15 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/10/26/with-this-amazing-trick-you-can-stay-an-extra-night-in-paris-but-only-if-you-use-linux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/10/26/with-this-amazing-trick-you-can-stay-an-extra-night-in-paris-but-only-if-you-use-linux/time-4-25-5-25-pm.jpg&#34;
         alt=&#34;A screenshot of my desktop showing that my machine thinks the local time in Paris is 4:25PM while it is actually 5:25PM.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Give or take an hour.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I was supposed to be back in Cork right now but instead I’m staying an extra night in Paris and it’s all because I was using my Linux machine instead of my Mac at the airport. If you too want to take advantage of this nifty little trick, here’s how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make sure you fly into France from a country that’s in a different time zone and connect through Paris on your way back.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; In my case, I flew into France from Ireland, which is one hour behind.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure that both &lt;em&gt;Automatic Date &amp;amp; Time&lt;/em&gt; and &lt;em&gt;Automatic Time Zone&lt;/em&gt; are set to &lt;em&gt;ON&lt;/em&gt; in the Settings app of your Linux laptop&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Also, make sure you understand that for those settings to work you must connect to the Internet and thus make a point of connecting your Linux machine to the Internet so that you can safely assume that the time zone and time are automatically updated as promised.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/10/26/with-this-amazing-trick-you-can-stay-an-extra-night-in-paris-but-only-if-you-use-linux/settings-time-and-date.png&#34;
         alt=&#34;Screenshot of my Time and Date settings showing that even though automatic date and time and automatic time zone are set to ON, the time has not updated.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Linux can automatically update your time and timezone… sometimes… maybe.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start working on something on your Linux computer&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; but make sure to keep your eye on the clock on your desktop so that you don’t miss your flight.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With about 20 minutes to go to your boarding time, close the lid of your computer and start to make your way to the gate so that you can get there in plenty of time for your flight.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the way, glance at your iPhone and notice that it is actually one hour later in Paris than the time that was showing on your Linux machine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Realise that you’ve missed your flight.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Call your girlfriend and tell her you won’t be home tonight as planned.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Watch as your poor, loving girlfriend scrambles to book you a flight for the next day and a hotel for the night. End up paying €394.47 for the combo (€277.99 for the flight and €116.48 for the hotel room).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it, there is no Step 9! You’ve now got an extra night in Paris. And it’s all thanks to Linux!&lt;/p&gt;
&lt;p&gt;It does rather feel like in our relentless drive for MORE! FASTER! SHINIER!, we often neglect the essentials.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;And this isn’t even the first time something like this has happened to me. The only other time I’ve ever missed a flight was again due to technology and timezones. But that time it was Apple’s fault.&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Here’s hoping that this is yet another small reminder for those of us that build the new everyday things that when those things don’t work as they should &lt;a href=&#34;http://www.breakingthin.gs/this-is-all-there-is.html&#34;&gt;it has consequences for people’s lives&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;update-a-few-hours-later&#34;&gt;Update (a few hours later)&lt;/h2&gt;
&lt;p&gt;So, after writing this, I went searching to isolate the bug that led to the situation. I realised that the issue was due to a bug where if Location Services is off under the Privacy section of Settings, it disables the ability to automatically set the time and time zone. However, the interface does not reflect this in any way and allows you to enable the automatic time and time zone settings, thereby giving you a false impression that your time and timezone will be updated automatically.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mstdn.fr/@mathieu/100963293983797012&#34;&gt;Thanks to Mathieu&lt;/a&gt;, we now know that this was a known issue in Gnome &lt;strong&gt;for over a year&lt;/strong&gt; because the Gnome developers themselves were effected by it when travelling for their developer conference. Although &lt;a href=&#34;https://bugzilla.gnome.org/show_bug.cgi?id=788714&#34;&gt;the issue was reported in October, 2017&lt;/a&gt;, it &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/165&#34;&gt;took about a year for it to be addressed&lt;/a&gt;. And, even then, it was decided to delay its release for 3.30 (which I’m running) and instead wait for 3.32 due to a string freeze.&lt;/p&gt;
&lt;p&gt;It’s clear that something seriously went wrong here where a core feature that affects the reality presented to the person using the computer took over a year to be fixed and then wasn’t seen as important enough to include in a major release even though it was ready.&lt;/p&gt;
&lt;p&gt;I’d love to have a post-mortem about this with the Gnome folks at some point to see what we can do to improve the triage process so that such core issues can be identified as such and handled with priority. This bug cost me €394.47 and a day away from home. I’m sure I’m not the only who was effected in the last year. How can we improve the bug triage procedure at Gnome to make sure that we can catch such issues earlier and give them the priority they deserve in the future?&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This trick will actually work in any country as long as you fly in from a different time zone. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Mine’s running Pop!_OS 18.10 so your milage may vary but given that Pop!_OS is based on Ubuntu, there’s a chance that this trick will work for you if you have a recent Ubuntu derivative. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In my case, I was connected to the Internet for several hours at the airport. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s essential here that you focus and only use your Linux machine. If you glance at your iPhone or start working on your Mac, this trick will not work for you. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;As I write this, connected to the Internet, the time zone and displayed time still hasn’t updated. I just tried manually flipping the &lt;em&gt;Automatic Date &amp;amp; Time&lt;/em&gt; and &lt;em&gt;Automatic Time Zone&lt;/em&gt; switches off and on again and that didn’t do anything either. Either in Pop!_OS 18.10 or in Ubuntu 18.10 or maybe even somewhere further upstream, that feature seems to be entirely broken. (I was finally able to get the timezone to update by setting it manually.) &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Many years ago, the calendar app used to have a bug where it would recalculate the times of events based on the timezone they were entered in when you changed timezones. So if you were in Timezone A when you entered the time of a flight back from Timezone B, when you travelled to Timezone B the calendar would offset the time of the flight based on the time difference between Timezone A and B, thereby presenting your flight back at the wrong local time. Apple has long since fixed this issue. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>version: display Linux version information</title>
      <link>https://ar.al/2018/10/26/version-display-linux-version-information/</link>
      <pubDate>Fri, 26 Oct 2018 12:55:59 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/10/26/version-display-linux-version-information/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/10/26/version-display-linux-version-information/version.png&#34;
         alt=&#34;Screenshot of my simple version script running in Terminal. The information displayed is recreated at the end of this post.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The dark art of displaying Linux version information.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One thing I find myself always having to look up is how to display version information in Linux. That’s because the commands for displaying version information are in no way intuitive or memorable.&lt;/p&gt;
&lt;p&gt;Specifically, if you want to display information about your distribution, including its version, you have to use the &lt;code&gt;lsb_release&lt;/code&gt; command&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. And to see the Linux kernel version, you have to remember to enter &lt;code&gt;uname -r&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why? Because fuck you, that’s why.&lt;/p&gt;
&lt;p&gt;I guess it was just too damn hard to create a version command or script. Thankfully, using my uber niche koding skillz, I was able to come up with one after literally seconds of hard work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create the following script in your text editor of choice and save it in a file called &lt;em&gt;version&lt;/em&gt; somewhere that’s on your system’s execution path (I put it in &lt;code&gt;/usr/local/bin&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;span style=&#34;color:#007020&#34;&gt;&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;\nOperating system:\n&amp;#34;&lt;/span&gt;
lsb_release -i
lsb_release -d
lsb_release -r
lsb_release -c

&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;\nLinux kernel:\n&amp;#34;&lt;/span&gt;
uname -r

&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make the script executable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo chmod +x /usr/local/bin/version&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In Terminal, run &lt;code&gt;version&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should see output similar to the following that displays information about your distribution and kernel versions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;Operating system:

Distributor ID:	Ubuntu
Description:	Pop!_OS 18.10
Release:	18.10
Codename:	cosmic

Linux kernel:

4.18.0-10-generic&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;LSB stands for ‘Linux Standard Base’. Don’t you feel smarter now? &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Gnomit 1.0.4</title>
      <link>https://ar.al/2018/10/19/gnomit-1.0.4/</link>
      <pubDate>Fri, 19 Oct 2018 21:43:08 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/10/19/gnomit-1.0.4/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/10/19/gnomit-1.0.4/gnomit.png&#34;
         alt=&#34;Gnomit’s git message composition window showing the new look with a standard window that contains the Cancel and Commit buttons.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Gnomit’s new look&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;There’s a new version of &lt;a href=&#34;https://flathub.org/apps/details/ind.ie.Gnomit&#34;&gt;Gnomit&lt;/a&gt;, my little commit message editor for Linux, thanks to &lt;a href=&#34;https://mastodon.technology/@bugaevc/100737595071420219&#34;&gt;the initiative&lt;/a&gt; of &lt;a href=&#34;https://mastodon.technology/@bugaevc&#34;&gt;Sergey Bugaev&lt;/a&gt; who sent me &lt;a href=&#34;https://gitlab.gnome.org/snippets/341&#34;&gt;a patch&lt;/a&gt; a few weeks ago.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; Sergey’s patch updates Gnomit to use a standard window instead of the dialog window I was using&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;  and moves the Cancel and Commit buttons to the new window’s header bar.&lt;/p&gt;
&lt;p&gt;The new look actually makes it mirror its inspiration, &lt;a href=&#34;https://github.com/zorgiepoo/Komet&#34;&gt;the Komet app on macOS&lt;/a&gt;, almost exactly.&lt;/p&gt;
&lt;p&gt;Thank you for the patch, Sergey :)&lt;/p&gt;
&lt;p&gt;You can install Gnomit via &lt;a href=&#34;https://wiki.gnome.org/Apps/Software&#34;&gt;Gnome Software&lt;/a&gt; and &lt;a href=&#34;https://flathub.org/apps/details/ind.ie.Gnomit&#34;&gt;Flathub&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;flatpak install flathub ind.ie.Gnomit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For detailed installation instructions, please see the &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs/blob/master/README.md&#34;&gt;readme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source code is available from &lt;a href=&#34;ttps://source.ind.ie/gnome/gnomit&#34;&gt;source.ind.ie&lt;/a&gt; and there’s also &lt;a href=&#34;https://github.com/indie-mirror/gnomit&#34;&gt;a GitHub mirror&lt;/a&gt; where you can open issues and submit pull requests.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Sergey actually sent me the patch quite a few weeks ago but first &lt;a href=&#34;http://localhost:1313/2018/09/25/better-blocker-thank-you-for-our-best-week-yet/&#34;&gt;the new release of Better&lt;/a&gt; and then my XPS 13’s battery dying meant I only got round to taking a look at it now. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Using a regular window has the welcome side-effect of removing &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs/issues/25&#34;&gt;the annoying GTK warning in the terminal&lt;/a&gt; complaining about the use of a dialogue window as the main window of the application. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Updating firmware on Dell XPS 13 With Pop!_OS 18.04</title>
      <link>https://ar.al/2018/10/16/updating-firmware-on-dell-xps-13-with-popos-18.04/</link>
      <pubDate>Tue, 16 Oct 2018 22:59:30 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/10/16/updating-firmware-on-dell-xps-13-with-popos-18.04/</guid>
      <description>&lt;p&gt;Firmware upgrades were failing for me on my Dell XPS 13. Running &lt;code&gt;sudo fwupdmgr update&lt;/code&gt; would give me the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;UEFI firmware update failed: &lt;span style=&#34;color:#666&#34;&gt;{&lt;/span&gt;error &lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#0} libfwup.c:1501 get_fd_and_media_path(): failed to make /boot/efi/EFI/ubuntu/fw: No such file or directory&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A little online research led to me to a page on &lt;a href=&#34;https://github.com/rhboot/fwupdate/wiki/Debugging-UEFI-Capsule-updates&#34;&gt;Debugging UEFI Capsule updates&lt;/a&gt;, which in turn suggested that I try the latest &lt;em&gt;fwupdate&lt;/em&gt; from master. My inability to RTFM&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; (coupled with being spoiled by package managers because it’s 2018), led me to open &lt;a href=&#34;https://github.com/rhboot/fwupdate/issues/123&#34;&gt;an issue on the fwpdate issue tracker&lt;/a&gt; when compilation failed.&lt;/p&gt;
&lt;p&gt;Thankfully, I also wrote &lt;a href=&#34;https://github.com/rhboot/fwupdate/issues/123#issuecomment-430410732&#34;&gt;a comment&lt;/a&gt; explaining the core problem I was trying to solve and the author, &lt;a href=&#34;https://github.com/superm1&#34;&gt;Mario Limonciello&lt;/a&gt;, responded immediately with a solution: &lt;a href=&#34;https://github.com/rhboot/fwupdate/issues/123#issuecomment-430411563&#34;&gt;don’t use fwupdate, install fwupd via snap instead&lt;/a&gt;. (Thank you, Mario!)&lt;/p&gt;
&lt;p&gt;That worked a charm and I am now running on the latest firmware.&lt;/p&gt;
&lt;p&gt;I’m reproducing his instructions, below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt purge fwupd
sudo snap install --candidate --classic fwupd
fwupdmgr refresh
fwupdmgr update&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/RTFM&#34;&gt;Read The Fucking Manual&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Setting up Hiawatha web server on Ubuntu 16.04</title>
      <link>https://ar.al/2018/10/04/setting-up-hiawatha-web-server-on-ubuntu-16.04/</link>
      <pubDate>Thu, 04 Oct 2018 17:09:00 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/10/04/setting-up-hiawatha-web-server-on-ubuntu-16.04/</guid>
      <description>&lt;p&gt;The unfortunately-named &lt;a href=&#34;https://www.hiawatha-webserver.org&#34;&gt;Hiawatha web server&lt;/a&gt; with its problematic logo&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; is an independent, non-commercial, free and open web server built by Dutchman Hugo Leisink as a hobby project for the last 15 years or so. It’s primarily focused on security and the author appears to have &lt;a href=&#34;https://www.hiawatha-webserver.org/weblog/123&#34;&gt;a no-nonsense approach to its development&lt;/a&gt;.&lt;/p&gt;
&lt;iframe src=&#34;https://mastodon.ar.al/@aral/100825658318496939/embed&#34; class=&#34;mastodon-embed&#34; style=&#34;max-width: 100%; border: 0&#34; width=&#34;400&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://mastodon.ar.al/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;h2 id=&#34;installing-hiawatha-on-ubuntu&#34;&gt;Installing Hiawatha on Ubuntu&lt;/h2&gt;
&lt;p&gt;You can install Hiawatha on &lt;a href=&#34;https://www.hiawatha-webserver.org/download&#34;&gt;various platforms&lt;/a&gt;, including Linux, macOS, and Windows&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;To test it out, I first installed it &lt;a href=&#34;https://www.hiawatha-webserver.org/howto/compilation_and_installation&#34;&gt;from source&lt;/a&gt; on an Ubuntu 18.04 machine I commissioned from my regular host, &lt;a href=&#34;https://cloudscale.ch&#34;&gt;CloudScale&lt;/a&gt;. While that worked, it was rather involved. If you are going to install it, I would highly recommed using &lt;a href=&#34;https://launchpad.net/~octavhendra/+archive/ubuntu/hiawatha&#34;&gt;the Hiawatha apt package&lt;/a&gt;. Sadly, there isn’t one for 18.04 yet so you’ll have to use 16.04.&lt;/p&gt;
&lt;h2 id=&#34;installing-via-apt&#34;&gt;Installing via apt&lt;/h2&gt;
&lt;p&gt;To begin, add the apt repository to your system, update your packages, and install the Hiawatha apt package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo add-apt-repository ppa:octavhendra/hiawatha
sudo apt update
sudo apt upgrade
sudo apt install hiawatha&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That will install the server but it will not run it. To do that, you need to use systemd:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo systemctl &lt;span style=&#34;color:#007020&#34;&gt;enable&lt;/span&gt; hiawatha
sudo systemctl start hiawatha&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That will set up Hiawatha to be started automatically on restarts and start it up for the first time.&lt;/p&gt;
&lt;p&gt;If you go to your domain/IP at this point, you should see the default Hiawatha welcome page.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;One of the goals of Hiawatha is to be easy to configure and to provide sane, safe defaults. To that end, once I learned the basics, I did find it much easier to understand than my regular web server, nginx.&lt;/p&gt;
&lt;p&gt;The following is my setup&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;highlights&#34;&gt;Highlights:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Binds to port 80 (insecure; we will set up TLS later)&lt;/li&gt;
&lt;li&gt;Binds to both IPv4 and IPv6&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;directory-details&#34;&gt;Directory details:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Hiawatha’s general logs: &lt;em&gt;/var/log/hiawatha&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;The default web site (this should be an empty page to thwart IP scanners as recommedended in the default configuation file comments): &lt;em&gt;/var/www/default&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;My web site’s root: &lt;em&gt;/var/www/equinodal.org/public&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;configuration-file&#34;&gt;Configuration file:&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# General settings.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;ConnectionsTotal = 1000&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;ConnectionsPerIP = 25&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;SystemLogfile = /var/log/hiawatha/system.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;GarbageLogfile = /var/log/hiawatha/garbage.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Insecure servers (IPv4 and IPv6).&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Binding {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Port = 80&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Binding {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Port = 80&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Interface = 2a06:c01:1:1104::9349:73&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Default site.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# The default website is just a blank webpage. This is to thwart automated&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# scanners as recommended in the default configuration.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Hostname = 127.0.0.1&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;WebsiteRoot = /var/www/default&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;StartFile = index.html&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;AccessLogfile = /var/log/hiawatha/access.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;ErrorLogfile = /var/log/hiawatha/error.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Virtual hosts.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;VirtualHost {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Hostname = equinodal.org, www.equinodal.org&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;WebsiteRoot = /var/www/equinodal.org/public&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;AccessLogfile = /var/www/equinodal.org/log/access.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;ErrorLogfile = /var/www/equinodal.org/log/error.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To activate the current configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo systemctl restart hiawatha&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should now be able to access your site from your domain over HTTP (port 80).&lt;/p&gt;
&lt;h2 id=&#34;tls&#34;&gt;TLS&lt;/h2&gt;
&lt;p&gt;Now that your site is up and running, it’s time to implement TLS. The days of HTTP are behind us, long live HTTPS.&lt;/p&gt;
&lt;h3 id=&#34;download-php-cli&#34;&gt;Download PHP CLI:&lt;/h3&gt;
&lt;p&gt;Hiawatha has &lt;a href=&#34;https://letsencrypt.org&#34;&gt;Let’s Encrypt&lt;/a&gt; support in the form of &lt;a href=&#34;https://www.hiawatha-webserver.org/letsencrypt&#34;&gt;a script you can download&lt;/a&gt; but it is written in PHP and so you will need a bit of setup:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install php7-cli&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;dowload-and-install-the-hiawatha-lets-encrypt-script&#34;&gt;Dowload and install the Hiawatha Let’s Encrypt script:&lt;/h3&gt;
&lt;p&gt;Once you have the PHP CLI installed, &lt;a href=&#34;https://www.hiawatha-webserver.org/letsencrypt&#34;&gt;download the Let’s Encrypt script&lt;/a&gt; (get the ACME v2 version) and uncompress it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; /usr/local/etc
wget https://www.hiawatha-webserver.org/files/letsencrypt-2.0.tar.gz
tar -zxf letsencrypt-2.0.tar.gz
rm letsencrypt-2.0.tar.gz
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; letsencrypt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;configure-the-hiawatha-lets-encrypt-script&#34;&gt;Configure the Hiawatha Let’s Encrypt script:&lt;/h3&gt;
&lt;p&gt;In the &lt;em&gt;letsencrypt&lt;/em&gt; folder, you’ll find a configuration file named &lt;em&gt;letsencrypt.conf&lt;/em&gt; that you need to customise for your own needs.&lt;/p&gt;
&lt;p&gt;The only changes I made were to add my email address for the &lt;code&gt;ACCOUNT_EMAIL_ADDRESS&lt;/code&gt; field and to change the &lt;code&gt;HIAWATHA_RESTART_COMMAND&lt;/code&gt; field to use systemd:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;HIAWATHA_RESTART_COMMAND = sudo systemctl hiawatha restart&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You also have to create the &lt;em&gt;tls&lt;/em&gt; folder as specified by the &lt;code&gt;HIAWATHA_CONFIG_DIR&lt;/code&gt; and &lt;code&gt;HIAWATHA_CERT_DIR&lt;/code&gt; settings or the script will fail (this feels like a bug):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;mkdir /etc/hiawatha/tls&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;request-the-tls-certificate-from-the-staging-server&#34;&gt;Request the TLS certificate from the staging server:&lt;/h3&gt;
&lt;p&gt;Once you’ve configured the script, you need to actually request the certificate. Initially, you will be using the Let’s Encrypt staging server to make sure that everything works and then you will switch to using the production server.&lt;/p&gt;
&lt;p&gt;With the current script, requesting a certificate is a two-step process (this should really be simplified to a single step) that involves registering and requesting.&lt;/p&gt;
&lt;p&gt;From the folder that contains the &lt;em&gt;letsencrypt&lt;/em&gt; script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;./letsencrypt register
./letsencrypt request equinodal.org&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;request-the-certificate-from-the-production-server&#34;&gt;Request the certificate from the production server:&lt;/h3&gt;
&lt;p&gt;Unless you changed the configuration file, the first attempt will use the Let’s Encrypt staging server. Once that runs correctly, you can change the configuration file to use the production server and actually get your certificates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Let&amp;#39;s Encrypt API settings&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;LE_CA_HOSTNAME = acme-v02.api.letsencrypt.org		# Production&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;LE_ISSUERS = Let&amp;#39;s Encrypt Authority X3 \&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;             &lt;/span&gt;Let&amp;#39;s Encrypt Authority X4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;update-your-hiawatha-configuration-to-use-tls&#34;&gt;Update your Hiawatha configuration to use TLS:&lt;/h3&gt;
&lt;p&gt;Now it’s time to update your configuration to implement TLS. Here’s what my configuration looks like with TLS enabled and required.&lt;/p&gt;
&lt;h4 id=&#34;highlights-1&#34;&gt;Highlights:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Forwards all connections to TLS&lt;/li&gt;
&lt;li&gt;Gets an A on &lt;a href=&#34;https://www.ssllabs.com/ssltest/&#34;&gt;the SSLLabs tests&lt;/a&gt; (with the default settings)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;directories&#34;&gt;Directories:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The TLS certificate (you will need to create this directory manually): &lt;em&gt;/etc/hiawatha/tls&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# General settings.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;ConnectionsTotal = 1000&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;ConnectionsPerIP = 25&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;SystemLogfile = /var/log/hiawatha/system.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;GarbageLogfile = /var/log/hiawatha/garbage.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Insecure servers (IPv4 and IPv6).&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Binding {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Port = 80&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Binding {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Port = 80&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Interface = 2a06:c01:1:1104::9349:73&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Secure servers (IPv4 and IPv6).&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Binding {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Port = 443&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;TLScertFile = /etc/hiawatha/tls/equinodal.org.pem&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Binding {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Port = 443&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Interface = 2a06:c01:1:1104::9349:73&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;TLScertFile = /etc/hiawatha/tls/equinodal.org.pem&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Default site.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# The default website is just a blank webpage. This is to thwart automated&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# scanners as recommended in the default configuration.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;Hostname = 127.0.0.1&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;WebsiteRoot = /var/www/default&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;StartFile = index.html&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;AccessLogfile = /var/log/hiawatha/access.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;ErrorLogfile = /var/log/hiawatha/error.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Virtual hosts.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;VirtualHost {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Hostname = equinodal.org, www.equinodal.org&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;WebsiteRoot = /var/www/equinodal.org/public&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;RequireTLS = yes&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;AccessLogfile = /var/www/equinodal.org/log/access.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;ErrorLogfile = /var/www/equinodal.org/log/error.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The only changes are the addition of secure IPv4 and IPv6 binding sections for TLS that reference the Let’s Encrypt certificate and the addition of the &lt;code&gt;RequireTLS = yes&lt;/code&gt; setting to the virtual host.&lt;/p&gt;
&lt;p&gt;Restart your server and you should be up and running over HTTPS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo systemctl restart hiawatha&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&#34;nodejs-with-a-reverse-proxy&#34;&gt;Node.js with a reverse proxy&lt;/h2&gt;
&lt;p&gt;The above is all you need if you want to host a static site with Hiawatha but what if you want to host your Node.js site? For that, we need to configure a reverse proxy and it is stupidly simple to do.&lt;/p&gt;
&lt;p&gt;First, &lt;a href=&#34;https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions&#34;&gt;install Node.js on your server&lt;/a&gt; if you haven’t already:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, create a project to hold a very simple test server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;mkdir ~/node-server &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; ~/node-server
npm init -y
nano index.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In &lt;em&gt;index.js&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; http &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;http&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;const&lt;/span&gt; server &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; http.createServer((request, response) =&amp;gt; {
  response.statusCode &lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;200&lt;/span&gt;
  response.end(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Hello from Node.js!\n&amp;#39;&lt;/span&gt;)
})

server.listen(&lt;span style=&#34;color:#40a070&#34;&gt;3000&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run the Node server&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;node index.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In another ssh session (or terminal window if local), test that the Node server works:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;curl http://localhost:3000&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should see something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;ubuntu@equinodal:~/spike$ curl http://localhost:3000
Hello from Node.js!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, now, all we need to do is to update our Hiawatha configuration to use a reverse proxy that forwards all requests to our Node server. You do this in the virtual host section:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;VirtualHost {&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;Hostname = equinodal.org, www.equinodal.org&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;ReverseProxy .* http://localhost:3000/&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;WebsiteRoot = /var/www/equinodal.org/public&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;RequireTLS = yes&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;AccessLogfile = /var/www/equinodal.org/log/access.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;ErrorLogfile = /var/www/equinodal.org/log/error.log&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The only change is the addition of the &lt;code&gt;ReverseProxy&lt;/code&gt; setting. (For some reason, the virtual host still needs the &lt;code&gt;WebsiteRoot&lt;/code&gt; setting even though it is overriden by the reverse proxy setting.)&lt;/p&gt;
&lt;p&gt;Restart the Hiawatha server and make sure that your Node server is running and you should be able to access it over TLS via your domain.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Hiawatha feels like a great alternative to nginx and other heavier web servers for personal projects and I look forward to using it in some of mine. I do hope that Hugo will do something to update the problematic name and iconography for 2018 so that I don’t have any reservations when I recommend it to others. If you do give it a shot, &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;let me know how you get on&lt;/a&gt; via &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Cultural_appropriation&#34;&gt;Cultural appropriation&lt;/a&gt; is never fun. &lt;a href=&#34;https://en.wikipedia.org/wiki/Hiawatha&#34;&gt;Hiawatha&lt;/a&gt; is &lt;a href=&#34;https://duckduckgo.com/?q=%23notyourmascot&#34;&gt;#NotYourMascot&lt;/a&gt;. I do hope that Hugo will reconsider the naming and iconography of an otherwise excellent project. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Note the following &lt;a href=&#34;https://www.hiawatha-webserver.org/download&#34;&gt;caveat&lt;/a&gt; for Windows: “I&amp;rsquo;ve never fully tested the impact that Cygwin or Windows has on Hiawatha, so don&amp;rsquo;t use the Windows version for production websites unless you&amp;rsquo;ve tested it yourself.” &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Don’t try and access the site itself, I’m just using that domain to test with so it will most likely be offline. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Normally, you would run the server with a script like &lt;a href=&#34;https://github.com/foreverjs/forever&#34;&gt;forever&lt;/a&gt;. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Better Blocker: thank you for our best week yet!</title>
      <link>https://ar.al/2018/09/25/better-blocker-thank-you-for-our-best-week-yet/</link>
      <pubDate>Tue, 25 Sep 2018 10:03:40 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/09/25/better-blocker-thank-you-for-our-best-week-yet/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/25/better-blocker-thank-you-for-our-best-week-yet/better-blocker-sales-lifetime.png&#34;
         alt=&#34;App Store Connect graph of Better Blocker’s lifetime sales. It shows two peaks, one at the original launch, two years ago, and one at the start of last week. A red line connecting the peaks makes it clear that the one from last week is higher.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better Blocker’s sales last week topped our original launch, two years ago.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://2017.ind.ie/about/team/&#34;&gt;Laura and I&lt;/a&gt; want to thank those of you who purchased &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; last week. Thanks to you, we had our best week ever and sold over 2,500 units. That’s quite something given that we have sold just over 20,000 units in total over the last two years.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/25/better-blocker-thank-you-for-our-best-week-yet/better-blocker-unit-sales-percentage-increase.png&#34;
         alt=&#34;Graph showing the percentage increase in unit sales for Better Blocker: sales are up over 999%&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better Blocker: sales are up over 999%.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229&#34;&gt;Better Blocker macOS app&lt;/a&gt; became the #1 paid app overall on the Netherlands app store and in Guatalama&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, entered the top ten in 14 countries, and was in the top 100 in 27 countries.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/25/better-blocker-thank-you-for-our-best-week-yet/../../24/better-blocker-for-macos-mojave/intro-light.jpg&#34;
         alt=&#34;Screenshot of the Better Blocker introduction. It’s a popover from the status bar. Copy reads: “Better. Please activate Better in Safari to get started.” There is a screenshot of the Extensions tab active in the Safari Preferences window showing exactly where and how to do that. The bottom of the popover has a button titled Launch Safari.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The newly-redesigned Better Blocker on macOS Mojave.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The statistics cover the week from &lt;a href=&#34;https://ar.al/2018/09/17/better-blocker-for-ios-12/&#34;&gt;the launch&lt;/a&gt; of &lt;a href=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/&#34;&gt;the new design of the iOS app&lt;/a&gt; to yesterday, when the new macOS app had been live for just a day or two and we hadn’t even properly announced it yet as we were waiting for yesterday’s Mojave launch to do so.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The uptick seems to be driven in large part by sales of the macOS app, combined with an upturn in interest in the iOS 12 app since the launch of the new design. The rise in macOS purchases also no doubt corresponds with some popular legacy “ad block” Safari Extensions becoming unusable under Safari 12.&lt;/p&gt;
&lt;p&gt;It’s too soon to tell what the longer-term impact of lowering the price on the iOS and macOS apps to the lowest tier ($0.99/£0.99/€1.09/etc.) will be on purchases. I hope that it will at least mean that price will not be a stumbling block in your being protected by Better.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; The more people are protected from trackers, the better our herd immunity against surveillance capitalism.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Laura and I are so happy to see the renewed interest in &lt;a href=&#34;https://betterf.yi&#34;&gt;Better&lt;/a&gt;. Thank you for enabling us to keep working on Better and thank you for telling your friends about Better.&lt;/p&gt;
&lt;p&gt;You can purchase &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker for iOS&lt;/a&gt; and &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229&#34;&gt;Better Blocker for macOS&lt;/a&gt; on the App Store.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;We were also 2nd in Estonia, 3rd in Germany and 4th in Poland and rose to #18 in the US app store. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;We don’t have a marketing budget and we don’t use online ads (guess why) so by “properly announce” I mean toot about it on &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt; and tweet about it and hope that people tell their friends via word of mouth. So do tell your friends about &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;, it’s the main way people hear about it. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can also &lt;a href=&#34;https://source.ind.ie/better/app&#34;&gt;download the source code for the Better apps&lt;/a&gt; for free and build them yourself for macOS and iOS. Better is “free as in freedom” software and released under a GPLv3 license. There is also an &lt;a href=&#34;https://better.fyi/blockerList.txt&#34;&gt;EasyList version of the Better tracker blocking rules&lt;/a&gt; that you can use in &lt;a href=&#34;https://github.com/gorhill/uBlock/&#34;&gt;uBlock Origin&lt;/a&gt; on other platforms and browsers. If you’re on Linux, see these &lt;a href=&#34;https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/&#34;&gt;instructions for using the Better Blocker tracking rules with Gnome Web&lt;/a&gt;. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;See &lt;a href=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/&#34;&gt;Better Blocker: two year review and thoughts on the future&lt;/a&gt; for further discussion on the direction we’re heading in with Better. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Better Blocker for macOS Mojave</title>
      <link>https://ar.al/2018/09/24/better-blocker-for-macos-mojave/</link>
      <pubDate>Mon, 24 Sep 2018 12:02:54 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/09/24/better-blocker-for-macos-mojave/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/24/better-blocker-for-macos-mojave/intro-light.jpg&#34;
         alt=&#34;Screenshot of the Better Blocker introduction. It’s a popover from the status bar. Copy reads: “Better. Please activate Better in Safari to get started.” There is a screenshot of the Extensions tab active in the Safari Preferences window showing exactly where and how to do that. The bottom of the popover has a button titled Launch Safari.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better Blocker for macOS Mojave: simpler, more affordable, and just as effective.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A little under a month ago, in &lt;a href=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/&#34;&gt;Better Blocker: two-year review and thoughts on the future&lt;/a&gt;, I mentioned I wanted to radically simplify the &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; iOS and macOS apps.&lt;/p&gt;
&lt;p&gt;I went on to say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’m proud of what we’ve achieved with Better so far. However, I’m unhappy with the reach Better Blocker has and we must figure out a way of increasing that reach while not cutting off a source of revenue without which we could not afford to pay the rent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To that end, on September 17th, after several weeks of hard work and in tandem with the launch of iOS 12, we launched the redesigned &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker iOS app&lt;/a&gt; (see &lt;a href=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/&#34;&gt;Better, simpler, and more affordable&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;With the launch of the new version, we also dropped the price to the lowest tier on the App Store ($0.99/£0.99/€1.09/etc.) in line with our goal to get as many people as possible protected with Better while hopefully not destroying our ability to pay the rent in the process.&lt;/p&gt;
&lt;p&gt;Today, we complete the roll out of the new design with the launch of the new &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229&#34;&gt;Better Blocker for macOS&lt;/a&gt;. In line with the iOS app, we have also dropped the price to the lowest tier for the Mac app.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/24/better-blocker-for-macos-mojave/menu-light.jpg&#34;
         alt=&#34;Screenshot of the new Better Blocker app on macOS Mojave. It is a status bar app. In the screenshot the menu is open. It has a blue shield with a check mark on it. Under it, the text: “Better is enabled.” Under that header is the rest of the app menu.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better Blocker is now a status bar app on macOS.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Even though the new apps are radical redesigns, we decided not to release a new app. If you already bought &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker for iOS&lt;/a&gt; and/or &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229&#34;&gt;Better Blocker for macOS&lt;/a&gt;, you can simply upgrade to the new apps via the App Store. Again, this is because we want to see Better in as many hands as possible. So we’d appreciate it if you could spread the word and tell your friends who don’t have Better yet.&lt;/p&gt;
&lt;h2 id=&#34;whats-new&#34;&gt;What’s new&lt;/h2&gt;
&lt;p&gt;The design of the macOS app mirrors the iOS app as much as possible. Given how much of the interface was removed in the process, it made sense to remove the main window altogether and make the app into a status bar app.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/24/better-blocker-for-macos-mojave/exceptions-light-dark.jpg&#34;
         alt=&#34;A split-screen of a screenshot of the Better Blocker Exceptions window shows how it look in Light Mode versus Dark Mode on macOS Mojave.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Whether you choose the Light Side or the Dark Side in Mojave, Better Blocker will protect you from the Dark Side of the Web. (I missed a career in marketing, I know!)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The only part of the app that has a window is now the Exceptions section, where you can specify sites that you don’t want Better to block trackers on. If you’re doing this because a site breaks Better, please use the &lt;em&gt;Report a Site&lt;/em&gt; feature to let us know so we can fix it for everyone.&lt;/p&gt;
&lt;p&gt;As part of the redesign, we also wanted to support the new Dark Mode in Mojave. Which, as you can see in the screenshot above, we did. &lt;a href=&#34;https://mastodon.ar.al/@aral/100692526698981604&#34;&gt;I’m not a huge fan of Mojave’s Dark Mode&lt;/a&gt; (I find it too dark) but I have to admit that Better does look pretty cool in Dark Mode.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/24/better-blocker-for-macos-mojave/menu-dark.jpg&#34;
         alt=&#34;The status bar menu of Better Blocker in Dark Mode on macOS Mojave.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better does look pretty cool in Dark Mode on macOS Mojave.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;no-rest-for-the-wicked&#34;&gt;No rest for the wicked&lt;/h2&gt;
&lt;p&gt;The 2018.1 releases of both apps are just the first step in a new direction. You can read more about where we intend to take them &lt;a href=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/&#34;&gt;in my previous post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This week, we are working on the next minor release, 2018.2.&lt;/p&gt;
&lt;p&gt;If you want to support our work, please tell your friends about &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker for iOS&lt;/a&gt; and &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229&#34;&gt;Better Blocker for macOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope you enjoy the new Better Blocker. I’m excited to see where the second chapter of our little independent adventure in making the Web a safer place takes us.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Better Blocker for iOS 12</title>
      <link>https://ar.al/2018/09/17/better-blocker-for-ios-12/</link>
      <pubDate>Mon, 17 Sep 2018 17:58:22 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/09/17/better-blocker-for-ios-12/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/17/better-blocker-for-ios-12/better-content-blocker-for-ios-on-the-apple-app-store.jpg&#34;
         alt=&#34;Screenshot of the Better Blocker content blocker app on the App Store showing that it has a 4.8 star rating on the US store with 32 ratings.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better for iOS 12 on the App Store. Is that a 4.8-star rating I see?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker for iOS 12&lt;/a&gt; is now available on the App Store.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; is our content blocker for iOS and &lt;a href=&#34;http://localhost:1313/2018/09/24/better-blocker-for-macos-mojave/&#34;&gt;macOS&lt;/a&gt; that protects you from privacy-eroding surveillance-based behavioural advertising (adtech) and other trackers. It also removes ads that negatively impact your Web experience.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This release is a major redesign. We’ve worked hard to radically simplify the app.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/&#34;&gt;Better, simpler, and more affordable&lt;/a&gt; and &lt;a href=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/&#34;&gt;Better Blocker: two year review and thoughts on the future&lt;/a&gt; for more details on the release itself and our roadmap for Better in general.&lt;/p&gt;
&lt;p&gt;I hope you enjoy the new iOS app&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You can purchase &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker for iOS&lt;/a&gt; as well as &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229&#34;&gt;Better Blocker for macOS&lt;/a&gt; (now with macOS Mojave support) on the App Store.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Our blocking rules are based on the principles of the &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;Ethical Design Manifesto&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;As this is not a separate app but an update, if you previously bought Better on iOS, all you need to do is to update the app from the App Store. Please also tell your friends about Better and help support our work at &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Workaround for unclickable app menu bug with window.makeKeyAndOrderFront and NSApp.activate on macOS</title>
      <link>https://ar.al/2018/09/17/workaround-for-unclickable-app-menu-bug-with-window.makekeyandorderfront-and-nsapp.activate-on-macos/</link>
      <pubDate>Mon, 17 Sep 2018 11:08:47 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/09/17/workaround-for-unclickable-app-menu-bug-with-window.makekeyandorderfront-and-nsapp.activate-on-macos/</guid>
      <description>&lt;p&gt;To open a window and make it appear on top of other windows on macOS, you can do the following:&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;myWindow.makeKeyAndOrderFront(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;nil&lt;/span&gt;)
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This works to show the window, make it topmost and also display the app menu. The problem is that the app menu is not clickable until you tab away from the application and back again.&lt;/p&gt;
&lt;p&gt;I couldn’t find this issue addressed with some cursory search engine trawling but the symptoms led to me think that it was most likely a timing problem. Which, it turns out, it probably is.&lt;/p&gt;
&lt;p&gt;After my initial workaround&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; exhibited random failures, I dug deeper into the issue and found &lt;a href=&#34;https://stackoverflow.com/a/43780588&#34;&gt;a more comprehensive workaround&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;myWindow.makeKeyAndOrderFront(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;nil&lt;/span&gt;)
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;)

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// Workaround for window activation issues:&lt;/span&gt;
&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;// toggle focus away from the app and back.&lt;/span&gt;
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;if&lt;/span&gt; (NSRunningApplication.runningApplications(withBundleIdentifier: &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;com.apple.dock&amp;#34;&lt;/span&gt;).first?.activate(options: []))&lt;span style=&#34;color:#666&#34;&gt;!&lt;/span&gt;
{
    &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;deadlineTime&lt;/span&gt; = DispatchTime.now() &lt;span style=&#34;color:#666&#34;&gt;+&lt;/span&gt; .milliseconds(&lt;span style=&#34;color:#40a070&#34;&gt;200&lt;/span&gt;)
    DispatchQueue.main.asyncAfter(deadline: deadlineTime)
    {
        NSApp.setActivationPolicy(.regular)
        NSApp.activate(ignoringOtherApps: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;)
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This second workaround seems to be solid so far in my testing, even though it does cause a slight flash of the window, especially the first time it is opened. It’s what I’m currently using in &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker for macOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NB. There does seem to be a random element to this bug as, interestingly, as I was testing the app while writing this update, I coud not reproduce the bug even when testing without the workaround, on the latest macOS Mojave. I am keeping the workaround in for the time being in any case it is a failsafe. When I get some time I will try and see if Apple is aware of this issue and follow up on its resolution.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The &lt;code&gt;NSApp.setActivationPolicy(.regular)&lt;/code&gt; should not be necessary unless you are trying this from a status bar application like I was. The activation policy was originally set to &lt;code&gt;.accessory&lt;/code&gt; so we have to set it back for it behave like a regular application (with a toolbar icon, etc.). &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;My initial solution was to invoke the &lt;code&gt;NSApp.activate&lt;/code&gt; call on the next stack frame using my little &lt;a href=&#34;https://source.ind.ie/project/delay&#34;&gt;delay&lt;/a&gt; library like this: &lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;myWindow.makeKeyAndOrderFront(&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;nil&lt;/span&gt;)
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;_&lt;/span&gt; = delay(&lt;span style=&#34;color:#40a070&#34;&gt;0&lt;/span&gt;) {
    NSApp.activate(ignoringOtherApps: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;true&lt;/span&gt;)
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Responsive design got my app rejected</title>
      <link>https://ar.al/2018/09/16/responsive-design-got-my-app-rejected/</link>
      <pubDate>Sun, 16 Sep 2018 13:52:05 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/09/16/responsive-design-got-my-app-rejected/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/16/responsive-design-got-my-app-rejected/rejected.jpg&#34;
         alt=&#34;The App Store Connect Resolution Center page for the latest Better submission showing that it is Metadata Rejected because the 9.7-inch screenshots show an older iOS version in the status bar.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Rejection: a love story.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/&#34;&gt;The iOS 12 version of Better&lt;/a&gt; is currently in review with a status of Metadata Rejected and I blame responsive design.&lt;/p&gt;
&lt;p&gt;Let me explain.&lt;/p&gt;
&lt;p&gt;In previous versions, I was using &lt;a href=&#34;https://fastlane.tools&#34;&gt;Fastlane&lt;/a&gt; (now owned by surveillance capitalist Google – &lt;em&gt;spit!&lt;/em&gt;) to automatically generate screenshots for all possible screen resolutions and uploading them via the App Store API.&lt;/p&gt;
&lt;p&gt;With the new version, I decided to take and upload the screenshots manually and to limit them to the only two required screen sizes: 5.5-inch (e.g., iPhone 6 Plus) and 12.9-inch display (i.e., iPad Pro) that I saw listed on the App Store Connect page.&lt;/p&gt;
&lt;p&gt;So I used the App Store Connect site to delete the existing screenshots and upload the new ones and, along with an updated description and binary, submitted the app for review on the 14th. I woke up on the 15th to find that it had been Metadata Rejected. The note read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We noticed that your screenshots do not sufficiently reflect your app in use. Specifically, your 9.7-inch iPad screenshots display older IOS version in the status bar.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Wait a minute! 9.7-inch iPad screenshots? I didn’t upload any 9.7-inch iPad screenshots!&lt;/p&gt;
&lt;p&gt;Oh, but previous versions of Fastlane had and so I must have forgotten to delete them before submitting the app.&lt;/p&gt;
&lt;p&gt;I was so sure I had checked the interface to try and find all screen sizes but back I went to the App Store Connect site and, lo and behold, there was a faint blue-on-grey link titled “View All Sizes in Media Manager” under App Previews and Screenshots that I had apparently somehow missed while preparing the submission.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/16/responsive-design-got-my-app-rejected/app-store-connect-with-link.jpg&#34;
         alt=&#34;The App Store Connect submission page for Better, showing that it is Metadata Rejected. There is a link to view all screen sizes under App Previews and Screenshots (which I’ve emphasised by framing it with a red rectangle).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The link to View All Sizes in Media Manager (emphasis mine)… how had I missed it?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Cussing my scatterbrained effort, I deleted all of the old screenshots and responded to the comment in the resolution centre.&lt;/p&gt;
&lt;p&gt;However, something didn’t sit right with me. I mean, goodness knows I can be terribly aloof but I was certain I had searched for that link earlier and it wasn’t there. Going back to the site and resizing the window to half screen on my 4K 21&amp;quot; monitor, I realised what the problem was: the link disappears on viewports that are smaller than 1154px wide.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/16/responsive-design-got-my-app-rejected/app-store-connect-no-link.jpg&#34;
         alt=&#34;The App Store Connect submission page for Better, showing that it is Metadata Rejected. The link to view all screen sizes under App Previews and Screenshots is hidden.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Exhibit A: the missing link.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So I wasn’t losing my mind. I had been thwarted by a CSS breakpoint. When I was preparing my submission, the link simply wasn’t there.&lt;/p&gt;
&lt;p&gt;And that’s how responsive design got my app rejected.&lt;/p&gt;
&lt;h2 id=&#34;epilogue&#34;&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;The app is still in review but in the meanwhile, I reported the issue to Apple (radar 44499555) so hopefully it will be fixed soon.&lt;/p&gt;
&lt;p&gt;One way to fix it would be to forgo the use of a link altogether and add another tab to the interface, as below:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/16/responsive-design-got-my-app-rejected/app-previews-fix.png&#34;
         alt=&#34;A simple iteration on the design of the App Previews section on App Store Connect to add a tab bar item for other sizes.&#34;/&gt; 
&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>Better, simpler, and more affordable</title>
      <link>https://ar.al/2018/09/14/better-simpler-and-more-affordable/</link>
      <pubDate>Fri, 14 Sep 2018 19:34:56 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/09/14/better-simpler-and-more-affordable/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/better-v2.jpg&#34;
         alt=&#34;Photo of a hand, it mine, holding an iPhone X running the next bersion of Better. It is a simple, white screen with a blue shield that has a checkmark on it. Under the shield it says “Better is enabled.” On the bottom of the screen are three options: Report a site, Exceptions…, and Get support.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The next version of Better embraces the essentials.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Two weeks ago, in &lt;a href=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/&#34;&gt;Better Blocker: two year review and thoughts on the future&lt;/a&gt;, I wrote that I was going to “radically simplify” the &lt;a href=&#34;https://better.fyi&#34;&gt;Better&lt;/a&gt; apps.&lt;/p&gt;
&lt;p&gt;Today, I submitted a radical redesign of &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;Better Blocker for iOS&lt;/a&gt; to the App Store for approval in time for the launch of iOS 12.&lt;/p&gt;
&lt;p&gt;The app has a minimal new design and a beautiful new icon that Laura and I designed together.&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/better-badge.png&#34;
         alt=&#34;Better’s new icon is a blue shield with a stark shadow running half-way throught it and white heart at its centre.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better’s new icon.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;minimal-look-minimal-price&#34;&gt;Minimal look, minimal price&lt;/h2&gt;
&lt;p&gt;Alongside the minimal new look, Better is also getting a minimal new price. We want more people protected by Better and we want to increase the impact of our tracker blocking. So, come this release, we will drop the price of Better to the lowest tier on the App Store. The Mac app will follow suit with its Mojave release.&lt;/p&gt;
&lt;h2 id=&#34;just-as-effective&#34;&gt;Just as effective&lt;/h2&gt;
&lt;p&gt;The crucial functionality stays the same: Better still blocks trackers using &lt;a href=&#34;https://better.fyi/trackers&#34;&gt;our unique, hand-curated tracker blocker database&lt;/a&gt;. What’s different is that it only includes the content blocker rules instead of the full human-readable encyclopaedic entries, informational articles, etc. These will still be available on &lt;a href=&#34;https://better.fyi&#34;&gt;the Better web site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The new design decouples the app from the web site and back-end. This is going to make it easier for us to port the app to more platforms and to try something new with the back-end.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/09/14/better-simpler-and-more-affordable/better-mac-os-work-in-progress.jpg&#34;
         alt=&#34;Some printouts of the new Mac client for Better showing the new design and macOS Dark Mode states.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better for macOS Mojave: work in progress.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is the first iteration of a new chapter in Better’s evolution.&lt;/p&gt;
&lt;p&gt;We are now working hard on getting &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229?mt=12&#34;&gt;the Mac version&lt;/a&gt; ready to submit in time for the macOS Mojave launch on September 24th.&lt;/p&gt;
&lt;p&gt;Laura and I hope you’ll enjoy the minimal new design, not to mention the minimal new price.&lt;/p&gt;
&lt;p&gt;(Do tell your friends.) ;)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Extended Codice Interview With Rai 1</title>
      <link>https://ar.al/2018/08/29/extended-codice-interview-with-rai-1/</link>
      <pubDate>Wed, 29 Aug 2018 23:29:21 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/29/extended-codice-interview-with-rai-1/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/722461174.jpg?mw=2880&amp;mh=1620&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/287338222.m3u8?s=4be821f2faeb3d5cc88e2e9b96f9609e14ac7e10&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/287338222.hd.mp4?s=9d4193a90a3e52071e1f6b6f3fc6f10f69a315f4&amp;profile_id=174&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;An extended cut of a recent interview I gave on cyborg rights (human rights in the digital/networked age), surveillance capitalism, and ethical design.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I was recently featured in &lt;a href=&#34;https://www.raiplay.it/video/2018/07/Codice-La-vita-e-digitale-Superuomini-o-postumani-9dddbdec-b750-449b-addd-3e3d08285bad.html&#34;&gt;an episode&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; of a technology show called &lt;a href=&#34;https://www.raiplay.it/programmi/codicelavitaedigitale/&#34;&gt;Codice&lt;/a&gt; on Italian national television channel Rai 1. I just stumbled on an extended cut they released on surveillance-based video site YouTube. You can watch the segment here without being tracked.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Registration is required to watch the original show (it is in Italian and my segments are dubbed). &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Better Blocker: two year review and thoughts on the future</title>
      <link>https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/</link>
      <pubDate>Mon, 27 Aug 2018 11:33:37 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/better-blocker.jpg&#34;
         alt=&#34;Screenshot of the Better Blocker Website with a header showing a blue sky with sunbeam and white clouds, in front of which Better is shown running on Mac, iPad, and iPhone. The headline reads: “The web, tracker free.”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Better Blocker web site.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; is a no-bullshit tracker blocker &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I built because we wanted &lt;a href=&#34;https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/#why-s-better-any-better&#34;&gt;an easy-to-use tracker blocker that wasn’t funded by or working for the adtech industry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We released the iOS version of Better Blocker &lt;a href=&#34;https://2017.ind.ie/blog/better/&#34;&gt;on June 3rd, 2016&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since then, we’ve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Released a macOS version alongside the iOS version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintained a &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1080964978&#34;&gt;4.7-star rating the iOS App Store&lt;/a&gt; and a &lt;a href=&#34;https://itunes.apple.com/us/app/better-blocker/id1121192229?mt=12&#34;&gt;4.2-star rating on the macOS App Store&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issued lots of &lt;a href=&#34;https://better.fyi/news/&#34;&gt;content updates&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Re-written the &lt;a href=&#34;https://source.ind.ie/better/inspector&#34;&gt;Inspector&lt;/a&gt; to use Headless Chrome instead of Electron-HAR (yes, it’s pretty sweet that we‘re using the tools of surveillance capitalism against its interests).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Released an &lt;a href=&#34;https://better.fyi/blockerList.txt&#34;&gt;EasyList version&lt;/a&gt; of &lt;a href=&#34;https://better.fyi/trackers&#34;&gt;our block rules&lt;/a&gt; for use in other blockers on other platforms via tracker blockers like the excellent &lt;a href=&#34;https://github.com/gorhill/uBlock/&#34;&gt;uBlock Origin&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Been featured in a number of &lt;a href=&#34;http://www.wbrc.com/story/38159211/the-best-iphone-apps-available-right-now-august-2018&#34;&gt;best&lt;/a&gt; &lt;a href=&#34;https://appadvice.com/collection/ad-blocker-apps&#34;&gt;of&lt;/a&gt; &lt;a href=&#34;https://www.liquidvpn.com/top-10-adblockers-apple-ios-devices/&#34;&gt;lists&lt;/a&gt; and &lt;a href=&#34;http://observer.com/2016/10/aral-balkan-dokutech/&#34;&gt;mainstream&lt;/a&gt; &lt;a href=&#34;https://motherboard.vice.com/en_us/article/d7yynj/this-iphone-app-blocks-behavior-tracking-ads-and-evades-blocker-blockers&#34;&gt;articles&lt;/a&gt; to raise awareness about surveillance capitalism in general.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By all accounts, Better does what it says on the tin and is loved by those who use it. But, sadly, given its niche audience, it’s not as effective as it could be against the scurge of Web surveillance.&lt;/p&gt;
&lt;h2 id=&#34;limited-reach&#34;&gt;Limited reach&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/stats.jpg&#34;
         alt=&#34;Screenshot of Better’s stats on App Store Connect from April 1, 2016 to August 31,. 2018 showing approximately 17,200 units sold across iOS and macOS. On the Units per Month graph, you see that there was a large spike when it app was first released and two other smaller spikes that correspond to media coverage of Ind.ie.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The challenge: increasing our reach from thousands of people to millions.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I just checked our stats on Apple’s App Store Connect and it looks like in the two years that we’ve been going, Better has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sold ~17,200 copies (~12.5K on iOS, 4.76K on macOS).&lt;/li&gt;
&lt;li&gt;Made us roughly $60,700 (€52,200) in proceeds (~€26,100/year).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While those proceeds have helped us to pay the rent, they are not enough to hire a full-time developer to work on the project.&lt;/p&gt;
&lt;p&gt;Also, due to its limited platform support and the relatively high price (tier five; the equivalent of ~£4.99 in various currencies), our reach in two years has been under 20,000 people.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/27/better-blocker-two-year-review-and-thoughts-on-the-future/stats2.jpg&#34;
         alt=&#34;Screenshot of Better’s stats on App Store Connect from April 1, 2016 to August 31,. 2018 showing approximately $60,700 in proceeds across iOS and macOS.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Our proceeds from Better over its lifetime to date.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While selling close to 20,000 copies of anything is nothing to scoff at for a tiny self-funded two-person not-for-profit with no marketing budget, it is a far cry from the sort of reach we could have if we weren’t worried about being able to sustain ourselves.&lt;/p&gt;
&lt;p&gt;If two hundred thousand or two million people were using our blocking rules instead of under twenty thousand, we would be making a much greater dent in the tracking capabilities and bottom lines of surveillance capitalists. Especially considering that we cannot be bought no matter how many millions of dollars they might throw our way to be unblocked.&lt;/p&gt;
&lt;h2 id=&#34;getting-from-here-to-there&#34;&gt;Getting from here to there&lt;/h2&gt;
&lt;p&gt;So, to re-cap, I’m proud of what we’ve achieved with Better so far. However, I’m unhappy with the reach Better Blocker has and we must figure out a way of increasing that reach while not cutting off a source of revenue without which we could not afford to pay the rent.&lt;/p&gt;
&lt;p&gt;Here are some of my thoughts about the future, on how we can optimise for the right things to address these concerns:&lt;/p&gt;
&lt;h3 id=&#34;native-apps-on-ios-and-macos&#34;&gt;Native apps on iOS and macOS&lt;/h3&gt;
&lt;p&gt;What matters is to effectively block trackers and to keep those blocking rules up to date. What is nice to have but not essential is having the full database of trackers, etc., on every copy of the app. So let’s optimise for the former.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/better&#34;&gt;The current architecture&lt;/a&gt; of Better is a reflection of our philosophy towards decentralised design (every app has a full copy of the tracker database, uses Git to download data, etc.). It’s pretty neat, my inner geek loves it, and I’m rather proud of how well it has worked for us so far but I’ve come to think that this design is overkill for the purposes of the apps.&lt;/p&gt;
&lt;p&gt;To that end, I am going to radically simplify the iOS and macOS apps to remove all but the most essential functionality. This should also make it much easier for us to maintain those apps on Apple’s rapidly-changing platforms and to port them to other platforms.&lt;/p&gt;
&lt;h3 id=&#34;simplify-the-back-end-and-reduce-costs&#34;&gt;Simplify the back-end and reduce costs&lt;/h3&gt;
&lt;p&gt;The back-end architecture reflects the decoupled and decentralised design philosophy and thus relies on several servers, including two Git servers running GitLab. We host them with an excellent European host called &lt;a href=&#34;https://cloudscale.ch&#34;&gt;CloudScale.ch&lt;/a&gt; but it costs us about ~€250/month to do so. I am going to look into consolidating the &lt;a href=&#34;https://source.ind.ie/better/inspector&#34;&gt;Inspector&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/better/builder&#34;&gt;Builder&lt;/a&gt;, and &lt;a href=&#34;https://source.ind.ie/better/drafts-server-setup&#34;&gt;Drafts&lt;/a&gt; servers and also the &lt;a href=&#34;https://source.ind.ie/better/web-server-setup&#34;&gt;web&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/better/data-server-setup&#34;&gt;data&lt;/a&gt;, and &lt;a href=&#34;https://source.ind.ie/better/archive-server-setup&#34;&gt;archive&lt;/a&gt; servers.&lt;/p&gt;
&lt;h3 id=&#34;continuous-inspections-and-blocking-rule-updates&#34;&gt;Continuous inspections and blocking rule updates&lt;/h3&gt;
&lt;p&gt;Currently, the &lt;a href=&#34;https://source.ind.ie/better/inspector&#34;&gt;Better Inspector&lt;/a&gt; is designed to run on demand. I want it to become an engine that runs continuously and feeds into a simple, online mechanism for updating the blocking rules that members of the community can contribute to also.&lt;/p&gt;
&lt;h3 id=&#34;multi-platform-support&#34;&gt;Multi-platform support&lt;/h3&gt;
&lt;p&gt;Since we already have an &lt;a href=&#34;https://better.fyi/blockerList.txt&#34;&gt;EasyList version&lt;/a&gt; of our rules being automatically generated, I would love to see us create a version of Better that runs on other platforms via a fork of uBlock Origin.&lt;/p&gt;
&lt;p&gt;That should go a long way in helping to increase our reach and effectiveness.&lt;/p&gt;
&lt;p&gt;You can already use the Better blocking rules via uBlock Origin by including the EasyList version. You can also &lt;a href=&#34;https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/&#34;&gt;enable Better in Gnome Web&lt;/a&gt;. In fact, there is an open issue debating whether to &lt;a href=&#34;https://gitlab.gnome.org/GNOME/epiphany/issues/77&#34;&gt;make Better the default blocker in Gnome Web&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;possibly-moving-to-a-patronage-or-subscription-based-model-on-ios-and-macos&#34;&gt;Possibly moving to a patronage or subscription-based model on iOS and macOS&lt;/h3&gt;
&lt;p&gt;This is the hardest one to decide on. If we made Better Blocker free on iOS and macOS, I have no doubt that usage would skyrocket. But that would also mean greater strain on our servers, greater costs, and more support requests while simultaneously cutting off our revenue stream from sales.&lt;/p&gt;
&lt;p&gt;We could compensate by encouraging &lt;a href=&#34;https://ind.ie/fund/&#34;&gt;patronage&lt;/a&gt; from within the app using subscriptions. The gamble is whether or not this would replace the revenue we lose from sales and cover the increase in costs.&lt;/p&gt;
&lt;h2 id=&#34;heres-to-a-better-future&#34;&gt;Here’s to a better future&lt;/h2&gt;
&lt;p&gt;Regardless of what we end up doing, Laura and I are committed to developing and maintaining Better Blocker&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; alongside our ongoing work in helping create &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;a decentralised platform for the next web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We’d love to hear your thoughts and feedback &lt;a href=&#34;https://joinmastodon.org&#34;&gt;via the fediverse&lt;/a&gt;. You can reach &lt;a href=&#34;https://mastodon.ind.ie/@better&#34;&gt;Better&lt;/a&gt;, &lt;a href=&#34;https://mastodon.ind.ie/@indie&#34;&gt;Ind.ie&lt;/a&gt;, &lt;a href=&#34;https://mastodon.laurakalbag.com/@laura&#34;&gt;Laura&lt;/a&gt;, and &lt;a href=&#34;https://mastodon.ar.al/@aral&#34;&gt;yours truly&lt;/a&gt; via our Mastodon instances.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For as long as there is a need for it, that is. Better is just technological regulation. Its goal is to protect you from being abused by surveillance capitalists. There is nothing we would like more than to be able to retire Better Blocker because it is no longer needed as that would mean that we’ve been successful in transitioning the web from the sewer of surveillance capitalism that it is today to become the decentralised, free and open, and interoperable infrastructure we need to protect the personhood, rights, and freedoms of humankind and foster the sort of commons and public sphere that is a prerequisite for democracy in the digital/networked age. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Get Unicode-aware length of string in JavaScript</title>
      <link>https://ar.al/2018/08/26/get-unicode-aware-length-of-string-in-javascript/</link>
      <pubDate>Sun, 26 Aug 2018 21:10:59 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/26/get-unicode-aware-length-of-string-in-javascript/</guid>
      <description>&lt;p&gt;&lt;code&gt;stringInstance.length&lt;/code&gt; in JavaScript is not Unicode aware.&lt;/p&gt;
&lt;p&gt;That means, for example, that the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;🤓&amp;#34;&lt;/span&gt;.length
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Will return 2, not 1.&lt;/p&gt;
&lt;p&gt;If you want a Unicode-aware string length, use this function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;function&lt;/span&gt; unicodeLength(str) {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;return&lt;/span&gt; [...str].length
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Using that function, the following code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;unicodeLength(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;🤓&amp;#34;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Will return 1.&lt;/p&gt;
&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The function is based on the method by &lt;a href=&#34;https://stackoverflow.com/users/46395/daxim&#34;&gt;daxim&lt;/a&gt; in &lt;a href=&#34;https://stackoverflow.com/questions/51396490/getting-a-string-length-that-contains-unicode-character-exceeding-0xffff#comment89813733_51396686&#34;&gt;his response&lt;/a&gt; to the question &lt;a href=&#34;https://stackoverflow.com/questions/51396490/getting-a-string-length-that-contains-unicode-character-exceeding-0xffff#comment89813733_51396686&#34;&gt;Getting a string length that contains unicode character exceeding 0xffff&lt;/a&gt; on Stack Overflow.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Gnomit Flatpak bundle</title>
      <link>https://ar.al/2018/08/20/gnomit-flatpak-bundle/</link>
      <pubDate>Mon, 20 Aug 2018 16:06:10 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/20/gnomit-flatpak-bundle/</guid>
      <description>&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/20/gnomit-flatpak-bundle/gnomit-512.png&#34;
         alt=&#34;Gnomit logo: Three nodes that resemble the Git icon, in the same portland orange colour as the Git icon. The top node is much larger than the others and contains a filled vector of a pencil.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Gnomit icon&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs&#34;&gt;Gnomit&lt;/a&gt;, my simple Git commit message editor for Gnome,&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; is now available as a &lt;a href=&#34;https://www.flatpak.org/&#34;&gt;Flatpak&lt;/a&gt; bundle.&lt;/p&gt;
&lt;p&gt;To get started, see the &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs#installation&#34;&gt;installation instructions.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Please note that the Flatpak version is currently slower to launch than &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs/tree/bare&#34;&gt;running the GJS script directly&lt;/a&gt;. For more information, see &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs#known-issues&#34;&gt;known issues&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope to make the installation process simpler by submitting Gnomit to &lt;a href=&#34;https://flathub.org/home&#34;&gt;Flathub&lt;/a&gt; and I eventually want to host a Flatpak repository at &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you notice any issues, &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;please let me know&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Inspired by the excellent &lt;a href=&#34;https://github.com/zorgiepoo/Komet&#34;&gt;Komet app&lt;/a&gt; for macOS. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Gnomit: a simple Git commit message editor for Gnome</title>
      <link>https://ar.al/2018/08/15/introducing-gnomit-a-simple-git-commit-message-editor-for-gnome/</link>
      <pubDate>Wed, 15 Aug 2018 17:16:31 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/15/introducing-gnomit-a-simple-git-commit-message-editor-for-gnome/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/15/introducing-gnomit-a-simple-git-commit-message-editor-for-gnome/gnomit.png&#34;
         alt=&#34;Screenshot of Gnomit showing the overflow highlighting on the subject line and the automatically inserted empty line between the subject line and the rest of the commit message.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Gnomit helps me write better commit messages.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs&#34;&gt;Gnomit&lt;/a&gt; is a simple Git commit message editor for Gnome, inspired by the excellent &lt;a href=&#34;https://github.com/zorgiepoo/Komet&#34;&gt;Komet&lt;/a&gt; app by &lt;a href=&#34;https://zgcoder.net/&#34;&gt;Mayur Pawashe&lt;/a&gt; that I was using on macOS&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I started working on Gnomit this weekend and I’m currently happily using it as my default Git editor. It’s a simple first project and a way for me to get acquainted with &lt;a href=&#34;https://www.gtk.org/&#34;&gt;GTK+&lt;/a&gt;, &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gjs/wikis/Home&#34;&gt;GJS&lt;/a&gt;, and &lt;a href=&#34;https://wiki.gnome.org/Projects/Vala&#34;&gt;Vala&lt;/a&gt; – some of the core elements of &lt;a href=&#34;https://wiki.gnome.org/&#34;&gt;Gnome&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I do need to properly package it up and complete the Vala version. If you &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs&#34;&gt;try it out&lt;/a&gt;, please do &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;let me know&lt;/a&gt; if you have any trouble with the &lt;a href=&#34;https://wiki.gnome.org/Projects/gspell&#34;&gt;Gspell&lt;/a&gt; spell checker dependency. I had all sorts of issues with the import and I have a suspicion that you might also.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I hope to have a proper package (via &lt;a href=&#34;https://www.flatpak.org/&#34;&gt;Flatpak&lt;/a&gt;, perhaps) ready soon&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; but, if you want to play with it in the meanwhile and don’t mind getting your hands dirty with a possible Gspell dependency, the GJS version is entirely usable and &lt;a href=&#34;https://source.ind.ie/gnome/gnomit&#34;&gt;available on source.ind.ie&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Before &lt;a href=&#34;https://ar.al/2018/07/16/changes/&#34;&gt;I switched&lt;/a&gt; to &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I have a lot to learn on this new platform, especially concerning dependency management and packaging-related matters. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;16 Aug 2018: I now have &lt;a href=&#34;https://source.ind.ie/gnome/gnomit/gjs/tree/builder&#34;&gt;a working Flatpak version&lt;/a&gt; built using Gnome Builder. It launches considerably slower than the plain script, however, so I’m going to hold off merging it into master. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to install the Vala Reference Manual into Devhelp using the apt package manager</title>
      <link>https://ar.al/2018/08/13/how-to-add-the-vala-reference-manual-to-devhelp-using-the-apt-package-manager/</link>
      <pubDate>Mon, 13 Aug 2018 10:59:49 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/13/how-to-add-the-vala-reference-manual-to-devhelp-using-the-apt-package-manager/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/13/how-to-add-the-vala-reference-manual-to-devhelp-using-the-apt-package-manager/devhelp.jpg&#34;
         alt=&#34;Screenshot of the Devhelp documentation app showing the Vala Reference Manual’s Getting Started section. The description begins: “Vala is a programming language that aims to bring modern language features to GNOME developers without imposing any additional runtime requirements and without using a different ABI than applications and libraries written in C.”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Vala Refence Manual in Devhelp.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The &lt;a href=&#34;https://wiki.gnome.org/Projects/Vala/Documentation&#34;&gt;Vala Documentation&lt;/a&gt; states that the &lt;a href=&#34;http://www.vala-project.org/doc/vala/&#34;&gt;Vala Reference Manual&lt;/a&gt; is available in &lt;a href=&#34;https://www.vala-project.org/doc/vala/&#34;&gt;HTML&lt;/a&gt; and &lt;a href=&#34;https://www.vala-project.org/doc/manual.pdf&#34;&gt;PDF&lt;/a&gt; versions and “is also available for your installed version of Vala as &lt;a href=&#34;https://wiki.gnome.org/Apps/Devhelp&#34;&gt;DevHelp&lt;/a&gt; from your distribution, such as Fedora or Ubuntu.”&lt;/p&gt;
&lt;p&gt;Sadly, though, it doesn’t tell you the package names or link to the packages.&lt;/p&gt;
&lt;p&gt;For Debian’s apt-based systems like &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt; and Ubuntu, the package you’re looking for is called &lt;em&gt;vala-0.40-doc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To install the Vala Reference Manual into DevHelp, run the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install vala-0.40-doc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In case you’re reading this far into the future and there is a different version of Vala and a different version of the package, you can search for the latest version with the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;apt search &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;vala-[0-9]+\.[0-9]+-doc&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>New Philosopher: How power corrupts</title>
      <link>https://ar.al/2018/08/07/new-philosopher-how-power-corrupts/</link>
      <pubDate>Tue, 07 Aug 2018 10:24:10 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/07/new-philosopher-how-power-corrupts/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/07/new-philosopher-how-power-corrupts/cover.jpg&#34;
         alt=&#34;The cover of New Philosopher magazine. Red background with a flat illustration of a raised fist in white, shackled at the wrist with the chains of the shakle disappearing off page lower left. The headline is &amp;#39;how power corrupts&amp;#39;&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;New Philosopher: How power corrupts&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Reading the latest issue of New Philosopher magazine, theme: how power corrupts, I was reminded of this quote by Michel Foucault that I love and which beautifully explain how I feel about what I do also:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All my books… are – if you like – little toolboxes. If you want to open them, and use this or that sentence, this or that idea or analysis as a screwdriver or wrench to short-circuit, dismantle, or explode the systems of power… all right, all the better.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Off to Denmark for Smukfest</title>
      <link>https://ar.al/2018/08/07/off-to-denmark-for-smukfest/</link>
      <pubDate>Tue, 07 Aug 2018 05:14:34 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/07/off-to-denmark-for-smukfest/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/07/off-to-denmark-for-smukfest/in-car.jpg&#34;
         alt=&#34;Laura (blurred in the background) and me in the car.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Laura and me, in a car at silly o’clock.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;4am&#34;&gt;4AM&lt;/h3&gt;
&lt;p&gt;I’m in a car at silly o’clock, being driven to the airport by the ever-wonderful &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;6am&#34;&gt;6AM&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/07/off-to-denmark-for-smukfest/me.jpg&#34;
         alt=&#34;Photo of me on the plane looking sleepless.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Me, on a plane at also silly o&amp;rsquo;clock.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m on a plane at also silly o&amp;rsquo;clock, en route to Denmark via London &lt;a href=&#34;https://www.smukfest.dk/musik/kunstnere/verden-ifoelge-vaerten-overvaagningskapitalisme-er-vejen-til-helvede-brolagt-med-likes&#34;&gt;to speak at Smukfest tomorrow&lt;/a&gt; near &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Skanderborg&#34;&gt;Skanderborg&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;9am&#34;&gt;9AM&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/07/off-to-denmark-for-smukfest/mags-and-muffin.jpg&#34;
         alt=&#34;Table top with three magazines: Linux Magazine on top with bits of New Scientist and New Philosopher showing underneath. Partial: coffee cup, lemonade, and muffin.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Magazines and muffins FTW!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Landed at London Stansted airport and preparing to wait a couple of hours with three magazines (&lt;a href=&#34;https://www.linux-magazine.com/&#34;&gt;Linux Magazine&lt;/a&gt;, &lt;a href=&#34;https://www.newscientist.com/&#34;&gt;New Scientist&lt;/a&gt;, and &lt;a href=&#34;http://newphilosopher.com/&#34;&gt;New Philosopher&lt;/a&gt;)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, a lovely organic cappuccino, a lemon, lime, and ginger concoction of some persuasion, and an avocado and halloumi muffin.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;TFW you realise you just summarised yourself with a stack of magazines 🙄 &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Getting your iCloud contacts on GNU/Linux</title>
      <link>https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/</link>
      <pubDate>Sun, 05 Aug 2018 22:43:21 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/contacts.jpg&#34;
         alt=&#34;Screenshot of the Gnome Contacts app, showing a portion of my own contact card.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;I’m my own best contact.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Just like you can &lt;a href=&#34;../using-icloud-calendars-on-gnu-linux&#34;&gt;use iCloud calendars on GNU/Linux&lt;/a&gt;, you can also synchronise your contacts as iCloud uses an open standard called &lt;a href=&#34;https://en.wikipedia.org/wiki/CardDAV&#34;&gt;CardDAV&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;no-photos-please-were-gnomish&#34;&gt;No photos please, we’re Gnomish!&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/error.jpg&#34;
         alt=&#34;Screenshot of the error message you get when you try to create a contact with a photo on iCloud.&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;The caveat is that contacts with photos will be missing the photos. And you cannot create a contact with a photo either.&lt;/p&gt;
&lt;p&gt;I’ve opened two issues for this in the &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-contacts&#34;&gt;Gnome Contacts source code repository&lt;/a&gt;. If this is something you would like to see fixed, please show them some love:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-contacts/issues/100&#34;&gt;#100&lt;/a&gt;: Photos do not sync with iCloud via CardDAV&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-contacts/issues/101&#34;&gt;#101&lt;/a&gt;: Error when creating a contact that has a photo when using CardDAV with iCloud&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not having photos is not a show stopper but it does make the experience less than ideal. Needless to say, this is of course something that will be fixed in time. If anyone from Apple is reading this and would like to lend the Gnome Contacts team a friendly hand with this, that would also be very much appreciated.&lt;/p&gt;
&lt;h2 id=&#34;instructions&#34;&gt;Instructions&lt;/h2&gt;
&lt;p&gt;The instructions for setting up contacts are similar to those for &lt;a href=&#34;../using-icloud-calendars-on-gnu-linux&#34;&gt;setting up calendars&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To use your iCloud contacts under GNU/Linux&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;h3 id=&#34;a-install-the-required-software&#34;&gt;A. Install the required software.&lt;/h3&gt;
&lt;p&gt;Install the following apps if you don’t already have them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://wiki.gnome.org/Apps/Evolution/&#34;&gt;Evolution&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install evolution&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://wiki.gnome.org/Apps/Contacts&#34;&gt;Gnome Contacts&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install gnome-contacts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We will be using Evolution to set up the iCloud accounts but you will most likely want to use Gnome Calendar as your daily calendar as it offers a minimal, beautiful experience.&lt;/p&gt;
&lt;h3 id=&#34;b-set-up-an-app-specific-password-on-icloud&#34;&gt;B. Set up an app-specific password on iCloud.&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(If you’ve already set-up an app-specific password for your &lt;a href=&#34;../using-icloud-calendars-on-gnu-linux&#34;&gt;calendars&lt;/a&gt;, you can skip this step and use the same password.)&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Sign into your Apple account at &lt;a href=&#34;https://appleid.apple.com/&#34;&gt;https://appleid.apple.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/../using-icloud-calendars-on-gnu-linux/apple-1.jpg&#34;
         alt=&#34;The Apple ID sign-in page&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scroll down to the &lt;em&gt;App-Specific Passwords&lt;/em&gt; area in the &lt;em&gt;Security&lt;/em&gt; section and select the &lt;em&gt;Generate Password…&lt;/em&gt; link.&lt;/p&gt;
&lt;figure class=&#34;hairline-border&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/../using-icloud-calendars-on-gnu-linux/apple-2.jpg&#34;
         alt=&#34;Screenshot of the Generate Password… pop-over under the Security → App-specific passwords section with “CalDAV on notebook” entered in the textbox followed by Cancel and Create buttons.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting pop-over, enter a descriptive name for this password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the password onto the clipboard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;c-set-up-your-icloud-address-book-in-evolution&#34;&gt;C. Set up your iCloud address book in Evolution&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/contacts-section.png&#34;
         alt=&#34;Screenshot of the main navigation item with the contacts icon with the word Contacts next to it.&#34;/&gt; 
&lt;/figure&gt;
 Select the &lt;em&gt;Contacts&lt;/em&gt; section in the main navigation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the drop-down menu next to the &lt;em&gt;New&lt;/em&gt; button and select &lt;em&gt;Address Book&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/new-address-book.jpg&#34;
         alt=&#34;Screenshot of the drop-down menu next to the New button. The dropdown is marked up with a red circle.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting &lt;em&gt;New Address Book&lt;/em&gt; window, select &lt;em&gt;CardDAV&lt;/em&gt; from the &lt;em&gt;Type&lt;/em&gt; drop-down.&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/new-address-book-window.jpg&#34;
         alt=&#34;Screenshot of the New Address Book window. All of the settings shown are described in the instructions here.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;em&gt;URL&lt;/em&gt; field, enter &lt;em&gt;&lt;a href=&#34;https://contacts.icloud.com&#34;&gt;https://contacts.icloud.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;em&gt;User&lt;/em&gt; field, enter your Apple ID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press the &lt;em&gt;Find Address Books&lt;/em&gt; button.&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/getting-your-icloud-contacts-on-gnu-linux/choose-an-address-book.jpg&#34;
         alt=&#34;Screenshot of the Choose an Address Book window. There is only one address book shown in the table and it is selected: Name: card, Supports: Contacts. At the bottom of the dialog are two buttons: Cancel and OK.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting password entry pop-up, paste the app-specific password you copied onto the clipboard in the last section.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting &lt;em&gt;Choose an Address Book&lt;/em&gt; window, select the address book shown and press the &lt;em&gt;OK&lt;/em&gt; button. When I did this, there was only one entry and it was called &lt;em&gt;card&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Back in the &lt;em&gt;New Address Book&lt;/em&gt; window, check the options you want to set for your address book and press the &lt;em&gt;OK&lt;/em&gt; button. I checked all of the options except for &lt;em&gt;Avoid IfMatch&lt;/em&gt; – mostly because I don’t know what an IfMatch is and nor would I recognise one if were to stumble upon it in a dark alley.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;this-_should_-be-easier&#34;&gt;This &lt;em&gt;should&lt;/em&gt; be easier…&lt;/h3&gt;
&lt;p&gt;Once you’ve set up your address book, launch Gnome Contacts and you should see your contacts show up, sans any photos you may have had for them.&lt;/p&gt;
&lt;p&gt;Any entries you make in Gnome Contacts will sync to iCloud and, from there, to all of your Apple toys, and vice-versa. Just remember not to include photos in your contacts or things will get borked.&lt;/p&gt;
&lt;p&gt;Ideally, CardDAV setup should be a seamless process that’s built into Gnome Contacts&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://support.blackberry.com/kb/articleDetail?ArticleNumber=000033812&#34;&gt;How to integrate iCloud contact, calendar, or email accounts on the BlackBerry 10 smartphone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The examples here are tested to work on &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt; 18.04 running Gnome 3.28.2. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I opened an issue for this at the &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-contacts&#34;&gt;Gnome Contacts source code repository&lt;/a&gt;: &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-contacts/issues/102&#34;&gt;Feature request: integrate iCloud CardDAV setup&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Using iCloud calendars on GNU/Linux</title>
      <link>https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/</link>
      <pubDate>Sun, 05 Aug 2018 19:25:22 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/gnome-calendar.png&#34;
         alt=&#34;Screenshot of Gnome Calendar showing my calendar entries from my iCloud calendars. I’m travelling to Denmark on Tuesday morning at 6:25AM with Ryanair and staying there until Thursday to speak at a festival called Smukfest. Laura is in Menorca Wednesday to Saturday. Meanwhile, Barry is housesitting and looking after Osky.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My week, courtesy of iCloud.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/ICloud&#34;&gt;iCloud&lt;/a&gt; isn’t just for your Apple toys.&lt;/p&gt;
&lt;p&gt;Since iCloud uses an open standard called &lt;a href=&#34;https://en.wikipedia.org/wiki/CalDAV&#34;&gt;CalDAV&lt;/a&gt;, you can synchronise your calendars to your other devices on other operating systems like GNU/Linux.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;To use your iCloud calendars under GNU/Linux&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;h3 id=&#34;a-install-the-required-software&#34;&gt;A. Install the required software.&lt;/h3&gt;
&lt;p&gt;Install the following apps if you don’t already have them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://wiki.gnome.org/Apps/Evolution/&#34;&gt;Evolution&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install evolution&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://wiki.gnome.org/Apps/Calendar&#34;&gt;Gnome Calendar&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install gnome-contacts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We will be using Evolution to set up the iCloud accounts but you will most likely want to use Gnome Calendar as your daily calendar as it offers a minimal, beautiful experience.&lt;/p&gt;
&lt;h3 id=&#34;b-set-up-an-app-specific-password-on-icloud&#34;&gt;B. Set up an app-specific password on iCloud.&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Sign into your Apple account at &lt;a href=&#34;https://appleid.apple.com/&#34;&gt;https://appleid.apple.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/apple-1.jpg&#34;
         alt=&#34;The Apple ID sign-in page&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scroll down to the &lt;em&gt;App-Specific Passwords&lt;/em&gt; area in the &lt;em&gt;Security&lt;/em&gt; section and select the &lt;em&gt;Generate Password…&lt;/em&gt; link.&lt;/p&gt;
&lt;figure class=&#34;hairline-border&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/apple-2.jpg&#34;
         alt=&#34;Screenshot of the Generate Password… pop-over under the Security → App-specific passwords section with “CalDAV on notebook” entered in the textbox followed by Cancel and Create buttons.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting pop-over, enter a descriptive name for this password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the password onto the clipboard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;c-set-up-your-calendars-in-evolution&#34;&gt;C. Set up your calendar(s) in Evolution.&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/calendar.png&#34;
         alt=&#34;Screenshot of the main navigation with the calender icon with the word Calendar next to it.&#34;/&gt; 
&lt;/figure&gt;
 Select the &lt;em&gt;Calendar&lt;/em&gt; section in the main navigation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the drop-down menu next to the &lt;em&gt;New&lt;/em&gt; button and select &lt;em&gt;Calendar&lt;/em&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/new-calendar-button.jpg&#34;
         alt=&#34;Screenshot of the drop-down menu next to the New button. The dropdown is marked up with a red circle.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting &lt;em&gt;New Calendar&lt;/em&gt; window, select &lt;em&gt;CalDAV&lt;/em&gt; from the &lt;em&gt;Type&lt;/em&gt; drop-down.&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/new-calendar.jpg&#34;
         alt=&#34;Screenshot of the New Calendar window. All of the settings shown are described in the instructions here.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;em&gt;URL&lt;/em&gt; field, enter &lt;em&gt;&lt;a href=&#34;https://caldav.icloud.com&#34;&gt;https://caldav.icloud.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;em&gt;User&lt;/em&gt; field, enter your Apple ID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press the &lt;em&gt;Find Calendars&lt;/em&gt; button.&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/choose-a-calendar.jpg&#34;
         alt=&#34;Screenshot of the Choose a Calendar window showing my various calendars. They are presented in a two-column table with headings that read Name and Supports. The names of the calendars shown are Jo and Aral (partial), Old events, Calendar, Home, Aral Work, Laura and Aral, Ind.ie Team, Laura’s Events, Laura’s Work, Holidays, CalChat, Laura Gym. All the entries in the Supports column read Events. Underneath the table is a field titled User mail. The value is one of my email addresses (aral@aralbalkan.com). At the bottom of the dialog are two buttons: Cancel and OK.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting password entry pop-up, paste the app-specific password you copied onto the clipboard in the last section.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the resulting &lt;em&gt;Choose a Calendar&lt;/em&gt; window, select the calendar you want to set up&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Back in the &lt;em&gt;New Calendar&lt;/em&gt; window, choose a colour to match the one you use on iCloud.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set your options: I select &lt;em&gt;Copy calendar contents locally for offline operation&lt;/em&gt;, as I want to be able to access the calendar even if I don’t have an Internet connection, and &lt;em&gt;Server handles meeting invitations&lt;/em&gt;.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you want this to be your default calendar, also check &lt;em&gt;Mark as default calendar&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the &lt;em&gt;Refresh every&lt;/em&gt; setting&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; to decide how frequently your calendars should synchronise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press the &lt;em&gt;OK&lt;/em&gt; button to create the calendar when you’re happy with your choices.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;this-_should_-be-easier&#34;&gt;This &lt;em&gt;should&lt;/em&gt; be easier…&lt;/h3&gt;
&lt;p&gt;That’s it! If all goes well, you should see your calendar entries begin to pop up in Evolution. If you want to set up additional calendars, rinse and repeat the instructions in this section.&lt;/p&gt;
&lt;p&gt;Once you’ve set up your accounts, fire up Gnome Calendar and enjoy your synchronised calendars in a beautifully minimal interface.&lt;/p&gt;
&lt;p&gt;Any entries you make in Gnome Calendar will sync to iCloud and, from there, to all of your Apple toys, and vice-versa.&lt;/p&gt;
&lt;p&gt;Ideally, this should be a seamless process that’s built into Gnome Calendar&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&#34;issues&#34;&gt;Issues&lt;/h3&gt;
&lt;p&gt;As I discover issues, I will document them here.&lt;/p&gt;
&lt;h4 id=&#34;you-cannot-move-an-event-between-calendars&#34;&gt;You cannot move an event between calendars&lt;/h4&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/05/using-icloud-calendars-on-gnu-linux/error-move-event-to-different-calendar.png&#34;
         alt=&#34;Error dialogue: Calendar authentication request. Failed to put data: HTTP error code 403 (Forbidden): Found component … with same UID in a different collection.&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-calendar/issues/304&#34;&gt;Issue #304&lt;/a&gt;: Moving an event from one iCloud CalDAV calendar to another, results in a 403 (Forbidden) error. The exact error is “Failed to put data: HTTP error code 403 (Forbidden): Found component … with same UID in a different collection.”&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;http://support.blackberry.com/kb/articleDetail?ArticleNumber=000033812&#34;&gt;How to integrate iCloud contact, calendar, or email accounts on the BlackBerry 10 smartphone&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://askubuntu.com/a/1008701&#34;&gt;CalDAV: how to sync iCloud calendar? (2018 update)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;That said, the process is not well-documented at Apple or elsewhere. There is a clear need for better documentation as well as a seamless process for using CalDAV-based calendars in GNU/Linux. This post aims to contribute to the former. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The examples here are tested to work on &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt; 18.04 running Gnome 3.28.2. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I would have taken a screenshot of the menu itself but the system-wide screenshot hot-key stops responding in Evolution when any drop-down is open. The same thing happens on the &lt;em&gt;New Calendar&lt;/em&gt; window with the &lt;em&gt;Type&lt;/em&gt; field. I’ve noticed similar issues in modal states in some other apps also. Not sure if this is a bug with the apps, with Gnome, or even Xorg. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You will have to set up your calendars one by one. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Because iCloud reportedly &lt;a href=&#34;https://stackoverflow.com/a/27200424&#34;&gt;supports scheduling as described in RFC 6638&lt;/a&gt;. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I have mine set to every ten minutes at the moment but I might tweak that as – no pun intended – ­time goes by. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I opened an issue for this on the &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-calendar/&#34;&gt;Gnome Calendar source code repository&lt;/a&gt;: &lt;a href=&#34;https://gitlab.gnome.org/GNOME/gnome-calendar/issues/303&#34;&gt;Add support for iCloud CalDAV&lt;/a&gt;. &lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Multi-writer Dat could power the next Web</title>
      <link>https://ar.al/2018/08/04/multiwriter-dat-could-power-the-next-web/</link>
      <pubDate>Sat, 04 Aug 2018 23:18:16 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/04/multiwriter-dat-could-power-the-next-web/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/717451773.jpg?mw=2880&amp;mh=1620&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/283218705.hd.mp4?s=c93938a844ce574d8a27a7796bedb206a4f08f2a&amp;profile_id=174&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;A demonstration of multi-writer Dat from the sample app by Jim Pick.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Dat is an exciting new technology that enables you to synchronise data privately and in a peer-to-peer fashion. It uses the same underlying concepts as blockchain&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, sans global consensus&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, and it’s run by the not-for-profit &lt;a href=&#34;https://codeforscience.org/&#34;&gt;Code for Science &amp;amp; Society&lt;/a&gt;. The community has top-notch people like &lt;a href=&#34;https://github.com/mafintosh&#34;&gt;Mathias Buus&lt;/a&gt;, &lt;a href=&#34;https://taravancil.com/&#34;&gt;Tara Vancil&lt;/a&gt;, &lt;a href=&#34;https://okdistribute.xyz/&#34;&gt;Karissa McKelvey&lt;/a&gt;, and &lt;a href=&#34;https://jimpick.com/&#34;&gt;Jim Pick&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dat is currently implemented in Node.js&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; and made up of numerous smaller components including &lt;a href=&#34;https://github.com/mafintosh/hyperdb&#34;&gt;HyperDB&lt;/a&gt;, a distributed scalable database, and &lt;a href=&#34;https://github.com/mafintosh/hyperdrive&#34;&gt;Hyperdrive&lt;/a&gt;. The &lt;a href=&#34;https://github.com/datproject/dat&#34;&gt;Dat commandline app&lt;/a&gt; and &lt;a href=&#34;https://github.com/dat-land/dat-desktop&#34;&gt;Dat desktop app&lt;/a&gt; both rely on &lt;a href=&#34;https://github.com/datproject/dat-node&#34;&gt;Dat Node&lt;/a&gt;, which in turn uses Hyperdrive.&lt;/p&gt;
&lt;p&gt;There is also &lt;a href=&#34;https://beakerbrowser.com/&#34;&gt;Beaker Browser&lt;/a&gt;, which is a peer Web browser you can use to both view and fork/edit peer Web sites like mine. (To view this site on the peer Web, open the following URL in Beaker Browser: &lt;a href=&#34;dat://ar.al&#34;&gt;dat://ar.al&lt;/a&gt;.)&lt;/p&gt;
&lt;h3 id=&#34;multi-writer-dat&#34;&gt;Multi-writer Dat&lt;/h3&gt;
&lt;p&gt;Dat is currently single-writer, meaning that although anyone with the archive’s key&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; can read from the archive, only the original author can write to it. However, work is already underway to implement &lt;em&gt;multi-writer support&lt;/em&gt; in Dat. In fact, it’s already in HyperDB and in the &lt;a href=&#34;https://github.com/mafintosh/hyperdrive/tree/hyperdb-backend&#34;&gt;HyperDB back-end branch of Hyperdrive&lt;/a&gt;. And that is what Jim Pick used to create the &lt;a href=&#34;https://dat-shopping-list.glitch.me/&#34;&gt;Collaborative Dat Shopping List&lt;/a&gt; application that I demonstrate in the video, above.&lt;/p&gt;
&lt;p&gt;Multi-writer Dat is hugely exciting. It opens up a plethora of potential use cases – anything from a peer-to-peer iCloud to peer-to-peer secure messaging. We’re witnessing the birth of the foundations of the next Web; the peer Web. We will see this implemented at the operating system level of future free-and-open mobile and desktop devices.&lt;/p&gt;
&lt;p&gt;I cannot stress enough how groundbreaking Dat is or how important it is to creating a free and open, decentralised, and interoperable world. It’s rare to encounter a technology that you feel could have this potential and rarer still when the people behind it are doing it for entirely the right reasons. I say rare, but what I really mean is unprecedented.&lt;/p&gt;
&lt;p&gt;You can find out more about Jim’s Dat Shopping list by reading &lt;a href=&#34;https://blog.datproject.org/2018/05/14/dat-shopping-list/&#34;&gt;his blog post&lt;/a&gt; and learn more about Dat (and try it out) on the &lt;a href=&#34;https://datproject.org&#34;&gt;Dat homepage&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.datprotocol.com/&#34;&gt;The protocol&lt;/a&gt; is based on Directed Acyclic Graphs/Merkle trees, public-key cryptography, and a mechanism for sparse replication. You can read up about it in depth in the &lt;a href=&#34;https://github.com/datproject/docs/blob/master/papers/dat-paper.md&#34;&gt;DAT whitepaper&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;So there’s no proof of work and it’s not meant for creating cryptocurrencies because it is not a right-libertarian get rich quick scheme. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Although there are efforts underway to implement it on other platforms, like &lt;a href=&#34;https://github.com/datrs&#34;&gt;Rust&lt;/a&gt;. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;More precisely, the archive’s public key, which – confusingly – should &lt;em&gt;not&lt;/em&gt; be made public as it is used to encrypt the archive. While this makes perfect sense when you understand &lt;a href=&#34;https://blog.datproject.org/2017/09/21/dat-cryptography/&#34;&gt;how Dat’s cryptography works&lt;/a&gt;, it can be confusing to explain and that’s why I’ve started referring to the public key as just “the key” as that feels safer even if it’s less accurate technically. Revealing the public key gives anyone who has it read-only access to the archive addressed by that key. For this reason, when Dat does discovery, it doesn’t broadcast the public key. It broadcasts a cryptographically-secure hash of the key instead. In a nutshell, anyone who has your private key can &lt;em&gt;write&lt;/em&gt; to your Dat archive and anyone who has your public key can &lt;em&gt;read&lt;/em&gt; it. To ensure the privacy of your archives, you should share neither. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>How to enable the Browse Files setting in GSConnect</title>
      <link>https://ar.al/2018/08/03/how-to-enable-the-browse-files-setting-in-gsconnect/</link>
      <pubDate>Fri, 03 Aug 2018 15:47:01 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/03/how-to-enable-the-browse-files-setting-in-gsconnect/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/03/how-to-enable-the-browse-files-setting-in-gsconnect/browse-files-error.jpg&#34;
         alt=&#34;The GSConnect settings panel showing the error condition for the Browse Files setting that is described in this post.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Browse Files? Not so fast…&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://ar.al/2018/08/02/crafting-a-continuous-client-desktop-mobile-experience-on-linux-with-gsconnect/&#34;&gt;GSConnect&lt;/a&gt; is a beautiful shell extension for &lt;a href=&#34;https://wiki.gnome.org/Projects/GnomeShell&#34;&gt;Gnome Shell&lt;/a&gt; that integrates mobile devices that can run &lt;a href=&#34;https://community.kde.org/KDEConnect&#34;&gt;KDE Connect&lt;/a&gt; (like phones and tablets that run &lt;a href=&#34;https://lineageos.org&#34;&gt;LineageOS&lt;/a&gt;) with desktop computers that run Gnome on GNU/Linux (like my notebook running &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;One of the features that you might have trouble with is Browse Files as &lt;a href=&#34;https://github.com/andyholmes/gnome-shell-extension-gsconnect/issues/162&#34;&gt;it is broken by default&lt;/a&gt;. It fails unless you have a separate app installed.&lt;/p&gt;
&lt;p&gt;When you flip the relevant switch in the Mobile Settings for your device to turn it on, you are greeted with an exclamation mark icon and the setting stays off. So you have a good idea that something went wrong but you don’t know what, exactly&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. By turning on the debugging option&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, I was able to see the actual error messages in one of the developer consoles and realised that it had to do with a missing dependency; a package called &lt;a href=&#34;https://github.com/libfuse/sshfs&#34;&gt;SSHFS&lt;/a&gt; that mounts a remote filesystem locally using SFTP.&lt;/p&gt;
&lt;p&gt;To fix this error, you have to install SSHFS separately:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install sshfs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once that’s installed, you can successfully flip the switch and start browsing the file system of your phone on your desktop.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/03/how-to-enable-the-browse-files-setting-in-gsconnect/browse-files-context-menu.jpg&#34;
         alt=&#34;My computer desktop, showing the Gnome system menu with the Browse Files options expanded for my mobile phone. The relevant options are All Files, Camera Pictures, and Unmount.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Press the third button from left (highlighted) to see the Browse Files options.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Needless to say, and quite strikingly so given how polished the rest of the extension is, this is not a great experience.&lt;/p&gt;
&lt;p&gt;As I mentioned in my &lt;a href=&#34;https://github.com/andyholmes/gnome-shell-extension-gsconnect/issues/162&#34;&gt;bug report&lt;/a&gt;, GSConnect should ideally seamlessly install and use SSHFS or, if there is some reason for which that is impossible, detect whether it is installed, disable the setting for turning the Browse Files on if the dependency is missing, and provide instructions&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; for how to install it.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/03/how-to-enable-the-browse-files-setting-in-gsconnect/browse-files.jpg&#34;
         alt=&#34;Thumbnails of the photos on my phone being displayed in Gnome Files on my desktop. The location bar reads (a hash code)/storage/emulated/0/DCIM/Camera.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Photos from my phone on my desktop, courtesy of GSConnect.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While developers may understand that many apps in Linux make use of specialised commandline packages behind the scenes&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, that’s an implementation detail as far as the people using these tools are concerned.&lt;/p&gt;
&lt;p&gt;If a feature in an app doesn’t work, it’s because the app is broken, not because I haven’t installed a dependency. Installing an app should install everything that the app needs to work. No ifs, no buts&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;This is not the first time I’ve encountered apps that are broken by default &lt;a href=&#34;https://ar.al/2018/07/16/changes/&#34;&gt;since switching my main development machine&lt;/a&gt; &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;to GNU/Linux&lt;/a&gt;. A principle such as the following should be included in the &lt;a href=&#34;https://developer.gnome.org/hig/stable/design-principles.html.en&#34;&gt;design principles&lt;/a&gt; section of the &lt;a href=&#34;https://developer.gnome.org/hig/stable/&#34;&gt;Gnome Human Interface Guidelines&lt;/a&gt; to avoid this antipattern:&lt;/p&gt;
&lt;h3 id=&#34;your-application-should-work-by-default&#34;&gt;Your application should work by default&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Installing an application should install all dependencies necessary for running the application. If this is not possible (for example, for legal reasons), then the application should detect missing dependencies and both inform and guide people on how to install them. It should not be possible to access or attempt to use features that depend on missing dependencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you hover over the icon you do get a tooltip with the error message informing you that SSHFS has not been installed but, of course, that is a hidden gesture and we cannot expect people to intuit it. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;em&gt;Mobile Devices&lt;/em&gt; → (My device name) → &lt;em&gt;Mobile Settings&lt;/em&gt; → &lt;em&gt;About&lt;/em&gt; → &lt;em&gt;Debug Mode&lt;/em&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or a link to instructions. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Unix_philosophy&#34;&gt;Unix philosophy&lt;/a&gt; &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And if it cannot for reasons beyond its control (e.g., for legal reasons), it should be very humble and ashamed about this and do whatever it can to make the person aware of this limitation, apologise for it (even if it’s not its fault) and help the person to fix the problem that it created for them (even if it was for reasons outside of its control). &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Crafting a continuous-client desktop/mobile experience on Linux with GSConnect</title>
      <link>https://ar.al/2018/08/02/crafting-a-continuous-client-desktop-mobile-experience-on-linux-with-gsconnect/</link>
      <pubDate>Thu, 02 Aug 2018 23:57:29 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/08/02/crafting-a-continuous-client-desktop-mobile-experience-on-linux-with-gsconnect/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/02/crafting-a-continuous-client-desktop-mobile-experience-on-linux-with-gsconnect/gsconnect.jpg&#34;
         alt=&#34;Screenshot of my desktop screen showing the GSConnect app’s settings on the left and the system menu open with details of my mobile phone showing under mobile devices.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A continuous free and open experience between desktop and mobile.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A cornerstone of Apple’s approach to seamless design is reflected in a feature they call &lt;a href=&#34;https://www.apple.com/macos/continuity/&#34;&gt;Continuity&lt;/a&gt;. Continuity aims to provide a “seamless experience” between your various devices. This is essentially what Joshua Topolsky called the &lt;a href=&#34;https://www.engadget.com/2010/05/26/a-modest-proposal-the-continuous-client/&#34;&gt;continuous client&lt;/a&gt; back in 2010 and a concept that I wrote at length about in my chapter titled &lt;a href=&#34;https://www.smashingmagazine.com/2012/06/mobile-considerations-in-user-experience-design-web-or-native/#fn11&#34;&gt;Mobile Considerations In User Experience Design: “Web or Native?”&lt;/a&gt; in Smashing Magazine’s Redesign The Web book back in 2012.&lt;/p&gt;
&lt;p&gt;Well, tonight, I discovered that some amazing folks in the free and open source community have been busy implementing similar functionality that works on my desktop running &lt;a href=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/&#34;&gt;Pop!_OS&lt;/a&gt; and my phone running &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt;. Those folks are the team behind &lt;a href=&#34;https://community.kde.org/KDEConnect&#34;&gt;KDEConnect&lt;/a&gt; and &lt;a href=&#34;https://github.com/andyholmes&#34;&gt;Andy Holmes&lt;/a&gt;, who implemented it for Gnome Shell 3.24+ in &lt;a href=&#34;https://extensions.gnome.org/extension/1319/gsconnect/&#34;&gt;a Gnome Extension&lt;/a&gt; called &lt;a href=&#34;https://github.com/andyholmes/gnome-shell-extension-gsconnect&#34;&gt;GSConnect&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With GSConnect installed, my desktop and phone become best buddies and can share all sorts of things, including notifications and files. You can also send SMS messages (although you should really be using end-to-end encrypted messaging via &lt;a href=&#34;https://wire.com&#34;&gt;Wire&lt;/a&gt; or &lt;a href=&#34;https://signal.org&#34;&gt;Signal&lt;/a&gt; instead) and locate your phone if you’ve misplaced it.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/08/02/crafting-a-continuous-client-desktop-mobile-experience-on-linux-with-gsconnect/kde-connect.jpg&#34;
         alt=&#34;Screenshot of KDE Connect on my Samsung S9&amp;#43; showing the name of my desktop Aral-XPS13 as well as a hamburger menu and a second hamburger menu with dots (let&amp;#39;s not go there). There is a list underneath with the following options: Send files, multimedia control, remote input, run command. The menu bar is orange.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;KDE Connect on mobile.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;On the phone, you will need the KDE Connect app which you can &lt;a href=&#34;https://f-droid.org/packages/org.kde.kdeconnect_tp/&#34;&gt;get from the F-Droid catalogue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s quite amazing how well it works. And by that I mean that I’ve found sending files between my devices easier to use and more reliable than AirDrop. And that’s saying something given that AirDrop was created by a multibillion-dollar corporation that touts design as its unique selling point.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to install DAT on mobile under Termux</title>
      <link>https://ar.al/2018/07/31/how-to-install-dat-on-mobile-under-termux/</link>
      <pubDate>Tue, 31 Jul 2018 17:21:29 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/31/how-to-install-dat-on-mobile-under-termux/</guid>
      <description>&lt;p&gt;The instructions below document how to install DAT&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; under &lt;a href=&#34;https://termux.com/&#34;&gt;Termux&lt;/a&gt; and have been tested on &lt;a href=&#34;https://lineageos.org&#34;&gt;LineageOS&lt;/a&gt; 15.1 running on an S9+&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install dependencies&lt;/strong&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;pkg install libtool autoconf automake python2 nodejs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Patch node-gyp&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is &lt;a href=&#34;https://github.com/termux/termux-packages/issues/307#issuecomment-244601906&#34;&gt;a bug in node-gyp&lt;/a&gt; that prevents the installation of node projects that use native libraries. To fix it, you need to apply the changes shown in the following patch to the &lt;a href=&#34;https://github.com/nodejs/node/blob/master/common.gypi&#34;&gt;common.gypi file&lt;/a&gt; in your Node installation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-patch&#34; data-lang=&#34;patch&#34;&gt;&lt;span style=&#34;color:#a00000&#34;&gt;--- a/common.gypi
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+++ b/common.gypi
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#800080;font-weight:bold&#34;&gt;@@ -90,8 +90,8 @@
&lt;/span&gt;&lt;span style=&#34;color:#800080;font-weight:bold&#34;&gt;&lt;/span&gt;             &amp;#39;ldflags&amp;#39;: [ &amp;#39;-Wl,-bbigtoc&amp;#39; ],
           }],
           [&amp;#39;OS == &amp;#34;android&amp;#34;&amp;#39;, {
&lt;span style=&#34;color:#a00000&#34;&gt;-            &amp;#39;cflags&amp;#39;: [ &amp;#39;-fPIE&amp;#39; ],
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;-            &amp;#39;ldflags&amp;#39;: [ &amp;#39;-fPIE&amp;#39;, &amp;#39;-pie&amp;#39; ]
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+            &amp;#39;cflags&amp;#39;: [ &amp;#39;-fPIC&amp;#39; ],
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+            &amp;#39;ldflags&amp;#39;: [ &amp;#39;-fPIC&amp;#39; ]
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;&lt;/span&gt;           }],
           [&amp;#39;node_shared==&amp;#34;true&amp;#34;&amp;#39;, {
             &amp;#39;msvs_settings&amp;#39;: {
&lt;span style=&#34;color:#800080;font-weight:bold&#34;&gt;@@ -144,8 +144,8 @@
&lt;/span&gt;&lt;span style=&#34;color:#800080;font-weight:bold&#34;&gt;&lt;/span&gt;             &amp;#39;cflags&amp;#39;: [ &amp;#39;-fno-omit-frame-pointer&amp;#39; ],
           }],
           [&amp;#39;OS == &amp;#34;android&amp;#34;&amp;#39;, {
&lt;span style=&#34;color:#a00000&#34;&gt;-            &amp;#39;cflags&amp;#39;: [ &amp;#39;-fPIE&amp;#39; ],
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;-            &amp;#39;ldflags&amp;#39;: [ &amp;#39;-fPIE&amp;#39;, &amp;#39;-pie&amp;#39; ]
&lt;/span&gt;&lt;span style=&#34;color:#a00000&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+            &amp;#39;cflags&amp;#39;: [ &amp;#39;-fPIC&amp;#39; ],
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;+            &amp;#39;ldflags&amp;#39;: [ &amp;#39;-fPIC&amp;#39; ]
&lt;/span&gt;&lt;span style=&#34;color:#00a000&#34;&gt;&lt;/span&gt;           }],
           [&amp;#39;node_shared==&amp;#34;true&amp;#34;&amp;#39;, {
             &amp;#39;msvs_settings&amp;#39;: {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can either do this manually by editing the file by hand, or, if you’re on the same version as me (Node 8.11.3), you can use the following command to automatically apply &lt;a href=&#34;android.patch&#34;&gt;the patch&lt;/a&gt; I generated:&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;curl https://ar.al/2018/07/31/how-to-install-dat-on-mobile-under-termux/android.patch -o android.patch &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; patch ~/.node-gyp/8.11.3/include/node/common.gypi android.patch&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install DAT&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that your environment is ready, you should be able to install DAT in the regular way:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;npm install -g dat&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.akehir.com/2017/05/building-node-sass-libsass-python.html&#34;&gt;Building Node-Sass || LibSass-Python Natively on Android 6 &amp;amp; 7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;http://datproject.org/&#34;&gt;DAT&lt;/a&gt; is one of the technologies currently vying to be the fundamental protocol of the peer-to-peer Web. In my opinion, it is also the most promising. It’s what powers the peer web elements of &lt;a href=&#34;https://ar.al/2018/06/15/hello-peer-to-peer-web/&#34;&gt;this web site&lt;/a&gt;, it’s a core component of &lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;Web+&lt;/a&gt;, and it’s what we’re basing our current and future work on at &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m currently running Termux on two devices: on my Nexus 5 under LineageOS 14.1 and on my S9+ under LineageOS 15.1. The instructions in this post have been tested and work on the S9+, which is a 64-bit device. I was able to get DAT to compile &lt;a href=&#34;https://github.com/datproject/dat/issues/1007&#34;&gt;but not run&lt;/a&gt; on the Nexus 5. Whether that was due to its 32-bit architecture or due to LineageOS 14.1, I cannot say for sure at the moment. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You might want to do a &lt;code&gt;pkg upgrade&lt;/code&gt; beforehand to make sure you have the latest and greatest of your currently-installed packages. If you already have the required dependencies this command should not hurt your setup, although it may update them to the latest versions. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;android.patch (809 bytes, MD5: &lt;code&gt;2f0b9b25e4fa12f9d9db16e2f6616f7c&lt;/code&gt;). To verify, download the patch and compare the result you get from running &lt;code&gt;md5sum android.patch&lt;/code&gt; in Terminal with the MD5 hash presented here. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Workaround for npm install error on Lineageos 15.1</title>
      <link>https://ar.al/2018/07/30/workaround-for-npm-install-error-on-lineageos-15.1/</link>
      <pubDate>Mon, 30 Jul 2018 21:46:04 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/30/workaround-for-npm-install-error-on-lineageos-15.1/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/30/workaround-for-npm-install-error-on-lineageos-15.1/the-fix.png&#34;
         alt=&#34;Screenshot of Emacs showing the fix outlined in this post being applied.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The fix.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt; fails in Termux on LineageOS 15.1&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; with the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;NPM ERR! Cannot &lt;span style=&#34;color:#007020&#34;&gt;read&lt;/span&gt; property &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;length&amp;#39;&lt;/span&gt; of undefined&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/npm/npm/issues/19265&#34;&gt;The issue&lt;/a&gt; originates from &lt;a href=&#34;https://github.com/nodejs/node/issues/19022&#34;&gt;a bug in node&lt;/a&gt;. The affected module has implemented &lt;a href=&#34;https://github.com/rvagg/node-worker-farm/commit/0b2349c6c7ed5c51e234e418fad226875313e773&#34;&gt;a workaround&lt;/a&gt; that you can manually apply to your npm installation to avoid the issue until the upstreams fix it properly.&lt;/p&gt;
&lt;p&gt;To implement the workaround:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the file you need to patch in your editor&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$EDITOR&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;$PREFIX&lt;/span&gt;/lib/node_modules/npm/node_modules/worker-farm/lib/farm.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update line 5:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;From:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;, maxConcurrentWorkers &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;os&amp;#39;&lt;/span&gt;).cpus().length
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;To:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;, maxConcurrentWorkers &lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; (require(&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;os&amp;#39;&lt;/span&gt;).cpus() &lt;span style=&#34;color:#666&#34;&gt;||&lt;/span&gt; { length&lt;span style=&#34;color:#666&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; }).length
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replace the &lt;code&gt;1&lt;/code&gt; in the code snippet above with the number of cores that your phone has and save the file. (For my Samsung S9+, I used 8 as it has an octa-core processor.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That should fix the problem and you should be able to use npm again.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I did not encounter this error on my Nexus 5 with LineageOS 14.1. The phone exhibiting the error is running LineageOS 15.1, Termux version 0.64, Node.js version 8.11.3 and npm version 5.6.0. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you get a &lt;em&gt;command not found&lt;/em&gt; error at this step, it&amp;rsquo;s most likely because you haven&amp;rsquo;t specified a default editor to use in your shell configuration. To fix that and use a simple editor called nano as your default, execute the following command: &lt;code&gt;echo &amp;quot;export EDITOR=&#39;nano&#39;&amp;quot; &amp;gt;&amp;gt; ~/.bashrc &amp;amp;&amp;amp; source ~/.bashrc&lt;/code&gt;. Replace &lt;code&gt;.bashrc&lt;/code&gt; with &lt;code&gt;.zshrc&lt;/code&gt; if you&amp;rsquo;re using zsh instead of bash as your shell. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Web development on a phone with Hugo and Termux</title>
      <link>https://ar.al/2018/07/30/web-development-on-a-phone-with-hugo-and-termux/</link>
      <pubDate>Mon, 30 Jul 2018 20:44:12 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/30/web-development-on-a-phone-with-hugo-and-termux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/30/web-development-on-a-phone-with-hugo-and-termux/s9.jpg&#34;
         alt=&#34;My S9&amp;#43; phone on a wooden table with part of my Microsoft foldable bluetooth keyboard showing at the bottom of the photo. The S9&amp;#43; is running Emacs with this blog post on the left side of the screen and the DuckDuckGo browser with the rendered version of the page on the right.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Web development in 2018: all you need is a phone.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; is an excellent static site generator and website framework.&lt;/p&gt;
&lt;p&gt;You can build a static web site using your phone by running Hugo on &lt;a href=&#34;https://lineageos.org&#34;&gt;LineageOS&lt;/a&gt; under &lt;a href=&#34;https://termux.com/&#34;&gt;Termux&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how:&lt;/p&gt;
&lt;p&gt;(The instructions below have been tested with LineageOS 15.1 and Termux 0.64.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;https://f-droid.org/packages/com.termux/&#34;&gt;Termux from the F-Droid catalogue&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install Go (substitute &lt;em&gt;.bashrc&lt;/em&gt; with &lt;em&gt;.zshrc&lt;/em&gt; if you use zsh):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install go.&lt;/span&gt;
pkg install golang

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Update your shell configuration.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;export GOPATH=&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/go\nexport PATH=&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$PATH&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$GOROOT&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/bin:&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$GOPATH&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;/bin&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; ~/.bashrc

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Reload your shell configuration.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;source&lt;/span&gt; ~/.bashrc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compile Hugo from source:&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Install Mage (a build tool for go).&lt;/span&gt;
go get github.com/magefile/mage

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Retrieve, compile, and install Hugo.&lt;/span&gt;
go get -d github.com/gohugoio/hugo
&lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; &lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;GOPATH&lt;/span&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;:-&lt;/span&gt;&lt;span style=&#34;color:#bb60d5&#34;&gt;$HOME&lt;/span&gt;/go&lt;span style=&#34;color:#70a0d0;font-style:italic&#34;&gt;}&lt;/span&gt;/src/github.com/gohugoio/hugo
env &lt;span style=&#34;color:#bb60d5&#34;&gt;DEPNOLOCK&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt; mage vendor
mage install&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it!&lt;/p&gt;
&lt;p&gt;You should now have a working Hugo setup. Enjoy web development from your phone and, if you want an easy way to deploy from your phone and/or to join the peer web, check out my other posts on Web+&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Written on my LineageOS phone using Termux, Emacs and Hugo. Deployed via rsync and available on the peer web at &lt;a href=&#34;dat://ar.al/2018/07/30/compiling-hugo-for-mobile-phones-under-termux/%7C&#34;&gt;dat://ar.al/2018/07/30/compiling-hugo-for-mobile-phones-under-termux/&lt;/a&gt;. To view peer web sites, use &lt;a href=&#34;https://beakerbrowser.com/&#34;&gt;Beaker Browser&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The &lt;a href=&#34;https://gohugo.io/getting-started/installing#fetch-from-github&#34;&gt;official Hugo compilation instructions&lt;/a&gt; &lt;a href=&#34;https://github.com/golang/dep/issues/947&#34;&gt;hang under Termux&lt;/a&gt; at the &lt;code&gt;mage vendor&lt;/code&gt; command. The instructions here differ from those by setting an environment variable that avoids the hang. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;Web+&lt;/a&gt; and &lt;a href=&#34;https://ar.al/2018/07/05/web+-on-a-phone/&#34;&gt;Web+ on a phone&lt;/a&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Out of the frying pan and into the fire</title>
      <link>https://ar.al/2018/07/30/out-of-the-frying-pan-and-into-the-fire/</link>
      <pubDate>Mon, 30 Jul 2018 16:33:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/30/out-of-the-frying-pan-and-into-the-fire/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/30/out-of-the-frying-pan-and-into-the-fire/lets-not.jpg&#34;
         alt=&#34;Illustration from Mariana’s article showing a person’s head being opened up with a hand. My version has it crossed out in red.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;You cannot base a public good on a normalised systemic violation of human rights.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;nav style=&#34;text-align:center&#34;&gt;&lt;strong&gt;English&lt;/strong&gt; | &lt;a href=&#34;https://www.alexhinojo.cat/2018/07/30/fugir-del-foc-i-caure-a-les-brases/&#34;&gt;Català&lt;/a&gt; | &lt;a href=&#34;https://www.alexhinojo.cat/2018/07/30/huir-del-fuego-para-caer-en-las-brasas/&#34;&gt;Español&lt;/a&gt;&lt;/nav&gt;
&lt;p&gt;Mariana Mazzucato&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; has an article in MIT Technology Review titled &lt;a href=&#34;https://www.technologyreview.com/s/611489/lets-make-private-data-into-a-public-good/&#34;&gt;Let’s make private data into a public good&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s not.&lt;/p&gt;
&lt;p&gt;While Mariana’s criticisms of surveillance capitalism are spot on, her proposed remedy is as far from the mark as it possibly could be.&lt;/p&gt;
&lt;h3 id=&#34;yes-surveillance-capitalism-is-bad&#34;&gt;Yes, surveillance capitalism is bad&lt;/h3&gt;
&lt;p&gt;Mariana starts off by making the case, and rightly so, that surveillance capitalists&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; like Google or Facebook “are making huge profits from technologies originally created with taxpayer money.”&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Google’s algorithm was developed with funding from the National Science Foundation, and the internet came from DARPA funding. The same is true for touch-screen displays, GPS, and Siri. From this the tech giants have created de facto monopolies while evading the type of regulation that would rein in monopolies in any other industry. And their business model is built on taking advantage of the habits and private information of the taxpayers who funded the technologies in the first place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There’s nothing to argue with here. It’s a succinct summary of the tragedy of the commons that lies at the heart of surveillance capitalism and, indeed, that of neoliberalism itself.&lt;/p&gt;
&lt;p&gt;Mariana also accurately describes the business model of these companies, albeit without focusing on the actual mechanism by which the data is gathered to begin with&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Facebook’s and Google’s business models are built on the commodification of personal data, transforming our friendships, interests, beliefs, and preferences into sellable propositions. … The so-called sharing economy is based on the same idea.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So far, so good.&lt;/p&gt;
&lt;p&gt;But then, things quickly take a very wrong turn:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is indeed no reason why the public’s data should not be owned by a public repository that sells the data to the tech giants, rather than vice versa.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is every reason why we shouldn’t do this.&lt;/p&gt;
&lt;p&gt;Mariana’s analysis is fundamentally flawed in two respects: First, it ignores a core injustice in surveillance capitalism – violation of privacy – that her proposed recommendation would have the effect of normalising. Second, it perpetuates a fundamental false dichotomy ­– that there is no other way to design technology than the way Silicon Valley and surveillance capitalists design technology – which then means that there is no mention of the true alternatives: free and open, decentralised, interoperable &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical technologies&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;no-we-must-not-normalise-violation-of-privacy&#34;&gt;No, we must not normalise violation of privacy&lt;/h3&gt;
&lt;p&gt;The core injustice that Mariana’s piece ignores is that the business model of surveillance capitalists like Google and Facebook is based on the violation of a fundamental human right. When she says “let’s not forget that a large part of the technology and necessary data was created by all of us” it sounds like we voluntarily got together to create a dataset for the common good by revealing the most intimate details of our lives through having our behaviour tracked and aggregated. In truth, we did no such thing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://2018.ar.al/notes/we-didnt-lose-control-it-was-stolen/&#34;&gt;We were farmed.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We might have resigned ourselves to being farmed by the likes of Google and Facebook because we have no other choice but that’s not a healthy definition of consent by any standard. If 99.99999% of all investment goes into funding surveillance-based technology (and it does), then people have neither a true choice nor can they be expected to give any meaningful consent to being tracked and profiled. Surveillance capitalism is the norm today. It is mainstream technology. It’s what we funded and what we built.&lt;/p&gt;
&lt;p&gt;It is also fundamentally unjust.&lt;/p&gt;
&lt;p&gt;There is a very important reason why the public’s data should not be owned by a public repository that sells the data to the tech giants because it’s not the public’s data, it is personal data and it should never have been collected by a third party to begin with. You might hear the same argument from people who say that we must nationalise Google or Facebook.&lt;/p&gt;
&lt;p&gt;No, no, no, no, no, no, no! The answer to &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;the violation of personhood&lt;/a&gt; by corporations isn&amp;rsquo;t violation of personhood by government, it&amp;rsquo;s &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;not violating personhood to begin with&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That’s not to say that we cannot have a data commons. In fact, we must. But we must learn to make a core distinction between data about people and data about the world around us.&lt;/p&gt;
&lt;h3 id=&#34;data-about-people--data-about-rocks&#34;&gt;Data about people ≠ data about rocks&lt;/h3&gt;
&lt;p&gt;Our fundamental error when talking about data is that we use a single term when referring to both information about people as well as information about things. And yet, there is a world of difference between data about a rock and data about a human being. I cannot deprive a rock of its freedom or its life, I cannot emotionally or physically hurt a rock, and yet I can do all those things to people. When we posit what is permissible to do with data, if we are not specific in whether we are talking about rocks or people, one of those two groups is going to get the short end of the stick and it’s not going to be the rocks.&lt;/p&gt;
&lt;p&gt;Here is a simple rule of thumb:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data about individuals must belong to the individuals themselves. Data about the commons must belong to the commons.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I implore anyone working in this area – especially professors writing books and looking to shape public policy – to understand and learn this core distinction.&lt;/p&gt;
&lt;h3 id=&#34;there-is-an-alternative&#34;&gt;There is an alternative&lt;/h3&gt;
&lt;p&gt;I mentioned above that the second fundamental flaw in Mariana’s article is that it perpetuates a false dichotomy. That false dichotomy is that the Silicon Valley/surveillance capitalist model of building modern/digital/networked technology is the only possible way to build modern/digital/networked technology and that we must accept it as a given.&lt;/p&gt;
&lt;p&gt;This is patently false.&lt;/p&gt;
&lt;p&gt;It’s true that all modern technology works by gathering data. That’s not the problem. The core question is “who owns and controls that data and the technology by which it is gathered?” The answer to that question today is “corporations do.” Corporations like Google and Facebook own and control our data not because of some inevitable characteristic of modern technology but because of how they designed their technology in line with the needs of their business model.&lt;/p&gt;
&lt;p&gt;Specifically, surveillance capitalists like Google and Facebook design proprietary and centralised technologies to addict people and lock them in. In such systems, your data originates in a place you do not own. On “other people’s computers,” as the Free Software Foundation calls it. Or on “the cloud” as we colloquially reference it.&lt;/p&gt;
&lt;p&gt;The crucial point here, however, is that this toxic way of building modern technology is not the only way to design and build modern technology.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We know how to build free and open, decentralised, and interoperable systems where your data originates in a place that you – as an individual – own and control.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In other words, we know how to build technology where the algorithms remain on your own devices and where you are not farmed for personal information to begin with.&lt;/p&gt;
&lt;p&gt;To say that we must take as given that some third party will gather our personal data is to capitulate to surveillance capitalism. It is to accept the false dichotomy that either we have surveillance-based technology or we forego modern technology.&lt;/p&gt;
&lt;p&gt;This is neither true, nor necessary, nor acceptable.&lt;/p&gt;
&lt;p&gt;We can and we must build &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical technology&lt;/a&gt; instead.&lt;/p&gt;
&lt;h3 id=&#34;regulate-and-replace&#34;&gt;Regulate and replace&lt;/h3&gt;
&lt;p&gt;As I’m increasingly hearing these defeatist arguments that inherently accept surveillance as a foregone conclusion of modern technology, I want to reiterate what a true solution looks like.&lt;/p&gt;
&lt;p&gt;There are two things we must do to create &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;an ethical alternative to surveillance capitalism&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regulate the shit out of surveillance capitalists.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The goal here is to limit their abuses and harm. This includes limiting their ability to gather, process, and retain data, as well as fining them meaningful amounts and even breaking them up.&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fund and build ethical alternatives.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In other words, &lt;em&gt;replace&lt;/em&gt; them with ethical alternatives.&lt;/p&gt;
&lt;p&gt;Ethical alternatives do exist today but they do so mainly thanks to the extraordinary personal efforts of disjointed bands of so-called &lt;a href=&#34;https://www.theguardian.com/technology/2018/feb/01/punk-rock-internet-diy-rebels-working-replace-tech-giants-snoopers-charter&#34;&gt;DIY rebels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Whether they are the punk rockers of the tech world or its ragamuffins – and perhaps a little bit of both – what is certain is that they lead a precarious existence on the fringes of mainstream technology. They rely on anything from personal finances to selling the things they make, to crowdfunding and donations – and usually combinations thereof – to etch out an existence that both challenges and hopes to alter the shape of mainstream technology (and thus society) to make it fairer, kinder, and more just.&lt;/p&gt;
&lt;p&gt;While they build everything from computers and phones (&lt;a href=&#34;https://puri.sm&#34;&gt;Puri.sm&lt;/a&gt;) to federated social networks (&lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;) and decentralised alternatives to the centralised Web (&lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;), they do so usually with little or no funding whatsoever. And many are a single personal tragedy away from not existing at all.&lt;/p&gt;
&lt;p&gt;Meanwhile, we use taxpayer money in the EU to fund surveillance-based startups. Startups, which, if they succeed will most likely be bought by larger US-based surveillance capitalists like Google and Facebook. If they fail, on the other hand, the European taxpayer foots the bill. Europe, bamboozled by and living under the digital imperialism of Silicon Valley, has become its unpaid research and development department.&lt;/p&gt;
&lt;p&gt;This must change.&lt;/p&gt;
&lt;p&gt;Ethical technology does not grow on trees. Venture capitalists will not fund it. Silicon Valley will not build it.&lt;/p&gt;
&lt;p&gt;A meaningful counterpoint to surveillance capitalism that protects human rights and democracy will not come from China. If we fail to create one in Europe then I’m afraid that humankind is destined for centuries of feudal strife. If it survives &lt;a href=&#34;https://2017.ind.ie/excuse-me/&#34;&gt;the unsustainable trajectory that this social system has set it upon&lt;/a&gt;, that is.&lt;/p&gt;
&lt;p&gt;If we want ethical technological infrastructure – and we should, because the future of our human rights, democracy, and quite possibly that of the species depends on it – then we must fund and build it.&lt;/p&gt;
&lt;p&gt;The answer to surveillance capitalism isn’t to better distribute the rewards of its injustices or to normalise its practices at the state level.&lt;/p&gt;
&lt;p&gt;The answer to surveillance capitalism is a socio-techno-economic system that is just at its core. To create the technological infrastructure for such a system, we must fund independent organisations from the common purse to work for the common good to build &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical technology&lt;/a&gt; to &lt;a href=&#34;https://2018.ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;protect individual sovereignty and nurture a healthy commons&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;According to the bio in the article: “Mariana Mazzucato is a professor in the economics of innovation and public value at University College London, where she directs the &lt;a href=&#34;https://www.ucl.ac.uk/bartlett/public-purpose/home&#34;&gt;Institute for Innovation and Public Purpose&lt;/a&gt;.” The article I’m referencing is an edited excerpt from her new book The Value of Everything: Making and Taking in the Global Economy. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Although she never explicitly uses that term in the article. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Centralised architectures based on surveillance. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Break them up, by all means. But don’t do anything silly like nationalising them (for all the reasons I mention in this post). Nationalising a surveillance-based corporation would simply shift the surveillance to the state. We must embrace the third alternative: funding and building technology that isn’t based on surveillance to begin with. In other words, free and open, decentralised, interoperable technology. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Broadcasting your phone’s screen to web browsers using Screen Stream</title>
      <link>https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/</link>
      <pubDate>Sun, 29 Jul 2018 17:58:16 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/streaming.jpg&#34;
         alt=&#34;Gnome Web browser window showing a stream of the screen of my LineageOS phone from http://192.168.2.183:8080. The current app is called ObscuraCam and it&amp;#39;s showing a photo of Oskar, our huskamute, and me. My face is pixellated by the app.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My phone’s screen, streaming on my desktop in Gnome Web.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/dkrivoruchko/ScreenStream&#34;&gt;Screen Stream&lt;/a&gt; by &lt;a href=&#34;https://github.com/dkrivoruchko&#34;&gt;Dmitriy Krivoruchko&lt;/a&gt; is an app for streaming your phone’s screen over HTTP so other people on the same network can view it using a web browser. It is free and open source, available from the &lt;a href=&#34;https://www.f-droid.org/en/packages/info.dvkr.screenstream/&#34;&gt;F-Droid catalogue&lt;/a&gt;, and works beautifully on &lt;a href=&#34;https://lineageos.org&#34;&gt;LineageOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the screenshot above, you can see the screen of my phone streaming into my browser on my GNU/Linux desktop. The app being streamed is called &lt;a href=&#34;https://guardianproject.info/apps/obscuracam/&#34;&gt;ObscuraCam&lt;/a&gt;, a free and open source tool for automatically anonymising faces in photos&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;figure class=&#34;hairline-border&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/f-droid.jpg&#34;
         alt=&#34;The Screen Stream page on F-Droid&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screen Stream is free and open source and available via F-Droid.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Using Screen Stream couldn’t be simpler:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Copy your phone’s IP address and port by pressing the &lt;em&gt;copy button&lt;/em&gt; next to your device’s address.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/start-stream.jpg&#34;
         alt=&#34;Screen Stream’s interface, with the following labels: “Device addresses: wlan0: http://192.168.2.183:8080. Pin is disabled. Resize factor: 50%: 1396 × 720.” Next, there is a Start Stream button, followed by two other labels: Traffic: 0.00 Mbit/s, and Connected clients: 0&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Screen Stream interface is simple and informative.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press the &lt;em&gt;Start Stream&lt;/em&gt; button.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/stream-started.jpg&#34;
         alt=&#34;The home screen of my phone, showing the KISS launcher with the KISS search bar at the bottom. Along the left side are the icons for the DuckDuckGo, F-Droid, Settings, and Termux apps and down the bottom, those for the Camera, Twidere, Signal, and Wire, in my favourites list. At the top of the screen is a notification with the Screen Stream icon that reads: “Stream… Press stop to end stream.” On the right-hand-end of the notification there is a stop icon/button.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screen Stream will notify you when it starts streaming your screen.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send the address you copied earlier to the people you want to share your screen with and ask them to enter it into their web browsers’ address bars.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/traffic-graph.jpg&#34;
         alt=&#34;The Screen Stream traffic graph screen as streamed on my Linux desktop in Gnome Web. It shows traffic currently at 12.15 Mbit/s, a red zigzagging line for bandwidth that alternatives between 0.0 and 40.0 on the graph, 2 connected clients, and the IP addresses of the connected clients as well as an icon indicating that one of them has a slow connection.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Anyone with a web browser can then see your screen. Here’s the Screen Stream traffic graph, streaming on my desktop via Screen Stream.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;fixing-the-empty-black-screen-in-browser-error&#34;&gt;Fixing the empty black screen in browser error&lt;/h3&gt;
&lt;p&gt;If you see an empty black screen in the browser while attempting to view the stream, try this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go back to the Start Stream application on your phone and swipe from the left edge of the screen to show the app menu and select &lt;em&gt;Settings&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/stream-settings.png&#34;
         alt=&#34;The app menu, showing options titled Main, Traffic &amp;amp; clients, Settings (divider) Rate app, About (divider) Exit.&#34;/&gt; 
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the &lt;em&gt;Disable MJPEG check&lt;/em&gt; setting.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/broadcasting-your-phones-screen-to-web-browsers-using-screen-stream/settings-disable-mjpeg-check.png&#34;
         alt=&#34;The Settings screen, with a back button, showing the following options: Minimize on stream (minimize app on stream start) (checked), Stop on sleep (stop stream if screen turns off) (unchecked), Start on boot (start stream after device boot) (unchecked), Disable MJPEG check (disable check for browser MJPEG support) (checked)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Check the “Disable MJPEG check” setting for greater browser compatibility.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It seems that the code that checks for MJPEG support has a tendency to give false negatives and that stops the stream from working in browsers that otherwise do support the stream. Screen Stream might not work in every browser&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; but disabling this check will increase the number of browsers it does work in.&lt;/p&gt;
&lt;h3 id=&#34;fixing-the-no-address-found-error&#34;&gt;Fixing the “no address found” error&lt;/h3&gt;
&lt;p&gt;Sometimes after I stop a stream, the Screen Stream app will report “no address found” under &lt;em&gt;Device addreses&lt;/em&gt;. In these situations, merely closing the app from the task launcher and relaunching it doesn’t appear to have an effect.&lt;/p&gt;
&lt;p&gt;To fix this, select &lt;em&gt;Settings&lt;/em&gt; → &lt;em&gt;Apps &amp;amp; Notifications&lt;/em&gt; → (&lt;em&gt;See All Apps&lt;/em&gt;, if you cannot see Screen Stream) → &lt;em&gt;Screen Stream&lt;/em&gt; → &lt;em&gt;Force Stop&lt;/em&gt; and then restart the app and you should have an address again.&lt;/p&gt;
&lt;h3 id=&#34;a-lovely-useful-and-ethical-app&#34;&gt;A lovely, useful, and ethical app&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.f-droid.org/en/packages/info.dvkr.screenstream/&#34;&gt;Screen Stream&lt;/a&gt; is a beautiful ethical app for easily sharing your screen with others on the same network&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. It’s also a testament to the wonderful and inspiring free and open source app ecosystem on non-proprietary platforms today.&lt;/p&gt;
&lt;p&gt;Thank you, &lt;a href=&#34;https://github.com/dkrivoruchko&#34;&gt;Dmitriy&lt;/a&gt;, for making it and sharing it with the world.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Did they miss a trick by not calling it Camera Obscura? &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In our brief tests, Laura was not able to access the stream from Safari on macOS but was able to using Chrome (by surveillance capitalist Google/Alphabet, Inc.) I was able to get the stream to work under Gnome Web, Firefox (by surveillance capitalist Mozilla), and Chromium (the open source project by surveillance capitalist Google/Alphabet, Inc.) &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Which, if you set up a proxy or expose your IP, could be the whole world. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Fix for “No Bluetooth Found” error after wake from sleep</title>
      <link>https://ar.al/2018/07/29/fix-for-no-bluetooth-found-error-after-wake-from-sleep/</link>
      <pubDate>Sun, 29 Jul 2018 16:05:55 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/29/fix-for-no-bluetooth-found-error-after-wake-from-sleep/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/29/fix-for-no-bluetooth-found-error-after-wake-from-sleep/no-bluetooth-found.png&#34;
         alt=&#34;Bluetooth settings panel in Pop!_OS showing the Bluetooth logo and the message: “No Bluetooth Found. Plug in a dongle to use Bluetooth.” The switch normally used to turn Bluetooth on looks broken in the window’s top bar, with only the thumb showing.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Bluetooth: still asleep, apparently.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After waking from sleep, my computer&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; would stop detecting the built-in bluetooth interface. The exact error I was getting in the Settings panel was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No Bluetooth Found. Plug in a dongle to use Bluetooth.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://askubuntu.com/a/1037065&#34;&gt;The fix&lt;/a&gt; was to update &lt;a href=&#34;http://www.bluez.org/&#34;&gt;BlueZ&lt;/a&gt;, the official Linux Bluetooth stack:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo add-apt-repository ppa:bluetooth/bluez
sudo apt install bluez&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I no longer appear to have the issue with BlueZ version 5.50.&lt;/p&gt;
&lt;p&gt;To check the version of your bluetooth driver, you can &lt;a href=&#34;https://askubuntu.com/a/446466&#34;&gt;query the BlueZ daemon&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;bluetoothd -v&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m running Pop!_OS 18.04 but this issue may affect you if you’re on Ubuntu 18.04 or some other distribution also as it appears to be related to a bug in an earlier version of the official Linux bluetooth stack. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Getting started with WireGuard on Linux using AzireVPN</title>
      <link>https://ar.al/2018/07/27/getting-started-with-wireguard-on-linux-using-azirevpn/</link>
      <pubDate>Fri, 27 Jul 2018 11:05:32 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/27/getting-started-with-wireguard-on-linux-using-azirevpn/</guid>
      <description>&lt;p&gt;A &lt;a href=&#34;https://en.wikipedia.org/wiki/Virtual_private_network&#34;&gt;Virtual Private Network&lt;/a&gt; (VPN) can be a practical tool that reinforces your security and privacy when using an Internet-connected device. It is not a panacea, however. You should be aware of both your own &lt;a href=&#34;https://en.wikipedia.org/wiki/Threat_model&#34;&gt;threat model&lt;/a&gt; and the security and privacy traits of VPNs so you can use one responsibly and ensure that you don’t succumb to a false sense of security or privacy.&lt;/p&gt;
&lt;h3 id=&#34;what-is-a-vpn&#34;&gt;What is a VPN?&lt;/h3&gt;
&lt;p&gt;A VPN is basically an encrypted (private) connection to some other network through which all your traffic – web browsing, email, chats, etc. – is routed. Since the connection is encrypted, the network you&amp;rsquo;re currently connected to cannot eavesdrop on you. All your Internet Service Provider (ISP) or the open WiFi network you just connected to at your local cafe can see is that you’ve made an encrypted connection to your VPN provider but they cannot see any of the traffic that flows through that connection.&lt;/p&gt;
&lt;p&gt;Similarly, as far as any sites you connect to are concerned, your traffic originates from the servers of your VPN provider and not your own machine. This is why VPNs are commonly used to thwart region-locking schemes by the likes of Netflix and their cohorts in the copyright lobby&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In a nutshell, if you live in a somewhat free society and have an everyday threat model, a VPN limits the number of parties that can trivially eavesdrop on your Internet activity to just your VPN provider.&lt;/p&gt;
&lt;h3 id=&#34;on-trust&#34;&gt;On trust&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Never trust anything that can think for itself if you can&amp;rsquo;t see where it keeps its brain.&lt;/p&gt;
&lt;p&gt;– J.K. Rowling, Harry Potter and the Chamber of Secrets&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Given that you have to trust your VPN provider, the choice of VPN provider is what determines the level of security and privacy you get by using a VPN.&lt;/p&gt;
&lt;p&gt;Nothing about the design of VPN implies that you should in any way trust VPN providers.&lt;/p&gt;
&lt;iframe src=&#34;https://octodon.social/@cwebber/100442926544174062/embed&#34; class=&#34;mastodon-embed&#34;&gt;&lt;/iframe&gt;&lt;script src=&#34;https://octodon.social/embed.js&#34; async=&#34;async&#34;&gt;&lt;/script&gt;
&lt;p&gt;It is trivial for your VPN provider (or your web host, if you&amp;rsquo;re hosting your VPN server yourself) to violate your privacy. Your connection is encrypted between you and the private network you&amp;rsquo;re connecting to but it is not end-to-end encrypted between your computer and the parties you&amp;rsquo;re communicating with.&lt;/p&gt;
&lt;p&gt;This is why you should never trust free (as in cost) VPN providers. How are they making their money if not by you paying them? What are you paying them with? (Remember, they can see all of your Internet activity.)&lt;/p&gt;
&lt;p&gt;Even with a VPN provider that you’ve chosen to trust, you should connect to web sites only using &lt;a href=&#34;https://en.wikipedia.org/wiki/Transport_Layer_Security&#34;&gt;Transport Layer Security&lt;/a&gt; (HTTPS) and use end-to-end-encrypted communication apps like &lt;a href=&#34;https://wire.com/en/unsupported/&#34;&gt;Wire&lt;/a&gt; and &lt;a href=&#34;https://signal.org/&#34;&gt;Signal&lt;/a&gt;. If you need greater levels of anonymity (e.g., if you are sharing information that might result in your assasination by an autocratic regime), consider using &lt;a href=&#34;https://www.torproject.org/&#34;&gt;Tor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It might seem like I am rambling and not getting to the point of the post but I cannot stress how important it is that you understand the security and threat models of the tools that you use. If nothing else, remember that a VPN is only as trustworthy as the people who run it.&lt;/p&gt;
&lt;h3 id=&#34;so-which-vpn-do-you-use-and-recommend&#34;&gt;So which VPN do you use and recommend?&lt;/h3&gt;
&lt;p&gt;I get this question a lot. So before I get to the point of the post (oh, ffs!), I want to cover this too.&lt;/p&gt;
&lt;p&gt;On iOS and macOS, I use &lt;a href=&#34;https://www.encrypt.me/&#34;&gt;Encrypt.me&lt;/a&gt; (née Cloak). Why? Because my friend &lt;a href=&#34;https://davepeck.org/&#34;&gt;Dave Peck&lt;/a&gt; set it up and I trust Dave. Dave’s since sold the company but it still seems to be run by good folks.&lt;/p&gt;
&lt;p&gt;When &lt;a href=&#34;https://ar.al/2018/07/16/changes/&#34;&gt;I recently switched my main phone from an iPhone to one running LineageOS&lt;/a&gt; and &lt;a href=&#34;https://twitter.com/aral/status/1014430133837582338&#34;&gt;asked them&lt;/a&gt; for an &lt;a href=&#34;https://en.wikipedia.org/wiki/Android_application_package&#34;&gt;Android application package&lt;/a&gt; (APK) – as I didn’t want to use the Google Play store – they responded immediately&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and sent it to me.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; In addition to iOS, macOS, and Android, Encrypt.me is also available on Windows.&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/07/27/getting-started-with-wireguard-on-linux-using-azirevpn/encryptme.jpg&#34;
         alt=&#34;The interface for Encrypt.me, with a blue background, a WiFi symbol and text that reads: “Connecte to bean&amp;amp;amp;leaf, an untrusted Wi-Fi network, and secured with Encrypt.me. There is also switch control that reads Encrypted and three icons at the bottom of the screen for choosing server locations (a pin icon), network settings (a lock icon), and account settings (a gear icon).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Encrypt.me: a beautiful VPN experience.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;on-usability&#34;&gt;On usability&lt;/h3&gt;
&lt;p&gt;The main reason I&amp;rsquo;ve been using Encrypt.me is its ease of use. You install and launch it and it just works. Also, it automatically blocks network traffic before the VPN connection has been established. This should be – but sadly isn’t – standard functionality in VPN clients to prevent &lt;a href=&#34;https://arstechnica.com/information-technology/2015/06/even-with-a-vpn-open-wi-fi-exposes-users/&#34;&gt;data leaks between Wi-Fi connect and VPN launch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&#34;https://ar.al/2018/07/16/changes/&#34;&gt;I switched my main machine to GNU/Linux&lt;/a&gt; and Encrypt.me is currently not available for it, I signed up for &lt;a href=&#34;https://airvpn.org/&#34;&gt;AirVPN&lt;/a&gt;, which describes itself as “a VPN based on OpenVPN and operated by activists and hacktivists in defence of net neutrality, privacy and against censorship.” AirVPN has &lt;a href=&#34;https://airvpn.org/enter/&#34;&gt;setup instructions for nearly every platform on the face of the planet&lt;/a&gt; and &lt;a href=&#34;https://airvpn.org/linux/&#34;&gt;an app called Eddie for GNU/Linux&lt;/a&gt; that implements an automatic network block feature that protects against data leaks in the time between you connect to a WiFi network and before a VPN connection can be established.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/27/getting-started-with-wireguard-on-linux-using-azirevpn/eddie.jpg&#34;
         alt=&#34;The interface of Eddie, the AirVPN client for GNU/Linux&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Eddie: an (inter)face only a mother could love.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Unlike Encrypt.me, however, you cannot mark certain networks as trusted and so it’s currently an all or nothing setting. Also, on my machine at least, it’s possible to lose the ability to access the app’s interface while it remains running in the background. And the kindest thing you can say about the interface itself is that it’s what happened when &lt;em&gt;what-the-fuck&lt;/em&gt; had a lovechild with &lt;em&gt;oh-hell-no!&lt;/em&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;So those are the VPNs I currently use. If you were to ask me which VPN I’d unconditionally trust, however, I would say &lt;a href=&#34;https://www.ipredator.se/&#34;&gt;iPredator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because it’s run by my friend &lt;a href=&#34;https://en.wikipedia.org/wiki/Peter_Sunde&#34;&gt;Peter&lt;/a&gt; and I trust Peter unconditionally. Also, I’ve been in the cool little bunker in Sweden where everything’s hosted and I know that Peter runs it on his own machines that only he and his crew have access to.&lt;/p&gt;
&lt;p&gt;The reason I don’t use iPredator myself is because Encrypt.me and AirVPN have apps that make them easier to setup and use. So convenience wins yet again. (I almost feel like there’s a lesson to be learned here for those of us that make the new everyday things for everyday people but I just can’t seem to put my finger on it… 🤔)&lt;/p&gt;
&lt;p&gt;And that also brings us, finally, to the subject of this post: WireGuard and how to set it up on Linux.&lt;/p&gt;
&lt;h3 id=&#34;wireguard&#34;&gt;WireGuard&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.wireguard.com/&#34;&gt;WireGuard&lt;/a&gt; describes itself as a “fast, modern, secure VPN tunnel.”&lt;/p&gt;
&lt;p&gt;Part of the reason VPNs can be hassle to host, install, and use is because they are – relatively speaking – based on ancient, bloated technology. The two main protocols in use today, &lt;a href=&#34;https://en.wikipedia.org/wiki/IPsec&#34;&gt;IPsec&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/OpenVPN&#34;&gt;OpenVPN&lt;/a&gt; date from the Nineties and the Noughties and are behemoths weighing in at roughly 400,000 and 100,000 lines of code, respectively. In comparison, WireGuard is a recent protocol that uses modern cryptographic algorithms and weighs in at ~4,000 lines of code.&lt;/p&gt;
&lt;p&gt;In programming, like in most things, less is usually better. As long as it doesn&amp;rsquo;t come at the expense of obfuscating intent, fewer lines of code means fewer places things can go wrong and less to audit.&lt;/p&gt;
&lt;p&gt;That WireGuard is new, however, is both a blessing and a curse. It is currently labelled as a “work in progress” by its authors.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;WireGuard is not yet complete. You should not rely on this code. It has not undergone proper degrees of security auditing and the protocol is still subject to change. We&amp;rsquo;re working toward a stable 1.0 release, but that time has not yet come. – &lt;a href=&#34;https://www.wireguard.com/&#34;&gt;WireGuard&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you can live with that and want to try out the future of VPNs today, read on as there are already VPN hosts beta testing the service. One of those is &lt;a href=&#34;https://www.azirevpn.com&#34;&gt;AzireVPN&lt;/a&gt;, by a Swedish company called Netbouncer AB based in Stockholm. According to their &lt;a href=&#34;https://www.azirevpn.com/about&#34;&gt;about page&lt;/a&gt;, they’ve been running &lt;a href=&#34;https://www.azirevpn.com/wireguard&#34;&gt;a WireGuard beta programme&lt;/a&gt; since September 2017.&lt;/p&gt;
&lt;h3 id=&#34;wireguard-on-azirevpn&#34;&gt;WireGuard on AzireVPN&lt;/h3&gt;
&lt;p&gt;AzireVPN makes its money selling VPN services. &lt;strike&gt;but, for the duration of the WireGuard beta, you can use WireGuard for “free” (in other words, in exchange for helping them test it. Remember, nothing is ever truly free as in cost unless it is also free as in freedom.)&lt;/strike&gt;&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You can set things up on Android using &lt;a href=&#34;https://f-droid.org/en/packages/com.wireguard.android/&#34;&gt;the WireGuard app&lt;/a&gt; (which is available on the &lt;a href=&#34;https://f-droid.org/&#34;&gt;F-Droid catalogue&lt;/a&gt;) but installation on Linux currently requires use of the command line. You will need to install some packages that the installation script doesn’t automatically install, including &lt;a href=&#34;https://www.wireguard.com/install/&#34;&gt;WireGuard itself&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;installing&#34;&gt;Installing&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/27/getting-started-with-wireguard-on-linux-using-azirevpn/installation.jpg&#34;
         alt=&#34;Screenshot of terminal showing the output of the installer script&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The AzireVPN WireGuard installation is carried out through the terminal.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.wireguard.com/install/&#34;&gt;Install WireGuard&lt;/a&gt;. On Debian/Ubuntu-based distributions (like Pop!_OS):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo add-apt-repository ppa:wireguard/wireguard
sudo apt install wireguard&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install curl and jq (if you already have them installed, the following commands will not hurt your system. The installer should really do this, and the above step, for you):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install curl
sudo apt install jq&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the installer script&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;curl -LO https://www.azirevpn.com/dl/azirevpn-wg.sh &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; chmod +x ./azirevpn-wg.sh &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./azirevpn-wg.sh&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;using&#34;&gt;Using&lt;/h3&gt;
&lt;p&gt;You activate and deactivate the WireGuard VPN connection using the terminal commands &lt;code&gt;wg-quick up&lt;/code&gt; and &lt;code&gt;wg-quick down&lt;/code&gt; with the name of the server you want to connect to as the second argument.&lt;/p&gt;
&lt;p&gt;To connect to AzireVPN’s server in Stockholm, Sweden, for example, you would type:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;wg-quick up azirevpn-se1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Similarly, to disconnect from that server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;wg-quick down azirevpn-se1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At the time I installed it, the servers available to me were:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Connection Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stockholm, Sweden&lt;/td&gt;
&lt;td&gt;&lt;code&gt;azirevpn-se1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;London, UK&lt;/td&gt;
&lt;td&gt;&lt;code&gt;azirevpn-uk1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malaga, Spain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;azirevpn-es1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Miami, US&lt;/td&gt;
&lt;td&gt;&lt;code&gt;azirevpn-us1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toronto, Canada&lt;/td&gt;
&lt;td&gt;&lt;code&gt;azirevpn-ca1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Once you’ve activated your VPN connection, you can check that it is working by visiting &lt;a href=&#34;https://www.azirevpn.com/check&#34;&gt;AzireVPN’s security check page&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/27/getting-started-with-wireguard-on-linux-using-azirevpn/security-check.jpg&#34;
         alt=&#34;A screenshot of the AzireVPN Security Check page. Copy: Security Check: Make sure that all tests below are green before you proceed. We’re here to keep your privacy. Service status: all services operational. VPN: you are connected. DNS leak: You are not leaking DNS. WebRTC leak: You are not leaking WebRTC.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The AzireVPN Security Check page.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;These are the bastards that &lt;a href=&#34;https://edri.org/the-eu-gets-another-opportunity-to-improve-copyright-rules/&#34;&gt;want to destroy how the Internet works&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Thank you, folks! I haven&amp;rsquo;t had a chance to try it yet as I&amp;rsquo;ve been busy, but I will. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://1password.com/&#34;&gt;1password&lt;/a&gt;, on the other hand, &lt;a href=&#34;https://twitter.com/1Password/status/1014993975407730688&#34;&gt;told me I could pound sand&lt;/a&gt; when I asked them the same thing. The cost of being 1password’s customer, if you are running an Android Open Source Project (AOSP)-based mobile operating system like &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt;, is to be tracked and profiled by Google (&lt;a href=&#34;https://en.wikipedia.org/wiki/Alphabet_Inc.&#34;&gt;Alphabet, Inc.&lt;/a&gt;) Needless to say, I am actively considering alternatives to 1password as we speak. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;That said, the app is free and open source so anyone (hint, hint, maybe me?), can help make it better. And that’s just one reason why free and open source rocks. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It looks like the free beta period is over. I noticed that my connection wasn’t working and, when I logged in, I got a prompt to refill my account with credit (which I did). AzireVPN’s prices are entirely reasonable and their service is solid. I’m connected via Wireguard over Sweden as I write this in an airport lounge in Paris. &lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Before you download and run any script, you should really &lt;a href=&#34;https://www.azirevpn.com/dl/azirevpn-wg.sh&#34;&gt;download the script&lt;/a&gt; and check out what it does first. That script also downloads &lt;a href=&#34;https://api.azirevpn.com/v1/locations&#34;&gt;a list of locations&lt;/a&gt;, so you might want to make sure that what’s in that file is kosher too before running it. &lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Pop!_OS 18.04: the state of the art in GNU/Linux on desktop</title>
      <link>https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/</link>
      <pubDate>Thu, 26 Jul 2018 12:02:42 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/lock-screen.jpg&#34;
         alt=&#34;The Pop!_OS lock screen showing a beautifully illustrated space scene with planets and the current time (3:33PM on Saturday, 21 July)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Pop!_OS is beautiful, thanks in no small part to a consistent minimalist visual style and Kate Hazen’s beautiful space-themed illustrations.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS 18.04&lt;/a&gt; is a GNU/Linux distribution curated by US-based computer maker &lt;a href=&#34;https://system76.com&#34;&gt;System76&lt;/a&gt;. It is the state of the art in usability and experience when it comes to desktop Linux today&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The genius of the System76 team was in realising that all the components for a usable, convenient, and delightful GNU/Linux desktop experience are already here, they&amp;rsquo;re just not tastefully curated. They took the best bits of the Linux ecosystem, added some of their own special sauce, and ended up creating a minimal, coherent, consistent and – at times – &lt;em&gt;delightful&lt;/em&gt; experience.&lt;/p&gt;
&lt;p&gt;Take note, because this is a inflection point in desktop Linux.&lt;/p&gt;
&lt;p&gt;If you had asked me a few years ago, these are not words I thought I would ever be using to describe a Linux distribution. And they’re not just words. &lt;a href=&#34;https://ar.al/2018/07/16/changes/&#34;&gt;I recently switched my main development machine&lt;/a&gt; from a Macbook to a notebook running Pop!_OS after falling in love with it in a virtual machine.&lt;/p&gt;
&lt;h3 id=&#34;the-ingredients-of-success&#34;&gt;The ingredients of success&lt;/h3&gt;
&lt;p&gt;A number of &lt;a href=&#34;https://system76.com/pop/tech&#34;&gt;core elements&lt;/a&gt; constitute the Pop!_OS experience.&lt;/p&gt;
&lt;p&gt;The main ones are &lt;a href=&#34;https://arstechnica.com/information-technology/2018/05/ubuntu-18-04-the-return-of-a-familiar-interface-marks-the-best-ubuntu-in-years/&#34;&gt;Ubuntu 18.04&lt;/a&gt; LTS, &lt;a href=&#34;https://www.gnome.org/gnome-3/&#34;&gt;Gnome 3&lt;/a&gt; with &lt;a href=&#34;https://wiki.gnome.org/Projects/GnomeShell&#34;&gt;Gnome Shell&lt;/a&gt;, a unified theme that uses &lt;a href=&#34;https://wiki.gnome.org/Projects/GnomeShell&#34;&gt;material design&lt;/a&gt;, &lt;a href=&#34;https://pop.system76.com/docs/keyboard-shortcuts/&#34;&gt;consistent keyboard shortcuts&lt;/a&gt;, an adapted version of the &lt;a href=&#34;https://elementary.io/&#34;&gt;elementaryOS&lt;/a&gt; &lt;a href=&#34;https://github.com/elementary/appcenter&#34;&gt;AppCenter&lt;/a&gt;, and a minimal set of &lt;a href=&#34;https://pop.system76.com/docs/default-apps/&#34;&gt;default apps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You combine these elements with &lt;a href=&#34;https://pop.system76.com/docs/pop-os-development-approach/&#34;&gt;a formal approach&lt;/a&gt; focussed on creating a simple experience for a specific target audience and you get Pop!_OS.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We’re building an OS for the software developer, maker, and computer science professional who uses their computer as a tool to discover and create. – &lt;a href=&#34;https://system76.com/pop&#34;&gt;System76&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I will be exploring the various aspects of the design in detail in future posts. Suffice to say for now that Pop!_OS offers a more minimalist – and definitely less corporate – experience than macOS without attempting to recreate it. A huge amount of the kudos for that goes to Gnome 3 and Gnome Shell.&lt;/p&gt;
&lt;h3 id=&#34;gnome-shell-3-a-masterpiece-in-interface-design&#34;&gt;Gnome Shell 3: A masterpiece in interface design&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.gnome.org/gnome-3/&#34;&gt;Gnome 3&lt;/a&gt; and &lt;a href=&#34;https://wiki.gnome.org/Projects/GnomeShell&#34;&gt;Gnome Shell&lt;/a&gt; should be case studies in great interaction design&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Instead of copying Windows or macOS (an affliction that affects other alternatives and is inexplicably seen as a feature in those communities), Gnome 3 has its own culture and consistencies. These clearly derive from a first-principles approach to design and are refreshing to behold.&lt;/p&gt;
&lt;p&gt;Much of the simplicity of Gnome 3 comes from the conceptual unity provided by the Activities view. A single key (&lt;a href=&#34;https://en.wikipedia.org/wiki/Super_key_(keyboard_button)&#34;&gt;the super key&lt;/a&gt;), takes you to this view where you can see all of your current apps as well as search for anything on the system or the store from a single place.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/activities-view.jpg&#34;
         alt=&#34;The Activities view in Pop!_OS showing me my currently active applications, desktop, and a search bar.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Activities view in Gnome 3 beats Windows and macOS on simplicity.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Unlike Windows, there is no ungainly multi-level start menu from hell. Unlike macOS, the task of finding things is encapsulated in a single place instead of being split among two different features (Mission Control and Spotlight). If you are looking for something, there is one place to go. I know that I can get to anything I want using the Activities button or pressing the super key. This is as low as you can get in terms of cognitive load and is an absolute joy to use. It&amp;rsquo;s the state of the art in operating system navigation interfaces bar none. It&amp;rsquo;s as good as it gets.&lt;/p&gt;
&lt;p&gt;Another brilliant design decision in Gnome 3 is to have a separate, always on App Menu in the menu bar. This creates what we call a landmark in interface design. Whereas I&amp;rsquo;m constantly lost on Windows, on Gnome 3, I can always tell exactly where I am. Which is why I was very sad to learn that &lt;a href=&#34;https://www.omgubuntu.co.uk/2018/06/gnome-app-menu-migration&#34;&gt;there is work to reverse this beautiful feature&lt;/a&gt;. I cannot stress enough how much of a backwards step this is. It breaks the conceptual integrity of the design. I would strongly urge the Gnome team to stop and reconsider this pending – yet still avoidable – regression.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/26/popos-18.04-the-state-of-the-art-in-linux-on-desktop/landmarks.jpg&#34;
         alt=&#34;Screenshot showing this blog post open in Visual Studio Code on the left half of the screen and a Web browser open on the right, showing the rendered post. The App Menu reads “Web”&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Am I in Visual Studio Code or Web? Web, of course. How do I know? Because the App Menu tells me so. It&amp;rsquo;s a crucial landmark.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;three-steps-forwards-two-steps-back&#34;&gt;Three steps forwards, two steps back?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://wiki.gnome.org/Design/Whiteboards/AppMenuMigration&#34;&gt;The reasons given for removing the App Menu&lt;/a&gt; simply do not make sense to me as an interaction designer. Having the App Menu separate from app windows is the conceptually-correct, properly-encapsulated grouping of elements and serves as a crucial landmark to designate the currently-active application and the actions that can be performed on it.&lt;/p&gt;
&lt;p&gt;Given how important I feel this decision is to the future of the platform that I now use myself, I want to take a moment to refute each one of the justifications given for removing the App Menu from Gnome 3:&lt;/p&gt;
&lt;h4 id=&#34;its-disconnected-from-the-app-window&#34;&gt;It&amp;rsquo;s disconnected from the app window&lt;/h4&gt;
&lt;p&gt;Yes, of course.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s because it is the &lt;em&gt;App Menu&lt;/em&gt;, not a &lt;em&gt;window&lt;/em&gt; menu. The actions within the App Menu affect the app, not the windows (notice the plural form) that may exist within the app. If desktop apps always had only a single window, then the statement would be true. As things stand, it is not.&lt;/p&gt;
&lt;h4 id=&#34;it-doesnt-make-sense-with-multiple-monitors&#34;&gt;It doesn&amp;rsquo;t make sense with multiple monitors&lt;/h4&gt;
&lt;p&gt;Yes, it does.&lt;/p&gt;
&lt;p&gt;Having multiple monitors doesn&amp;rsquo;t remove the need to know which app you&amp;rsquo;re in or to perform actions specific to that app. Just like the Activities button, the App Menu resides at a known location on your primary monitor and serves a crucial role as a landmark.&lt;/p&gt;
&lt;h4 id=&#34;no-other-platform-has-this-pattern&#34;&gt;No other platform has this pattern&lt;/h4&gt;
&lt;p&gt;This is correct as long as you don&amp;rsquo;t consider macOS a platform.&lt;/p&gt;
&lt;h4 id=&#34;the-distinction-between-app-level-and-view-level-menu-items-can-be-murky&#34;&gt;The distinction between app-level and view-level menu items can be murky&lt;/h4&gt;
&lt;p&gt;This is only true for apps that do not properly conform to the &lt;a href=&#34;https://developer.gnome.org/hig/stable/&#34;&gt;Gnome Human Interface Guidelines&lt;/a&gt; (GHIG&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;)&lt;/p&gt;
&lt;p&gt;There is no murkiness in the conceptual separation between an app and its windows. That’s a basic concept in desktop interface design. An app contains one or more windows. Actions that affect the app reside in the App Menu and actions that affect specific windows reside in the window menus of those windows.&lt;/p&gt;
&lt;h4 id=&#34;its-only-accessible-for-the-app-which-currently-has-focus&#34;&gt;It&amp;rsquo;s only accessible for the app which currently has focus&lt;/h4&gt;
&lt;p&gt;Yes, that&amp;rsquo;s the whole idea!&lt;/p&gt;
&lt;p&gt;Unless you&amp;rsquo;re a dual-mouse wielding multitasking wizard (in which case, an operating system hasn&amp;rsquo;t been written for you yet), you can only interact with one app at a time. The reason the App Menu is a landmark is because it shows you which app currently has focus and encapsulates the actions you can perform on that app. This is not a problem, it is one of the main reasons why the feature exists in the first place.&lt;/p&gt;
&lt;h4 id=&#34;our-own-apps-use-it-inconsistently&#34;&gt;Our own apps use it inconsistently&lt;/h4&gt;
&lt;p&gt;This is a great reason to get our own apps to use it consistently, not to formalise the inconsistency by breaking an otherwise consistent conceptual model. There&amp;rsquo;s a difference between paving the cow paths and framing cow shit.&lt;/p&gt;
&lt;h4 id=&#34;third-party-apps-have-not-widely-adopted-it-so-it-mostly-sits-there-empty&#34;&gt;Third-party apps have not widely adopted it, so it mostly sits there empty&lt;/h4&gt;
&lt;p&gt;Same point as above and my response is the same: we should expend effort on getting third parties to adhere to the human interface guidelines (HIG) instead of breaking the conceptual integrity of the platform to cater to nonconforming applications. If the HIG is not clear enough, let’s update the HIG.&lt;/p&gt;
&lt;h4 id=&#34;some-new-users-dont-seem-to-find-it-and-eg-assume-that-there-are-no-preferences&#34;&gt;Some new users don&amp;rsquo;t seem to find it, and e.g., assume that there are no preferences&lt;/h4&gt;
&lt;p&gt;It is normal for people who start interacting with a new thing to not know how the thing works. The better the &lt;a href=&#34;https://2018.ar.al/notes/design-is-not-veneer/&#34;&gt;affordances&lt;/a&gt; of that thing, the easier they will learn. This is also where landmarks play an important role. The problem isn&amp;rsquo;t that the App Menu isn&amp;rsquo;t a strong landmark or that it doesn&amp;rsquo;t provide a strong affordance but that it is inconsistently applied by apps.&lt;/p&gt;
&lt;p&gt;The rest of the proposal goes on to conflate a desktop interface model (multiple windows) with a mobile interface model (single screen). A single menu makes sense on the mobile interface model because that is screen based, not window based. In the screen-based model, the app menu &lt;em&gt;is&lt;/em&gt; the screen menu. It does not follow, however, that this then also makes sense in a window-based model where we have a clear separation of an app and its windows.&lt;/p&gt;
&lt;p&gt;The only real problem with the App Menu is that some apps don’t use it in conformance with the human interface guidelines. So the only real problem with the App Menu isn’t a problem with the App Menu at all but with developers violating the human interface guidelines and a failing on part of the platform itself to attempt to adequately enforce its human interface guidelines.&lt;/p&gt;
&lt;p&gt;Likewise, the only intellectually-honest reason I can see for removing the App Menu from Gnome 3 is “we want it to work more like Windows.” That&amp;rsquo;s the worst-possible reason I can think of for breaking the conceptual unity of an otherwise beautiful design.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls src=&#34;https://player.vimeo.com/external/133430959.hd.mp4?s=11ab523d76408b6b4cac51a1c3c99c58cdb28fa2&amp;profile_id=119&#34; poster=&#34;https://i.vimeocdn.com/video/526512828.jpg?mw=2880&amp;mh=1620&amp;q=70&#34;&gt;&lt;a href=&#39;https://vimeo.com/133430959&#39;&gt;Video of a talk I gave on experience design at the Thinking Digital conference.&lt;/a&gt;&lt;/video&gt;
  &lt;figcaption&gt;A little background: my approach to design.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;on-consistency&#34;&gt;On consistency&lt;/h3&gt;
&lt;p&gt;There is a very real problem in the GNU/Linux ecosystem but it&amp;rsquo;s not the App Menu in Gnome 3. The problem is lack of consistency. Or maybe, more precisely, a culture that celebrates lack of consistency as a feature, confusing it with “choice”.&lt;/p&gt;
&lt;p&gt;Beautiful, consistent defaults are not mutually exclusive with choice. Choice is about having the option of diverging from the defaults, not whether or not those defaults mandate a certain cultural cohesion or consistency.&lt;/p&gt;
&lt;p&gt;Gnome should not be ashamed of &lt;a href=&#34;https://developer.gnome.org/hig/stable/&#34;&gt;its culture&lt;/a&gt;. On the contrary, it should be working to implement it as consistently as possible in its ecosystem. So let’s get Gnome apps to use the App Menu consistently instead of removing it. Let’s improve the consistency of the platform instead of destroying a valuable landmark and violating the conceptual integrity of the design.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls src=&#39;https://player.vimeo.com/external/134835662.hd.mp4?s=6f919dd129fef4231ece40d7c36591c2a9e03357&amp;profile_id=113&#39; poster=&#39;https://i.vimeocdn.com/video/528502794.jpg?mw=2880&amp;mh=1620&amp;q=70&#39;&gt;&lt;a href=&#39;https://vimeo.com/134835662&#39;&gt;Video of a talk I gave on experience design at the Thinking Digital conference.&lt;/a&gt;&lt;/video&gt;
  &lt;figcaption&gt;A short demonstration: great design should empower, amuse, and delight.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The lack of a strong, coherent culture for applications is the last thing holding desktop Linux back.&lt;/p&gt;
&lt;p&gt;As evidenced by Pop!_OS, it&amp;rsquo;s entirely possible to build a Linux distribution that can challenge the flagships of multibillion-dollar behemoths like Apple and Microsoft. The bit where the experience falls flat is the moment you install an app that doesn&amp;rsquo;t respect the culture of the platform. Using a KDE app on a Gnome desktop is like following a recipe for a cake in imperial measurements when your country uses the metric system. At best, you increase the cognitive load of baking a cake and at worst you end up with an inedible mess.&lt;/p&gt;
&lt;p&gt;This is the biggest challenge both for innovative organisations like &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt; and System76 who are making progress towards controlling the whole free and open stack, and thus the whole experience. They are competing with the meticulously-crafted and (like it or not) &lt;em&gt;consistent&lt;/em&gt; cultures of platforms like macOS.&lt;/p&gt;
&lt;p&gt;Imagine if every macOS app looked and behaved differently. Imagine if some third-party could arbitrarily decide to make menus in macOS work more like the menus in Windows 10 in the next release. Apple wouldn&amp;rsquo;t be where it is today. And yet that&amp;rsquo;s exactly the situation Purism and System76 find themselves in today because they both use Gnome 3.&lt;/p&gt;
&lt;p&gt;If GNU/Linux is going to compete with the likes of Apple, we must start to value consistency, not despise it. And we must not be afraid to create and enforce strong, consistent cultures for our platforms.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls src=&#34;https://player.vimeo.com/external/281704944.hd.mp4?s=232971103213d9def6cc584c0e701202449517d0&amp;profile_id=174&#34; poster=&#34;https://i.vimeocdn.com/video/715518313.jpg?mw=1600&amp;mh=899&amp;q=70&#34;&gt;&lt;a href=&#39;https://vimeo.com/281704944&#39;&gt;Video of a talk I gave on design vs. decoration.&lt;/a&gt;&lt;/video&gt;
  &lt;figcaption&gt;Our greatest advantage: we practice design; surveillance capitalists practice decoration.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Instead of merely going on my gut instinct and over three decades of experience in living and breathing technology, I wanted to make sure I tried a wide gamut of what’s currently available so, before writing this I tried the following distributions: Ubuntu 18.04, elementaryOS, Kubuntu, Mint, Nitrux, Solus, Deepin, Feren, and PureOS. The distribution that comes closest to Pop!_OS right now is PureOS. I also have notebooks running the latest Windows 10 and macOS. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;That&amp;rsquo;s not to say that it is perfect (nothing is), but that there is tremendous potential here. As I write this footnote, for example, my external trackpad (an Apple Magic Trackpad) isn&amp;rsquo;t working because the bluetooth adapter &lt;a href=&#34;https://bbs.archlinux.org/viewtopic.php?id=236647&#34;&gt;didn&amp;rsquo;t survive waking from sleep&lt;/a&gt;. The other day it was the WiFi. On the same trackpad, when I click, the mouse cursor jumps, giving me the electronic equivalent shaky hands. These are the sorts of issues that we must triage to high priority. They&amp;rsquo;re not as easy to fix or sexy to work on as moving things around in an interface. But they constitute the core of an experience and make all the difference in empowering people when they work well and enfeebling them when they don&amp;rsquo;t. For a great example of work in this area, &lt;a href=&#34;https://williambharding.com/blog/technology/linux-touchpad-like-a-macbook-goal-worth-pursuing/&#34;&gt;see (and support, if you can) William B. Harding&amp;rsquo;s effort to improve the experience of touchpads under Linux&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Which I’ll henceforth be pronouncing as “gig”, because why not? &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Camera fix for Samsung S9 (LineageOS 15.1)</title>
      <link>https://ar.al/2018/07/22/camera-fix-for-samsung-s9-lineageos-15.1/</link>
      <pubDate>Sun, 22 Jul 2018 13:30:37 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/22/camera-fix-for-samsung-s9-lineageos-15.1/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/22/camera-fix-for-samsung-s9-lineageos-15.1/s9&amp;#43;-first-photo.jpg&#34;
         alt=&#34;A photo of my XPS 13 on our dining room table. On screen is the Gnome Web browser showing the LineageOS bug thread for the S9 camera issue, focussed on the comment by Jesse Chan with the link to the vendor.img file that works to fix it.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The first photo from my Samsung S9+ is of the solution that got the camera working.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The camera app on LineageOS 15.1 &lt;a href=&#34;https://jira.lineageos.org/browse/BUGBASH-1784&#34;&gt;is broken&lt;/a&gt; for Samsung S9 and S9+ phones. The app launches successfully but hangs shortly thereafter with a “camera not responding” error while attempting to activate the camera. This seems to be &lt;a href=&#34;https://www.reddit.com/r/LineageOS/search?q=s9%20camera&amp;amp;restrict_sr=1&#34;&gt;a common problem&lt;/a&gt;, and the common advice given is to &lt;a href=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/&#34;&gt;flash the latest stock Samsung firmware&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sadly, that didn&amp;rsquo;t work for me.&lt;/p&gt;
&lt;p&gt;What did work, after flashing the latest stock ROM and reinstalling LineageOS 15.1, was flashing &lt;a href=&#34;https://jira.lineageos.org/browse/BUGBASH-1784?focusedCommentId=25316&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-25316&#34;&gt;the vendor.img file provided by Jesse Chan&lt;/a&gt;.&lt;/p&gt;
&lt;p class=&#39;important-warning&#39;&gt;&lt;svg class=&#39;warning-icon&#39; viewBox=&#39;0 0 1792 1896.0833&#39; alt=&#39;Warning!&#39;&gt;&lt;use class=&#39;warning-path&#39; xlink:href=&#39;https://ar.al/icons/font-awesome.svg#warning&#39;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;span&gt;These instructions are specific to my unique setup. You might have to adapt them to yours. Flashing firmware carries the risk of potentially bricking your device. Needless to say, &lt;strong&gt;proceed at your own risk.&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To flash the vendor image, &lt;a href=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/&#34;&gt;follow the instructions in my previous post to set up Heimdall, etc.&lt;/a&gt;, and, once you&amp;rsquo;ve got LineageOS 15.1 running successfully on the phone:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reboot into Download Mode&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Download and flash the &lt;em&gt;vendor.img&lt;/em&gt; file provided by Jesse:
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;heimfall flash --VENDOR vendor.img&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The phone should reboot and your camera app should now work.&lt;/p&gt;
&lt;p&gt;How this is different from the &lt;em&gt;vendor.img&lt;/em&gt; file in the latest stock ROM I flashed, I do not know. I do hope that the LineageOS folks will document this issue at the very least and hopefully incorporate a seamless fix for it in the future.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Hold down the power, volume down, and Bixby buttons - that’s the button on the top-right, along with the second and third buttons from the top on the left. When you see the Download Mode splash screen, press the Volume Up button as instructed to enter Download Mode. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Upgrade Little Snitch Before Upgrading to Mojave Beta</title>
      <link>https://ar.al/2018/07/21/upgrade-little-snitch-before-upgrading-to-mojave-beta/</link>
      <pubDate>Sat, 21 Jul 2018 08:40:56 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/21/upgrade-little-snitch-before-upgrading-to-mojave-beta/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/21/upgrade-little-snitch-before-upgrading-to-mojave-beta/little-snitch-nightly.jpg&#34;
         alt=&#34;Header from the Little Snitch Nightly download web page showing the Little Snitch propeller hat logo, the following text: &amp;#39;Nightly BuildLittle Snitch 4.1.3 (5180). Runs on macOS 10.11&amp;#43;. Nightly builds are pre-release versions of Little Snitch that include the latest bug fixes and feature enhancements.&amp;#39;, and a green Download button.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Little Snitch: use the nightly build on the macOS Mojave beta.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you have the current release version of &lt;a href=&#34;https://www.obdev.at/products/littlesnitch/index.html&#34;&gt;Little Snitch&lt;/a&gt; (4.1.2) installed under High Sierra, upgrade it to &lt;a href=&#34;https://obdev.at/products/littlesnitch/download-nightly.html&#34;&gt;the latest nightly build&lt;/a&gt; before upgrading to the &lt;a href=&#34;https://developer.apple.com/macos/&#34;&gt;macOS&lt;/a&gt; &lt;a href=&#34;https://www.apple.com/macos/mojave-preview/&#34;&gt;Mojave&lt;/a&gt; beta or your system will lose network access.&lt;/p&gt;
&lt;p&gt;If, like me, you did upgrade and lose network access, the workaround is simple: uninstall the release version of Little Snitch (make sure you keep your rules and settings) reinstall the nightly build.&lt;/p&gt;
&lt;p&gt;To uninstall Little Snitch, run the uninstaller app and follow the on-screen instructions.&lt;/p&gt;
&lt;p&gt;You can run the uninstaller from Terminal with the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;/Library/Little&lt;span style=&#34;color:#4070a0;font-weight:bold&#34;&gt;\ &lt;/span&gt;Snitch/Little&lt;span style=&#34;color:#4070a0;font-weight:bold&#34;&gt;\ &lt;/span&gt;Snitch&lt;span style=&#34;color:#4070a0;font-weight:bold&#34;&gt;\ &lt;/span&gt;Uninstaller.app&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://forums.developer.apple.com/thread/103802&#34;&gt;Internet access not working after install of Mojave&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forums.macrumors.com/threads/how-to-uninstall-little-snitch-completely.876772/&#34;&gt;How to uninstall Little Snitch completely&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>iOTA 360</title>
      <link>https://ar.al/2018/07/19/iota-360/</link>
      <pubDate>Thu, 19 Jul 2018 22:49:33 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/19/iota-360/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/19/iota-360/iota-shell.jpg&#34;
         alt=&#34;A red iOTA 360 notebook resting on a wooden table&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Do you give an iota about a £99 computer?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m writing this on a £99 computer.&lt;/p&gt;
&lt;p&gt;Well, it &lt;em&gt;was&lt;/em&gt; a £99 computer over at Amazon&amp;rsquo;s Prime Day sale this week. Today, it&amp;rsquo;s apparently a £171.32 computer.&lt;/p&gt;
&lt;p&gt;While I hate contributing to Jeff Bezoz&amp;rsquo;s Fuck Off To Mars Fund, I wanted to see what a £99 notebook could do so I ordered one. If nothing else, I&amp;rsquo;d have a low-end device for testing sites on IE and Edge instead of firing up a VM.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/19/iota-360/iota-open.jpg&#34;
         alt=&#34;The iOTA 360 in laptop orientation with the guide sticker on the monitor pointing out the various buttons and ports.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The iOTA 360 shows attention to detail and polish you wouldn&amp;rsquo;t expect at this price point.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So what kind of notebook does £99 get you these days? One with a 1.96Ghz quad core Atom processor and 2GB of RAM which aren&amp;rsquo;t going to win any performance awards but haven&amp;rsquo;t stopped me from doing anything I&amp;rsquo;ve wanted to do so far, 32GB of &lt;a href=&#34;https://www.howtogeek.com/196541/emmc-vs.-ssd-not-all-solid-state-storage-is-equal/&#34;&gt;eMMC&lt;/a&gt; storage that&amp;rsquo;s ample for web development, and Windows 10, which is what you get when you combine the elegance of old men who wear socks in sandals with the Chinese government&amp;rsquo;s respect for privacy and Martin Shkreli&amp;rsquo;s sense of ethics.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/19/iota-360/iota-keyboard.jpg&#34;
         alt=&#34;Close-up of the iOTA 360&amp;#39;s keyboard&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The keyboard does flex while typing but isn&amp;rsquo;t uncomfortable.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It also has a passable 11.6&amp;quot; screen running at 1366 x 768 that is best viewed at 125% scale. (It&amp;rsquo;s even nicer at 150% but some system dialogs get cut off and cannot be scrolled, leaving certain buttons unreachable.)&lt;/p&gt;
&lt;p&gt;The iOTA 360 quite happily runs &lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;my blogging setup&lt;/a&gt;, including node.js, &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;, and &lt;a href=&#34;https://code.visualstudio.com/&#34;&gt;Visual Studio Code&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/19/iota-360/screenshot.png&#34;
         alt=&#34;Screenshot of my blogging setup with the code for this post open in Visual Studio Code on the left and a browser window showing the post itself on the right-side of the screen.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Web development is definitely possible with this machine.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Could this democratise computing? Could you use this thing for web design? Is it a surveillance device?&lt;/p&gt;
&lt;p&gt;Yes to all.&lt;/p&gt;
&lt;p&gt;I look forward to elaborating on those points in future posts.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/19/iota-360/iota-reflection.jpg&#34;
         alt=&#34;My reflection in the screen as I take a photo of the iOTA 360 as it boots.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Join me next time when I turn the iOTA 360 on and step into the panopticon cum shopping mall that is Windows 10&amp;hellip;&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>Typographical typing habits for Linux</title>
      <link>https://ar.al/2018/07/18/typographical-typing-habits-for-linux/</link>
      <pubDate>Wed, 18 Jul 2018 20:02:37 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/18/typographical-typing-habits-for-linux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/18/typographical-typing-habits-for-linux/antique-typewriter.jpg&#34;
         alt=&#34;Photo of antique typewriter&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Many of our bad habits date back to the limitations of typewriters.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Typography lends us the voice that the written word takes away from us. Good typography makes your message accessible and comprehensible and strengthens its intent. Bad typography can have the opposite effect: it can cloud your meaning and give you a raspy voice.&lt;/p&gt;
&lt;p&gt;I first started making an effort to write using semantic typographical habits &lt;a href=&#34;http://www.breakingthin.gs/2012-07-13-on-practicality.html&#34;&gt;about six years ago&lt;/a&gt;. This involved, among other things, memorising the various keyboard shortcuts on macOS for printing typographical quotes instead of typewriter quotes and using en dashes, em dashes, and the minus sign for their allotted purposes instead of hyphen-dashes for everything like a caveman that thinks everything is a nail because all he has is a tiny little club&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Switching to Linux means that I have to learn new habits&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Some environments provide automatic conversion of such things (i.e, “smart quotes” functionality) and some even do it well. I strongly support such efforts and believe they should be the norm. However, it’s still a good habit to get into to write using semantic typography to begin with. Not least because smart quotes is not a universal feature and even the best implementations won&amp;rsquo;t necessarily cover all typographical elements.&lt;/p&gt;
&lt;h3 id=&#34;alt-graph-and-compose-keys&#34;&gt;Alt Graph and Compose keys&lt;/h3&gt;
&lt;p&gt;When seeking to develop good typographical typing habits on Linux, you have two friends you can count on: the &lt;a href=&#34;https://en.wikipedia.org/wiki/AltGr_key&#34;&gt;Alt Graph key&lt;/a&gt; (&lt;kbd&gt;AltGr&lt;/kbd&gt;) and the Compose key.&lt;/p&gt;
&lt;p&gt;More than likely, if you&amp;rsquo;re not on a touch-based device, you can see the &lt;kbd&gt;AltGr&lt;/kbd&gt; key on the keyboard in front of you. You might be wondering about the other one, however. That&amp;rsquo;s because the Compose doesn&amp;rsquo;t exist on modern keyboard. You can, however, easily map an existing key to be your compose key under Gnome. We’ll see how after the next section.&lt;/p&gt;
&lt;h3 id=&#34;the-alt-graph-key&#34;&gt;The Alt Graph Key&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start with the simpler of the two methods; the one that works right out of the box.&lt;/p&gt;
&lt;p&gt;The &lt;kbd&gt;AltGr&lt;/kbd&gt; key modifier, combined with the &lt;kbd&gt;Shift&lt;/kbd&gt; key modifier, exposes two separate layers of characters on the keyboard. Using the &lt;kbd&gt;AltGr&lt;/kbd&gt; key, you can easily enter a number of useful everyday characters including the following&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key(s)&lt;/th&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;v&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;“&lt;/td&gt;
&lt;td&gt;Open double curly quotes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;b&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;”&lt;/td&gt;
&lt;td&gt;Close double curly quotes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;v&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;‘&lt;/td&gt;
&lt;td&gt;Open single curly quote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;b&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;’&lt;/td&gt;
&lt;td&gt;Open single curly quote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;3&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;#&lt;/td&gt;
&lt;td&gt;Octothorpe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;,&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;─&lt;/td&gt;
&lt;td&gt;Minus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;,&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;Multiplication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;.&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;td&gt;Middle dot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;.&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;÷&lt;/td&gt;
&lt;td&gt;Division&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;©&lt;/td&gt;
&lt;td&gt;Copyright&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;y&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;←&lt;/td&gt;
&lt;td&gt;Left arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;i&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Right arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;u&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;↓&lt;/td&gt;
&lt;td&gt;Down arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;kbd&gt;Shift&lt;/kbd&gt; &lt;kbd&gt;u&lt;/kbd&gt;&lt;/td&gt;
&lt;td&gt;↑&lt;/td&gt;
&lt;td&gt;Up arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So those are glyphs that you can start to use right away via the &lt;kbd&gt;AltGr&lt;/kbd&gt; key. For the second method, you’re going to have to install a Gnome app.&lt;/p&gt;
&lt;h3 id=&#34;the-compose-key&#34;&gt;The Compose Key&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/18/typographical-typing-habits-for-linux/tweaks.png&#34;
         alt=&#34;Screenshot of the Gnome Tweaks app, showing the Compose Key setting under the Keyboard &amp;amp;amp; Mouse section&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A cheeky tweak gets you the coveted Compose key.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Compose key, introduced in 1983, never became a standard but it is hugely useful and rather delightful to use nevertheless. It&amp;rsquo;s based on the concept of using a mnemonic sequence of easy-to-type characters to specify glyphs that are not readily available on your keyboard.&lt;/p&gt;
&lt;p&gt;To set up and use your Compose key:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install and launch &lt;a href=&#34;https://wiki.gnome.org/Apps/Tweaks&#34;&gt;Gnome Tweaks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate to &lt;em&gt;Keyboard &amp;amp; Mouse&lt;/em&gt; and choose a &lt;em&gt;Compose Key&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#39;half-width-flush-right&#39;&gt;
  &lt;img src=&#39;compose-key.png&#39; alt=&#39;Screenshot of the compose key selection dialog.&#39;&gt;
  &lt;figcaption&gt;&lt;p&gt;Turn it on and choose a key.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Initially, I set my Compose key to &lt;kbd&gt;Scroll Lock&lt;/kbd&gt; while using my external keyboard&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, only to discover that my laptop didn’t have one. So now I use &lt;kbd&gt;Caps Lock&lt;/kbd&gt;, an otherwise rather useless key when you think about it.&lt;/p&gt;
&lt;p&gt;To enter extended characters with this method, you press the Compose key followed by a key combination. The key combinations are delightfully clever and make a huge amount of sense.&lt;/p&gt;
&lt;p&gt;Here are some examples of the most useful ones I’ve discovered so far:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;th&gt;Keys (spelled out)&lt;/th&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ndash;.&lt;/td&gt;
&lt;td&gt;dash-dash-dot&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;en-dash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;dash-dash-dash&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;em-dash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;..&lt;/td&gt;
&lt;td&gt;dot-dot&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;ellipsis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.-&lt;/td&gt;
&lt;td&gt;dot-dash&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;td&gt;middle dot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.=&lt;/td&gt;
&lt;td&gt;dot-equals&lt;/td&gt;
&lt;td&gt;•&lt;/td&gt;
&lt;td&gt;filled bullet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;oc&lt;/td&gt;
&lt;td&gt;o-c&lt;/td&gt;
&lt;td&gt;©&lt;/td&gt;
&lt;td&gt;copyright&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tm&lt;/td&gt;
&lt;td&gt;t-m&lt;/td&gt;
&lt;td&gt;™&lt;/td&gt;
&lt;td&gt;trademark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;c=&lt;/td&gt;
&lt;td&gt;c-equals&lt;/td&gt;
&lt;td&gt;€&lt;/td&gt;
&lt;td&gt;Euro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;c/&lt;/td&gt;
&lt;td&gt;c–forward slash&lt;/td&gt;
&lt;td&gt;¢&lt;/td&gt;
&lt;td&gt;Cent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;l-&lt;/td&gt;
&lt;td&gt;l-dash&lt;/td&gt;
&lt;td&gt;£&lt;/td&gt;
&lt;td&gt;Pound&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&amp;gt;&lt;/td&gt;
&lt;td&gt;dash–greater than&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;Right arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-&amp;lt;&lt;/td&gt;
&lt;td&gt;less than-dash&lt;/td&gt;
&lt;td&gt;←&lt;/td&gt;
&lt;td&gt;Left Arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xx&lt;/td&gt;
&lt;td&gt;x-x&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;Multiplication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-:&lt;/td&gt;
&lt;td&gt;dash-colon&lt;/td&gt;
&lt;td&gt;÷&lt;/td&gt;
&lt;td&gt;Division&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;=/&lt;/td&gt;
&lt;td&gt;equals–forward slash&lt;/td&gt;
&lt;td&gt;≠&lt;/td&gt;
&lt;td&gt;Not equal to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;one-two&lt;/td&gt;
&lt;td&gt;½&lt;/td&gt;
&lt;td&gt;Half&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;three-four&lt;/td&gt;
&lt;td&gt;¾&lt;/td&gt;
&lt;td&gt;Three quarters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;^0&lt;/td&gt;
&lt;td&gt;caret-zero&lt;/td&gt;
&lt;td&gt;⁰&lt;/td&gt;
&lt;td&gt;Superscript zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;^9&lt;/td&gt;
&lt;td&gt;caret-nine&lt;/td&gt;
&lt;td&gt;⁹&lt;/td&gt;
&lt;td&gt;Superscript nine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If the built-in ones do not meet your needs, you can also add &lt;a href=&#34;https://askubuntu.com/questions/47496/how-can-i-add-a-custom-compose-key-sequence#71335&#34;&gt;custom Compose key sequences&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope you find this useful and that it helps you express yourself better while typing on a Linux machine. If you have any favourite glyphs that I’ve missed, &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;let me know via Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://alvinalexander.com/linux-unix/how-to-smart-quotes-on-ubuntu-keystrokes-macros&#34;&gt;How to type smart quotes on Ubuntu Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://askubuntu.com/questions/1028957/how-to-set-a-compose-key-in-ubuntu-18-04&#34;&gt;How to set a Compose Key in Ubuntu 18.04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;credits&#34;&gt;Credits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://skitterphoto.com/photos/2332/old-typewriter&#34;&gt;Old typewriter&lt;/a&gt; by &lt;a href=&#34;https://skitterphoto.com/photographers/7/peter-heeling&#34;&gt;Peter Heeling&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;style&gt;
  /* Set the table to fill (and slightly overflow, for visual effect) the width. */
  table { width: 105%; }

  /* Left-align headings. */
  th { text-align: left; }
&lt;/style&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Yakub Marian has a beautifully-written and comprehensive article titled &lt;a href=&#34;https://jakubmarian.com/hyphen-minus-en-dash-and-em-dash-difference-and-usage-in-english/&#34;&gt;Hyphen, minus, en-dash, and em-dash: difference and usage in English&lt;/a&gt; that I strongly urge you to read. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And that&amp;rsquo;s part of the fun. You always learn the most when you force yourself outside of your comfort zone. In the last two weeks alone I&amp;rsquo;ve learned heaps about how operating systems boot (due to an issue I had while trying to install first Ubuntu 18.04 and then &lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS&lt;/a&gt; on my XPS 13) and about flashing ROMs onto mobile phones. (I&amp;rsquo;m also switching my main phone to &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt; and I had an issue with the camera not working on my S9+ so &lt;a href=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/&#34;&gt;I had to revert back to the stock ROM&lt;/a&gt;.) &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The key sequences listed in the Alt Graph Key section are from my keyboard layout, which is English (UK, Macintosh). The actual physical keyboard layout I use is ISO. The exact combinations may vary depending on specific setup. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The excellent &lt;a href=&#34;https://deskthority.net/wiki/Filco_Majestouch&#34;&gt;Filco Majestouch&lt;/a&gt; 2 Ninja Tenkeyless. &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Open from terminal in Linux</title>
      <link>https://ar.al/2018/07/17/open-from-terminal-in-linux/</link>
      <pubDate>Tue, 17 Jul 2018 22:38:23 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/17/open-from-terminal-in-linux/</guid>
      <description>&lt;p&gt;On macOS, if you want to open a file in its default viewer/editor from Terminal, you use the &lt;code&gt;open&lt;/code&gt; command like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;open name-of.file&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On X-based Linux systems, you can do the same thing using a command called &lt;code&gt;xdg-open&lt;/code&gt;. However, it doesn&amp;rsquo;t really have the same ring to it, does it?&lt;/p&gt;
&lt;p&gt;If you want to speak human instead of xdg, add an alias to your shell configuration file (for bash, &lt;em&gt;~/.bashrc&lt;/em&gt;, for zsh, &lt;em&gt;~/.zshrc&lt;/em&gt;). In my case, all I had to do was:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Add the alias to your shell configuration. &lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;alias open=&amp;#39;xdg-open&amp;#39;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; ~/.zshrc

&lt;span style=&#34;color:#60a0b0;font-style:italic&#34;&gt;# Load your updated configuration.&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;source&lt;/span&gt; ~/.zshrc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now you can open directories in &lt;a href=&#34;https://en.wikipedia.org/wiki/GNOME_Files&#34;&gt;Files&lt;/a&gt; using:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;open .&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And you can open images in &lt;a href=&#34;https://wiki.gnome.org/Apps/EyeOfGnome/&#34;&gt;Image Viewer&lt;/a&gt; and any other type of file in its default app using &lt;code&gt;open &amp;lt;name of file&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Enabling Better Blocker in GNOME Web</title>
      <link>https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/</link>
      <pubDate>Tue, 17 Jul 2018 17:40:50 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/my-site-in-gnome-web.png&#34;
         alt=&#34;Screenshot of the index of my site in GNOME Web&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;GNOME Web: what ethical design looks like.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Update: (2020-03-14)&lt;/strong&gt; GNOME Web now supports &lt;a href=&#34;https://webkit.org/blog/3476/content-blockers-first-look/&#34;&gt;WebKit Content Blocker rules&lt;/a&gt; but sadly &lt;a href=&#34;https://gitlab.gnome.org/GNOME/epiphany/issues/77&#34;&gt;still comes with&lt;/a&gt; AdBlock rules that do not protect your privacy (they block some ads) by default. I’ve updated this instructions here for replacing them with &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt;’s rules instead if you care about protecting your privacy.&lt;/p&gt;
&lt;p&gt;TL;DR: Enter the following line in a terminal and you’re done:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;gsettings &lt;span style=&#34;color:#007020&#34;&gt;set&lt;/span&gt; org.gnome.Epiphany content-filters &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;[&amp;#39;https://better.fyi/blockerList.json&amp;#39;]&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;&lt;strike&gt;&lt;a href=&#34;https://wiki.gnome.org/Apps/Web&#34;&gt;GNOME Web&lt;/a&gt; is an &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical&lt;/a&gt;, simple, elegant, and beautiful web browser. And, since &lt;a href=&#34;https://ar.al/2018/07/16/changes/&#34;&gt;I switched my main development machine to one running GNOME Shell&lt;/a&gt;, it is now my default browser.&lt;/p&gt;
&lt;p&gt;Unlike products like Chrome and Firefox from surveillance capitalists like Google and Mozilla, GNOME Web has safe, private defaults that respect your human rights. As&lt;/strike&gt; their privacy policy states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GNOME Web receives no funding from advertisers, and as such is able to offer privacy features like built-in adblocking and tracking query removal that are relegated to extensions by other browsers. We aim to offer the best out-of-the-box privacy settings of any general purpose web browser.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How refreshing. How just as it should be.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/gnome-web-defaults.png&#34;
         alt=&#34;Screenshot of the GNOME Web Preferences panel, showing that the &amp;#39;try to block advertisements&amp;#39; and &amp;#39;try to block web trackers&amp;#39; settings are on by default.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;GNOME Web is private by default. That&amp;rsquo;s the only definition of privacy that matters. Contrast that to &amp;lsquo;tracked and profiled by default but with the choice of turning the surveillance off&amp;rsquo; in Mozilla Firefox.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;tracker-and-adtech-blocking&#34;&gt;Tracker and adtech blocking&lt;/h3&gt;
&lt;p&gt;By default, GNOME Web comes with ad &lt;strike&gt;and tracker&lt;/strike&gt; blocking based on AdBlock Plus . To see the default lists, you can use the following command in a terminal window:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;gsettings get org.gnome.Epiphany content-filters&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The result should be an array of URLs to blocking lists in EasyList format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;[&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;https://easylist-downloads.adblockplus.org/easylist_min_content_blocker.json&amp;#39;&lt;/span&gt;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strike&gt;This is good but I feel&lt;/strike&gt; we can make it… uhum… Better! 🤓&lt;/p&gt;
&lt;h3 id=&#34;whys-better-any-better&#34;&gt;Why&amp;rsquo;s Better any better?&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/17/enabling-better-blocker-in-gnome-web/better-blocker.png&#34;
         alt=&#34;Screenshot of the Better Blocker web site showing better running on a Mac, iPad, and iPhone&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Better Blocker: it&amp;rsquo;s not just for Apple&amp;rsquo;s platforms anymore.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; and I built &lt;a href=&#34;https://better.fyi&#34;&gt;Better Blocker&lt;/a&gt; for three reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We were hugely frustrated with the bullshit of so-called &amp;lsquo;ad blockers&amp;rsquo; like AdBlock Plus by Eyeo who actually &lt;a href=&#34;https://betanews.com/2015/02/02/whats-the-point-in-adblock-plus-if-google-microsoft-and-amazon-can-pay-to-bypass-it/&#34;&gt;get paid millions of dollars by surveillance capitalists like Google &lt;strong&gt;not to block&lt;/strong&gt; adtech and trackers&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We wanted to have a curated list of blocking rules that we understood. Looking at &lt;a href=&#34;https://easylist.to/easylist/easylist.txt&#34;&gt;an EasyList block list&lt;/a&gt;, I have no idea why any of the rules were included. Reading through the entries of &lt;a href=&#34;https://better.fyi/trackers&#34;&gt;our hand-curated database of trackers&lt;/a&gt;, on the other hand, I know exactly why.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We wanted a tracker blocker that didn&amp;rsquo;t require any technical knowledge to install and use on the platforms that were the primary platforms for both of us at the time (iOS and macOS).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So we built Better as a &lt;a href=&#34;https://source.small-tech.org/better&#34;&gt;free and open&lt;/a&gt; tracker blocker that is also available for sale on the &lt;a href=&#34;https://itunes.apple.com/us/app/better-by-ind.ie/id1080964978?ls=1&amp;amp;mt=8&#34;&gt;iOS&lt;/a&gt; and &lt;a href=&#34;https://itunes.apple.com/us/app/better/id1121192229?ls=1&amp;amp;mt=12&#34;&gt;macOS&lt;/a&gt; app stores.&lt;/p&gt;
&lt;p&gt;Better is entirely independent of the adtech industry and surveillance capitalism and uses our &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical design manifesto&lt;/a&gt; as its guiding principle in deciding what to block and what not to block. Sales of Better don&amp;rsquo;t pay our bills entirely but they do contribute towards our ability to do so when combined with &lt;a href=&#34;https://small-tech.org/videos/&#34;&gt;our professional speaking&lt;/a&gt; fees and the proceeds from &lt;a href=&#34;https://small-tech.org/fund-us/&#34;&gt;our patrons&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;how-better-works&#34;&gt;How Better works&lt;/h3&gt;
&lt;p&gt;With Better, we have a straightforward and transparent process for deciding which trackers to block:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We &lt;a href=&#34;https://source.small-tech.org/better/inspector&#34;&gt;crawl&lt;/a&gt; the most popular domains in the world to find the third-party trackers on them.&lt;/li&gt;
&lt;li&gt;We organise the trackers by popularity.&lt;/li&gt;
&lt;li&gt;We manually go through these top trackers, document what they do, and block them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This works very well because the Achilles&#39; heel of centralisation can be summed up by the old adage &amp;ldquo;the bigger they are, the harder they fall.&amp;rdquo; Centralised topologies are, by their very nature, both very powerful and extremely fragile.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&#34;cleaning-up-the-web&#34;&gt;Cleaning up the Web&lt;/h3&gt;
&lt;p&gt;You do not need to block every tracker ever made to choke the income stream of surveillance capitalism, you just need to block the centres; the Goliaths – the biggest players, who among themselves account for 99% of all tracking on the Web today.&lt;/p&gt;
&lt;p&gt;Google, for example, has eyes on 70-80%&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; of the web. While that&amp;rsquo;s horrible, it also means that once we block their trackers, we&amp;rsquo;ve cleared a huge swath of surveillance from the Web in one go. Rinse and repeat for the top-however-many-thousand trackers and you&amp;rsquo;ve done a pretty good job of rinsing the web of surveillance devices.&lt;/p&gt;
&lt;p&gt;A happy side-effect of blocking trackers and adtech is that you also greatly improve the experience of the Web. Adtech and tracking scripts amount for a huge portion of the physical size and visual complexity of web sites. So much so that a few years ago, the worst site we could find was costing people in the US &lt;a href=&#34;http://observer.com/2016/10/aral-balkan-dokutech/&#34;&gt;over $4 million every month in mobile bandwidth costs&lt;/a&gt; just to load their trackers.&lt;/p&gt;
&lt;p&gt;So blocking adtech and trackers makes sites load faster, stops them from interrupting your experience with interstitial modals, reduces visual complexity and cognitive load, and generally gets the Web looking more like it would if it hadn&amp;rsquo;t been poisoned by the surveillance-based business model of Silicon Valley.&lt;/p&gt;
&lt;p&gt;The hand-curated nature of what we do also means that we can try and respond to those cases where things break as we control over and a good understanding of what the totality of the block rules actually do.&lt;/p&gt;
&lt;p&gt;&lt;strike&gt;However, because Better was originally created as a &lt;a href=&#34;https://webkit.org/blog/3476/content-blockers-first-look/&#34;&gt;native WebKit content blocker&lt;/a&gt; for iOS and macOS, the only people who could take advantage of it were people on Apple&amp;rsquo;s platforms. I was never happy with this but it was what we needed to do in order to keep the lights on.&lt;/p&gt;
&lt;p&gt;So, when I got the opportunity &lt;a href=&#34;https://twitter.com/aral/status/892123479453167616&#34;&gt;last year&lt;/a&gt;, I updated the &lt;a href=&#34;https://source.small-tech.org/better/site&#34;&gt;build process&lt;/a&gt; to also output &lt;a href=&#34;https://better.fyi/blockerList.txt&#34;&gt;the Better blocking rules in EasyList format&lt;/a&gt;.&lt;/strike&gt;&lt;/p&gt;
&lt;h3 id=&#34;a-better-gnome&#34;&gt;A Better GNOME&lt;/h3&gt;
&lt;p&gt;&lt;strike&gt;As Better&amp;rsquo;s blocking rules are now available in EasyList format, it means that you can use them on other platforms. For example, you can use them in Chromium and Firefox across all platforms supported by those browsers via &lt;a href=&#34;https://github.com/gorhill/uBlock/&#34;&gt;uBlock Origin&lt;/a&gt; – another no-bullshit tracker and adtech blocker.&lt;/p&gt;
&lt;p&gt;It also means that you can use Better in GNOME Web. Which brings me, finally, to the great reveal: &lt;em&gt;how?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s rather anti-climactic how easy it is.&lt;/strike&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update: (2020-03-14)&lt;/strong&gt; GNOME Web now supports WebKit content blocking rules, the same as Safari on iOS and macOS. I’ve updated the instructions below accordingly.&lt;/p&gt;
&lt;p&gt;To get GNOME Web to use Better&amp;rsquo;s blocking rules, all you need to do is to enter the following command in terminal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;gsettings &lt;span style=&#34;color:#007020&#34;&gt;set&lt;/span&gt; org.gnome.Epiphany content-filters &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;[&amp;#39;https://better.fyi/blockerList.json&amp;#39;]&amp;#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;That&amp;rsquo;s it!&lt;/strong&gt; 🎉&lt;/p&gt;
&lt;p&gt;That will replace the default filters with Better&amp;rsquo;s list.&lt;/p&gt;
&lt;p&gt;&lt;strike&gt;As long as you install it using the exact method above and our list is the only one you include, we will also be happy to offer you support&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. You can use our &lt;a href=&#34;https://source.ind.ie/better/content/issues&#34;&gt;issue tracker&lt;/a&gt; and &lt;a href=&#34;https://forum.ind.ie/c/better&#34;&gt;forums&lt;/a&gt; to report issues and keep in touch.&lt;/p&gt;
&lt;p&gt;GNOME Web is a beautiful example of &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical design&lt;/a&gt; that makes me smile every time I use it. Now that I have &lt;a href=&#34;https://better.fyi&#34;&gt;Better&lt;/a&gt; on it, I have another reason to smile. Here&amp;rsquo;s hoping it puts a smile on some of your faces too.&lt;/strike&gt;&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.archlinux.org/index.php/GNOME/Web#Blocking_advertisements&#34;&gt;Blocking advertisements in GNOME Web&lt;/a&gt; (GNOME Web Wiki)&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is a lesson that autocratic governments know all too well. It is also why they fear their own people more than anyone or anything else and try to control them through fear, mass surveillance, and violence. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The 70% statistic is from &lt;a href=&#34;https://www.technologyreview.com/s/601488/largest-study-of-online-tracking-proves-google-really-is-watching-us-all/&#34;&gt;a Princeton study&lt;/a&gt;. The 80% statistic is from our own web crawls using &lt;a href=&#34;https://source.ind.ie/better/inspector&#34;&gt;Better Inspector&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Laura and I can&amp;rsquo;t offer support when more than one blocking list is active as we simply do not have the capacity to debug what could be going wrong with third-party lists. It&amp;rsquo;s hard enough trying to fix the Web when you have full control over the blocking rules. It&amp;rsquo;s next to impossible when you have multiple lists that might be interacting with each other in wild and wonderful ways. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Changes</title>
      <link>https://ar.al/2018/07/16/changes/</link>
      <pubDate>Mon, 16 Jul 2018 16:42:29 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/16/changes/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/16/changes/current-setup.jpg&#34;
         alt=&#34;Photo of my desk showing a number of devices: from left to right, my MacBook, and iPad, a Samsung S9&amp;#43; with a Microsoft foldable keyboard, a 21&amp;#34; LG monitor, a Filco Majestouch keyboard, a pair of AirPods in their case, a Logitech presentation remote in its gray leather sleeve, a Nexus 5 running LineageOS resting on a Logitech bluetooth keyboard originally for the iPad, a Magic Trackpad, and a Dell XPS 13 running Pop!_OS 18.04. All screens are off. Peeking from behind them is my Nabaztag bunny with one blue ear and one pink ear. To the far right, partially in the photo, is a pen holder with Muji pens, a coffee  mug, and my Leuchtturm notebook.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My current setup: work in progress.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For the last 12 years, my main development machine has been a Mac. As of last week, it&amp;rsquo;s a Dell XPS 13 running &lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS 18.04&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From the day the first iPhone was released, my main phone has been an iPhone. As of this week, it will be a Samsung S9+ running &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt;. (I had it running LineageOS last week but there was an issue with the camera that meant that &lt;a href=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/&#34;&gt;I had to reinstall the stock surveillance-ridden Samsung ROM&lt;/a&gt;. I plan to rectify that this week.)&lt;/p&gt;
&lt;h3 id=&#34;why-the-change&#34;&gt;Why the change?&lt;/h3&gt;
&lt;p&gt;There are two reasons I have been using and developing on Apple&amp;rsquo;s platforms for the last decade. Here they are in the order in which I was aware of them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Apple&amp;rsquo;s focus on experience design. This is not a cosmetic consideration. &lt;a href=&#34;http://www.breakingthin.gs/this-is-all-there-is.html&#34;&gt;Experiences are literally all we have in life&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apple has &lt;a href=&#34;https://2018.ar.al/notes/apple-vs-google-on-privacy-a-tale-of-absolute-competitive-advantage/&#34;&gt;an absolute competitive advantage in privacy&lt;/a&gt; over surveillance capitalists like Google, Facebook, etc. This is because they inherited their business model of selling products (not people) from the personal computer era.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, all in all, Apple does exceedingly well on the top two layers of the &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical design manifesto&lt;/a&gt; (respect for human effort and respect for human experience) and is the least of possible evils when it comes to multibillion-dollar multinational corporations that make proprietary mainstream technology in relation to the core layer (respect for human rights).&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/16/changes/ethical-design-hierarchy.png&#34;
         alt=&#34;Ethical design hierarchy: respect for human rights, effort, and experience&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Ethical design respects human rights, human effort, and human experience.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;That said, I am severely disappointed with how Apple employs one set of ethics when it comes to &amp;ldquo;the West&amp;rdquo; and another set when it comes to countries like China&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Apple has shown that they are willing to be complicit to government surveillance and censorship in authoritarian markets like China. Just because I have the privilege of living in a Western country where - for the time being - it is in Apple&amp;rsquo;s economic interests to protect my privacy doesn&amp;rsquo;t give me any trust in them for the future given that we live in a world of Trump and Brexit. I also wonder which of my friends in Turkey (where my family is from) will be sold out by Apple to Erdoğan once the latter starts imposing China-like demands (or maybe the market isn&amp;rsquo;t large enough so Apple will rediscover its backbone in Turkey). Needless to say, no multibillion-dollar multinational company is your friend.&lt;/p&gt;
&lt;h3 id=&#34;why-now-not-earlier&#34;&gt;Why now, not earlier?&lt;/h3&gt;
&lt;p&gt;Where proprietary systems and the people farming devices of surveillance capitalists fail at the core layer of the hierarchy of ethical design by violating human rights, Free and Open Source alternatives have a rich history of succeeding radically at the core while failing at respecting human effort and experience.&lt;/p&gt;
&lt;p&gt;If freedom technologies have any chance at competing with the products of surveillance capitalism, they must embrace design. If we expect people to use our wares because of our superior ideological arguments, we will be waiting a long time. Thankfully, there is a huge momentum of development groups that understand this and aren&amp;rsquo;t waiting around. Instead, they&amp;rsquo;re working hard to make convenient, usable, and - dare I say, delightful - tools and experiences that happen to protect your rights and freedoms.&lt;/p&gt;
&lt;p&gt;This brings us to the answer to the &amp;ldquo;why now?&amp;rdquo; question. Because I felt I could without negatively impacting my day to day experience.&lt;/p&gt;
&lt;p&gt;And it&amp;rsquo;s all thanks to the work that went into &lt;a href=&#34;https://www.gnome.org/gnome-3/&#34;&gt;Gnome Shell&lt;/a&gt;, &lt;a href=&#34;https://arstechnica.com/information-technology/2018/05/ubuntu-18-04-the-return-of-a-familiar-interface-marks-the-best-ubuntu-in-years/&#34;&gt;Ubuntu 18.04&lt;/a&gt;, &lt;a href=&#34;https://elementary.io/&#34;&gt;elementary OS&lt;/a&gt; and the curation of all three of these by &lt;a href=&#34;https://system76.com&#34;&gt;System76&lt;/a&gt; that resulted in the state of the art of GNU/Linux distribution that is &lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS 18.04&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Take note: this is the first operating system I&amp;rsquo;ve used that is simpler, more elegant, and does certain things better than macOS.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I never thought I&amp;rsquo;d ever write those words about GNU/Linux and it gives me a lot of hope for the future.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;See &lt;a href=&#34;https://techcrunch.com/2017/07/30/apples-capitulation-to-chinas-vpn-crack-down-will-return-to-haunt-it-at-home/&#34;&gt;Apple&amp;rsquo;s capitulation to China&amp;rsquo;s VPN crack-down will return to haunt it at home&lt;/a&gt;, &lt;a href=&#34;https://techcrunch.com/2017/07/30/apples-capitulation-to-chinas-vpn-crack-down-will-return-to-haunt-it-at-home/&#34;&gt;Apple&amp;rsquo;s China-friendly censorship caused an iPhone-crashing bug&lt;/a&gt;, &lt;a href=&#34;https://sanfrancisco.cbslocal.com/2018/02/28/apple-under-fire-icloud-data-china/&#34;&gt;Apple is under fire for moving iCloud data to China&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I&amp;rsquo;ll explore some of these in detail in future posts. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Adding “command not found” apt package suggestions to zsh</title>
      <link>https://ar.al/2018/07/16/adding-command-not-found-apt-package-suggestions-to-zsh/</link>
      <pubDate>Mon, 16 Jul 2018 12:49:05 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/16/adding-command-not-found-apt-package-suggestions-to-zsh/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/16/adding-command-not-found-apt-package-suggestions-to-zsh/enabling-apt-package-suggestions-in-zsh.png&#34;
         alt=&#34;Tilix terminal window showing the series of commands, detailed in this post, for enabling apt package suggestions in zsh.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;How apt: package suggestions under zsh.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Under the &lt;a href=&#34;https://www.gnu.org/software/bash/&#34;&gt;GNU Bash&lt;/a&gt; shell on a Debian-based system, if you type a command that is unrecognised, in addition to an error message telling you that the command is not found, you also get package suggestions for the &lt;a href=&#34;https://en.wikipedia.org/wiki/APT_(Debian)&#34;&gt;apt package manager&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is a wonderful pattern in interface design: instead of just telling you that something went wrong and leaving you to figure out what it was, we give you helpful suggestions to try and lead you in the right direction. In this specific use case, it also means that you get to discover new app packages that you might otherwise not have.&lt;/p&gt;
&lt;p&gt;My shell of choice, &lt;a href=&#34;https://www.zsh.org/&#34;&gt;zsh&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, doesn&amp;rsquo;t have this feature built-in but, thankfully, someone created a package called &lt;em&gt;command-not-found&lt;/em&gt; that you can easily install to enable it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt update
sudo apt install command-not-found
&lt;span style=&#34;color:#007020&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;source /etc/zsh_command_not_found&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; ~/.zshrc
&lt;span style=&#34;color:#007020&#34;&gt;source&lt;/span&gt; ~/.zshrc&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, when you try to execute a command that doesn&amp;rsquo;t exist, zsh will helpfully suggest a package or two that you might want to install that might have the command you were thinking of.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I use zsh with the excellent &lt;a href=&#34;https://ohmyz.sh/&#34;&gt;Oh My ZSH!&lt;/a&gt; configuration with the &lt;a href=&#34;https://github.com/agnoster/agnoster-zsh-theme&#34;&gt;agnoster&lt;/a&gt; theme with &lt;a href=&#34;https://github.com/powerline/fonts&#34;&gt;powerline fonts&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Flashing stock firmware onto a Samsung Galaxy S9&#43; (SM-G965F) on Ubuntu 18.04 using Heimdall</title>
      <link>https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9&#43;-sm-g965f-on-ubuntu-18.04-using-heimdall/</link>
      <pubDate>Sun, 15 Jul 2018 20:06:45 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9&#43;-sm-g965f-on-ubuntu-18.04-using-heimdall/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/samsung-stock-rom.jpg&#34;
         alt=&#34;Photo of my Samsung S9&amp;#43; showing a folder in the stock ROM with a bunch of Samsung apps.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Samsung stock Android.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;On the way back from London last week, I picked up an unlocked, EU region Samsung Galaxy S9+ to install &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt; on.&lt;/p&gt;
&lt;p&gt;When I got back, I proceeded to install the July 3rd &lt;a href=&#34;https://download.lineageos.org/star2lte&#34;&gt;nightly build&lt;/a&gt; and everything was good until I tried the camera app and it kept hanging. Apparently, &lt;a href=&#34;https://www.reddit.com/r/LineageOS/comments/8oflu2/samsung_s9_how_to_update_the_firmware/&#34;&gt;this is a known issue&lt;/a&gt; and a &amp;ldquo;won&amp;rsquo;t fix&amp;rdquo; as far as the LineageOS folks are concerned as it can be resolved by updating the stock firmware. Which, of course, means that you have to install said firmware again.&lt;/p&gt;
&lt;p&gt;(Lesson learned: before installing a different operating system, make sure you update your phone to the latest official firmware to potentially save yourself some trouble.)&lt;/p&gt;
&lt;h3 id=&#34;adventures-in-firmware&#34;&gt;Adventures in firmware&lt;/h3&gt;
&lt;p&gt;To complicate things, Samsung UK doesn&amp;rsquo;t make the firmware for its phones available on &lt;a href=&#34;https://www.samsung.com/uk/support/model/SM-G965FZAEXEU&#34;&gt;the S9+ support site&lt;/a&gt;. When I reached them for help via DM on Twitter, they very nicely told me where I could stick my new €1,000 phone because I had had the audacity to install a different operating system on it than the dark-pattern-ridden spyware/bloatware that it came with. Thanks, folks, great customer service! (Every day that passes, I&amp;rsquo;m thankful that Todd and the lovely folks at &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt; are working on full-stack freedom with their computers and upcoming phone.)&lt;/p&gt;
&lt;p&gt;Samsung&amp;rsquo;s reluctance to offer system restoration firmware for download means that a cottage industry, most of it rather shady, has popped up to fill the demand. Of the more reputable sites for information is &lt;a href=&#34;https://www.xda-developers.com/&#34;&gt;XDA Developers&lt;/a&gt; and that&amp;rsquo;s where I found &lt;a href=&#34;https://forum.xda-developers.com/galaxy-s9-plus/how-to/official-stock-firmware-thread-samsung-t3764479&#34;&gt;the firmware for my S9+&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Again, Samsung, you are failing your customers and potentially opening them up to security issues by not making the firmware available on your site. Please reconsider. The folks on XDA and SamMobile get it from you somehow and you know this is happening so why not &amp;ldquo;legalise&amp;rdquo; it so that people know they&amp;rsquo;re getting the real deal and don&amp;rsquo;t end up installing trojans and backdoors on their devices and end up making both themselves and the rest of us less safe.)&lt;/p&gt;
&lt;p&gt;That all said, finding the firmware was only the beginning of the journey.&lt;/p&gt;
&lt;p&gt;Here are the other steps I had to follow to install said firmware.&lt;/p&gt;
&lt;h3 id=&#34;flashing-with-heimdall&#34;&gt;Flashing with Heimdall&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/./heimdall.jpg&#34;
         alt=&#34;The god Heimdall stands among a group of people with a torch in his hand, framed by a tree in the center of this painting.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Heimdallr brings forth the gift of the gods to mankind (1907) by Nils Asplund&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;These instructions are for the Samsung S9+ (SM-G965F), EU region, unlocked, using the stock firmware linked to from &lt;a href=&#34;https://forum.xda-developers.com/galaxy-s9-plus/how-to/official-stock-firmware-thread-samsung-t3764479&#34;&gt;the &amp;ldquo;Official Stock Firmware Thread for S9+ (SM-G965F) on XDA&lt;/a&gt;. Furthermore, they assume that you are carrying out the flashing using &lt;a href=&#34;https://www.glassechidna.com.au/heimdall/&#34;&gt;Heimdall&lt;/a&gt; (son of &lt;a href=&#34;https://en.wikipedia.org/wiki/Odin_(firmware_flashing_software)&#34;&gt;Odin&lt;/a&gt;), on a computer running some variant of Debian so you can use the excellent &lt;a href=&#34;https://en.wikipedia.org/wiki/APT_(Debian)&#34;&gt;apt package manager&lt;/a&gt;. I&amp;rsquo;m currently running &lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS 18.04&lt;/a&gt; which is based on &lt;a href=&#34;https://elementary.io/&#34;&gt;elementary OS&lt;/a&gt;/&lt;a href=&#34;https://en.wikipedia.org/wiki/Ubuntu_(operating_system)&#34;&gt;Ubuntu&lt;/a&gt;.&lt;/p&gt;
&lt;p class=&#39;important-warning&#39;&gt;&lt;svg class=&#39;warning-icon&#39; viewBox=&#39;0 0 1792 1896.0833&#39; alt=&#39;Warning!&#39;&gt;&lt;use class=&#39;warning-path&#39; xlink:href=&#39;https://ar.al/icons/font-awesome.svg#warning&#39;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;span&gt;These instructions are specific to my unique setup. You might have to adapt them to yours. Flashing firmware carries the risk of potentially bricking your device. Needless to say, &lt;strong&gt;proceed at your own risk.&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Heimdall:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install heimdall-flash&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;a href=&#34;http://manpages.ubuntu.com/manpages/xenial/man1/lz4c.1.html&#34;&gt;lz4&lt;/a&gt; (you will need this later to uncompress files):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install liblz4-tool&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unzip all the files from the firmware file (which, in my case, is called &lt;em&gt;G965FXXU1BRF8.zip&lt;/em&gt;. This is a little like unpacking Russian dolls given that there&amp;rsquo;s a zip that contains &lt;a href=&#34;https://stackoverflow.com/questions/39173256/how-to-handle-tar-md5-files&#34;&gt;tar.md5&lt;/a&gt; files which contain lz4 files. The following commands will extract everything (this may take some time):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;unzip G965FXXU1BRF8.zip -d firmware &lt;span style=&#34;color:#666&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#007020&#34;&gt;cd&lt;/span&gt; firmware
&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;for&lt;/span&gt; f in *.tar; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;do&lt;/span&gt; tar xf &lt;span style=&#34;color:#bb60d5&#34;&gt;$f&lt;/span&gt;; &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;done&lt;/span&gt;
lz4 -dm *.lz4&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You should now have a bunch of files. The important ones end in &lt;em&gt;.img&lt;/em&gt; and &lt;em&gt;.bin&lt;/em&gt;. These are what we&amp;rsquo;re going to flash onto the partitions on your phone using Heimdall. If you want to jump right into doing that without understanding how we know how to map the files to the partitions, you can safely jump to Step 9 now. Otherwise, read on and learn&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To find out where we need to flash the various files we have, we need to ask Heimdall to inspect our phone and dump a Partition Information Table (PIT) file for us. To do this, first connect your phone to your computer via USB and boot the phone into Download Mode (hold down the &lt;em&gt;power&lt;/em&gt;, &lt;em&gt;volume down&lt;/em&gt;, and &lt;em&gt;Bixby&lt;/em&gt; buttons - that&amp;rsquo;s the button on the top-right, along with the second and third buttons from the top on the left). When you see the Download Mode splash screen, press the Volume Up button as instructed to enter Download Mode.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test the connection:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;heimdall detect&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump the PIT file using Heimdall. (Note: your phone will reboot after this. Enter Download Mode again using the technique you learned in Step 5):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;heimdall print-pit &amp;gt; phone.pit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open up &lt;em&gt;phone.pit&lt;/em&gt; in a text editor and search for the names of the extracted files in Step 3 that end with &lt;em&gt;.img&lt;/em&gt; and &lt;em&gt;.bin&lt;/em&gt;. Note the corresponding &lt;em&gt;Partition Name&lt;/em&gt; values as those are what you will be using in the next step as the names of the flags to the &lt;code&gt;heimdall flash&lt;/code&gt; command.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/15/flashing-stock-firmware-onto-a-samsung-galaxy-s9+-sm-g965f-on-ubuntu-18.04-using-heimdall/vscode-pit-file.png&#34;
         alt=&#34;Excerpt of my phone&amp;#39;s PIT file showing that the boot.img flash file maps to the BOOT partition name.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Partition name to image file name mapping.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;span id=&#39;step-9&#39;&gt;Flash&lt;span&gt; the firmware using Heimdall (your partition name -&amp;gt; flash filename mappings may vary. I&amp;rsquo;d highly recommend not skipping Steps 4-8 above and confirming that the mappings in your PIT file match before executing the following command):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo heimdall flash --BOOT boot.img --CACHE cache.img --CM cm.bin --DQMDBG dqmdbg.img --HIDDEN hidden.img --KEYSTORAGE keystorage.bin --RADIO modem.bin --CP_DEBUG modem_debug.bin --ODM odm.img --OMR omr.img --PARAM param.bin --RECOVERY recovery.img --BOOTLOADER sboot.bin --SYSTEM system.img --UP_PARAM up_param.bin --USERDATA userdata.img --VENDOR vendor.img&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That should do it! (Whew!)&lt;/p&gt;
&lt;p&gt;Your phone should restart and you may be dumped into a recovery screen (I was). Just unplug your USB cable and choose to reboot your phone and you should see the stock Samsung firmware boot up following a pulsating Samsung logo (this might take a little while). You should eventually be greeted with the &amp;ldquo;Hello!&amp;rdquo; screen and prompted to set up your phone.&lt;/p&gt;
&lt;p&gt;This process is much more convoluted than it should be (due, in no small part, to Samsung&amp;rsquo;s lack of cooperation). I hope that this post makes it a bit easier to grasp and carry out.&lt;/p&gt;
&lt;p&gt;A huge thank-you to everyone who documented their own experiences (see links above and references, below) and to &lt;a href=&#34;https://gitlab.com/BenjaminDobell&#34;&gt;Benjamin Dobell&lt;/a&gt; and &lt;a href=&#34;https://glassechidna.com.au/&#34;&gt;Glass Echidna&lt;/a&gt; for Heimdall without which none of this would be possible.&lt;/p&gt;
&lt;p&gt;Next up: I&amp;rsquo;ll be installing LineageOS again and hopefully have the camera work this time.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.xda-developers.com/showthread.php?t=1508703&#34;&gt;How to use Heimdall (xda)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://forum.xda-developers.com/showthread.php?t=2317311&#34;&gt;How to install stock ROM using Heimdall under Linux (xda)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://zderadicka.eu/flashing-rom-to-samsung-phone/&#34;&gt;Flashing ROM to Samsung Phone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/a/583891&#34;&gt;How can you untar more than one file at a time?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Heimdallr&#34;&gt;Heimdallr (Wikipedia)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Initial git configuration</title>
      <link>https://ar.al/2018/07/13/initial-git-configuration/</link>
      <pubDate>Fri, 13 Jul 2018 20:00:22 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/13/initial-git-configuration/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m setting up my new XPS 13 running &lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS&lt;/a&gt; and one of the things I always have to do on any new machine is to configure &lt;a href=&#34;https://git-scm.com/&#34;&gt;git&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, both for my own reference and in case it helps anyone else, here&amp;rsquo;s a list of things I do to set up git:&lt;/p&gt;
&lt;h3 id=&#34;set-up-my-identity-used-in-commits-tags-etc&#34;&gt;Set up my identity (used in commits, tags, etc.)&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;git config --global user.name &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;Aral Balkan&amp;#34;&lt;/span&gt;
git config --global user.email mail@ar.al&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;set-up-automatic-signing-of-my-commits&#34;&gt;Set up automatic signing of my commits&lt;/h3&gt;
&lt;p&gt;I &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work&#34;&gt;sign my work&lt;/a&gt; and so should you (it&amp;rsquo;s about security, not vanity).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Either &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work&#34;&gt;generate GPG keys&lt;/a&gt; if you don&amp;rsquo;t already have a pair or &lt;a href=&#34;https://www.debuntu.org/how-to-importexport-gpg-key-pair/&#34;&gt;export/import your existing GPG keys&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure git to sign all your work:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;git config --global commit.gpgsign true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can check that it&amp;rsquo;s working by making a commit and then viewing at it in the log with the &lt;code&gt;--show-signature&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;git log -1 --show-signature&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you get a warning along the lines of &lt;em&gt;WARNING: This key is not certified with a trusted signature! There is no indication that the signature belongs to the owner&lt;/em&gt; after importing your GPG key to a new machine, do the following (substituting your email address for mine):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;gpg --edit-key aral@ind.ie&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, in the gpg shell, use the &lt;code&gt;trust&lt;/code&gt; command and select &lt;em&gt;5 = I trust ultimately&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/13/initial-git-configuration/gpg-edit-key-trust-5.png&#34;
         alt=&#34;Screenshot of terminal showing the result of the commands issued in the instructions above.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;If you don&amp;rsquo;t trust yourself, who are you going to trust?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;create-ssh-keys-for-the-machine-and-import-them-into-hosted-git-remotes-like-gitlab&#34;&gt;Create ssh keys for the machine and import them into hosted git remotes like GitLab&lt;/h3&gt;
&lt;p&gt;In order to clone and push to git remotes using ssh, you have to create an ssh keypair and then upload it to your git remote (in my case, we use our own &lt;a href=&#34;https://about.gitlab.com/&#34;&gt;GitLab&lt;/a&gt; instance running on &lt;a href=&#34;https://source.ind.ie&#34;&gt;source.ind.ie&lt;/a&gt;).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generate your ssh keys (substitute your email address for mine):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;ssh-keygen -t rsa -C &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;aral@ind.ie&amp;#34;&lt;/span&gt; -b &lt;span style=&#34;color:#40a070&#34;&gt;4096&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy it onto the system clipboard.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;cat ~/.ssh/id_rsa.pub | pbcopy&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note: On Linux, you can simulate the hugely useful pbcopy and pbpaste commands on macOS by adding the following to your &lt;em&gt;.zshrc&lt;/em&gt; or &lt;em&gt;.bashrc&lt;/em&gt; files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;pbcopy&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;xsel --clipboard --input&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#007020&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;pbpaste&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;xsel --clipboard --output&amp;#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&#34;disable-paging&#34;&gt;Disable paging&lt;/h3&gt;
&lt;p&gt;Set &lt;code&gt;cat&lt;/code&gt; as the pager so that git commands do not use paging (I&amp;rsquo;d much rather scroll than switch modes.)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;git config --global core.pager cat&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s all I can think off at the moment. If I notice that I&amp;rsquo;ve left anything off, I&amp;rsquo;ll update the post accordingly.&lt;/p&gt;
&lt;p&gt;If you have any initial configuration steps that you cannot live without, please &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;let me know on Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gnupg.org/gph/en/manual/x334.html&#34;&gt;Validating other keys on your public keyring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.gitlab.com/ee/ssh/README.html#generating-a-new-ssh-key-pair&#34;&gt;GitLab and SSH keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://superuser.com/a/288333&#34;&gt;pbcopy and pbpaste for Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/a/6986231&#34;&gt;To get rid of pager for all commands for all repositories&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Casino Data</title>
      <link>https://ar.al/2018/07/06/casino-data/</link>
      <pubDate>Fri, 06 Jul 2018 12:31:49 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/06/casino-data/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/06/casino-data/casino-entrance.jpg&#34;
         alt=&#34;Entrance to the data casino installation&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Wouldn’t it be nice if every spider clearly labelled the entrance of its web?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Welcome to Casino Data, where you pay with your data. No, I’m not talking about just another day in our present dystopia under surveillance capitalism.&lt;/p&gt;
&lt;p&gt;Unlike our world, this installation at FutureFest makes it very clear that what you’re gambling with is aspects of your self.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/06/casino-data/data-table.jpg&#34;
         alt=&#34;A gambling table on which people bet with their data&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Gambling with our data&amp;hellip; an analogy for life today.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>Whither ethics, New Scientist?</title>
      <link>https://ar.al/2018/07/06/whither-ethics-new-scientist/</link>
      <pubDate>Fri, 06 Jul 2018 06:49:36 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/06/whither-ethics-new-scientist/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/06/whither-ethics-new-scientist/magazine.jpg&#34;
         alt=&#34;New Scientist mmagazine on the tray table of my seat&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;New Scientist: what&amp;rsquo;s science sans ethics?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m on a flight from Cork to London to speak at Nesta&amp;rsquo;s FutureFest this weekend and I&amp;rsquo;m reading New Scientist. A full-spread ad for their conference in September, New Scientist Live catches my eye. Hmm, might be interesting. And then I see the list of sponsors. Wow.&lt;/p&gt;
&lt;p&gt;When attempting to explain why a privacy or human rights conference having Google (Alphabet, Inc.) or Facebook, Inc., as sponsors is problematic, I often use the analogy that no one would trust a healthcare conference spnsored by Philip Morris or a conference on the environment sponsored by Exxon Mobil. Well, guess what?&lt;/p&gt;
&lt;p&gt;New Scientist&amp;rsquo;s &amp;ldquo;Earth Zone Sponsor&amp;rdquo; for their conference is (drumroll) Shell.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/06/whither-ethics-new-scientist/sponsors.jpg&#34;
         alt=&#34;Detail of New Scientist&amp;#39;s sponsors for their New Scientist Live conference: Earth Zone Sponsor: Shell, Technology Zone Sponsor: BAE Systems.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Greenwashing. (And whatever it is you call legimitising weapons manufacturers.)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Take a bow, New Scientist. I thought we lacked ethics in the technology world, but you are exploring new frontiers in corruption. How do the folks who work at New Scientist sleep at night knowing that their publication is using whatever legitimacy it has in the field of science to greenwash a petrolium company?&lt;/p&gt;
&lt;p&gt;And it doesn&amp;rsquo;t stop there.&lt;/p&gt;
&lt;p&gt;Their &amp;ldquo;technology zone sponsor&amp;rdquo; is BAE systems. You know, the company that makes technology that kills people.&lt;/p&gt;
&lt;p&gt;Seriously, New Scientist, it&amp;rsquo;s time you took a long, hard look at the ethics of your choices.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Rsync Permissions and Termux</title>
      <link>https://ar.al/2018/07/05/rsync-permissions-and-termux/</link>
      <pubDate>Thu, 05 Jul 2018 18:08:48 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/05/rsync-permissions-and-termux/</guid>
      <description>&lt;p&gt;The way my blogging setup works in live mode is that I have a &lt;a href=&#34;https://source.ind.ie/ar.al/sync&#34;&gt;watcher&lt;/a&gt; that uses rsync to deploy changes to my server.&lt;/p&gt;
&lt;p&gt;The first time I started live mode while blogging on my phone, I ended up with a 403 Forbidden error on my site.&lt;/p&gt;
&lt;p&gt;Connecting to the server via &lt;code&gt;ssh&lt;/code&gt;, a quick &lt;code&gt;ls -la&lt;/code&gt; on the web root&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; showed me that the permissions were more restrictive than they should have been. The immediate fix was easy:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;chmod -R &lt;span style=&#34;color:#40a070&#34;&gt;755&lt;/span&gt; &amp;lt;web root&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, the problem would resurface the next time rsync ran from my phone. So I looked at the permissions for the local folder on my phone and saw that they matched the restrictive ones on the server. Thinking that the permissions were simply getting copied over, I tried setting the local folder’s permissions to 755 and trying rsync again. No go. The permissions reset again to the restrictive ones.&lt;/p&gt;
&lt;p&gt;What did end up working was to add the &lt;code&gt;--chmod=755&lt;/code&gt; flag to the rsync command (I was already passing the &lt;code&gt;--archive&lt;/code&gt; flag, which includes the &lt;code&gt;--perm&lt;/code&gt; flag, without which this might not have worked.&lt;/p&gt;
&lt;p&gt;Since this isn’t an issue when running rsync from my Mac, I’m assuming it’s an oddity related to how Termux manages directory permissions.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you forget where you set your web root to under nginx, as I sometimes do, look for it in your server definition in the relevant server configuration file that’s symlinked from &lt;em&gt;/etc/nginx/sites-available&lt;/em&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Web&#43; on a Phone</title>
      <link>https://ar.al/2018/07/05/web&#43;-on-a-phone/</link>
      <pubDate>Thu, 05 Jul 2018 12:47:04 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/05/web&#43;-on-a-phone/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/05/web+-on-a-phone/blogging.jpg&#34;
         alt=&#34;Photo of my Nexus 5 phone. The screen is split horizontally down the middle. On the left side is Emacs running with the text of this blog post showing in it. On the right side is the DuckDuckGo browser showing the rendered HTML from localhost:1313&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;E.T. blog phone.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m blogging this from a phone.&lt;/p&gt;
&lt;p&gt;OK, so your mind isn&amp;rsquo;t blown. I understand. After all, anyone can blog from a phone using a native or web app that speaks to a server somewhere. The difference is I have an entire copy of my site on my phone and I&amp;rsquo;m blogging using the exact same &lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;Web+&lt;/a&gt; setup I described earlier on my Mac.&lt;/p&gt;
&lt;p&gt;To recap: there&amp;rsquo;s &lt;a href=&#34;https://source.ind.ie/ar.al/sync&#34;&gt;a Node.js-based watcher running&lt;/a&gt; which, if I&amp;rsquo;m online, synchronises my local site with the site on my server using &lt;a href=&#34;https://rsync.samba.org/&#34;&gt;rsync&lt;/a&gt;. And I&amp;rsquo;m using a Go-based static site generator, &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt;, to generate the site every time I save.  And all this is running on the old Nexus 5 I had lying around thanks to a little miracle of an app called &lt;a href=&#34;https://termux.com/&#34;&gt;Termux&lt;/a&gt;. And, of course, on my server, I&amp;rsquo;m running &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; so that updates to my site automatically get shared on the peer web. The only thing that&amp;rsquo;s different is that I&amp;rsquo;m writing this in &lt;a href=&#34;https://www.gnu.org/software/emacs/&#34;&gt;GNU Emacs&lt;/a&gt; instead of in VSCode.&lt;/p&gt;
&lt;p&gt;Just like on my main machine, I have full local/offline storage, local and (if online and running in live mode) remote live reload, etc.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls src=&#34;https://player.vimeo.com/external/278310665.hd.mp4?s=b9921ca43511ea5180abddad05edaf2e0689d348&amp;profile_id=175&#34; poster=&#34;https://i.vimeocdn.com/video/711309596.jpg?mw=1920&amp;mh=1080&amp;q=70&#34;&gt;&lt;a href=&#39;https://vimeo.com/278310665&#39;&gt;Video of static site generation with live reload on my phone.&lt;/a&gt;&lt;/video&gt;
  &lt;figcaption&gt;Live reload on a phone.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;setup&#34;&gt;Setup&lt;/h3&gt;
&lt;p&gt;Here are some brief notes on how I got this working in case any other developers want to play with a similar setup at any point.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I installed &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt; on my old Nexus 5 &lt;a href=&#34;https://wiki.lineageos.org/devices/hammerhead&#34;&gt;using the instructions on their Wiki&lt;/a&gt;.  They have &lt;a href=&#34;https://wiki.lineageos.org/devices/&#34;&gt;detailed installation instructions for a plethora of devices&lt;/a&gt; there. I also compiled &lt;a href=&#34;https://ar.al/2018/07/03/phones-lineageos-15.1-is-officially-supported-on/&#34;&gt;a list of phones that can run the latest version of LineageOS&lt;/a&gt; (15.1). My Nexus 5 can only run 14.1, which runs just fine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I installed the &lt;a href=&#34;https://f-droid.org/&#34;&gt;F-Droid free software catalogue&lt;/a&gt;. This is a freedom technology alternative for people who don&amp;rsquo;t want Google&amp;rsquo;s surveillance services on their phones.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using F-Droid, I installed &lt;a href=&#34;https://termux.com&#34;&gt;Termux&lt;/a&gt;. This is where the magic happens. Termux gives you a lightweight Linux environment on your Android phone and doesn&amp;rsquo;t even need root access on your device to do so. I could have installed it on a stock Android phone (but then I&amp;rsquo;d have a Google surveillance device in my pocket&amp;hellip; so no thanks).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As Termux comes with the &lt;a href=&#34;https://wiki.debian.org/Apt&#34;&gt;apt package manager&lt;/a&gt; via the &lt;code&gt;pkg&lt;/code&gt; command (this is amazing), I was able to install &lt;a href=&#34;https://git-scm.com/&#34;&gt;git&lt;/a&gt;, clone &lt;a href=&#34;https://source.ind.ie/ar.al&#34;&gt;my Web+ setup repositories&lt;/a&gt;, install &lt;a href=&#34;https://nodejs.org&#34;&gt;Node.js&lt;/a&gt;, &lt;a href=&#34;https://golang.org&#34;&gt;Go&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, and &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; (which I had to &lt;a href=&#34;https://gohugo.io/getting-started/installing#fetch-from-github&#34;&gt;compile from source&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Following those steps, I was up and running with essentially the same setup that I have on my Macbook.&lt;/p&gt;
&lt;h3 id=&#34;the-experience&#34;&gt;The experience&lt;/h3&gt;
&lt;p&gt;In a nutshell: it&amp;rsquo;s rather amazing.&lt;/p&gt;
&lt;p&gt;I am using a &lt;a href=&#34;https://www.logitech.com/en-sg/product/ultrathin-keyboard-cover&#34;&gt;Logitech hardware keyboard&lt;/a&gt; that I had for my iPad, without which this would not be a viable tool for coding or blogging. With it&amp;hellip; well, my mind is blown by how well this four-year-old phone runs all this stuff.&lt;/p&gt;
&lt;p&gt;There is absolutely no lag! Hugo&amp;rsquo;s rebuilds are instant as is the responsiveness of the rsync watcher. As far as I can tell, everything runs as well as it does on my Macbook.&lt;/p&gt;
&lt;h3 id=&#34;challenges-and-processes&#34;&gt;Challenges and processes&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s not to say that it&amp;rsquo;s perfect. This is not the most ergonomic setup ever, even with the excellent Logitech keyboard. Using an external monitor via &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Miracast&#34;&gt;Miracast&lt;/a&gt; mirroring the screen does help make it better but also adds a slight lag to the display. I have to find &lt;a href=&#34;https://en.m.wikipedia.org/wiki/DisplayPort#SlimPort&#34;&gt;a slimport adapter&lt;/a&gt; and see what it&amp;rsquo;s like using a wired connection. From a demonstration video I watched on the web, it looks like it has a perfectly smooth mirroring experience.&lt;/p&gt;
&lt;p&gt;Also, I initially battled with my lazy fallback editor of choice on Linux terminals, &lt;a href=&#34;https://www.nano-editor.org/&#34;&gt;GNU nano&lt;/a&gt;. I ended up somehow pressing some keyboard combination, I guess, that got nano to hard-wrap the lines of my post. While shaving that particular yak, I ended up giving &lt;a href=&#34;https://www.gnu.org/software/emacs/&#34;&gt;GNU Emacs&lt;/a&gt; a shot. After finally working my way through its tutorial some 30-odd years after it was written, I have to say, I really like Emacs. (I know, I&amp;rsquo;m shocked too&amp;hellip; here, have some tea.)&lt;/p&gt;
&lt;p&gt;There is nothing intuitive about Emacs but it is the product of another time and there is method behind the madness. And, for the moment at least, it suits my coding and blogging needs just fine. Running it in split screen with my browser also works really well (nano had trouble recovering from being resized).&lt;/p&gt;
&lt;p&gt;Using a combination of split screen and task switching, I can easily browse the web to find and copy links.&lt;/p&gt;
&lt;p&gt;Adding images and screenshots taken on the phone itself is slightly more convoluted due to the lack of a visual process (e.g., drag and drop). Instead &lt;a href=&#34;https://wiki.termux.com/wiki/Termux-setup-storage&#34;&gt;I gave Termux storage access&lt;/a&gt; and I use plain old &lt;code&gt;cp&lt;/code&gt; to copy images in from &lt;em&gt;~/storage/pictures&lt;/em&gt; and &lt;em&gt;~/storage/dcim/Camera&lt;/em&gt;. I also use the built-in Gallery app to browse images and the &lt;code&gt;termux-open&lt;/code&gt; command from the &lt;a href=&#34;https://termux.com/add-on-api.html&#34;&gt;Termux API add-on&lt;/a&gt; to trigger image previews from the commandline.&lt;/p&gt;
&lt;p&gt;As Termux supports multiple sessions, I can have my watchers running in one session and Emacs in another. When that, combined with Emacs&amp;rsquo;s support for easy suspend and resume (&lt;kbd&gt;Ctrl&lt;/kbd&gt; &lt;kbd&gt;x&lt;/kbd&gt; &lt;kbd&gt;Ctrl&lt;/kbd&gt; &lt;kbd&gt;z&lt;/kbd&gt; to suspend, &lt;code&gt;fg&lt;/code&gt; on the commandline to resume) isn&amp;rsquo;t enough, I can also use &lt;a href=&#34;https://github.com/tmux/tmux/wiki&#34;&gt;tmux&lt;/a&gt; – a terminal multiplexer – to split my screen with multiple terminal sessions. On a mobile phone. I know! 😱&lt;/p&gt;
&lt;h3 id=&#34;communicating-with-the-outside-world&#34;&gt;Communicating with the outside world&lt;/h3&gt;
&lt;p&gt;Another issue is how to get things on this phone from my other devices, all of which are locked into Apple&amp;rsquo;s nursery. This is where cross-platform &lt;a href=&#34;https://www.fsf.org/about/what-is-free-software&#34;&gt;freedom apps&lt;/a&gt; become an invaluable lifeline.&lt;/p&gt;
&lt;p&gt;First off, &lt;a href=&#34;https://www.signal.org/&#34;&gt;Signal&lt;/a&gt; and &lt;a href=&#34;https://wire.com/en/&#34;&gt;Wire&lt;/a&gt;. Freedom from the surveillance silos without becoming hermits requires that we have end-to-end encrypted means of communication between devices, platforms, and people. Both Signal and Wire solve this problem beautifully and in a manner that can be conveniently enjoyed by people without the necessary technical knowledge or time.&lt;/p&gt;
&lt;p&gt;I initially found it difficult to sign into services I was using as all my passwords are in &lt;a href=&#34;https://1password.com/&#34;&gt;1Password&lt;/a&gt;. 1Password does have an Android app but it&amp;rsquo;s only available from the Google Play Store. I will not be installing any of Google&amp;rsquo;s surveillance services on this phone. This creates a problem. (One that I also ran into with the VPN I&amp;rsquo;m using on Apple&amp;rsquo;s platforms, &lt;a href=&#34;https://encryptme.com.&#34;&gt;EncryptMe&lt;/a&gt; I&amp;rsquo;ve raised the issue with &lt;a href=&#34;https://twitter.com/aral/status/1014560504038256647&#34;&gt;both&lt;/a&gt; &lt;a href=&#34;https://twitter.com/aral/status/1014814579120070656&#34;&gt;companies&lt;/a&gt; and I hope that we can make some progress there. Otherwise, I&amp;rsquo;ll be looking for alternatives that enable people to use them without subjecting themselves to Google&amp;rsquo;s surveillance machine.&lt;/p&gt;
&lt;p&gt;To get around the problem of sharing information between my nursery machines (Apple devices) and my grown-up tools, I ended up just using Signal with disappearing messages. It&amp;rsquo;s not a hassle to send a password, end-to-end encrypted from 1Password on my Mac/iPhone to my freedom phone. I also use the same system to send photos I&amp;rsquo;ve taken on my iPhone to my freedom phone. For videos, I upload them to &lt;a href=&#34;https://vimeo.com&#34;&gt;Vimeo&lt;/a&gt; (&lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt; has a Pro account with Vimeo so that we can embed the video files directly without the Google surveillance that comes bundled with the default Vimeo player).&lt;/p&gt;
&lt;p&gt;In the middle-term, I plan to try out my old friend &lt;a href=&#34;https://syncthing.net&#34;&gt;SyncThing&lt;/a&gt; for cross-platform secure file transfers. In the longer term, I will be looking into &lt;a href=&#34;https://keepassxc.org/&#34;&gt;KeePassXC&lt;/a&gt; as an alternative to 1Password and my own &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;-based solutions for private/secure file sync, messaging, and publishing.&lt;/p&gt;
&lt;h3 id=&#34;its-a-great-time-for-ethical-technology&#34;&gt;It&amp;rsquo;s a great time for ethical technology&lt;/h3&gt;
&lt;p&gt;I have to say that I haven&amp;rsquo;t been this excited about technology in a long while because I can finally see the pieces coming together for ethical alternatives to start making inroads into the mainstream. In a lot of ways, this process has already begun. &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;, &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;, &lt;a href=&#34;https://signal.org&#34;&gt;Signal&lt;/a&gt;, &lt;a href=&#34;https://wire.com/en/&#34;&gt;Wire&lt;/a&gt;, &lt;a href=&#34;https://matrix.org&#34;&gt;Matrix&lt;/a&gt;, &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt;, &lt;a href=&#34;https://www.eelo.io/&#34;&gt;Eelo&lt;/a&gt;, &lt;a href=&#34;https://www.gnome.org/&#34;&gt;Gnome&lt;/a&gt;, &lt;a href=&#34;https://system76.com/pop&#34;&gt;Pop!_OS&lt;/a&gt;, &lt;a href=&#34;https://lineageos.org/&#34;&gt;LineageOS&lt;/a&gt;, &lt;a href=&#34;https://termux.com/&#34;&gt;Termux&lt;/a&gt;&amp;hellip; these are just some of the products and projects off the top of my head that give me immense hope for the future of &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical technology&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not to say that the setup I&amp;rsquo;ve described here and that I&amp;rsquo;m using is one that I expect people without technical knowledge and the time to spare to play with today. But it is giving me a myriad of ideas and this is stuff that developers (and not just I-think-in-binary-and-speak-fluent-hex developers) definitely can (and should) toy with. The challenge is exploring and then exposing what&amp;rsquo;s possible with such freedom-respecting and freedom-preserving setups conveniently so that people who don&amp;rsquo;t have the technical knowledge or perhaps just the time or resources can take advantage of the freedoms that they offer. For those of us that care about ethical technology, our job is to build the convenient bridges between the centralised, surveillance-based silos of today and the ethical, interoperable, and open freedom technologies of tomorrow.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create beautiful defaults and layer the seams.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I did discover a bug with Go under Termux: it doesn&amp;rsquo;t get the correct timezone. A symptom of this, for Hugo, is that the dates of posts have the wrong timezone. I&amp;rsquo;ve &lt;a href=&#34;https://github.com/termux/termux-app/issues/760&#34;&gt;opened an issue for it&lt;/a&gt;. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Spellcheck With Emacs on Termux</title>
      <link>https://ar.al/2018/07/05/spellcheck-with-emacs-on-termux/</link>
      <pubDate>Thu, 05 Jul 2018 11:12:08 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/05/spellcheck-with-emacs-on-termux/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/05/spellcheck-with-emacs-on-termux/spellcheck.png&#34;
         alt=&#34;Emacs under Termux running a spell check on my document using hunspell.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;I missspell sometimes.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m using Emacs on Termux to blog with on my phone and realised that, by default, spellcheck doesn&amp;rsquo;t work. &lt;code&gt;M-x ispell&lt;/code&gt; (&lt;kbd&gt;Alt&lt;/kbd&gt;&lt;kbd&gt;x&lt;/kbd&gt; &lt;em&gt;ispell&lt;/em&gt;) fails with the error &amp;ldquo;Searching for program: no such file or directory, ispell&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;After investigating a bit, it turns out that neither ispell or aspell are available as packages for Termux. Instead, they&amp;rsquo;re concentrating their efforts on supporting hunspell.&lt;/p&gt;
&lt;p&gt;To get spellcheck working under Emacs with hunspell:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the hunspell package
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;pkg install hunspell&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following to your Emacs configuration:
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-emacs&#34; data-lang=&#34;emacs&#34;&gt;(&lt;span style=&#34;color:#007020&#34;&gt;setq&lt;/span&gt; &lt;span style=&#34;color:#bb60d5&#34;&gt;ispell-program-name&lt;/span&gt; (&lt;span style=&#34;color:#bb60d5&#34;&gt;executable-find&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;hunspell&amp;#34;&lt;/span&gt;))&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart Emacs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That should get you a spell checker in Emacs.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Nginx: Remember to remove the default site</title>
      <link>https://ar.al/2018/07/05/nginx-remember-to-remove-the-default-site/</link>
      <pubDate>Thu, 05 Jul 2018 08:10:11 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/05/nginx-remember-to-remove-the-default-site/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/05/nginx-remember-to-remove-the-default-site/s9.jpg&#34;
         alt=&#34;Photo of a Samsung S9&amp;#43; phone hanging on a display in PC World. On screen is the browser showing the default nginx page for an http request to my site.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;What a minute, where did my site go?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I realised yesterday that people accessing my new site for the first time via HTTP (not HTTPS), were seeing the default page for my &lt;a href=&#34;https://nginx.org/&#34;&gt;nginx&lt;/a&gt; web server. I only realised it because I was trying the site out on a Samsung S9+ that I was playing with at PC World. (It&amp;rsquo;s always a good idea to test out your sites on the range of devices they have at such places. They don&amp;rsquo;t mind - in fact, you might find yourself having pleasant conversations about the state of surveillance capitalism and ethical technology with the folks who work there, like I did with Paul yesterday - and you might discover things you missed on your own setup. PC World is basically a large, free device lab.)&lt;/p&gt;
&lt;p&gt;Anyway, a quick search on DuckDuckGo &lt;a href=&#34;https://discourse.roots.io/t/http-does-not-redirect-to-https-only-at-the-first-access/8669&#34;&gt;revealed the issue&lt;/a&gt;: I hadn&amp;rsquo;t removed the &lt;em&gt;default&lt;/em&gt; site configuration from &lt;em&gt;/etc/nginx/sites-enabled/&lt;/em&gt; and it was competing with the HTTP → HTTPS redirect that I&amp;rsquo;d instructed &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; to set up for me.&lt;/p&gt;
&lt;p&gt;Deleting that symbolic link fixes the problem.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Phones LineageOS 15.1 is officially supported on</title>
      <link>https://ar.al/2018/07/03/phones-lineageos-15.1-is-officially-supported-on/</link>
      <pubDate>Tue, 03 Jul 2018 11:18:13 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/03/phones-lineageos-15.1-is-officially-supported-on/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/07/03/phones-lineageos-15.1-is-officially-supported-on/lineageos-on-a-nexus-5.jpg&#34;
         alt=&#34;Photo of my Nexus 5 running LineageOS 14.1. On screen is a tmux terminal session in Termux showing a split screen between my blogs Hugo and rsync watchers running in one panel and a terminal prompt showing the result of a ./new web&amp;#43;-on-a-phone command that created a Markdown file for a blog post.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;It’s GNU Linux. On a phone.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://lineageos.org&#34;&gt;LineageOS&lt;/a&gt; is the successor to Cyanogenmod – a Google-free, freedom technology alternative to stock Android that isn’t infected with Google Play Services (Google’s surveillance services).&lt;/p&gt;
&lt;figure class=&#34;half-width-flush-right&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/07/03/phones-lineageos-15.1-is-officially-supported-on/lineageos-logo.png&#34;
         alt=&#34;LineageOS logo: an downwards sloping arc with three hollow circles (nodes) overlayed. The two at the ends are smaller and the one in the middle is larger and has a filled in circle inside of it.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The LineageOS logo.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I have a Nexus 5 (hammerhead) from several years ago that I installed LineageOS 14.1 on and it works flawlessly. In fact, thanks to its freedom and the mind-blowingly awesome &lt;a href=&#34;https://termux.com&#34;&gt;Termux&lt;/a&gt;, I now have a basic Linux setup on it running Node.js, Go, and my &lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;Web+&lt;/a&gt; &lt;a href=&#34;https://source.ind.ie/ar.al/&#34;&gt;blogging setup&lt;/a&gt; that uses &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt;, &lt;a href=&#34;https://source.ind.ie/ar.al/sync/blob/master/config.json#L9&#34;&gt;rsync&lt;/a&gt;, and &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;. It’s awesome that LineageOS enables people to keep using their older handsets beyond the couple of years that some manufacturers deem an acceptable period for supporting their products with updates (hey, landfills are cheap and the Earth is boundless, right?… Right?!)&lt;/p&gt;
&lt;p&gt;Anyway… I do want to try LineageOS on a modern 64-bit device not least because &lt;a href=&#34;https://gitter.im/datproject/discussions?at=5b3b27a73572e970c17135f5&#34;&gt;I have a feeling&lt;/a&gt; &lt;a href=&#34;https://github.com/datproject/dat/issues/1007&#34;&gt;the segmentation faults I’m getting&lt;/a&gt; while trying to run &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; on it might be due to it being 32-bit. So I found myself sifting through the list of &lt;a href=&#34;https://download.lineageos.org&#34;&gt;downloads and installation instructions for all officially-supported handsets&lt;/a&gt; as it’s not grouped by version. I had to go through the whole thing, but there’s no reason anyone else should have to as I compiled the list of handsets that LineageOS 15.1 is currently supported on and you can find it, below.&lt;/p&gt;
&lt;p&gt;If you already have an older handset that no longer receives operating system updates, you should seriously consider extending its life, ditching Google’s spyware, and improving your security using LineageOS (and the &lt;a href=&#34;https://f-droid.org&#34;&gt;F-Droid freedom software catalogue&lt;/a&gt;). Version 14.1 is stable and runs flawlessly on older hardware. If you’re on the market for a new handset to run LineageOS on, however, I hope the following list helps you identify potential candidates easily.&lt;/p&gt;
&lt;p&gt;The list should be considered current as of the time of this post but I will not be maintaining it. (&lt;a href=&#34;https://github.com/aral/ar.al-site&#34;&gt;Pull requests&lt;/a&gt; are welcome.)&lt;/p&gt;
&lt;p&gt;Discounting the Google devices (because even though the bar on ethics is set pretty darn low in the industry, I’ll be damned if I give &lt;em&gt;that&lt;/em&gt; particular &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalist&lt;/a&gt; any more money), the newest/best-equipped models appear to be the &lt;a href=&#34;https://en.wikipedia.org/wiki/Samsung_Galaxy_S9&#34;&gt;Samsung Galaxy S9 and S9 plus&lt;/a&gt;, the &lt;a href=&#34;https://en.wikipedia.org/wiki/OnePlus_5T&#34;&gt;OnePlus 5T&lt;/a&gt;, and the &lt;a href=&#34;https://techcrunch.com/2016/11/24/leeco-lepro3-review/&#34;&gt;LeEco LePro 3&lt;/a&gt;. I’ll either pick one of those three up or wait a little to see how things shape up with LineageOS support on the new &lt;a href=&#34;https://www.xda-developers.com/project-treble-custom-rom-development/&#34;&gt;Project Treble&lt;/a&gt;-based phones.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Manufacturer&lt;/th&gt;
&lt;th&gt;Supported Models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Asus&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BQ&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fairphone&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/angler&#34;&gt;Nexus 6P (angler)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/shamu&#34;&gt;Nexus 6 (shamu)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/bullhead&#34;&gt;Nexus 5X (bullhead)&lt;/a&gt;, (note: &lt;em&gt;not&lt;/em&gt; Nexus 5), &lt;a href=&#34;https://download.lineageos.org/mako&#34;&gt;Nexus 4 (mako)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/dragon&#34;&gt;Pixel C (dragon)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/fugu&#34;&gt;Nexus Player (fugu)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTC&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/hiae&#34;&gt;One A9 (hiae)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Huawei&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LeEco&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/x2&#34;&gt;Le Max 2 (x2)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/zl1&#34;&gt;LePro 3&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LG&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motorola&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/griffin&#34;&gt;Moto Z (griffin)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/nash&#34;&gt;Moto Z2 Force (nash)&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nextbit&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nubia&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nvidia&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/foster&#34;&gt;Shield Android TV (foster)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OnePlus&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/bacon&#34;&gt;One (bacon)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/cheeseburger&#34;&gt;5 (cheeseburger)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/dumpling&#34;&gt;5T (dumpling)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/oneplus2&#34;&gt;2 (oneplus2)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/oneplus3&#34;&gt;3 / 3T (oneplus3)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPPO&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/find7&#34;&gt;Find 7a (find7)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samsung&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/gts210vewifi&#34;&gt;Galaxy Tab S2 9.7 2016 Wi-Fi (gts210vewifi)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/gts28vewifi&#34;&gt;Galaxy Tab S2 8.0 2016 Wi-Fi (gts28vewifi)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/kccat6&#34;&gt;Galaxy S5 Plus (kccat6)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/lentislte&#34;&gt;Galaxy S5 LTE-A (lentislte)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/star2lte&#34;&gt;Galaxy S9+ (star2lte)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/starlte&#34;&gt;Galaxy S9 (starlte)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sony&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/pioneer&#34;&gt;Xperia XA2 (pioneer)&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wileyfox&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Xiaomi&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://download.lineageos.org/capricorn&#34;&gt;Mi 5s (capricorn)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/chiron&#34;&gt;Mi MIX 2 (chiron)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/gemini&#34;&gt;Mi 5 (gemini)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/lithium&#34;&gt;Mi MIX (lithium)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/mido&#34;&gt;Redmi Note 4 (mido)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/natrium&#34;&gt;Mi 5s Plus (natrium)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/sagit&#34;&gt;Mi 6 (sagit)&lt;/a&gt;, &lt;a href=&#34;https://download.lineageos.org/scorpio&#34;&gt;Mi Note 2 (scorpio)&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YU&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZTE&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zuk&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;!-- Progressive enhancement: just for this page, refine the display of the table used to center the ✗’s in the cells (but to leave text left aligned. --&gt;
&lt;script&gt;
let tableCells = document.getElementsByTagName(&#39;td&#39;)
for (tableCell of tableCells) {
  if (tableCell.innerHTML === &#39;✗&#39;) { tableCell.style.textAlign = &#39;center&#39; }
}
&lt;/script&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Experimental. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Refining the RSS</title>
      <link>https://ar.al/2018/07/01/refining-the-rss/</link>
      <pubDate>Sun, 01 Jul 2018 01:37:19 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/07/01/refining-the-rss/</guid>
      <description>&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/07/01/refining-the-rss/my-feed-in-leaf.jpg&#34;
         alt=&#34;A screenshot of my feed displaying properly in the Leaf RSS reader with an icon for the site and images for posts that have them.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;I can’t optimise my feed for every reader out there but I can for the one I’m using.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I realised today&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that my site’s feed wasn’t displaying well on the &lt;a href=&#34;https://ar.al/2018/06/29/reclaiming-rss/&#34;&gt;RSS&lt;/a&gt; reader I’m using (&lt;a href=&#34;https://itunes.apple.com/us/app/leaf-rss-news-reader/id576338668&#34;&gt;Leaf&lt;/a&gt; on macOS). Some other feeds had image previews for items as well as an icon for the site itself while mine didn’t.&lt;/p&gt;
&lt;p&gt;How utterly unacceptable!&lt;/p&gt;
&lt;h3 id=&#34;sweat-the-small-stuff-its-all-small-stuff&#34;&gt;Sweat the small stuff (it’s all small stuff)&lt;/h3&gt;
&lt;p&gt;Given I’m writing this at almost 2AM on a Saturday night, you can probably deduce that there was a level of – ahem – &lt;em&gt;trial and error&lt;/em&gt; involved in getting things to work.&lt;/p&gt;
&lt;p&gt;Initially, I thought the icon for my site wasn’t showing up because I hadn’t added an &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; tag to my RSS feed. So I added that to my RSS template in &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;rss&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;version=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;xmlns:atom=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;http://www.w3.org/2005/Atom&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;channel&amp;gt;&lt;/span&gt;
    …
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;image&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;url&amp;gt;&lt;/span&gt;{{ &amp;#34;/apple-touch-icon-144x144.png&amp;#34; | absURL }}&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;link&amp;gt;&lt;/span&gt;{{ .Permalink }}&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;width&amp;gt;&lt;/span&gt;144&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/width&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;height&amp;gt;&lt;/span&gt;144&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/height&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/image&amp;gt;&lt;/span&gt;
    …
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/channel&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/rss&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sadly, Leaf didn’t bite.&lt;/p&gt;
&lt;p&gt;Images were working for &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura’s site&lt;/a&gt; so I took a look at &lt;a href=&#34;https://laurakalbag.com/index.xml&#34;&gt;her feed&lt;/a&gt; and found a clue: her images had absolute URLs while mine were relative.&lt;/p&gt;
&lt;p&gt;Hmm… 🤔&lt;/p&gt;
&lt;h3 id=&#34;relatively-speaking&#34;&gt;Relatively speaking&lt;/h3&gt;
&lt;p&gt;I can’t use absolute URLs on this site as it’s &lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;a Web+ site&lt;/a&gt;. Since its accessible over both old school HTTPS and on the peer web, I can’t hardcode a protocol like &lt;code&gt;https://&lt;/code&gt; as that would break links for the &lt;code&gt;dat://&lt;/code&gt; protocol.&lt;/p&gt;
&lt;p&gt;That limitation doesn’t really apply to the site’s RSS feed, however.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://validator.w3.org/feed&#34;&gt;W3C Feed Validation Service&lt;/a&gt; actually warns you &lt;em&gt;not&lt;/em&gt; to use relative URLs in your feeds. (This is a limitation I feel we should remove, going forward. Not least of all because peer replication of RSS feeds over DAT might have some interesting use cases for push/streaming syndication.)&lt;/p&gt;
&lt;p&gt;So to cut to the chase, I decided to use a regular expression to massage the URLs in my RSS feed template in Hugo to convert relative URLs to absolute ones:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
  …
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;description&amp;gt;&lt;/span&gt;{{ replaceRE &amp;#34;img src=\&amp;#34;(.*?)\&amp;#34;&amp;#34; (printf &amp;#34;%s%s%s&amp;#34; &amp;#34;img src=\&amp;#34;&amp;#34; .Permalink &amp;#34;$1\&amp;#34;&amp;#34;) .Content | html }}&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Initially, I also had a negative lookahead in there to ensure I wouldn’t rewrite any URLs that might have been absolute URLs to begin:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;img src&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;(((?!http?).)*?)&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But, sadly, &lt;a href=&#34;https://stackoverflow.com/questions/26771592/negative-look-ahead-go-regular-expressions#26792316&#34;&gt;Go’s regular expression engine does not support negative lookaheads&lt;/a&gt;. This isn’t a big deal, really, since it’s not good form to link to external images directly anyway.&lt;/p&gt;
&lt;p&gt;With that change, image previews started working in Leaf.&lt;/p&gt;
&lt;h3 id=&#34;favicon-blues&#34;&gt;Favicon blues&lt;/h3&gt;
&lt;p&gt;But my site’s icon still wasn’t showing up.&lt;/p&gt;
&lt;p&gt;It was one-something-AM and instead of going to bed like a sensible person, I decided that this needed fixing right now (the sadder part is that I’m writing this sentence while editing this post at 2:30AM).&lt;/p&gt;
&lt;p&gt;Anyway, looking at an icon for site that was displaying properly, I noticed that the image was rather high resolution. So it wasn’t just grabbing a &lt;em&gt;favicon.ico&lt;/em&gt;. I already had the ridiculous number of icons one needs for a respectable web site in 2018 generated using &lt;a href=&#34;http://www.favicomatic.com&#34;&gt;Favic-O-Matic&lt;/a&gt;. In the end, after more trial and error, I found that Leaf was looking for an &lt;em&gt;apple-touch-icon.png&lt;/em&gt; file in the root of the site.&lt;/p&gt;
&lt;p&gt;Adding that fixed the icon issue also.&lt;/p&gt;
&lt;h3 id=&#34;my-kingdom-for-standards-compliant-rss-readers&#34;&gt;My kingdom for standards-compliant RSS readers&lt;/h3&gt;
&lt;p&gt;It would be ridiculous, of course, to try and support the idiosyncrasies of every RSS reader out there but I can at least make my feed look good on the one(s) I use.&lt;/p&gt;
&lt;p&gt;That said, it would also be great if more RSS readers supported standard elements when available.&lt;/p&gt;
&lt;p&gt;Leaf, for example, should make use of the image tag if one exists in a feed. Similarly, RSS readers should present items in &lt;code&gt;enclosure&lt;/code&gt; elements (images, audio, and video) when possible. While not part of the standard, it also wouldn’t take much work to resolve relative URLs properly in feeds either.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;i.e., “Became unable to ignore any longer…” 😁 &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Reclaiming RSS</title>
      <link>https://ar.al/2018/06/29/reclaiming-rss/</link>
      <pubDate>Fri, 29 Jun 2018 11:33:13 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/29/reclaiming-rss/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/29/reclaiming-rss/rss-banner.svg&#34;
         alt=&#34;RSS symbol&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;RSS like it’s 1999.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;center&gt;&lt;strong&gt;English&lt;/strong&gt; | &lt;a href=&#39;https://framablog.org/2018/07/16/les-flux-rss-maintenant/&#39;&gt;French&lt;/a&gt;&lt;/center&gt;
&lt;p&gt;Before Twitter, before &lt;a href=&#34;https://motherboard.vice.com/en_us/article/bmvbaw/why-2016-was-the-year-of-the-algorithmic-timeline&#34;&gt;algorithmic timelines&lt;/a&gt; filtered our reality for us, before &lt;a href=&#34;https://2018.ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;surveillance capitalism&lt;/a&gt;, there was &lt;a href=&#34;https://en.wikipedia.org/wiki/RSS&#34;&gt;RSS&lt;/a&gt;: Really Simple Syndication.&lt;/p&gt;
&lt;h3 id=&#34;rs-what-now&#34;&gt;RS-what now?&lt;/h3&gt;
&lt;p&gt;For those of you born into the siloed world of the centralised web, RSS is an ancient technology from Web 1.0 (“the naïve Web?”). Like most things back then, it does what it says on the tin: it enables you to easily syndicate the content of your site. People interested in following your posts subscribe to your feed and receive updates using their RSS readers. There is no Twitter or Facebook in the middle to algorithmically &lt;strike&gt;censor&lt;/strike&gt; … ahem … “curate” your posts.&lt;/p&gt;
&lt;p&gt;RSS is stupidly simple to implement (it’s just an XML file). You could hand-roll it manually if you wanted to (although I wouldn’t recommend it).&lt;/p&gt;
&lt;p&gt;Here’s an excerpt of &lt;a href=&#34;https://ar.al/index.xml&#34;&gt;this site’s RSS feed&lt;/a&gt;, showing some of the fields of the current entry for this post:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;color:#007020&#34;&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34; standalone=&amp;#34;yes&amp;#34; ?&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;rss&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;version=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;xmlns:atom=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;http://www.w3.org/2005/Atom&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;gt;&lt;/span&gt;
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;channel&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Aral Balkan&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;link&amp;gt;&lt;/span&gt;https://ar.al/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Recent content on Aral Balkan&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;lastBuildDate&amp;gt;&lt;/span&gt;Fri, 29 Jun 2018 11:33:13 +0100&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/lastBuildDate&amp;gt;&lt;/span&gt;
    …
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Rediscovering RSS&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;link&amp;gt;&lt;/span&gt;https://ar.al/2018/06/29/rediscovering-rss/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;pubDate&amp;gt;&lt;/span&gt;Fri, 29 Jun 2018 11:33:13 +0100&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/pubDate&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;author&amp;gt;&lt;/span&gt;mail@ar.al (Aral Balkan)&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;
      &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;description&amp;gt;&lt;/span&gt;(The content of this post goes here.)&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    …
  &lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/channel&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;&amp;lt;/rss&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is also almost universally implemented.&lt;/p&gt;
&lt;p&gt;Chances are, if you have a web site, you already have an RSS feed whether you know it or not. If you use &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; to generate your site, for example (like I do), your RSS feed is at &lt;a href=&#34;https://ar.al/index.xml&#34;&gt;/index.xml&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other generators might put it at &lt;em&gt;/rss&lt;/em&gt;, &lt;em&gt;/feed&lt;/em&gt;, &lt;em&gt;/feed.xml&lt;/em&gt;, etc.&lt;/p&gt;
&lt;h3 id=&#34;wheres-the-rss&#34;&gt;Where’s the RSS?&lt;/h3&gt;
&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/29/reclaiming-rss/rss-icons.jpg&#34;
         alt=&#34;A selection of RSS icons from The Noun Project displayed in a grid.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Noun Project has a great selection of RSS icons you can use.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Time was, you couldn’t browse the web without seeing RSS icons of all persuasions gracing the façades of Web 1.0’s finest. This was before they were mercilessly devoured by the &lt;strike&gt;tracking devices&lt;/strike&gt; … ahem … “social sharing buttons” of people farmers like Google and Facebook.&lt;/p&gt;
&lt;p&gt;There was also once a push for browsers to auto-detect and expose RSS feeds. Currently, none of the major browsers appears to do so.&lt;/p&gt;
&lt;p&gt;It’s time to push back against this and demand first-class support for RSS as part of the move to re-decentralise the Web.&lt;/p&gt;
&lt;p&gt;But you don’t have to wait for browser vendors (some of which – like Google – are surveillance capitalists themselves, and others, like Mozilla, get all their money from surveillance capitalists). You can start making RSS more visible again today by finding the URL for your own RSS feed and exposing it visibly on your site.&lt;/p&gt;
&lt;p&gt;It’s not complicated: just a link in the head of your page&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; and a link in the body with an RSS icon and Bob’s your decentralised Uncle.&lt;/p&gt;
&lt;p&gt;Here’s the link in the head:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;link&lt;/span&gt; 
  &lt;span style=&#34;color:#4070a0&#34;&gt;rel&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;alternate&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;application/rss+xml&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;https://ar.al/index.xml&amp;#34;&lt;/span&gt;
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And here’s the header in the body that links to the RSS feed visually using an icon.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;a&lt;/span&gt; 
  &lt;span style=&#34;color:#4070a0&#34;&gt;rel&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;alternate&amp;#39;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;application/rss+xml&amp;#39;&lt;/span&gt;
  &lt;span style=&#34;color:#4070a0&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/index.xml&amp;#39;&lt;/span&gt;
&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;img&lt;/span&gt; 
    &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;rss&amp;#39;&lt;/span&gt; 
    &lt;span style=&#34;color:#4070a0&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;/icons/rss.svg&amp;#39;&lt;/span&gt;
    &lt;span style=&#34;color:#4070a0&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;RSS feed icon&amp;#39;&lt;/span&gt;
    &lt;span style=&#34;color:#4070a0&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;Subscribe to my RSS feed&amp;#39;&lt;/span&gt;
  &amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check out &lt;a href=&#34;https://thenounproject.com&#34;&gt;The Noun Project&lt;/a&gt; for &lt;a href=&#34;https://creativecommons.org&#34;&gt;a set of RSS icons&lt;/a&gt; you can use under &lt;a href=&#34;https://creativecommons.org&#34;&gt;Creative Commons&lt;/a&gt; licenses.&lt;/p&gt;
&lt;h3 id=&#34;full-fat-vs-skinny-rss&#34;&gt;Full-fat vs skinny RSS&lt;/h3&gt;
&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/29/reclaiming-rss/leaf-rss-reader.jpg&#34;
         alt=&#34;Screenshot of the Leaf RSS reader on macOS showing my subscriptions, the list of latest posts from my blog, and my post on Kyriarchy, being displayed perfectly.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Leaf RSS reader displays full HTML content perfectly.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When generating an RSS feed for your site, you have the option to include only summaries of your posts or the full content. I modified my Hugo configuration and the default RSS template &lt;a href=&#34;https://randomgeekery.org/2017/09/15/full-content-hugo-feeds/&#34;&gt;using these instructions by Brian Wisti&lt;/a&gt; to include the full content feed and I recommend that you do the same.&lt;/p&gt;
&lt;p&gt;Six years ago, &lt;a href=&#34;http://www.breakingthin.gs/reinvent-the-wheel.html&#34;&gt;I was arguing the opposite&lt;/a&gt;, stating that “full fat RSS is duplicate content by another name”. I was wrong. I was too obsessed with maintaining a formalistic stranglehold over my designs and thus failed to correctly weigh the decision using &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;ethical design criteria&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/29/reclaiming-rss/newsbar-rss-reader.jpg&#34;
         alt=&#34;Screenshot of the NewsBar RSS Reader on macOS showing my subscriptions, the list of latest posts from my blog, and a preview of my post on Kyriarchy, with the header image and styles missing.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Newsbar RSS Reader doesn’t display images or styles correctly in content previews.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The more ways people have of consuming your content, the more resilient that content becomes and the more freedom people have.&lt;/p&gt;
&lt;p&gt;Duplicate content? Yes, please. The more the better! Heck, on &lt;a href=&#34;dat://ar.al&#34;&gt;the peer-web version of this site&lt;/a&gt;, the goal is to ideally have the content duplicated as many times as there are people consuming it.&lt;/p&gt;
&lt;p&gt;Yes, your content may not display the same in certain RSS readers that are not standards-compliant but that’s their problem, not yours. In my limited testing, the &lt;a href=&#34;https://itunes.apple.com/gb/app/leaf-rss-news-reader/id576338668?mt=12&#34;&gt;Leaf RSS reader&lt;/a&gt; for macOS displayed my full-fat RSS perfectly while the &lt;a href=&#34;https://itunes.apple.com/gb/app/newsbar-rss-reader/id440472232?mt=12&#34;&gt;NewsBar app&lt;/a&gt; didn’t. That’s OK. (And I hope NewsBar will take note and improve its rendering in its next update. No app is born perfect.)&lt;/p&gt;
&lt;p&gt;As we move away from the centralised web to the peer web, it’s time to rediscover, re-embrace, and reclaim RSS.&lt;/p&gt;
&lt;p&gt;Everything old is new again.&lt;/p&gt;
&lt;p&gt;RSS was an essential part of Web 1.0 before surveillance capitalism (Web 2.0) took over.&lt;/p&gt;
&lt;p&gt;It will be a cherished part of &lt;a href=&#34;https://ar.al/2018/06/26/web+/&#34;&gt;Web+&lt;/a&gt; and beyond.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://inkdroid.org&#34;&gt;Ed Summers&lt;/a&gt; for &lt;a href=&#34;https://social.coop/@edsu/100288354478817313&#34;&gt;pointing out via Mastodon&lt;/a&gt; that I’d forgotten to add the RSS feed URL to the heads of my pages. I also noticed while looking into it &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/RSS/Getting_Started/Syndicating&#34;&gt;on MDN&lt;/a&gt; that there are additional semantics you can add to the links you use in the body. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Are You Logging IPs Without Even Knowing?</title>
      <link>https://ar.al/2018/06/28/are-you-logging-ips-without-even-knowing/</link>
      <pubDate>Thu, 28 Jun 2018 18:04:39 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/28/are-you-logging-ips-without-even-knowing/</guid>
      <description>&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/28/are-you-logging-ips-without-even-knowing/no-logs.jpg&#34;
         alt=&#34;A screenshot of a terminal session to my server for this blog in the /var/log/nginx folder showing an ls command with no output.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Log off.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My blog is served both over the centralised web via HTTPS by &lt;a href=&#34;https://nginx.org&#34;&gt;nginx&lt;/a&gt; and over the peer web via &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;nginx has an access log that is enabled by default that logs IP addresses. Needless to say, I don’t want this information.&lt;/p&gt;
&lt;p&gt;Thankfully, turning it off is easy:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;server&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;access_log&lt;/span&gt; &lt;span style=&#34;color:#60add5&#34;&gt;off&lt;/span&gt;;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;p&gt;(You might still be logging IP addresses in the error log so it’s a good idea to clear those out also on a regular basis.)&lt;/p&gt;
&lt;p&gt;Remember that clearing out existing logs in nginx is as easy as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;rm /var/log/nginx/*&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you want to keep logs (e.g., to calculate unique views, etc.), you can still do that in a privacy-respecting manner by storing a hash of the IP address in your logs instead of the IP address itself. There’s an nginx module called &lt;a href=&#34;https://github.com/masonicboom/ipscrub&#34;&gt;ipscrub&lt;/a&gt; you can use for that purpose.&lt;/p&gt;
&lt;p&gt;I do wish that servers like nginx came with privacy-respecting (and secure) settings by default.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Real News: Turkish Elections</title>
      <link>https://ar.al/2018/06/27/the-real-news-turkish-elections/</link>
      <pubDate>Wed, 27 Jun 2018 19:13:44 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/27/the-real-news-turkish-elections/</guid>
      <description>&lt;figure&gt;
  &lt;video controls poster=&#39;https://i.vimeocdn.com/video/710073332.jpg?mw=1800&amp;mh=1012&amp;q=70&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/277330755.m3u8?s=b551f1a1d4d877e726661bf2876247ca7493bf7b&#39; type=&#39;video/mp4&#39;&gt;
    &lt;source src=&#39;https://player.vimeo.com/external/277330755.hd.mp4?s=4b5f17fdae7f500eee0c8f62692f6462b038939d&amp;profile_id=174&#39; type=&#39;video/mp4&#39;&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;A short one-minute teaser of my interview on the Turkish elections with Ben Norton.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Yesterday, I had a lovely chat with &lt;a href=&#34;https://www.bennorton.com&#34;&gt;Ben Norton&lt;/a&gt; for &lt;a href=&#34;https://therealnews.com&#34;&gt;The Real News&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We talked about two things: this week’s Turkish elections and the effect of technology on human rights and democracy.&lt;/p&gt;
&lt;p&gt;The first part of our chat is now live.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch it here:&lt;/strong&gt; &lt;a href=&#34;https://therealnews.com/stories/turkeys-erdogan-expands-authoritarian-powers-with-fascist-coalition-election-victory&#34;&gt;Turkey’s Erdogan Expands Authoritarian Powers with Fascist Coalition Election Victory&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kyriarchy</title>
      <link>https://ar.al/2018/06/27/kyriarchy/</link>
      <pubDate>Wed, 27 Jun 2018 11:21:15 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/27/kyriarchy/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/27/kyriarchy/kyriarchy-josette-souza.jpg&#34;
         alt=&#34;Illustration by Josette Souza showing a warrior with blonde hair on a horse with a speak facing off a multi-headed green monster by the entrance of a cave. The necks of the monster have the words ‘body’, ‘race’, ‘sex’, ‘gender’, and ‘class’ written on them. The panel is titled ‘Kyriarchy’.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Kyriarchy: a many-headed monster. Illustration by Josette Souza.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I learned a new word today: &lt;a href=&#34;https://en.wikipedia.org/wiki/Kyriarchy&#34;&gt;Kyriarchy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I found it in Diana’s &lt;a href=&#34;https://garbados.github.io/my-blog/lets-begin.html&#34;&gt;hugely inspirational initial post&lt;/a&gt; on &lt;a href=&#34;https://garbados.github.io/my-blog/index.html&#34;&gt;her new blog&lt;/a&gt; and immediately realised it was the word I was struggling for all those times that “patriarchy” just wasn’t cutting the intersectional mustard in conversation.&lt;/p&gt;
&lt;p&gt;According to Wikipedia, Kyriarchy is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a social system or set of connecting social systems built around domination, oppression, and submission. The word was coined by &lt;a href=&#34;https://en.wikipedia.org/wiki/Elisabeth_Sch%C3%BCssler_Fiorenza&#34;&gt;Elisabeth Schüssler Fiorenza&lt;/a&gt; in 1992 to describe her theory of interconnected, interacting, and self-extending systems of domination and submission, in which a single individual might be oppressed in some relationships and privileged in others. It is an intersectional extension of the idea of patriarchy beyond gender.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://everydayfeminism.com&#34;&gt;Everyday Feminism magazine&lt;/a&gt; has &lt;a href=&#34;https://everydayfeminism.com/2015/03/slay-the-kyriarchy/&#34;&gt;a comic by Josette Souza&lt;/a&gt; that explains it visually.&lt;/p&gt;
&lt;p&gt;And in &lt;a href=&#34;https://garbados.github.io/my-blog/lets-begin.html&#34;&gt;Diana’s words&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am more resolved than ever that we will end the kyriarchy. No one needs to starve or freeze or die. We can all live without fear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here’s to that.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Web&#43;</title>
      <link>https://ar.al/2018/06/26/web&#43;/</link>
      <pubDate>Tue, 26 Jun 2018 16:59:55 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/26/web&#43;/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/26/web+/web-plus-training-wheels-for-the-peer-web.jpg&#34;
         alt=&#34;A bicycle with training wheels. One of the training wheels is highlighted with a red circle.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Web+ is like training wheels for the Peer Web. It creates a bridge between the centralised Web and the Peer Web to introduce the former to the latter.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My personal blog is now a Web+ site.&lt;/p&gt;
&lt;p&gt;A what?&lt;/p&gt;
&lt;p&gt;Well, it’s a web site but also a little more than a regular web site. It’s available on the regular web at &lt;a href=&#34;https://ar.al&#34;&gt;https://ar.al&lt;/a&gt;, but it is also available on the peer-to-peer Web (henceforth “Peer Web”) via the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt; protocol at &lt;a href=&#34;dat://ar.al&#34;&gt;dat://ar.al&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To load the second link, you will need a DAT-capable browser. Currently, the only browser with native support for the DAT protocol is &lt;a href=&#34;https://beakerbrowser.com&#34;&gt;Beaker Browser&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;web--&#34;&gt;Web + ?&lt;/h3&gt;
&lt;p&gt;So this site is a Web+ DAT site. It is accessible to the mainstream via the centralised Web and it progressively enhances and helps decentralise the centralised Web by layering on peer-to-peer functionality via a peer-to-peer channel.&lt;/p&gt;
&lt;p&gt;A Web+ site doesn’t have to use DAT to be a Web+ site. There are other federation and decentralisation protocols and systems that can and should be explored. These include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/ActivityPub&#34;&gt;ActivityPub&lt;/a&gt; (see &lt;a href=&#34;https://joinmastodon.org&#34;&gt;Mastodon&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.scuttlebutt.nz&#34;&gt;Secure ScuttleButt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://matrix.org&#34;&gt;Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://opennic.org&#34;&gt;OpenNIC&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/RSS&#34;&gt;RSS&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I’ve forgotten any (that aren’t blockchain-based – &lt;em&gt;spit!&lt;/em&gt; – or venture-capital-funded – &lt;em&gt;spit!&lt;/em&gt;), please &lt;a href=&#34;https://mastodon.ar.al&#34;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;how-do-i-web&#34;&gt;How do I Web+?&lt;/h3&gt;
&lt;p&gt;Well, right now, the honest answer is: not easily. Not if you’re not a developer.&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;are&lt;/em&gt; a developer, it’s not difficult but the experience can (and will) still be made easier.&lt;/p&gt;
&lt;p&gt;I was able to set up &lt;a href=&#34;https://ar.al/2018/06/15/hello-peer-to-peer-web/&#34;&gt;the first version of this site&lt;/a&gt; in an hour, including provisioning, configuring, and deploying the server (all while &lt;a href=&#34;https://mastodon.ar.al/@aral/100207852262520843&#34;&gt;live tooting it&lt;/a&gt;). I wrote &lt;a href=&#34;https://ar.al/2018/06/16/refining-the-blog/&#34;&gt;a little more about my setup&lt;/a&gt; earlier and I plan to streamline the setup further in the future.&lt;/p&gt;
&lt;p&gt;You can also &lt;a href=&#34;https://source.ind.ie/ar.al&#34;&gt;look through the source code&lt;/a&gt; to see how things work.&lt;/p&gt;
&lt;p&gt;TL; DR: I use &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; on my local machine to write my posts (in &lt;a href=&#34;https://code.visualstudio.com&#34;&gt;VSCode&lt;/a&gt;). If I’m in &lt;a href=&#34;https://source.ind.ie/ar.al/site/blob/master/live&#34;&gt;live mode&lt;/a&gt;, there’s &lt;a href=&#34;https://github.com/Splurov/rsync-watch&#34;&gt;a file system watcher that rsyncs updates to my server&lt;/a&gt; on every save. There, I have &lt;a href=&#34;https://forum.ind.ie/t/running-a-dat-share-as-a-service-with-systemctl-ubuntu-etc/2181/1&#34;&gt;DAT share running as a service&lt;/a&gt; and it automatically shares the updated DAT site on its peer-to-peer swarm.&lt;/p&gt;
&lt;p&gt;Of course, the ultimate goal isn’t just to create yet another fun toy for the technically-privileged. This is one step towards implementing a Web+ experience for everyone. That’s &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;the project&lt;/a&gt; I’m working on and I will be evolving it to use DAT.&lt;/p&gt;
&lt;h3 id=&#34;advantages&#34;&gt;Advantages&lt;/h3&gt;
&lt;p&gt;So why go to the trouble of creating a Web+ experience?&lt;/p&gt;
&lt;p&gt;Here are three advantages, off the top of my head:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Censorship-circumvention&lt;/li&gt;
&lt;li&gt;Streaming web site updates&lt;/li&gt;
&lt;li&gt;Scalability without additional resources&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While #2 might be the coolest feature for everyday use, #1 has the most potential to change the world for the better so we’ll start there.&lt;/p&gt;
&lt;h3 id=&#34;censorship-circumvention&#34;&gt;Censorship-circumvention&lt;/h3&gt;
&lt;p&gt;While you can access this site via a friendly DAT URL (using &lt;a href=&#34;https://www.datprotocol.com/deps/0005-dns/&#34;&gt;DAT DNS&lt;/a&gt;), the actual long-form DAT address that it maps to is a cryptographic public key that is unique to the &lt;a href=&#34;https://garbados.github.io/my-blog/distributed-datastructures.html&#34;&gt;mutable torrent-like archive&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; of this site:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;dat://d82084b53fce5f07696c2acf98a363601ec9410d06c633513269979786464eb3/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, let’s say I’m in a country with an autocratic government and that I post something that the government wants to block. They can easily block my domain. But what if three or four people have already accessed my site over DAT? Well, now the site exists in three or four other places. And they can serve it to maybe another thirty or forty. And as long as they share the long-form DAT address, the government will have to block everyone that has a copy to block the content.&lt;/p&gt;
&lt;p&gt;Right now, the long-form address is a bit hidden on this site but I will be exploring ways of exposing it more explicitly. One way to share the addresses might be with QR-Codes. (Might there actually finally be a good use case for QR-Codes after all?)&lt;/p&gt;
&lt;h3 id=&#34;streaming-web-site-updates&#34;&gt;Streaming web site updates&lt;/h3&gt;
&lt;p&gt;We can already do server push on the centralised Web using either long-polling (like a caveman) or WebSockets. The problem is that it’s not trivial. And the more popular your content gets, the more resources you will need in order to meet the demand.&lt;/p&gt;
&lt;p&gt;What if streaming updates to all clients just came for free? And you had to do nothing to support it? And what if the load on your server didn’t increase as your site got more popular.&lt;/p&gt;
&lt;p&gt;Well, that’s how it is with DAT.&lt;/p&gt;
&lt;p&gt;If you view this site in &lt;a href=&#34;https://beakerbrowser.com&#34;&gt;Beaker Browser&lt;/a&gt; and you turn on the Live Reload feature, you will get streaming updates on changes. I’ve already live blogged two events: the &lt;a href=&#34;https://ar.al/2018/06/19/public-stack-summit/&#34;&gt;Public Stack Summit&lt;/a&gt; and the &lt;a href=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/&#34;&gt;Next Generation Cities session at We Make the City&lt;/a&gt; in Amsterdam.&lt;/p&gt;
&lt;h3 id=&#34;scalability-without-additional-resources&#34;&gt;Scalability without additional resources&lt;/h3&gt;
&lt;p&gt;I touched upon this in the previous point also: one of the big advantages of peer-to-peer systems is that the network absorbs the load. As your site becomes more popular, you do not need to expand additional resources as your load is distributed on the network. Also, the more popular your site becomes, the more resilient it becomes as a growing number of other nodes begin to mirror your content.&lt;/p&gt;
&lt;p&gt;So basically, it’s win-win.&lt;/p&gt;
&lt;p&gt;And since we’re progressively enhancing the centralised Web, we are not leaving anyone behind. If people want to view the site over HTTPS, they can go right ahead. And if they want to download &lt;a href=&#34;https://beakerbrowser.com&#34;&gt;Beaker Browser&lt;/a&gt; out of curiosity (or because they’re staging a democratic revolution, I guess) then it could possibly be their introduction to the freedom of the Peer Web.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;http://opennic.org&#34;&gt;OpenNIC&lt;/a&gt; might seem like the odd one out in the list but it isn’t.&lt;/p&gt;
&lt;p&gt;The centralised and capitalist nature of the domain name system is a huge design problem for those of us working to build bridges between the centralised Web and the Peer Webs as the current system makes it very difficult to match the onboarding experience of centralised platforms. (You can sign up to a centralised social network in 30 seconds… can you register your own domain name, have your server and app set up, and be up and running with your decentralised one in that time? &lt;a href=&#34;https://small-tech.org/research-and-development/&#34;&gt;You could.&lt;/a&gt; But not with our current system.) Web+ must also move beyond the greed-led shortsightedness of ICAAN to embrace a world of zero time-to-live domain propagations and a domain name commons.) &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Yes, &lt;a href=&#34;2018/06/29/rediscovering-rss/&#34;&gt;good old RSS&lt;/a&gt;. &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;My original description said that the address was “unique to the &lt;em&gt;content&lt;/em&gt;” which was ambiguous as it could easily be interpreted to mean to an immutable hash of the content.&lt;/p&gt;
&lt;p&gt;An advantage of DAT is that while the hash of the public key is used to discover content on the swarm, the content itself is mutable and can be updated by the owner of the private key. Multi-writer DAT – which &lt;a href=&#34;https://blog.datproject.org/2018/05/14/dat-shopping-list/&#34;&gt;is being developed as we speak&lt;/a&gt; and is what I’m most excited about – extends this to allow multiple authorised writers. I’m grateful to &lt;a href=&#34;https://garbados.github.io/my-blog/&#34;&gt;Diana&lt;/a&gt; for &lt;a href=&#34;https://toot.cat/@garbados/100273034019785882&#34;&gt;pointing this out&lt;/a&gt; via Mastodon. &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Demonstrating Web &#43; Dat</title>
      <link>https://ar.al/2018/06/25/demonstrating-web-plus-dat/</link>
      <pubDate>Mon, 25 Jun 2018 21:29:27 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/25/demonstrating-web-plus-dat/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2018/06/25/demonstrating-web-plus-dat/meta.jpg&#34; alt=&#34;A screenshot of my screen showing VSCode on one half, three small Beaker Browser windows and one small Safari window on the other – all have this site loaded. This is meta!&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is a short demonstration to show live reload and the peer-to-peer features of my new blog using Web + DAT.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;214116&#34;&gt;21:41:16&lt;/h3&gt;
&lt;p&gt;The above content was created as part of a screen recording for my talk at Nesta’s FutureFest at the start of July. As part of the talk I will be showing this blog which progressively enhances the Web with peer-to-peer functionality using &lt;a href=&#34;http://datproject.org&#34;&gt;DAT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the recording (which I’ve just finished and haven’t even exported from &lt;a href=&#34;http://www.telestream.net/screenflow/overview.htm&#34;&gt;Screenflow&lt;/a&gt; yet), I show how changes to my posts are almost immediately reflected in &lt;a href=&#34;https://beakerbrowser.com/&#34;&gt;Beaker Browser&lt;/a&gt; when live reload is on whereas, with a plain web browser like Safari, you have to refresh manually.&lt;/p&gt;
&lt;p&gt;Also, while a growing number of people manually refreshing a site would create increasing load for the site, on the peer-to-peer web, the load is distributed. Unlike the old web, the more popular a site gets, the better it performs, without requiring additional resources from the site itself.&lt;/p&gt;
&lt;p&gt;In the talk I will also touch upon the importance of content-addressing and the peer-to-peer nature of DAT in relation to censorship prevention and network resilience in general.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>We Make the City: Next Generation Cities</title>
      <link>https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/</link>
      <pubDate>Thu, 21 Jun 2018 10:11:03 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was live blogged. Entries are in reverse chronological order.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;113003&#34;&gt;11:30:03&lt;/h3&gt;
&lt;p&gt;Next up: &lt;a href=&#34;https://www.arup.com/our-firm/dan-hill&#34;&gt;Dan Hill&lt;/a&gt; (Associate Director, Arup). He’s a visiting professor at UCL Bartlett “Institute of Innovation and Public Purpose” and “Mayor of London Design Advocate”.&lt;/p&gt;
&lt;p&gt;(So this is going to be a follow-up of Theo’s presentation to give details of the City of London’s &lt;strike&gt;Smart City&lt;/strike&gt; Panopticon efforts.)&lt;/p&gt;
&lt;p&gt;I won’t be live blogging this, see the previous talk notes for my thoughts on the matter.&lt;/p&gt;
&lt;h3 id=&#34;112708&#34;&gt;11:27:08&lt;/h3&gt;
&lt;p&gt;I just called him out in a question for following neoliberal/Silicon Valley talking points and asked him “what would you say to those who might say that your goal is to make London into an ever better panopticon.” Also asked him how he feels partnering with Google Deepmind and other surveillance capitalists and exposing the citizens of London to surveillance by these factory farmers for human beings.&lt;/p&gt;
&lt;p&gt;The response was essentially that he doesn’t see a problem with surveillance capitalism.&lt;/p&gt;
&lt;h3 id=&#34;110314&#34;&gt;11:03:14&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/theo-blackwell.jpg&#34;
         alt=&#34;Theo Blackwell, CTO London on stage.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Next up: Theo Blackwell, CTO London.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;“We need to set the foundations for innovation.” (There’s that word again.)&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/building-a-panopticon.jpg&#34;
         alt=&#34;Theo Blackwell tells us about their plans for the future of London&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Theo Blackwell outlining his plans for making London an even better panopticon.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Theo is basically parroting neoliberal/Silicon Valley talking points.&lt;/p&gt;
&lt;p&gt;London has… “Hundreds and hundreds of systems that collect data on people.” (Oh, yay!)&lt;/p&gt;
&lt;p&gt;Essentially, the City of London’s strategy from what I’m hearing is that they want London to become an even better panopticon.&lt;/p&gt;
&lt;p&gt;(I’m done live blogging this session. It’s clear what it is.)&lt;/p&gt;
&lt;h3 id=&#34;110128&#34;&gt;11:01:28&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/panel-end.jpg&#34;
         alt=&#34;The end of the panel: with links to GNU&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The end of the panel: with links to GNU.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “We’ve got to prohibit face recognition in the streets.”&lt;/p&gt;
&lt;h3 id=&#34;105810&#34;&gt;10:58:10&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Marleen:&lt;/strong&gt; “We don’t want backdoors in our cities… what do we do?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “use free software. Get rid of the Internet of Stings … they are designed to spy on people … Any product that works by talking to its manufacturer, treat it like an enemy … but the city can impose these things on people.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Francesca:&lt;/strong&gt; “this is part of GDPR…”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; ”I’m afraid they won’t interpret that this way.”&lt;/p&gt;
&lt;h3 id=&#34;105401&#34;&gt;10:54:01&lt;/h3&gt;
&lt;p&gt;Francesca: “public code, public money”&lt;/p&gt;
&lt;p&gt;Richard: “non-free software is full of malicious functionality like backdoors. Microsoft has a universal backdoor on their operating systems.”&lt;/p&gt;
&lt;h3 id=&#34;104907&#34;&gt;10:49:07&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/richard-marleen-francesca.jpg&#34;
         alt=&#34;Richard, Marleen, and Francesca&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Richard, Marleen, and Francesca.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “With free software you have a free market for support.”&lt;/p&gt;
&lt;p&gt;Richard suggests that Munich changed its mind about free software because Microsoft moved its headquarters there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “It’s illegal to bribe an individual politician but it is not a crime to bribe an entire government.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “The worship of the invisible hand is an absurd religion. You see all the suffering that religion has caused in the world. Neoliberalism … ‘trade treaties’ … they give foreign companies more rights than their own citizens.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “There is a powerful force that says ‘business is important, freedom is not’. Most of the governments that talk about software stay away from any talk of ethics.”&lt;/p&gt;
&lt;h3 id=&#34;104405&#34;&gt;10:44:05&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “Whenever they say ‘smart’ in relation to computing, think ‘spy’”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “All I’ve done in working about Barcelona is to give some advice. I discuss the ethical questions. What is it legitimate to do? What is it wrong to do? You see, Barcelona’s City Hall is a government … evertthing they do is supposed to be for the people or it is not justified at all and that includes all computing they do … it must never allow that computing to fall into private hands.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “If you let some company do your computing activity, that company controls your computing activity.”&lt;/p&gt;
&lt;p&gt;(Richard’s asked if Marleen’s opinion that switching fully to free software in Barcelona will take 25 years… “is that too long?”)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “The point is not to save one year. The point is: don’t make it take a century.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Francesca:&lt;/strong&gt; “We are enshrining Richard’s values in municipal law.”&lt;/p&gt;
&lt;h3 id=&#34;103758&#34;&gt;10:37:58&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “Either the users control the program or the program controls the users. If users control the program, that’s called free software.”&lt;/p&gt;
&lt;p&gt;(He’s now taking the audience through &lt;a href=&#34;https://www.fsf.org/events/js-20121019-santiagodecompostela&#34;&gt;the four freedoms&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;“Non-free software should not exist. 100% of the money Barcelona spends should go to free software. People should not work on making non-free software. Making non-free software works to subjugate people.”&lt;/p&gt;
&lt;h3 id=&#34;103216&#34;&gt;10:32:16&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/richard-stallman.jpg&#34;
         alt=&#34;Richard Stallman on stage&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Richard Stallman takes the stage to talk about freedom in technology.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Richard’s on stage now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Francesca:&lt;/strong&gt; “we’re working with Richard in Barcelona … not many people know that without the work Richard has done we couldn’t be doing what we’re doing to rebuild the city and give control back to people. We are starting to use free software in public administration.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “forget the term sharing economy … it’s mistreatment of workers – I call it the sweatshop economy. And don’t ever use Uber!”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richard:&lt;/strong&gt; “‘free’ in English is ambiguous. It means free as in cost and free as in freedom. When I use the word, I only mean ‘freedom’”&lt;/p&gt;
&lt;h3 id=&#34;102727&#34;&gt;10:27:27&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/marleen-and-francesca.jpg&#34;
         alt=&#34;Marleen and Francesca&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Marleen and Francesca talking about the work of the City of Barcelona in creating a commons-based city.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Following Marleen’s session, we are transitioning to a panel with Richard Stallman and Francesca Bria (CTO Barcelona).&lt;/p&gt;
&lt;p&gt;Said hi to Richard before the event and he was very kind: “I read about what you’re doing in print.” (Richard’s not on stage yet.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Francesca:&lt;/strong&gt; “giving back sovereignty to citizens is a fundamental concern… we must move away from surveillance capitalism.” (I agree: &lt;a href=&#34;https://ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;individual sovereignty is a prerequisite for a healthy commons&lt;/a&gt;.)&lt;/p&gt;
&lt;h3 id=&#34;101541&#34;&gt;10:15:41&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/marleen.jpg&#34;
         alt=&#34;Marleen Stikker presenting her opening keynote on the public stack&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Marleen Stikker from Waag presents her opening keynote on the public stack.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Opening keynote: Marleen Stikker from Waag. (I’m here as Marleen’s guest and she’s awesome.)&lt;/p&gt;
&lt;p&gt;“Who owns the city?”&lt;/p&gt;
&lt;p&gt;“Do we want to be smart or do we want shared cities?”&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/the-public-stack.jpg&#34;
         alt=&#34;Marleen presenting the public stack&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Marleen presenting the public stack we put together on the boat two days ago.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;(I really want us to start talking about incentivising &lt;em&gt;smart citizens&lt;/em&gt; not smart cities. The question to ask: “who’s getting smarter about whom?” If it’s corporations, cities, governments getting smarter about people, we are building a panopticon. If it’s individuals getting smarter about individuals then we have a system that is compatible with human rights, democracy, and &lt;a href=&#34;https://ar.al/notes/encouraging-individual-sovereignty-and-a-healthy-commons/&#34;&gt;a healthy commons&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;“It’s not just open… we need freedom in our technology.” (Stallman will be on stage soon.)&lt;/p&gt;
&lt;p&gt;“In the last 25 years we lost the Internet to the market and it might take us another 25 years to reclaim it for the commons.” (Do we have 25 years? I feel we must do this much more quickly.)&lt;/p&gt;
&lt;h3 id=&#34;101124&#34;&gt;10:11:24&lt;/h3&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-next-generation-cities/deputy-mayor.jpg&#34;
         alt=&#34;The deputy mayor of Amsterdam on stage&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The deputy mayor of Amsterdam kicking off the day with her introduction.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’m at the opening of the We Make The City event (Next Generation Cities). Right now, the deputy mayor is talking about where they see the “digital city” going forward. Introduces the “Amsterdam Innovation Tour” app.&lt;/p&gt;
&lt;p&gt;A good time to remind the kind reader that “innovation” is a Silicon Valley/neoliberal mantra.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>We Make the City: Opening Night</title>
      <link>https://ar.al/2018/06/21/we-make-the-city-opening-night/</link>
      <pubDate>Thu, 21 Jun 2018 08:15:57 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/21/we-make-the-city-opening-night/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-opening-night/we-make-the-city.jpg&#34; alt=&#34;We Make The City stage&#34;&gt;&lt;/p&gt;
&lt;p&gt;Last night, I was mostly positively surprised by the opening night of the We Make The City festival in Amsterdam. &lt;a href=&#34;https://twitter.com/aral/status/1009483631776141312&#34;&gt;I was expecting a wholly neoliberal affair&lt;/a&gt; and it didn’t turn out that way.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-opening-night/kate-raworth.jpg&#34;
         alt=&#34;Kate Raworth presenting&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Finally got to see Kate Raworth, she of doughnut fame, and it did not disappoint.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After speaking at at least two conferences together, I finally got to see &lt;a href=&#34;https://www.kateraworth.com&#34;&gt;Kate Raworth&lt;/a&gt; present on Doughnut Economics (for the city). It was great to see her directly address the need to move from centralised to distributed/decentralised systems.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-opening-night/kate-raworth-centralised-distributed.jpg&#34;
         alt=&#34;Kate Raworth presenting: slide: centralised vs distributed network topologies&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;We must from centralised to distributed/decentralised systems.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-opening-night/extractive-to-generative-mindset.jpg&#34;
         alt=&#34;Slide: Extractive vs Generative mindset&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;We must move from an extractive to a generative mindset.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In a refreshing all-female line-up, the evening included mostly progressive discourse.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/21/we-make-the-city-opening-night/panel.jpg&#34;
         alt=&#34;Panel&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;It was a refreshingly progressive affair for the most part.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Inexplicably, in the middle of this, we were presented by the CTO of Dubai in what I can only describe as a major whitewashing PR win for Dubai. Needless to say, there is nothing progressive about Dubai or the UAE. On stage, when asked about concerns about Big Brother-like surveillance, the CTO of Dubai responded with “We don’t call it Big Brother, we call it Fatherhood” to the sound of gasps from the audience.&lt;/p&gt;
&lt;p&gt;Today, I am going to be taking part in the &lt;a href=&#34;https://wemakethe.city/nl/programma-item?programid=4193&#34;&gt;Next Generation Cities&lt;/a&gt; event as well as an evening event before returning to Ireland tomorrow.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Digital Masquerade</title>
      <link>https://ar.al/2018/06/20/digital-masquerade/</link>
      <pubDate>Wed, 20 Jun 2018 17:10:39 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/20/digital-masquerade/</guid>
      <description>&lt;p&gt;“Today, we are all cyborgs. This is not to say that we implant ourselves with technology but that we extend our biological capabilities using technology. We are sharded beings; with parts of our selves spread across and augmented by our everyday things.” – &lt;a href=&#34;https://ar.al/notes/the-nature-of-the-self-in-the-digital-age/&#34;&gt;The Nature of the Self in the Digital Age&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/digital-masquerade/louisa-treichmann-digital-masquerade.jpg&#34;
         alt=&#34;Louisa preparing the camera&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Louisa preparing the camera for our shoot.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://www.wdka.nl/stories/its-yours-louisa&#34;&gt;Louisa Treichmann&lt;/a&gt; is preparing an exhibition titled &lt;a href=&#34;http://waag.org/en/event/digital-masquerade&#34;&gt;Digital Masquerade&lt;/a&gt; as part of the Manifesting Futures series. The event will take place next week in the Theatrum Anatomicum at De Waag. Today, I had the pleasure of chatting at length with Louisa on the topic of identity and to record a direct-to-camera piece on the cyborg nature of the self that will be projected on the wall during the event.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://waag.org/en/event/digital-masquerade&#34;&gt;Visit the Digital Masquerade web site&lt;/a&gt; to learn more about the event and to sign up.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Working From De Waag</title>
      <link>https://ar.al/2018/06/20/working-from-de-waag/</link>
      <pubDate>Wed, 20 Jun 2018 16:42:26 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/20/working-from-de-waag/</guid>
      <description>&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/working-from-de-waag/de-waag.jpg&#34;
         alt=&#34;De Waag building: outside view&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;De Waag: a historic building in the heart of Amsterdam&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This evening, Laura and I are attending the opening of &lt;a href=&#34;https://wemakethe.city/en/&#34;&gt;We Make The City&lt;/a&gt; as guests of &lt;a href=&#34;https://waag.org&#34;&gt;Waag&lt;/a&gt;. And today, we were working out of Waag’s beautiful home, De Waag, in central Amsterdam.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/working-from-de-waag/our-office-sophie-and-laura.jpg&#34;
         alt=&#34;Inside of our office at De Waag&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Our office for the day which we shared with Sophie Bloemen from the Commons Network.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/working-from-de-waag/laura-stairs.jpg&#34;
         alt=&#34;Laura on the staircase inside the De Waag&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Laura caught using the stairs at De Waag.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/working-from-de-waag/laura-rembrandt-appreciation-society.jpg&#34;
         alt=&#34;Laura pulling a funny face by a copy of the Rembrandt painting “The Anatomy Lesson of Dr. Nicolaes Tulp” which was set in this very room.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Laura appreciating the Rembrandt painting “The Anatomy Lesson of Dr. Nicolaes Tulp” which was set in this very room.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>Override BaseURL in Hugo Server</title>
      <link>https://ar.al/2018/06/20/override-baseurl-in-hugo-server/</link>
      <pubDate>Wed, 20 Jun 2018 15:33:28 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/20/override-baseurl-in-hugo-server/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; has a &lt;code&gt;baseURL&lt;/code&gt; setting in its configuration file (&lt;em&gt;config.toml&lt;/em&gt;) that is used when creating absolute URLs. Sadly, &lt;a href=&#34;https://github.com/gohugoio/hugo/issues/1046&#34;&gt;this setting is ignored&lt;/a&gt; if you’re running Hugo with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;hugo server&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I use relative URLs in all of my posts and templates but I got bitten by this with the generated &lt;a href=&#34;index.xml&#34;&gt;RSS feed&lt;/a&gt;. All the URLs were being written with &lt;em&gt;localhost:1313&lt;/em&gt; prefixes.&lt;/p&gt;
&lt;p&gt;To work around this issue, start Hugo using the &lt;code&gt;--baseURL&lt;/code&gt; and &lt;code&gt;--appendPort&lt;/code&gt; flags to override the default settings. e.g. I start a live session on this blog with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;hugo server -D --renderToDisk --baseURL&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;https://ar.al --appendPort&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;false&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Little Saigon</title>
      <link>https://ar.al/2018/06/20/little-saigon/</link>
      <pubDate>Wed, 20 Jun 2018 13:20:39 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/20/little-saigon/</guid>
      <description>&lt;p&gt;Having lunch at &lt;a href=&#34;https://www.littlesaigon.nl&#34;&gt;Little Saigon&lt;/a&gt; with &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/little-saigon/little-saigon-outside.jpeg&#34;
         alt=&#34;Little Saigon: view from the outside&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Little Saigon: view from the outside&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/little-saigon/laura.jpeg&#34;
         alt=&#34;Laura smiling with her salad roll and coconut juices in the foreground&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Laura, salad rolls, and coconut juice.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/little-saigon/menu.jpeg&#34;
         alt=&#34;The menu&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The menu&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://ar.al/2018/06/20/little-saigon/lauras-salad-roll.jpg&#34;
         alt=&#34;Laura’s salad roll (close up)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Close-up of Laura’s salad roll&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;location&#34;&gt;Location&lt;/h3&gt;
&lt;iframe width=&#34;425&#34; height=&#34;350&#34; frameborder=&#34;0&#34; scrolling=&#34;no&#34; marginheight=&#34;0&#34; marginwidth=&#34;0&#34; src=&#34;https://www.openstreetmap.org/export/embed.html?bbox=4.899481236934663%2C52.3736947908667%2C4.901149570941926%2C52.37477717326019&amp;amp;layer=mapnik&amp;amp;marker=52.3742359853793%2C4.9003154039382935&#34; style=&#34;border: 1px solid black&#34;&gt;&lt;/iframe&gt;&lt;br/&gt;&lt;small&gt;&lt;a href=&#34;https://www.openstreetmap.org/?mlat=52.37424&amp;amp;mlon=4.90032#map=19/52.37424/4.90032&#34;&gt;View Larger Map&lt;/a&gt;&lt;/small&gt;
</description>
    </item>
    
    <item>
      <title>Public Stack Summit</title>
      <link>https://ar.al/2018/06/19/public-stack-summit/</link>
      <pubDate>Tue, 19 Jun 2018 10:38:02 +0200</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/19/public-stack-summit/</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was live blogged. Entries are in reverse chronological order.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;142243&#34;&gt;14:22:43&lt;/h3&gt;
&lt;p&gt;Katja, Laura, Todd and myself had a lively conversation about what constitutes a public stack and presented it. Core principles: individual ownership/control and convenience. A main challenge is protecting personhood in the digital age. Personhood is a prerequisite to the social contract. Individual sovereignty is a necessary (but not sufficient) prerequisite for a healthy commons.&lt;/p&gt;
&lt;p&gt;Other teams raised points including: keeping values at the core of the stack (see &lt;a href=&#34;https://ind.ie/ethical-design&#34;&gt;Ethical Design Manifesto&lt;/a&gt;), ethics in sourcing and manufacture (see &lt;a href=&#34;https://fairphone.org&#34;&gt;Fairphone&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;112829&#34;&gt;11:28:29&lt;/h3&gt;
&lt;p&gt;I’m going to take a little break now so I can participate more directly. Will update if and when I have something interesting to share.&lt;/p&gt;
&lt;h3 id=&#34;112523&#34;&gt;11:25:23&lt;/h3&gt;
&lt;p&gt;This is how I’m &lt;a href=&#34;https://ar.al/2018/06/15/hello-peer-to-peer-web/&#34;&gt;live blogging&lt;/a&gt; with Hugo, rsync, and &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;, by the way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2018/06/19/public-stack-summit/2018-06-19-11-24-00.jpeg&#34; alt=&#34;My live blogging setup&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;111757&#34;&gt;11:17:57&lt;/h3&gt;
&lt;p&gt;Katja from &lt;a href=&#34;https://www.nesta.org.uk&#34;&gt;Nesta&lt;/a&gt; introducing herself. Laura and I will be at the &lt;a href=&#34;https://www.futurefest.org&#34;&gt;FutureFest&lt;/a&gt; conference they’re organising in London soon.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2018/06/19/public-stack-summit/2018-06-19-11-21-00.jpeg&#34; alt=&#34;Katja introducing herself&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;110128&#34;&gt;11:01:28&lt;/h3&gt;
&lt;p&gt;Introductions: &lt;a href=&#34;https://puri.sm&#34;&gt;Purism&lt;/a&gt;, &lt;a href=&#34;https://ind.ie&#34;&gt;Ind.ie&lt;/a&gt;, &lt;a href=&#34;https://www.yoti.com&#34;&gt;Yoti&lt;/a&gt;, &lt;a href=&#34;http://www.commonsnetwork.org&#34;&gt;Commons Network&lt;/a&gt; and others.&lt;/p&gt;
&lt;h3 id=&#34;104318&#34;&gt;10:43:18&lt;/h3&gt;
&lt;p&gt;We are on a boat so my connectivity may be limited.&lt;/p&gt;
&lt;h3 id=&#34;104236&#34;&gt;10:42:36&lt;/h3&gt;
&lt;p&gt;We’re being asked to draw each other an ice-breaker.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2018/06/19/public-stack-summit/2018-06-19-11-13-00.jpeg&#34; alt=&#34;The participants&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;103859&#34;&gt;10:38:59&lt;/h3&gt;
&lt;p&gt;Public Stack Summit kicking off. The boat is leaving.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/2018/06/19/public-stack-summit/2018-06-19-11-09-00.png&#34; alt=&#34;Marleen about to kick things off&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Giddy about CSS grid</title>
      <link>https://ar.al/2018/06/17/giddy-about-css-grid/</link>
      <pubDate>Sun, 17 Jun 2018 10:26:17 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/17/giddy-about-css-grid/</guid>
      <description>&lt;p&gt;The list of posts in &lt;a href=&#34;https://ar.al/&#34;&gt;the index&lt;/a&gt; of this blog are grouped by year, month, and day. Within the days, multiple posts are grouped by hour.&lt;/p&gt;
&lt;p&gt;The initial index page during &lt;a href=&#34;https://ar.al/2018/06/15/hello-peer-to-peer-web/&#34;&gt;the one-hour hack on Friday that resulted in this site&lt;/a&gt; simply listed the post titles (&lt;a href=&#34;http://c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html&#34;&gt;do the Simplest Thing That Could Possibly Work (STTCPW)&lt;/a&gt;).&lt;/p&gt;
&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/17/giddy-about-css-grid/2018-06-17-10-41-24.png&#34;
         alt=&#34;The site as it was on Friday at the end of its first hour of life&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Version 1: two days ago.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Yesterday, &lt;a href=&#34;https://ar.al/2018/06/16/refining-the-blog/&#34;&gt;I refined the site&lt;/a&gt; and implemented the chronological grouping. However, again going with STTCPW, I used a monospace script font to fake three-column layout of the list items:&lt;/p&gt;
&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/17/giddy-about-css-grid/2018-06-17-10-31-28.png&#34;
         alt=&#34;Yesterday’s implementation of the index faked columns in the list.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Yesterday’s site: fake it till you make it!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, the date and day name are being repeated for every entry in a day. Less than ideal. Removing the redundancy in the red date stamp was easy. I simply tweaked the template code to improve the grouping so it was only displayed once and then floated it left. Hiding the redundant day names was a bit more convoluted: I added class names using a counter to the day name spans and used the following CSS rule to hide all but the first:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;.&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;day&lt;/span&gt;:&lt;span style=&#34;color:#555;font-weight:bold&#34;&gt;not&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;(&lt;/span&gt;.&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;item-0&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;)&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;white&lt;/span&gt;; }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The remaining problem was that since I was faking columns, if the title of a post was too wide (or if you were viewing the site on a narrow viewport), the title would wrap to the start of the list instead of staying within its “column”.&lt;/p&gt;
&lt;h3 id=&#34;css-grids-to-the-rescue&#34;&gt;CSS grids to the rescue!&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt; waxes lyrical about CSS grids every chance she gets so I thought I’d give them a shot. How difficult could they be? (A question one learns early on never to ask when the topic is CSS.)&lt;/p&gt;
&lt;p&gt;Needless to say, judging my the title of this post, I was pleasantly surprised.&lt;/p&gt;
&lt;p&gt;A quick glance at the &lt;a href=&#34;https://gridbyexample.com/examples/example1/&#34;&gt;Defining a Grid&lt;/a&gt; example on &lt;a href=&#34;https://rachelandrew.co.uk&#34;&gt;Rachel Andrew&lt;/a&gt;’s excellent &lt;a href=&#34;https://gridbyexample.com&#34;&gt;Grid by Example&lt;/a&gt; site gave me all I needed to go on.&lt;/p&gt;
&lt;p&gt;I quickly &lt;a href=&#34;https://en.wikipedia.org/wiki/Spike_(software_development)&#34;&gt;spiked&lt;/a&gt; it with a simple three column, two row grid using an unordered list, saw that it worked as intended, and implemented it on the actual site. Here’s the source of the spike:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;list-style-type&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;none&lt;/span&gt;; }
&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt; {
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;display&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt; &lt;span style=&#34;color:#40a070&#34;&gt;1&lt;/span&gt;fr;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;grid-gap&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
  &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;margin-top&lt;/span&gt;: &lt;span style=&#34;color:#40a070&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#902000&#34;&gt;px&lt;/span&gt;;
}
.&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;first&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;lightblue&lt;/span&gt;; }
.&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;second&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;lightcoral&lt;/span&gt;; }
.&lt;span style=&#34;color:#0e84b5;font-weight:bold&#34;&gt;third&lt;/span&gt; { &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#007020;font-weight:bold&#34;&gt;khaki&lt;/span&gt;; }
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;

&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;first&amp;#39;&lt;/span&gt;&amp;gt;Hello&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;second&amp;#39;&lt;/span&gt;&amp;gt;there&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;third&amp;#39;&lt;/span&gt;&amp;gt;how are you doing?&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
  &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;first&amp;#39;&lt;/span&gt;&amp;gt;And&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;second&amp;#39;&lt;/span&gt;&amp;gt;another&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;third&amp;#39;&lt;/span&gt;&amp;gt;row.&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
  &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pop that into an &lt;em&gt;index.html&lt;/em&gt; file, run &lt;a href=&#34;https://www.npmjs.com/package/http-server&#34;&gt;http-server&lt;/a&gt; (&lt;code&gt;http-server -c-1&lt;/code&gt;) and you’ll see:&lt;/p&gt;
&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/17/giddy-about-css-grid/2018-06-17-10-58-57.png&#34;
         alt=&#34;CSS Grid spike showing a three-column, two-row layout with fixed width columns for the first two columsn and a flexible width for the third that takes up the rest of the space. The background colours of the columns are light blue, light coral, and khaki.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;A quick and dirty grid&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;(Notice the HTML isn’t even valid. That’s OK. It’s a spike. Think of it as a back-of-the-napkin sketch. It’s meant to be pragmatic.)&lt;/p&gt;
&lt;p&gt;So, with that, two days after its birth, the site is beginning to shape up with an index that has a proper four-column layout with a neat and logical chronological grouping.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Formatting an ISO 8601 date stamp in Hugo</title>
      <link>https://ar.al/2018/06/17/formatting-an-iso-8601-date-stamp-in-hugo/</link>
      <pubDate>Sun, 17 Jun 2018 09:14:55 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/17/formatting-an-iso-8601-date-stamp-in-hugo/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; has a plethora of functions that you can use in your &lt;a href=&#34;https://gohugo.io/templates/introduction/&#34;&gt;templates&lt;/a&gt;, including one for formatting dates called &lt;a href=&#34;https://gohugo.io/functions/dateformat/&#34;&gt;dateFormat&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://ar.al/&#34;&gt;index&lt;/a&gt; of this blog has a list of the posts in chronological order and uses a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time&#34;&gt;&lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt;&lt;/a&gt; tag. I wanted to include the machine-readable &lt;code&gt;datetime&lt;/code&gt; attribute but the standard serialisation of Hugo’s &lt;a href=&#34;https://gohugo.io/variables/page/#page-variables&#34;&gt;&lt;code&gt;.Date&lt;/code&gt;&lt;/a&gt; property doesn’t return an ISO 8601 timestamp. This led me to look up &lt;a href=&#34;https://gohugohq.com/howto/hugo-dateformat/&#34;&gt;the esoteric syntax of Hugo’s dateFormat function&lt;/a&gt; and use the following format string to create an ISO 8601 string:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;{{ &lt;span style=&#34;&#34;&gt;$&lt;/span&gt;post.Date.Format &lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;2006-01-02T15:04:05Z0700&amp;#34;&lt;/span&gt; }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And here it is in use in the block that renders the posts for a given day (&lt;a href=&#34;https://source.ind.ie/ar.al/site/blob/master/layouts/index.html&#34;&gt;full source&lt;/a&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt; 
  {{ range $index, $post := .Pages }}
    &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;time&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;datetime&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;{{ $post.Date.Format &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;2006-01-02T15:04:05Z0700&lt;/span&gt;&lt;span style=&#34;&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;&#34;&gt;}}&amp;#34;&lt;/span&gt;&amp;gt;
        &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;postdate day item-{{ $index }}&amp;#39;&lt;/span&gt;&amp;gt;{{ $post.Date.Format &amp;#34;Mon&amp;#34; }}&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
      &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;time&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#39;postdate&amp;#39;&lt;/span&gt;&amp;gt;{{ $post.Date.Format &amp;#34;15:04&amp;#34; }}&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
      &amp;lt;&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#4070a0&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#4070a0&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;{{ $post.Title }}&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;
    &amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;
  {{ end }}
&amp;lt;/&lt;span style=&#34;color:#062873;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Refining the blog</title>
      <link>https://ar.al/2018/06/16/refining-the-blog/</link>
      <pubDate>Sat, 16 Jun 2018 17:04:54 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/16/refining-the-blog/</guid>
      <description>&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/16/refining-the-blog/2018-06-16-20-23-44.png&#34;
         alt=&#34;The Markdown text for this blog post shown in my VSCode window&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Yes, I blog in VSCode. What was the question again?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Yesterday, in a one-hour session &lt;a href=&#34;https://mastodon.ar.al/@aral/100207852262520843&#34;&gt;that I live-tooted&lt;/a&gt;, I set up and deployed &lt;a href=&#34;https://source.ind.ie/ar.al/site&#34;&gt;this blog&lt;/a&gt; by hacking together plain HTML/CSS, &lt;a href=&#34;https://github.com/Splurov/rsync-watch&#34;&gt;Rsync Watch&lt;/a&gt;, and &lt;a href=&#34;https://datproject.org&#34;&gt;DAT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Maintaining a blog entirely by hand isn’t fun though.&lt;/p&gt;
&lt;h3 id=&#34;automate-strikeallstrike-some-of-the-things&#34;&gt;Automate &lt;strike&gt;all&lt;/strike&gt; some of the things&lt;/h3&gt;
&lt;p&gt;There are two things you must automate to make a usable web site authoring system. These are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Indices:&lt;/strong&gt; Index pages listing posts, articles, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Partials:&lt;/strong&gt; content like headers, footer, etc. that are included on multiple pages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then, there are nice-to-haves (especially for blogging) that are hard to live without in this day and age:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Markdown authoring&lt;/strong&gt;: Markdown lets you keep your focus on the content rather than having it bogged down with formatting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Live reload&lt;/strong&gt;: Live reload shows you your local changes immediately. It’s like having a WYSIWYG preview.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In &lt;a href=&#34;https://ar.al&#34;&gt;the previous version of my blog&lt;/a&gt;, I had hand-rolled a static site generator in Node.js to do these things for me. Today, there’s no reason to do that. We’re spoiled for choice when it comes to static site generators and one of the best around is called &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hugo checks all the boxes above, does a million other things that I will probably never use, and is &lt;em&gt;fast&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So I spent a few hours today setting up the blog to use Hugo. As I want the site to be as minimal as possible, I didn’t use an existing template. I’m hand-rolling the layouts and styles by hand.&lt;/p&gt;
&lt;p&gt;Right now, I have indices being generated, and partials for the head, header, and footer.&lt;/p&gt;
&lt;p&gt;I also set up a convention where content is stored chronologically (e.g., 2018/06/16/refining-the-blog) and I wrote &lt;a href=&#34;https://source.ind.ie/ar.al/site/blob/master/new&#34;&gt;a small script&lt;/a&gt; to make it easy for me to create new posts fitting that path structure:&lt;/p&gt;
&lt;p&gt;So my workflow right now for updating this blog is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start up the system (Hugo server and rsync watcher):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;./connect&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a post:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f0f0;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;./new my-latest-amazing-post&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit the post using VSCode.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Every time I save, my changes are automatically synchronised with my server using rsync and, on the server, automatically shared on the peer-to-peer web via DAT.&lt;/p&gt;
&lt;p&gt;I’ve been wanting to get back into blogging more frequently and I hope this new system will enable me to do just that.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Of course, on this blog, with DAT, the site itself becomes a streaming site and updates automatically with every change for people viewing it in Beaker Browser with Live Reload turned on. &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Revision history</title>
      <link>https://ar.al/2018/06/15/revision-history/</link>
      <pubDate>Fri, 15 Jun 2018 15:44:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/15/revision-history/</guid>
      <description>&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/15/revision-history/revision-history.png&#34;
         alt=&#34;Beaker Browser shows you the revision history of a site.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Beaker-ception Part II: a screenshot of this site in Beaker Browser with the revisions pane showing.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Another cool thing about DAT archives is that they’re based on append-only logs and thus maintain a full revision history. When you’re viewing this site via &lt;a href=&#34;https://beakerbrowser.com&#34;&gt;Beaker Browser&lt;/a&gt;, you can go back and see how the site evolved over time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Hello Peer-to-Peer Web</title>
      <link>https://ar.al/2018/06/15/hello-peer-to-peer-web/</link>
      <pubDate>Fri, 15 Jun 2018 15:00:00 +0100</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/2018/06/15/hello-peer-to-peer-web/</guid>
      <description>&lt;figure class=&#34;window-with-shadow&#34;&gt;
    &lt;img src=&#34;https://ar.al/2018/06/15/hello-peer-to-peer-web/beaker-ception.png&#34;
         alt=&#34;Beaker Browser showing a version of this page.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Beaker-ception: a screenshot of version of this page in Beaker browser with live reload on.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While documenting my &lt;a href=&#34;https://forum.ind.ie/t/deep-dive-into-multiwriter-hyperdb/2184&#34;&gt;deep dive into multiwriter hyperdb&lt;/a&gt;, I realised I was doing so on our forums, running Discourse, when I’d just demonstrated the day before &lt;a href=&#34;https://forum.ind.ie/t/deep-dive-into-multiwriter-hyperdb/2184/1&#34;&gt;how you can live blog on the peer-to-peer Web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So I went on &lt;a href=&#34;https://mastodon.ar.al/@aral/100207852262520843&#34;&gt;a little tangent&lt;/a&gt; and set up this “live” blog. Live as in, every time I save what I’m doing, the changes get pushed out to anyone viewing the site with live reload turned on in &lt;a href=&#34;https://beakerbrowser.com&#39;&#34;&gt;Beaker Browser&lt;/a&gt;. If you’re viewing the site on a regular web browser, you’ll just see a regular site and you won’t get the streaming updates. It’s an example of how we can progressively enhance the centralised web with the peer-to-peer web using the &lt;a href=&#34;https://datproject.org&#34;&gt;DAT protocol&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Biography</title>
      <link>https://ar.al/bio/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/bio/</guid>
      <description>&lt;p&gt;Aral’s been making things with computers since he was 7 years old.&lt;/p&gt;
&lt;script&gt;
// Progressive enhancement, innit? ;)
const years = Math.round((new Date().getTime() - new Date(&#39;1983-11-08&#39;).getTime())/1000/60/60/24/365)
document.write(`(That’s approximately ${years} years ago. And yes, he still thinks computers are magic portals into endless universes.)`)
&lt;/script&gt;
&lt;p&gt;Along the way, he dabbled in Flash, did some consulting, taught some workshops, gave a few talks, organised a smattering of conferences, made some apps, helped launch an after-school coding initiative for kids, got rather enraged by Silicon Valley’s bullshit, and co-founded &lt;a href=&#34;https://small-tech.org&#34;&gt;Small Technology Foundation&lt;/a&gt; to do something about it.&lt;/p&gt;
&lt;p&gt;He’s currently working on developing the &lt;a href=&#34;https://small-tech.org/research-and-development&#34;&gt;Small Web&lt;/a&gt; with &lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt; and &lt;a href=&#34;https://codeberg.org/domain/app&#34;&gt;Domain&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;headshot&#34;&gt;Headshot&lt;/h2&gt;
&lt;p&gt;Please credit Cristina von Poser when you use my headshot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ar.al/bio/https://ar.al/downloads/aral-balkan-headshot-please-credit-christina-von-poser/aral-balkan-by-christina-von-poser.png&#34; alt=&#34;Close-up of Aral Balkan: a white-passing man with short brown hair and beard looking off-camera in-front of a bokeh background&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Talks</title>
      <link>https://ar.al/talks/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <author>mail@ar.al (Aral Balkan)</author>
      <guid>https://ar.al/talks/</guid>
      <description>&lt;p&gt;I’ve given hundreds of talks over the last two decades and beyond. Here are a few of my favourites in chronological order so you can see how my focus and thoughts have evolved through the years.&lt;/p&gt;
&lt;p&gt;I hope you enjoy them.&lt;/p&gt;
&lt;p&gt;PS. If you want me to speak about the Small Web at your event, feel free to send a short message to &lt;a href=&#34;mailto:mail@ar.al&#34;&gt;mail@ar.al&lt;/a&gt; with the details.&lt;/p&gt;
&lt;p&gt;PPS. You can see Laura and me live every third Thursday of the month on &lt;a href=&#34;https://small-tech.org/videos/&#34;&gt;Small is Beautiful&lt;/a&gt;, which we stream from &lt;a href=&#34;https://owncast.small-web.org/&#34;&gt;our own Owncast server&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;superheroes-and-villains-in-design&#34;&gt;Superheroes and Villains in Design&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;superheroes--villains-in-design&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/external/133430959.hd.mp4?s=8a96e7ede72482a65add5610be0271eb&amp;amp;profile_id=119&#34;
    poster=&#34;https://small-tech.org/videos/ux-talk/poster-ux-talk.jpg&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://small-tech.org/videos/ux-talk/poster-ux-talk.jpg&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/external/133430959.hd.mp4?s=8a96e7ede72482a65add5610be0271eb&amp;amp;profile_id=119&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Superheroes &amp; Villains in Design at Thinking Digital 2013 in Newcastle, UK.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I believe that good design is crucial for what we do. Why? Watch this talk to find out or, if you’re short on time, &lt;a href=&#34;http://www.breakingthin.gs/this-is-all-there-is.html&#34;&gt;read this&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;free-is-a-lie&#34;&gt;Free is a Lie&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;free-is-a-lie&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/405769924/rendition/720p/file.mp4?loc=external&amp;signature=2e85903c1a6e7a0b92aebb4921291372c978b5e0f59686092efab45e25a3d212&#34;
    poster=&#34;https://i.vimeocdn.com/video/1587000379-f48a938238ae99aa2c3abcc5cc77c3a48c8da9db68f8064b4b9c5599d95b4cc5-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/1587000379-f48a938238ae99aa2c3abcc5cc77c3a48c8da9db68f8064b4b9c5599d95b4cc5-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/405769924/rendition/720p/free_is_a_lie_%E2%80%93_aral_balkan_%E2%80%93_thinking_digital_2014%20%28720p%29.mp4?loc=external&amp;signature=dca3dfada7b8b3627fd696ac5fb4e5ff45b4bc45fb3b68d2ca24bd8abf0d1da2&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Free is a Lie at Thinking Digital 2014 in Newcastle, UK.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This was one of the first talks I gave when I started speaking out against Silicon Valley’s toxic business model (what I now call “people farming”). I also gave a version of this as an &lt;a href=&#34;https://www.thersa.org/video/events/2014/04/free-is-a-lie&#34;&gt;RSA Talk.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;decentralise-everything&#34;&gt;Decentralise Everything&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;decenrtalise-everything&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/140838179/rendition/720p/file.mp4?loc=external&amp;signature=6154b25ae583f002738fa415f8b0e7edaf421da94bcd44c78c0fbdc2141d8346&#34;
    poster=&#34;https://i.vimeocdn.com/video/1587033200-e003dc089345fcd2136e18671d2f85c99c56a1a839cabd793430b15db3910649-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/1587033200-e003dc089345fcd2136e18671d2f85c99c56a1a839cabd793430b15db3910649-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/140838179/rendition/720p/decentralise_everything_%28opening_keynote_at_the_conference,_malm%C3%B6,_2015%29%20%28720p%29.mp4?loc=external&amp;signature=9383dae428ddcb0bd081955babfeaf6405ac5d5c846facc16b71939422b74a89&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Decentralise Everything at The Conference 2015 in Malmö, Sweden.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In this talk, I give the first public demonstration of Heartbeat, a native macOS social media app that never left alpha and was the spiritual predecessor of my work on the Small Web. You can also &lt;a href=&#39;https://vimeo.com/138997351&#39;&gt;watch just the Heartbeat reveal&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;beyond-the-clouds&#34;&gt;Beyond the Clouds&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;beyond-the-clouds&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/167697982/rendition/720p/file.mp4?loc=external&amp;signature=984f219ddafccd86149cc816bb065403de3d7fb18fe132aa487c6ba3866db10b&#34;
    poster=&#34;https://i.vimeocdn.com/video/572253717-74ab9f523ce7d0da69e18159a74e1e1fc1f06fe075d89dabc87ed1a00fe2d181-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/572253717-74ab9f523ce7d0da69e18159a74e1e1fc1f06fe075d89dabc87ed1a00fe2d181-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/167697982/rendition/720p/see_conference_%2311_2016%20%28720p%29.mp4?loc=external&amp;signature=548ea66ecd97d544f88f33736b342d7ef7980e447b9305dedd48250329592ed7&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Beyond the Clouds at See Conference 2016 in Wiesbaden, Germany.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;excuse-me-your-unicorn-keeps-shitting-in-my-back-yard-can-he-please-not&#34;&gt;Excuse Me, Your Unicorn Keeps Shitting In My Back Yard, Can He Please Not?&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;excuse-me&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/200666291/rendition/720p/file.mp4?loc=external&amp;signature=f49d1adc73e54af499e299c7a4983d64a5fe095fb1e78bd06b254793377cad59&#34;
    poster=&#34;https://i.vimeocdn.com/video/1587050497-3581d6bc4a29786312447ae586540188ab86602a46e53d875cb75168f7db7401-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/1587050497-3581d6bc4a29786312447ae586540188ab86602a46e53d875cb75168f7db7401-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/200666291/rendition/720p/excuse_me,_your_unicorn_keeps_shitting_in_my_back_yard,_can_he_please_not%3F%20%28720p%29.mp4?loc=external&amp;signature=786e071f0b6e026289e9d8efe2e964fea6706b713668ca1658d679818e31356b&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Excuse Me, Your Unicorn Keeps Shitting In My Back Yard, Can He Please Not?at Doku:Tech 2016 in Prishtina, Republic of Kosovo.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In this talk, alongside raging against the machine, I talk about our tracker blocker, &lt;a href=&#34;https://better.fyi&#34;&gt;Better&lt;/a&gt;, which we retired at the end of 2021, and some of the insights we gained using it.&lt;/p&gt;
&lt;p&gt;Don’t you just love the title, though? I know I do.&lt;/p&gt;
&lt;h2 id=&#34;design-or-decoration&#34;&gt;Design or Decoration?&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;design-or-decoration&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/281704944/rendition/720p/file.mp4?loc=external&amp;signature=51ff7c26ec1a31338fb64ef14c78d3440e5e34416374711bcd36de036baf169f&#34;
    poster=&#34;https://i.vimeocdn.com/video/1587056067-e42303bf78f45784af17ad057a062209651acc756fe1aaa90b1f37fe60cb55fc-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/1587056067-e42303bf78f45784af17ad057a062209651acc756fe1aaa90b1f37fe60cb55fc-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/281704944/rendition/720p/aral_balkan:_speech_at_ixda_berlin_%E2%80%93_design_or_decoration%3F%20%28720p%29.mp4?loc=external&amp;signature=9bd5661e356253f8064ba834ecc30b565e7d2d48fd54b2dd151e2697942af9dd&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Design or Decoration at IxDA Berlin 2017 in Germany.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In which I ask a room full of designers whether they are practicing design or decoration. A question we should all be asking ourselves. The answer to which might prove important in deciding whether we live in a democracy or an autocracy in the future.&lt;/p&gt;
&lt;!--

TODO: Add these talks in when I get time.

## Peerocracy 

&lt;figure&gt;
  &lt;video id=&#34;peerocracy&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/281704944/rendition/720p/file.mp4?loc=external&amp;signature=51ff7c26ec1a31338fb64ef14c78d3440e5e34416374711bcd36de036baf169f&#34;
    poster=&#34;https://i.vimeocdn.com/video/1587056067-e42303bf78f45784af17ad057a062209651acc756fe1aaa90b1f37fe60cb55fc-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/1587056067-e42303bf78f45784af17ad057a062209651acc756fe1aaa90b1f37fe60cb55fc-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/281704944/rendition/720p/aral_balkan:_speech_at_ixda_berlin_%E2%80%93_design_or_decoration%3F%20%28720p%29.mp4?loc=external&amp;signature=9bd5661e356253f8064ba834ecc30b565e7d2d48fd54b2dd151e2697942af9dd&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Design or Decoration at IxDA Berlin 2017 in Germany.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

Had you heard of [Elliot Shefler](https://www.forbes.com/sites/parmyolson/2019/01/15/a-shadowy-entrepreneur-claims-his-online-manipulation-business-is-thriving/), the Martin Shkreli of adtech, and his (now defunct) business The Spinner that used targeted advertising (you know, the business model of Silicon Valley) to (among other things) get men to influence women to have sex with them? You’ll learn more about that and more in this talk I gave to a room full of adtech folks.  

## Small Technology 

TODO: Record a video combining the slides and audio recording from UX Australia 2019.

## Beyond surveillance capitalism: alternatives, stopgaps, Small Web, and Site.js

2020

TODO: ADD VIDEO.

[Site.js](https://sitejs.org) is the precursor to [Kitten](https://codeberg.org/kitten/app). It’s what powers this site as well as most of our sites at Small Technology Foundation. In this talk I present stopgaps and alternatives to surveillance capitalism, introduce the concept of the Small Web and demonstrate Site.js.
--&gt;
&lt;h2 id=&#34;kitten-small-is-beautiful&#34;&gt;Kitten (Small is Beautiful)&lt;/h2&gt;
&lt;figure&gt;
  &lt;video id=&#34;small-is-beautiful-small-web-and-kitten&#34; controls=&#34;&#34; preload=&#34;none&#34;
    src=&#34;https://player.vimeo.com/progressive_redirect/playback/781770468/rendition/1080p/file.mp4?loc=external&amp;signature=198ebc4db39d89dae98feadfc6f5d2669355626b452b8b42dbf2a493ed140360&#34;
    poster=&#34;https://i.vimeocdn.com/video/1569267180-cd7bfc43b3814e9c11c16c4c37d2d4e607d73e1484e1117ee21cf6aa579581e9-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; width=&#34;800&#34;&gt;
    &lt;img src=&#34;https://ar.al/talks/https://i.vimeocdn.com/video/1569267180-cd7bfc43b3814e9c11c16c4c37d2d4e607d73e1484e1117ee21cf6aa579581e9-d.jpg?mw=1920&amp;mh=1080&amp;q=70&#34; alt=&#34;&#34; width=&#34;640&#34; height=&#34;360&#34;&gt;
    &lt;p&gt;Sorry, your browser doesn&#39;t support embedded videos. But that doesn’t mean you can’t watch it! You can &lt;a
        href=&#34;https://player.vimeo.com/progressive_redirect/download/781770468/rendition/1080p/small_web_&amp;_kitten:_identity,_authentication,_communication_%E2%80%93_small_is_beautiful_%2325_%E2%80%93_dec_15,_2022%20%281080p%29.mp4?loc=external&amp;signature=15e482399e4df15da5e2f34c4857e73ba3b98383d21f03753c9c439d01e07e4b&#34;&gt;download the video&lt;/a&gt;, and watch it with your favourite video player.&lt;/p&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;&lt;p&gt;Kitten demo at Small is Beautiful, December 2022 (online; live stream).&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A demonstration of authentication and public-key cryptography in &lt;a href=&#34;https://codeberg.org/kitten/app&#34;&gt;Kitten&lt;/a&gt; from our &lt;a href=&#34;https://small-tech.org/events/#small-is-beautiful&#34;&gt;Small is Beautiful&lt;/a&gt; &lt;a href=&#34;https://owncast.small-web.org&#34;&gt;live stream&lt;/a&gt; with &lt;a href=&#34;https://laurakalbag.com&#34;&gt;Laura&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
