test.atom.xml (442542B)
1 <?xml version="1.0" encoding="utf-8"?> 2 <feed xmlns="http://www.w3.org/2005/Atom"><title>man bytes gnu</title><link href="/" rel="alternate"></link><link href="feeds/all.atom.xml" rel="self"></link><id>/</id><updated>2024-07-07T21:03:40+02:00</updated><entry><title>Support Your Local Viewer</title><link href="local-markdown.html" rel="alternate"></link><published>2024-07-07T21:03:40+02:00</published><updated>2024-07-07T21:03:40+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2024-07-07:local-markdown.html</id><summary type="html"><p class="first last">Bash script to render and spawn a viewer for markdown files</p> 3 </summary><content type="html"><p>Markdown is the fast-food of document formats.</p> 4 <p>That doesn't change the fact that it's everywhere.</p> 5 <p>So much everywhere, in fact, that it's kind of puzzling there is not a dedicated tool around to view it.</p> 6 <div class="section" id="pinning-down-markdown"> 7 <h2>Pinning down markdown</h2> 8 <p>There is no shortage of applications that <em>can</em> render markdown. Among the alternatives are free code editors like <a class="reference external" href="https://atom-editor.cc/">Atom</a> or <a class="reference external" href="https://plugins.geany.org/markdown.html">Geany</a>, the browser plugin <a class="reference external" href="https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk">Markdown Viewer</a> and even a dedicated markdown editor like <a class="reference external" href="https://github.com/marktext/marktext">Marktext</a>, <a class="reference external" href="https://github.com/retext-project/retext">retext</a> and <a class="reference external" href="https://ghostwriter.kde.org/">ghostwriter</a>.</p> 9 <p>And of course, there exist SaaS offerings such as <a class="reference external" href="https://hackmd.io">hackmd</a>. But seeing as those are not alternatives for offline use, we don't concern ourselves with those here.</p> 10 <p>But what is the equivalent of <a class="reference external" href="https://github.com/muennich/sxiv">sxiv</a> or <a class="reference external" href="https://feh.finalrewind.org">feh</a> for Markdown?</p> 11 <p>Honestly, I couldn't find any such thing. If there is, I'd <a class="reference external" href="https://holbrook.no/msg">love to know</a>.</p> 12 <p>Fortunately, there is a perfectly reasonable workaround.</p> 13 </div> 14 <div class="section" id="step-by-step"> 15 <h2>Step by step</h2> 16 <p>After all, it is in the spirit of *nixes to use a choice of tools who <em>does one thing and does it well</em>.</p> 17 <p>So, no matter how bizarre it feels, it may make sense that a lurid format like <em>Markdown</em> should be treated in a separate step to produce a more well-established - and less ambiguous - format.</p> 18 <p>I asked a related question on <a class="reference external" href="https://tex.stackexchange.com/questions/341899/latex-to-markdown-converter">Stackexchange</a> long ago, and there the <tt class="docutils literal">pandoc</tt> tool came up as a solution.</p> 19 <p>And it turns out it works beautifully in this case aswell.</p> 20 <p>Consider the following script:</p> 21 <div class="highlight"><pre><span></span><span class="nv">t</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="w"> </span>--suffix<span class="o">=</span>.html<span class="k">)</span> 22 <span class="m">2</span>&gt;<span class="p">&amp;</span><span class="m">1</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$t</span> 23 pandoc<span class="w"> </span>-f<span class="w"> </span>gfm<span class="w"> </span>-t<span class="w"> </span>html<span class="w"> </span>-M<span class="w"> </span>document-css<span class="o">=</span><span class="nb">false</span><span class="w"> </span>--standalone<span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$t</span><span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>/dev/null 24 w3m<span class="w"> </span><span class="nv">$t</span> 25 </pre></div> 26 <p>Quite simply, we generate a standalone html file in <tt class="docutils literal">tmpfs</tt>, which in turn is read and renderered by a web browser.</p> 27 </div> 28 <div class="section" id="browsing-browsers"> 29 <h2>Browsing browsers</h2> 30 <p>No appreciation for <a class="reference external" href="https://w3m.sourceforge.net">w3m</a>, eh? Instead want that fuzzy feel of comforting colors, smooth scroll and fancy fonts?</p> 31 <p>I can understand. I used to suffer that affliction, too.</p> 32 <p>But nonetheless; it's an important point. For example, what if I wanted to use <a class="reference external" href="https://lynx.browser.org">lynx</a> or <a class="reference external" href="https://fanglingsu.github.io/vimb">vimb</a> instead? Choosing the browser to view with should definitely be the caller's call.</p> 33 <p>Is there a canonical way of doing that in Linux.</p> 34 <p>Kind of.</p> 35 <p>Let's review a couple of the options.</p> 36 <div class="section" id="the-environmental-solution"> 37 <h3>The environmental solution</h3> 38 <p>Some applications honor the <tt class="docutils literal">$BROWSER</tt> environment variable. So let's cover for that:</p> 39 <div class="highlight"><pre><span></span><span class="nv">browser</span><span class="o">=</span><span class="si">${</span><span class="nv">BROWSER</span><span class="k">:-</span><span class="nv">w3m</span><span class="si">}</span> 40 <span class="nv">t</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="w"> </span>--suffix<span class="o">=</span>.html<span class="k">)</span> 41 pandoc<span class="w"> </span>-f<span class="w"> </span>gfm<span class="w"> </span>-t<span class="w"> </span>html<span class="w"> </span>-M<span class="w"> </span>document-css<span class="o">=</span><span class="nb">false</span><span class="w"> </span>--standalone<span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$t</span><span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>/dev/null 42 <span class="nv">$browser</span><span class="w"> </span><span class="nv">$t</span> 43 </pre></div> 44 <p>Now, viewing the markdown file <tt class="docutils literal">README.md</tt> with <tt class="docutils literal">lynx</tt> is as easy as:</p> 45 <div class="highlight"><pre><span></span><span class="gp">$ </span><span class="nv">BROWSER</span><span class="o">=</span><span class="k">$(</span>which<span class="w"> </span>lynx<span class="k">)</span><span class="w"> </span>bash<span class="w"> </span>wmd.sh<span class="w"> </span>README.md 46 </pre></div> 47 </div> 48 <div class="section" id="the-cross-solution"> 49 <h3>The cross solution</h3> 50 <p>So, have you heard about the Cross Desktop Group? <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a> These are the guys you should be sending a thought of gratitude every time you intuitively look in your <tt class="docutils literal"><span class="pre">~/.config</span></tt> or <tt class="docutils literal"><span class="pre">~/.local</span></tt> folder for application data. And if you're a <strong>HUIf</strong> <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a> coder, chances are you have seen the string <tt class="docutils literal">xdg</tt> in some function call somewhere.</p> 51 <p>These days they go by the name <a class="reference external" href="https://www.freedesktop.org/">Free Desktop Group</a>, and among other things they have negotiated something particularly useful to us in this particular case.</p> 52 <p>To launch programs in a desktop environment in Linux, a <a class="reference external" href="https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html">Desktop Entry Specification</a> file format <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a> has been defined - appropriately suffixed <tt class="docutils literal">.desktop</tt>.</p> 53 <p>Take a look at a random <tt class="docutils literal">*.desktop</tt> file in <tt class="docutils literal">/usr/share/applications</tt> (your most likely default <tt class="docutils literal">$XDG_DATA_DIRS</tt> location, where xdg searches for desktop files). Every one will contain an top-level <tt class="docutils literal">Exec=</tt> entry.</p> 54 <p>For example, my <tt class="docutils literal">feh.desktop</tt> entry has <tt class="docutils literal">Exec=feh <span class="pre">--start-at</span> %u</tt> which means find <tt class="docutils literal">feh</tt> in <tt class="docutils literal">$PATH</tt> and execute it with the <tt class="docutils literal"><span class="pre">--start-at</span></tt> switch and one single <em>url</em> as argument:</p> 55 <div class="highlight"><pre><span></span><span class="k">[Desktop Entry]</span> 56 <span class="na">Name</span><span class="o">=</span><span class="s">Feh</span> 57 <span class="na">Name[en_US]</span><span class="o">=</span><span class="s">feh</span> 58 <span class="na">GenericName</span><span class="o">=</span><span class="s">Image viewer</span> 59 <span class="na">GenericName[en_US]</span><span class="o">=</span><span class="s">Image viewer</span> 60 <span class="na">Comment</span><span class="o">=</span><span class="s">Image viewer and cataloguer</span> 61 <span class="na">Exec</span><span class="o">=</span><span class="s">feh --start-at %u</span> 62 <span class="na">Terminal</span><span class="o">=</span><span class="s">false</span> 63 <span class="na">Type</span><span class="o">=</span><span class="s">Application</span> 64 <span class="na">Icon</span><span class="o">=</span><span class="s">feh</span> 65 <span class="na">Categories</span><span class="o">=</span><span class="s">Graphics</span><span class="c1">;2DGraphics;Viewer;</span> 66 <span class="na">MimeType</span><span class="o">=</span><span class="s">image/bmp</span><span class="c1">;image/gif;image/jpeg;image/jpg;image/pjpeg;image/png;image/tiff;image/webp;image/x-bmp;image/x-pcx;image/x-png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-tga;image/x-xbitmap;image/heic;</span> 67 <span class="na">NoDisplay</span><span class="o">=</span><span class="s">true</span> 68 </pre></div> 69 <p>Covering this case in our script:</p> 70 <div class="highlight"><pre><span></span><span class="nv">fallbackbrowsercmd</span><span class="o">=</span>w3m 71 <span class="nv">browsercmd</span><span class="o">=</span> 72 <span class="c1"># if browser env exists, then</span> 73 <span class="c1"># try handle it as a desktop entry</span> 74 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$BROWSER</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 75 <span class="w"> </span><span class="c1"># find the xdg paths</span> 76 <span class="w"> </span><span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="si">${</span><span class="nv">XDG_DATA_DIRS</span><span class="k">:-</span><span class="p">/usr/share</span><span class="si">}</span> 77 <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 78 <span class="w"> </span><span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="nv">$XDG_DATA_HOME</span>:<span class="nv">$XDG_DATA_DIRS</span> 79 <span class="w"> </span><span class="k">fi</span> 80 <span class="w"> </span><span class="c1"># split on &quot;:&quot; and try dirs one by one</span> 81 <span class="w"> </span><span class="c1"># use first matching browser desktop entry</span> 82 <span class="w"> </span><span class="nv">_ifs</span><span class="o">=</span><span class="nv">$IFS</span> 83 <span class="w"> </span><span class="nv">IFS</span><span class="o">=</span>: 84 <span class="w"> </span><span class="nv">dirs</span><span class="o">=(</span><span class="s2">&quot;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&quot;</span><span class="o">)</span> 85 <span class="w"> </span><span class="k">for</span><span class="w"> </span>d<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">$dirs</span><span class="p">;</span><span class="w"> </span><span class="k">do</span> 86 <span class="w"> </span><span class="nv">s</span><span class="o">=</span><span class="nv">$BROWSER</span>.desktop 87 <span class="w"> </span><span class="nv">a</span><span class="o">=</span><span class="nv">$d</span>/applications/<span class="nv">$BROWSER</span>.desktop 88 <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-f<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$a</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 89 <span class="w"> </span><span class="nv">browsercmd</span><span class="o">=</span><span class="s2">&quot;gtk-launch </span><span class="nv">$s</span><span class="s2">&quot;</span> 90 <span class="w"> </span><span class="k">fi</span> 91 <span class="w"> </span><span class="k">done</span> 92 <span class="w"> </span><span class="nv">IFS</span><span class="o">=</span> 93 <span class="k">fi</span> 94 <span class="c1"># if no browser set or could not be found in xdg,</span> 95 <span class="c1"># then try the browser env var as command, or</span> 96 <span class="c1"># ultimately the static fallback</span> 97 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$browsercmd</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 98 <span class="w"> </span><span class="nv">browsercmd</span><span class="o">=</span><span class="si">${</span><span class="nv">BROWSER</span><span class="k">:-</span><span class="nv">$fallbackbrowsercmd</span><span class="si">}</span> 99 <span class="k">fi</span> 100 <span class="nv">t</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="w"> </span>--suffix<span class="o">=</span>.html<span class="k">)</span> 101 pandoc<span class="w"> </span>-f<span class="w"> </span>gfm<span class="w"> </span>-t<span class="w"> </span>html<span class="w"> </span>-M<span class="w"> </span>document-css<span class="o">=</span><span class="nb">false</span><span class="w"> </span>--standalone<span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$t</span><span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>/dev/null 102 <span class="nv">$browsercmd</span><span class="w"> </span><span class="nv">$t</span> 103 </pre></div> 104 </div> 105 <div class="section" id="the-default-solution"> 106 <h3>The default solution</h3> 107 <p>Yes, there is such a thing as &quot;default web browser&quot; in <tt class="docutils literal">XDG</tt>, too.</p> 108 <p>Have a look at <tt class="docutils literal"><span class="pre">xdg-settings</span> <span class="pre">--list</span></tt>. On my system, it shows a rather modest output:</p> 109 <div class="highlight"><pre><span></span><span class="gp">$ </span>xdg-settings<span class="w"> </span>--list 110 <span class="go">Known properties:</span> 111 <span class="go"> default-url-scheme-handler Default handler for URL scheme</span> 112 <span class="go"> default-web-browser Default web browser</span> 113 </pre></div> 114 <p>And the default web browser is:</p> 115 <div class="highlight"><pre><span></span><span class="gp">$ </span>xdg-settings<span class="w"> </span>get<span class="w"> </span>default-web-browser 116 <span class="go">brave-browser.desktop</span> 117 </pre></div> 118 <p>Yeah, yeah, yeah. Busted. I use graphical browsers, too.</p> 119 <p>Now let's add this to the mix, then.</p> 120 <div class="highlight"><pre><span></span><span class="nv">fallbackbrowsercmd</span><span class="o">=</span>w3m 121 <span class="nv">browsercmd</span><span class="o">=</span> 122 <span class="c1"># if browser env var not set, set it with default browser</span> 123 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$BROWSER</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 124 <span class="w"> </span><span class="nv">BROWSER</span><span class="o">=</span><span class="k">$(</span>xdg-settings<span class="w"> </span>get<span class="w"> </span>default-web-browser<span class="k">)</span> 125 <span class="w"> </span><span class="nv">BROWSER</span><span class="o">=</span><span class="si">${</span><span class="nv">BROWSER</span><span class="p">%%.*</span><span class="si">}</span> 126 <span class="k">fi</span> 127 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$BROWSER</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 128 <span class="w"> </span><span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="si">${</span><span class="nv">XDG_DATA_DIRS</span><span class="k">:-</span><span class="p">/usr/share</span><span class="si">}</span> 129 <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 130 <span class="w"> </span><span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="nv">$XDG_DATA_HOME</span>:<span class="nv">$XDG_DATA_DIRS</span> 131 <span class="w"> </span><span class="k">fi</span> 132 <span class="w"> </span><span class="nv">_ifs</span><span class="o">=</span><span class="nv">$IFS</span> 133 <span class="w"> </span><span class="nv">IFS</span><span class="o">=</span>: 134 <span class="w"> </span><span class="nv">dirs</span><span class="o">=(</span><span class="s2">&quot;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&quot;</span><span class="o">)</span> 135 <span class="w"> </span><span class="k">for</span><span class="w"> </span>d<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">$dirs</span><span class="p">;</span><span class="w"> </span><span class="k">do</span> 136 <span class="w"> </span><span class="nv">s</span><span class="o">=</span><span class="nv">$BROWSER</span>.desktop 137 <span class="w"> </span><span class="nv">a</span><span class="o">=</span><span class="nv">$d</span>/applications/<span class="nv">$BROWSER</span>.desktop 138 <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-f<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$a</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 139 <span class="w"> </span><span class="nv">browsercmd</span><span class="o">=</span><span class="s2">&quot;gtk-launch </span><span class="nv">$s</span><span class="s2">&quot;</span> 140 <span class="w"> </span><span class="k">fi</span> 141 <span class="w"> </span><span class="k">done</span> 142 <span class="w"> </span><span class="nv">IFS</span><span class="o">=</span> 143 <span class="k">fi</span> 144 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$browsercmd</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 145 <span class="w"> </span><span class="nv">browsercmd</span><span class="o">=</span><span class="si">${</span><span class="nv">BROWSER</span><span class="k">:-</span><span class="nv">$fallbackbrowsercmd</span><span class="si">}</span> 146 <span class="k">fi</span> 147 <span class="nv">t</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="w"> </span>--suffix<span class="o">=</span>.html<span class="k">)</span> 148 pandoc<span class="w"> </span>-f<span class="w"> </span>gfm<span class="w"> </span>-t<span class="w"> </span>html<span class="w"> </span>-M<span class="w"> </span>document-css<span class="o">=</span><span class="nb">false</span><span class="w"> </span>--standalone<span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$t</span><span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>/dev/null 149 <span class="nv">$browsercmd</span><span class="w"> </span><span class="nv">$t</span> 150 </pre></div> 151 </div> 152 </div> 153 <div class="section" id="naming-the-executioner"> 154 <h2>Naming the executioner</h2> 155 <p>So now we have a markdown viewer. And it can be as lean or as heavy as you want it to be. It's all to the browser you choose.</p> 156 <p>To make it accessible, map the script as a command alias in your <tt class="docutils literal">.profile</tt> or <tt class="docutils literal">.bashrc</tt> and you have the viewer at your fingertips.</p> 157 <p>I call mine <tt class="docutils literal">wmd</tt>:</p> 158 <div class="highlight"><pre><span></span><span class="nb">alias</span><span class="w"> </span><span class="nv">wmd</span><span class="o">=</span><span class="s2">&quot;bash </span><span class="nv">$HOME</span><span class="s2">/scripts/markdown.sh&quot;</span> 159 </pre></div> 160 <p>And voilá:</p> 161 <div class="highlight"><pre><span></span><span class="gp">$ </span><span class="nb">export</span><span class="w"> </span><span class="nv">BROWSER</span><span class="o">=</span>lynx 162 <span class="gp">$ </span>wmd<span class="w"> </span>README.md 163 </pre></div> 164 <!-- --> 165 <blockquote> 166 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 167 <colgroup><col class="label" /><col /></colgroup> 168 <tbody valign="top"> 169 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Or X Desktop Group, as they were originally called.</td></tr> 170 </tbody> 171 </table> 172 </blockquote> 173 <!-- --> 174 <blockquote> 175 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 176 <colgroup><col class="label" /><col /></colgroup> 177 <tbody valign="top"> 178 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>Human User Interface. Don't bother looking - I made it up. As the cause of AI, robots and machines inevitably will become appropriated by the woke intersectionality complex, the terminology is probably going to need such distictions.</td></tr> 179 </tbody> 180 </table> 181 </blockquote> 182 <!-- --> 183 <blockquote> 184 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 185 <colgroup><col class="label" /><col /></colgroup> 186 <tbody valign="top"> 187 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>Actually the format is <a class="reference external" href="https://en.wikipedia.org/wiki/INI_file">ini</a>. The standard is rather in the file naming, really.</td></tr> 188 </tbody> 189 </table> 190 </blockquote> 191 </div> 192 </content><category term="Offlining"></category><category term="bash"></category><category term="markdown"></category><category term="pandoc"></category><category term="vimb"></category><category term="w3m"></category><category term="lynx"></category><category term="xdg"></category></entry><entry><title>Secrets in the shell</title><link href="clortho.html" rel="alternate"></link><published>2024-06-25T20:46:00+02:00</published><updated>2024-06-25T21:58:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2024-06-25:clortho.html</id><summary type="html"><p class="first last">A key value store at your fingertips</p> 193 </summary><content type="html"><p>Ever since I started using the <a class="reference external" href="https://www.passwordstore.org/">pass</a> CLI as my password manager, I've found myself putting all sorts of stuff in there; usernames, email, urls, crypto addresses, api keys, you name it.</p> 194 <p>It makes total sense that some of these items are in there. For example, I store the url to a service together with the password, usually accompanied by the username and the email used <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>. No password recoveries needed.</p> 195 <p>However, at some point I also started putting in things like crypto addresses, or even token smart contract addresses in there.</p> 196 <p>That seemed less of a good fit.</p> 197 <p>One thing is that it spams the password directory.</p> 198 <p>Another, perhaps more sinister, issue is that it's pretty clear for anyone reading the directory what items you are storing data for.</p> 199 <p>So what if I want to store key/value pairs, and at the same time I want to hide what I am storing?</p> 200 <div class="section" id="hiding-in-plain-cipher-text"> 201 <h2>Hiding in plain cipher text</h2> 202 <p>A simple solution is to hash the key together with some passphrase as &quot;salt&quot;, along the lines of this:</p> 203 <div class="highlight"><pre><span></span><span class="nv">key</span><span class="o">=</span><span class="nv">$1</span> 204 <span class="c1"># read passphrase from stdin</span> 205 stty<span class="w"> </span>-echo 206 <span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;passphrase: &quot;</span> 207 <span class="nb">read</span><span class="w"> </span>passphrase 208 stty<span class="w"> </span><span class="nb">echo</span> 209 hash_key<span class="o">()</span><span class="w"> </span><span class="o">{</span> 210 <span class="w"> </span><span class="c1"># dump the passphrase (assuming mktemp stores in volatile memory)</span> 211 <span class="w"> </span><span class="nv">ktt</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span> 212 <span class="w"> </span><span class="nv">kt</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span> 213 <span class="w"> </span>chmod<span class="w"> </span><span class="m">200</span><span class="w"> </span><span class="nv">$kt</span> 214 <span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$passphrase</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$kt</span> 215 <span class="w"> </span>chmod<span class="w"> </span><span class="m">600</span><span class="w"> </span><span class="nv">$kt</span> 216 <span class="w"> </span><span class="c1"># the salted key; first add the hashed passphrase (sha512)</span> 217 <span class="w"> </span><span class="nv">kc</span><span class="o">=</span><span class="k">$(</span>sha512sum<span class="w"> </span><span class="nv">$kt</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{print $1;}&#39;</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$ktt</span><span class="k">)</span> 218 <span class="w"> </span><span class="c1"># remove the stored passphrase</span> 219 <span class="w"> </span>shred<span class="w"> </span><span class="nv">$kt</span> 220 <span class="w"> </span><span class="c1"># then add the key to the mix, and hash again</span> 221 <span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$kp</span><span class="w"> </span>&gt;&gt;<span class="w"> </span><span class="nv">$ktt</span> 222 <span class="w"> </span><span class="nv">kc</span><span class="o">=</span><span class="k">$(</span>sha512sum<span class="w"> </span><span class="nv">$ktt</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{print $1;}&#39;</span><span class="k">)</span> 223 <span class="o">}</span> 224 </pre></div> 225 <p>The <tt class="docutils literal">kc</tt> variable at the end of the script above now contains the <em>salted</em> hash of the key.</p> 226 <p>Specifically, in this implementation, given key <tt class="docutils literal">&quot;foo&quot;</tt> and passphrase <tt class="docutils literal">&quot;bar&quot;</tt>, the resulting key will be:</p> 227 <p><tt class="docutils literal">a9454592edbed78c3abb739dd88567bc92cc4ea1feee8480c7204d48d7d0e9ce86f9c79d55e39751ceb3f2774913841056293a5e8e8f440a3f281dffabd6f540</tt>.</p> 228 </div> 229 <div class="section" id="added-value"> 230 <h2>Added value</h2> 231 <p>Now we can encrypt the store the corresponding value, and store it in a file that has that literal hash as the key.</p> 232 <p>Using <a class="reference external" href="https://ccrypt.sourceforge.net/">ccrypt</a> as an example:</p> 233 <div class="highlight"><pre><span></span><span class="nv">data_dir</span><span class="o">=</span><span class="nv">$HOME</span>/.obfuscated 234 <span class="nv">key</span><span class="o">=</span><span class="nv">$1</span> 235 <span class="nv">vp</span><span class="o">=</span><span class="nv">$2</span> 236 <span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$vp</span><span class="s2">&quot;</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$t</span> 237 <span class="c1"># create a file for ccrypt to read, because supplying any other way in shell is not so safe.</span> 238 <span class="nv">passfile</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span> 239 chmod<span class="w"> </span><span class="m">600</span><span class="w"> </span><span class="nv">$passfile</span> 240 <span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="nv">$passphrase</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$passfile</span> 241 chmod<span class="w"> </span><span class="m">400</span><span class="w"> </span><span class="nv">$passfile</span> 242 ccrypt<span class="w"> </span>-k<span class="w"> </span><span class="nv">$passfile</span><span class="w"> </span><span class="nv">$t</span> 243 shred<span class="w"> </span><span class="nv">$passfile</span> 244 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$?</span><span class="s2">&quot;</span><span class="w"> </span>-gt<span class="w"> </span><span class="s2">&quot;0&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> 245 <span class="w"> </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nb">set</span><span class="w"> </span>key<span class="w"> </span>fail 246 <span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span> 247 <span class="k">fi</span> 248 <span class="c1"># run the code in the previous section</span> 249 hash_key 250 mkdir<span class="w"> </span>-vp<span class="w"> </span><span class="nv">$data_dir</span> 251 cp<span class="w"> </span><span class="nv">$t</span>.cpt<span class="w"> </span><span class="nv">$data_dir</span>/<span class="nv">$kc</span> 252 shred<span class="w"> </span><span class="nv">$t</span>.cpt 253 </pre></div> 254 <p>At the end of this operation, the <em>encrypted</em> value is stored in a file whose name is the <em>salted</em> hash of the key.</p> 255 <div class="section" id="key-mastering"> 256 <h3>Key Mastering</h3> 257 <p>Let's say you want to store some <a class="reference external" href="https://kobl.one/blog/create-full-ethereum-keypair-and-address/">Ethereum addresses</a> that are of interest to you, but should be of interest to noone else.</p> 258 <p>So, let's choose a prefix for &quot;Ethereum addresses.&quot; Let it be <tt class="docutils literal">etha</tt></p> 259 <p>Next, let's pick an identifier that we will remember, that describes the <em>purpose</em> of the address. In this case it's for &quot;governance of the organization 'Xy Zzy Co.'&quot;. The key then becomes <tt class="docutils literal">ethagovxyzzyco</tt>.</p> 260 <p>Last, for a bit of extra fun, let's add some random stuff at the end of the string, like <a class="reference external" href="https://www.quotes.net/mquote/42425">shilling!shilling?oh,shilling</a>. Thus, we finally end up with <cite>ethagovxyzshilling!shilling?oh,shilling`</cite>.</p> 261 <p>What we need to memorize here is:</p> 262 <ul class="simple"> 263 <li>whenever referring to an <em>Ethereum address</em>. we use <tt class="docutils literal">etha</tt></li> 264 <li>whenever referring to <em>governance</em>. we use <tt class="docutils literal">gov</tt></li> 265 <li>whenever referring to an entity, we use the name of the organization <em>less whitespace and special characters</em>.</li> 266 <li>the three above are added one after another</li> 267 <li>add the extra fun</li> 268 </ul> 269 <p>There will of course always be a pattern to how you create and add similar structures for other categories, actions and entities.</p> 270 <p>But the point here is to show that it doesn't take <em>that much</em> to have some advantage of <em>plausible deniability</em>: Even faced with the <a class="reference external" href="https://xkcd.com/538/">5 Dollar Wrench Problem</a>, if you are able to hold your ground, there is no feasible way to prove that you <em>don't own something</em>. Bite the bullet, absorb the pain, deny ownership, and you'll be fine.</p> 271 </div> 272 </div> 273 <div class="section" id="perils-and-pitfalls"> 274 <h2>Perils and pitfalls</h2> 275 <p>None of this is in no way safe by itself.</p> 276 <p>Some concerns are named below. Surely there are others, too.</p> 277 <div class="section" id="out-of-sight-but-still-mined"> 278 <h3>Out of sight, but still mined</h3> 279 <p>You may find it a pain in the ass to type the passphrase for the key salt every time.</p> 280 <p>An obvious workaround for that is to store the passphrase in a file.</p> 281 <p>But keep in mind that, if you do that, the key obfuscation is going to be less safe than your regular passwords storage.</p> 282 <p>That is to say: If the attacker can read your passwords ciphertext, most likely the attacked can read you obfuscation passphrase, too.</p> 283 </div> 284 <div class="section" id="out-of-mind-out-of-luck"> 285 <h3>Out of mind, out of luck</h3> 286 <p>Maybe the most important thing to realize in this solution is that there is no way to recall the original keys from the file names under which the values are stored.</p> 287 <p>Instead, the idea is that you decide on a naming scheme for certain crucial values you want to store, and <em>remember</em> how to reconstruct them. <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a></p> 288 </div> 289 <div class="section" id="shell-shill"> 290 <h3>Shell shill</h3> 291 <p>The code above has been adapted from a <tt class="docutils literal">bash</tt> tool I've already written for the purpose, given the name <a class="reference external" href="https://holbrook.no/src/clortho/file/clortho.sh.html#l10">Clortho</a>.</p> 292 <p>Shell script is surely a very poor choice for this kind of thing, but at the time I simply wanted to write shell so that's how it ended up.</p> 293 <p>I probably will rewrite it for another environment at some point.</p> 294 <p>But for now it illustrates the point.</p> 295 <!-- --> 296 <blockquote> 297 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 298 <colgroup><col class="label" /><col /></colgroup> 299 <tbody valign="top"> 300 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>I use a different email for each service I sign up to, and for every other context I have to leave my email for something. I highly recommend this practice, aswell as running your own mail server. <a class="reference external" href="https://www.linuxbabe.com/mail-server/setup-basic-postfix-mail-sever-ubuntu">It really isn't that hard</a>.</td></tr> 301 </tbody> 302 </table> 303 </blockquote> 304 <!-- --> 305 <blockquote> 306 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 307 <colgroup><col class="label" /><col /></colgroup> 308 <tbody valign="top"> 309 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>I'm not going to lie: That kind of thing takes practice. As in, you need to remind yourself from time to time what your scheme is. Perhaps that is an unthinkable proposition in the age of the Smartphone and the illusion of auxiliary instant truths. But I come from a time where I knew all numbers and addresses I needed to know by heart. The more you do it, the more you remember. The less you do it, the more you are dependent on others. What others do you depend on? And so on it goes...</td></tr> 310 </tbody> 311 </table> 312 </blockquote> 313 </div> 314 </div> 315 </content><category term="Code"></category><category term="crypto"></category><category term="hash"></category><category term="sha512"></category><category term="bash"></category><category term="cli"></category><category term="ccrypt"></category></entry><entry><title>Keeping your gits in a row</title><link href="git-fresh.html" rel="alternate"></link><published>2024-06-18T16:28:00+02:00</published><updated>2024-06-18T16:33:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2024-06-18:git-fresh.html</id><summary type="html"><p class="first last">Scripts to keep your local git clone fresh, and help you move them around.</p> 316 </summary><content type="html"><p>I believe that if you use a piece of code, you are also responsible for making sure that that code is available in the future.</p> 317 <p>In this spirit, I decided a couple of years ago that I would keep a full clone of all VCS repositories that I use.</p> 318 <div class="section" id="can-t-someone-else-do-it"> 319 <h2>Can't someone else do it?</h2> 320 <p>Yeah, yeah, I hear ya.</p> 321 <p>But imagine that one day you cannot reach the code repository anymore.</p> 322 <p>It could be because you are working where internet is scarce or impossible to rely on.</p> 323 <p>It could be that you have to cope with what was in your faraday cage when a giant solar flare happened.</p> 324 <p>It could be that you, or the author of the code, have been cut off by the accelerating <a class="reference external" href="https://torrentfreak.com/the-eu-wants-its-own-dns-resolver-that-can-block-unlawful-traffic-220119/">weaponization of everything</a>.</p> 325 <p>Or maybe none of the above happened. But you still understand and appreciate what it means to build a truly decentralized society, where we all participate and contribute, not only consume.</p> 326 </div> 327 <div class="section" id="git-organized"> 328 <h2>Git organized</h2> 329 <p>For every <cite>git</cite> repository that I use, I actually keep a <em>local copy</em> on my daily device.</p> 330 <p>I also keep a copy on a device at home, <em>and</em> on a remote device.</p> 331 <p>My thinking is:</p> 332 <ol class="arabic simple"> 333 <li>If I lose my laptop, I have two copies</li> 334 <li>If my house burns down, I have two copies</li> 335 <li>If my house burns down <em>with</em> my laptop inside, I have <em>at least one more copy</em>.</li> 336 </ol> 337 <p>... and so on.</p> 338 </div> 339 <div class="section" id="i-hate-to-move-it-move-it"> 340 <h2>I hate to move it, move it</h2> 341 <p>Sometimes we have to, though,.</p> 342 <p>And what can be a real pain is to move heaps of code repositories around. For example if you are moving to a new machine, or want to bootstrap a new copy without having to source the data yourself.</p> 343 <p>To make this easier, I wrote the <a class="reference external" href="https://holbrook.no/src/gitrefresh/log.html">gitrefresh bash tool</a> to copy only the minimum of information required to source the data from a remote. <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a></p> 344 </div> 345 <div class="section" id="freshening-up"> 346 <h2>Freshening up</h2> 347 <p>To make sense of what is what in the repository store, I use a simple folder structure.</p> 348 <p>Obviously, when I create copies of the repository store, I would like to keep the same folder structure. So the tool needed to make that possible.</p> 349 <p>Additionally, what's needed are tools to bootstrap a repository group from a list, and a tool to refresh those repositories periodically once they've been bootstrapped.</p> 350 <p>To achieve this, I actually wrote <a class="reference external" href="https://holbrook.no/src/gitrefresh/log.html">three tools</a>, as follows:</p> 351 <div class="section" id="gitlist-sh"> 352 <h3><cite>gitlist.sh</cite></h3> 353 <p>create a list of <cite>git</cite> repositories under a filesystem path, with the option of preserving the directory structure.</p> 354 </div> 355 <div class="section" id="gitstart-sh"> 356 <h3><cite>gitstart.sh</cite></h3> 357 <p>clone <cite>git</cite> repositories from a list generated from <code>gitlist.sh</code>, with or without direcory structure.</p> 358 </div> 359 <div class="section" id="gitrefresh-sh"> 360 <h3><cite>gitrefresh.sh</cite></h3> 361 <p>fetch and merge updates from remotes of each repository under a directory.</p> 362 </div> 363 </div> 364 <div class="section" id="behavior"> 365 <h2>Behavior</h2> 366 <p>The <code>gitlist.sh</code> and <code>gitrefresh.sh</code> tools work more or less the same way.</p> 367 <p>They traverse a directory structure recursively.</p> 368 <p>Every time a valid git repository is found, that repository is processed. Afterwards, the tool will exit to the parent folder. <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a></p> 369 <div class="section" id="example"> 370 <h3>Example</h3> 371 <p>Let's say we have three repositories that we are mirroring locally:</p> 372 <ul class="simple"> 373 <li><code>https://github.com/bitcoin/bips</code> under <code>btc/bips</code></li> 374 <li><code>https://aur.archlinux.org/libkeccak.git</code> under <code>os/archlinux/aur/libkeccak</code></li> 375 <li><code>git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git</code> under <code>linux/linux</code></li> 376 </ul> 377 <p>First we use <code>gitlist.sh</code> to generate the list of repos to bootstrap <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a>:</p> 378 <pre class="code console literal-block"> 379 <span class="gp">$ </span>gitlist.sh<span class="w"> </span>-p<span class="w"> </span><span class="p">|</span><span class="w"> </span>tee<span class="w"> </span>gitlist.txt<span class="w"> 380 </span><span class="go">https://github.com/bitcoin/bips btc/bips 381 https://aur.archlinux.org/libkeccak.git os/archlinux/aur/libkeccak` 382 git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git linux/linux`</span> 383 </pre> 384 <p>Using <code>gitstart.sh</code> with this list, we can restore this bunch of repositories <em>with</em> the same directory structure anywhere else:</p> 385 <pre class="code console literal-block"> 386 <span class="gp">$ </span><span class="nb">cd</span><span class="w"> </span>/path/to/new/repos/location<span class="w"> 387 </span><span class="gp">$ </span>gitstart.sh<span class="w"> </span>&lt;<span class="w"> </span>gitlist.txt 388 </pre> 389 <p>Now, the idea is that from time to time you should get the latest changes from the upstream source.</p> 390 <p>I simply combine <code>gitrefresh.sh</code> with <code>cron</code> to do this on the remote, while manually doing the refresh locally once in awhile.</p> 391 <p>Using the tool, all it takes is:</p> 392 <pre class="code console literal-block"> 393 <span class="gp">$ </span><span class="nb">cd</span><span class="w"> </span>/path/to/new/repos/location<span class="w"> 394 </span><span class="gp">$ </span>gitrefresh.sh<span class="w"> </span>pull 395 </pre> 396 <!-- --> 397 <blockquote> 398 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 399 <colgroup><col class="label" /><col /></colgroup> 400 <tbody valign="top"> 401 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Yes. I didn't get beyond <cite>git</cite> yet. But at least it's a start.</td></tr> 402 </tbody> 403 </table> 404 </blockquote> 405 <!-- --> 406 <blockquote> 407 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 408 <colgroup><col class="label" /><col /></colgroup> 409 <tbody valign="top"> 410 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>This, of course, means that the tool will not automatically archive code from <em>submodules</em>. The submodule construct is a target of both a lot of love and a lot of hate. Personally, I like it. But at the same time it is my opinion that it does not absolve us from <em>knowing</em> and being <em>mindful</em> which submodules a repository is using, and thus making sure that we have an independent clone of that repository.</td></tr> 411 </tbody> 412 </table> 413 </blockquote> 414 <!-- --> 415 <blockquote> 416 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 417 <colgroup><col class="label" /><col /></colgroup> 418 <tbody valign="top"> 419 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>We add the <code>-p</code> flag to preserve the directory structure on disk.</td></tr> 420 </tbody> 421 </table> 422 </blockquote> 423 </div> 424 </div> 425 </content><category term="Archiving"></category><category term="git"></category><category term="bash"></category></entry><entry><title>A world clock in your terminal</title><link href="world-clock-terminal.html" rel="alternate"></link><published>2024-06-09T23:22:35+02:00</published><updated>2024-06-09T23:22:35+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2024-06-09:world-clock-terminal.html</id><summary type="html"><p class="first last">A single character command to display the current time of your favorite places in the world</p> 426 </summary><content type="html"><div class="section" id="zoning-in"> 427 <h2>Zoning in</h2> 428 <p>Timezones in Linux isn't a particularly intuitive issue.</p> 429 <p>On my distro, archlinux, time-zone files are located in <tt class="docutils literal">/usr/share/zoneinfo</tt>, and there are several different categories of them.</p> 430 <p>I've given up referencing time in the usual three- and four-letter acronyms like <tt class="docutils literal">CEST</tt> and <tt class="docutils literal">EST</tt> long ago. Not only are they ambiguous in themselves, but not all of them even have records in the zoneinfo tree.</p> 431 <p>The best approach I have found so far is to look at the world continent directories, and look for nearby cities. Your OS alone may not be enough in all cases to figure out the zone for obscure locations, but at least it gets you half the way there.</p> 432 <p>... as in, you ask your friend or associate to name some big cities close to them, and hope you find a match in your filesystem.</p> 433 </div> 434 <div class="section" id="environmental-zones"> 435 <h2>Environmental zones</h2> 436 <p>Turns out that the <tt class="docutils literal">date</tt> command of GNU/Linux uses the environment variable <tt class="docutils literal">TZ</tt>, which lets you define the time-zone you want the output to be translated to.</p> 437 <p>In my case at time of writing:</p> 438 <div class="highlight"><pre><span></span>$<span class="w"> </span><span class="nv">TZ</span><span class="o">=</span>America/El_Salvador<span class="w"> </span>date 439 Sun<span class="w"> </span>Jun<span class="w"> </span><span class="m">9</span><span class="w"> </span><span class="m">05</span>:00:16<span class="w"> </span>PM<span class="w"> </span>CST<span class="w"> </span><span class="m">2024</span> 440 441 <span class="c1"># sigh, see what I mean?</span> 442 $<span class="w"> </span><span class="nv">TZ</span><span class="o">=</span>CST<span class="w"> </span>date 443 Sun<span class="w"> </span>Jun<span class="w"> </span><span class="m">9</span><span class="w"> </span><span class="m">11</span>:04:12<span class="w"> </span>PM<span class="w"> </span>CST<span class="w"> </span><span class="m">2024</span> 444 </pre></div> 445 <p>Ugly, yes. Luckily <tt class="docutils literal">date</tt> also lets you format the output.</p> 446 </div> 447 <div class="section" id="times-tabled"> 448 <h2>Times, tabled</h2> 449 <p>Now, let's bring those two together.</p> 450 <div class="highlight"><pre><span></span><span class="nv">z</span><span class="o">=(</span><span class="s2">&quot;US/Hawaii&quot;</span><span class="w"> </span><span class="s2">&quot;America/El_Salvador&quot;</span><span class="w"> </span><span class="s2">&quot;US/Eastern&quot;</span><span class="w"> </span><span class="s2">&quot;Europe/Lisbon&quot;</span><span class="w"> </span><span class="s2">&quot;Europe/Berlin&quot;</span><span class="w"> </span><span class="s2">&quot;Africa/Nairobi&quot;</span><span class="w"> </span><span class="s2">&quot;Asia/Taipei&quot;</span><span class="o">)</span> 451 <span class="k">for</span><span class="w"> </span>z<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="si">${</span><span class="nv">z</span><span class="p">[@]</span><span class="si">}</span><span class="p">;</span><span class="w"> </span><span class="k">do</span> 452 <span class="w"> </span><span class="nv">d</span><span class="o">=</span><span class="k">$(</span><span class="nv">TZ</span><span class="o">=</span><span class="nv">$z</span><span class="w"> </span>date<span class="w"> </span>+<span class="s1">&#39;%H:%M:%S (%z)&#39;</span><span class="k">)</span> 453 <span class="w"> </span><span class="nv">s</span><span class="o">=</span><span class="k">$(</span><span class="nb">printf</span><span class="w"> </span>%-16s<span class="w"> </span><span class="nv">$z</span><span class="k">)</span> 454 <span class="w"> </span><span class="nb">echo</span><span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$s</span><span class="s2">\t</span><span class="nv">$d</span><span class="s2">&quot;</span> 455 <span class="w"> </span><span class="k">done</span> 456 </pre></div> 457 <p>This script iterates a list of valid time-zone strings from <tt class="docutils literal">/usr/share/zoneinfo</tt>, outputting the zone along with the time and offet in a readable, tabulated format.</p> 458 <p>This results in:</p> 459 <div class="highlight"><pre><span></span>$<span class="w"> </span>z 460 US/Hawaii<span class="w"> </span><span class="m">13</span>:02:12<span class="w"> </span><span class="o">(</span>-1000<span class="o">)</span> 461 America/El_Salvador<span class="w"> </span><span class="m">17</span>:02:12<span class="w"> </span><span class="o">(</span>-0600<span class="o">)</span> 462 US/Eastern<span class="w"> </span><span class="m">19</span>:02:12<span class="w"> </span><span class="o">(</span>-0400<span class="o">)</span> 463 Europe/Lisbon<span class="w"> </span><span class="m">00</span>:02:12<span class="w"> </span><span class="o">(</span>+0100<span class="o">)</span> 464 Europe/Berlin<span class="w"> </span><span class="m">01</span>:02:12<span class="w"> </span><span class="o">(</span>+0200<span class="o">)</span> 465 Africa/Nairobi<span class="w"> </span><span class="m">02</span>:02:12<span class="w"> </span><span class="o">(</span>+0300<span class="o">)</span> 466 Asia/Taipei<span class="w"> </span><span class="m">07</span>:02:12<span class="w"> </span><span class="o">(</span>+0800<span class="o">)</span> 467 </pre></div> 468 <p>The <tt class="docutils literal">z</tt> here is merely an alias mapping added to <tt class="docutils literal">.bashrc</tt> <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>:</p> 469 <div class="highlight"><pre><span></span><span class="nb">alias</span><span class="w"> </span><span class="nv">z</span><span class="o">=</span><span class="sb">`</span>bash<span class="w"> </span><span class="nv">$HOME</span>/scripts/timezones.sh<span class="sb">`</span> 470 </pre></div> 471 <p>Voila, now you have a world clock in your terminal at any time.</p> 472 <p>And all it takes is two mere keystrokes.</p> 473 <!-- --> 474 <blockquote> 475 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 476 <colgroup><col class="label" /><col /></colgroup> 477 <tbody valign="top"> 478 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Of course, be a bit careful with those aliases. There are even commands in GNU/Linux with only one character, like <tt class="docutils literal">w</tt>. If you override them in aliases, your override takes precedence.</td></tr> 479 </tbody> 480 </table> 481 </blockquote> 482 </div> 483 </content><category term="Offlining"></category><category term="bash"></category><category term="linux"></category></entry><entry><title>Introducing Wala</title><link href="wala.html" rel="alternate"></link><published>2022-10-05T14:39:00+02:00</published><updated>2022-10-05T14:39:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2022-10-05:wala.html</id><summary type="html"><p class="first last">A simple HTTP content-addressed storage with cryptographic identity pointers.</p> 484 </summary><content type="html"><p>This little project is heavily inspired by the &quot;Single-Owner Chunk&quot; (previously &quot;Mutable Resource&quot;) concept from the <a class="reference external" href="https://ethswarm.org">Swarm project</a> <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>.</p> 485 <p>Dubbed <a class="reference external" href="https://git.defalsify.org/wala">wala</a> <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a>, the rust application provides two features:</p> 486 <ol class="arabic simple"> 487 <li>Store any uploaded data under the SHA256 digest of the content itself. We call this &quot;content addressed&quot; storage.</li> 488 <li>Create a link alias to the content using a keyword and a cryptographic identity.</li> 489 </ol> 490 <p>First, let's briefly outline what the first item on the list is.</p> 491 <div class="section" id="curl-up-and-digest"> 492 <h2>Curl up and digest</h2> 493 <p>Say an instance of wala is listnening on <code>localhost:8000</code>.</p> 494 <p>A simple demonstration of the content addressed storage could be:</p> 495 <pre class="code console literal-block"> 496 <span class="gp">$&nbsp;</span>curl<span class="w"> </span>-X<span class="w"> </span>PUT<span class="w"> </span>http://localhost:8000<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;foo&quot;</span><span class="w"> 497 </span><span class="go">2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae 498 </span><span class="gp">$ </span>curl<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>http://localhost:8000/2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae<span class="w"> 499 </span><span class="go">foo 500 </span><span class="gp">$ </span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;foo&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sha256sum<span class="w"> 501 </span><span class="go">2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae -</span> 502 </pre> 503 <p>In short, anything uploaded to the endpoint will be stored under and can be retrieved by its hash.</p> 504 <p>That also means that if the content changes, so does the location.</p> 505 </div> 506 <div class="section" id="get-to-the-pointer"> 507 <h2>Get to the pointer</h2> 508 <p>Now let's say you want a permanent link to content that is changing.</p> 509 <p>If you are the only user of the instance, this would be straightforward thing. We could just use a keyword in the url.</p> 510 <p>But if you are not, then authentication is needed to make sure that one user doesn't overwrite the another user's permanent link.</p> 511 <p>This is usually accomplished with a username and password stored and controlled server-side.</p> 512 <p>But we can also do this by simply using public key cryptography instead.</p> 513 <div class="section" id="claiming-what-is-yours"> 514 <h3>Claiming what is yours</h3> 515 <p>Since I have a weakness for PGP, the first implementation of authentication in wala has been implemented using the <a class="reference external" href="https://sequoia-pgp.org">&quot;sequoia openpgp&quot;</a> library.</p> 516 <p>The procedure is straightforward enough:</p> 517 <ol class="arabic simple"> 518 <li>Sign the content with your pgp key.</li> 519 <li>Add an <cite>Authorization</cite> header <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a> with your public key and signature.</li> 520 <li>Upload the content with an arbitrary keyword. <a class="footnote-reference" href="#footnote-4" id="footnote-reference-4">[4]</a></li> 521 </ol> 522 <p>If the signature over the content matches the public key, then a symbolic link to the content will be created on the server. That symbolic link will be a digest of the <em>key fingerprint</em> and the <em>keyword</em>.</p> 523 <p>The content can then be accessed <em>both</em> using the content address <em>and</em> the symbolic link.</p> 524 <p>In wala, the symbolic link is referred to as a <em>mutable reference</em>. We will use this term from now on.</p> 525 </div> 526 <div class="section" id="it-ain-t-pretty-but-it-works"> 527 <h3>It ain't pretty, but it works</h3> 528 <p>Let's demonstrate creating such an entry in wala using some commonly available tools. <a class="footnote-reference" href="#footnote-5" id="footnote-reference-5">[5]</a></p> 529 <p>We will use a fictional gnupg private key with the fingerprint <code>F3FAF668E82EF5124D5187BAEF26F4682343F692</code> and the keyword <code>foo</code> to create the mutable reference.</p> 530 <pre class="code console literal-block"> 531 <span class="gp">$&nbsp;</span><span class="nv">sig</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;bar&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>gpg<span class="w"> </span>-b<span class="w"> </span>-u<span class="w"> </span>F3FAF668E82EF5124D5187BAEF26F4682343F692<span class="w"> </span><span class="p">|</span><span class="w"> </span>base64<span class="w"> </span>-w<span class="w"> </span><span class="m">0</span><span class="sb">`</span><span class="w"> 532 </span><span class="gp">$ </span><span class="nv">pub</span><span class="o">=</span><span class="sb">`</span>gpg<span class="w"> </span>--export<span class="w"> </span>F3FAF668E82EF5124D5187BAEF26F4682343F692<span class="w"> </span><span class="p">|</span><span class="w"> </span>base64<span class="w"> </span>-<span class="w"> </span>w<span class="w"> </span><span class="m">0</span><span class="sb">`</span><span class="w"> 533 </span><span class="gp">$ </span>curl<span class="w"> </span>-v<span class="w"> </span>-X<span class="w"> </span>PUT<span class="w"> </span>http://localhost:8000/foo<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: PUBSIG:</span><span class="nv">$pub</span><span class="s2">:</span><span class="nv">$sig</span><span class="s2">&quot;</span><span class="w"> </span>--data<span class="w"> </span>bar<span class="w"> 534 </span><span class="go">* Trying 127.0.0.1:8000... 535 </span><span class="gp"> % </span>Total<span class="w"> </span>%<span class="w"> </span>Received<span class="w"> </span>%<span class="w"> </span>Xferd<span class="w"> </span>Average<span class="w"> </span>Speed<span class="w"> </span>Time<span class="w"> </span>Time<span class="w"> </span>Time<span class="w"> </span>Current<span class="w"> 536 </span><span class="go"> Dload Upload Total Spent Left Speed 537 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Connected to localhost (127.0.0.1) port 8000 (#0) 538 &gt; PUT /foo HTTP/1.1 539 &gt; Host: localhost:8000 540 &gt; User-Agent: curl/7.85.0 541 &gt; Accept: */* 542 &gt; Authorization: PUBSIG pgp:mQGNBF+hSOgBDACpkPQEjADjnQtjmAsdPYpx5N+OMJBYj1DAoIYsDtV6vbcBJQt94Om3xl7RBhv9m2oLgzPsiRwjCEFRWyNSu0BUp5CFjcXfm0S4K2egx4erFnTnSSC9S6tmVNrVNEXvScE6sKAnmJ7JNX1ExJuEiWPbUDRWJ1hoI9+AR+8EONeJRLo/j0Np+S4IFDn0PsxdT+SB0GY0z2cEgjvjoPr4lW9IAb8Ft9TDYp+mOzejn1Fg7CuIrlBRSAv+sj7bVQw15dh1SpbwtS5xxubCa8ExEGI4ByXmeXdR0KZJ+EA5ksO0iSsQ/6ipSOdSg+i0niOClFNm1P/OhbUsYAxCUfiX654FMn2zoxVBEjJ3e7l0pH7ktodaxEctPofQLBA9LSDUIejqJsU0npw/DHDD2uvxG+/A6lgV9L8ETlvgp8RzeOCf2bHuiKYYz87txvkFwsXgU1+TZxbk+mtCBbngsVPLNarY/KGkVJL+yhcHRD0Pl4wXUd6auQuY6vQ9AuKiCT1We2sAEQEAAbQeTWVyIE1hbiA8bWVybWFuQGdyZXlza3VsbC5jb20+iQHUBBMBCAA+FiEE8/r2aOgu9RJNUYe67yb0aCND9pIFAl+hSOgCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ7yb0aCND9pLwiwwAhFJbAyUK05TJKfDz81757N472STtB8sfr0auwmRr8Zs1utHRVM0b/jkjTuo4uJNr7YVVKTKgE7+rJ+pwhm3wlTQ44LVLjByWAi/7NWg3E9b2elm+qkfgm/RfFt3vkuOxGSyZyIFFh+/twv6iABPvr6w7MZwrFaS0UP3g1VGa5TFqg6KNxod9H/gPLxv45lutXf3VvBZTJpr1pxn7aLHlFzEyIgNZbP/N1QF44GSrN/k0DfL631sZjauUXaZXbi5xGsKKCYwJ1g3q587pi6mTdTV3n0hKgVuipO8hGy5++YeOv+hXsCxDwyZ+Shv+qavd/SapxYgCdEueuwONIFfsIsWCd3SCcjKXicTTEFMu8nvBmf7xuo2hv6vEOxoijlXV+4LkGrskdB8ZMg8PywEx6DLmDokgnAhTLrTc1ShbkOtQ3yNjjyFK7BDpqobsJal6d8SpbhccUJLepaSmsk0CgJsTjhAl6EwX0EYgTo3kP5fScqrbD8VwQaT8CcE4rCV4uQGNBF+hSOgBDADHtpTT1k4x+6FN5OeURpKAaIsoPHghkJ2lb6yWmESCa+DaR6GXAKlbd0L9UMcXLqnaCn4SpZvbf8hP4fJRgWdRl5uVN/rmyVbZLUVjM8NcVdFRIrTsNyu4mLBmydc3iA/90sCTEOj9e7DSvxLmmLFjpwM5xXLd6z0l6+9G+woNmARXVS3V/RryFntyKC3ATCqVlJoQBG45Tj2gMIunpadTJXWmdioooeGW3sLeUv5MM98mSB4SjKRlJqGPNjx5lO6MmJbZeXZ/L/aO6EsXUQD2h82Wphll4rpGYWPiHTCYqZYiqNYr6E3xUpzcvWVp3uCYVJWP6Ds117p7BoyKVz00yxC9ledF3eppktZWqFVowCMihQE3676L3DDTZsnJf1/8xKUh5U2Mj3lBvjlvCECKi00qo8b1mn/OklQjJ5T4WzTrH6X+/zpez8ZkmtcOayHdUKD/64roZ9dXbXG/hp5A+UWj8oSVYKg2QNAwAnZ+aiZ2KVRE/Y61DCgFg6Ccx/cAEQEAAYkBvAQYAQgAJhYhBPP69mjoLvUSTVGHuu8m9GgjQ/aSBQJfoUjoAhsMBQkDwmcAAAoJEO8m9GgjQ/aSIPcL/3jqL2A2SmC+s0BO4vMPEfCpa2gZ/vo1azzjUieZu5WhIxb5ik0V6T75EW5F0OeZj9qXI06gW+IM8+C6ImUgaR3l47UjBiBPq+uKO9QuT/nOtbSs2dXoTNCLMQN7MlrdUBix+lnqZZGSDgh6n/uVyAYw8Sh4c3/3thHUiR7xzVKGxAKDT8LoVjhHshTzYuQq8MqlfvwVI4eESLaryQ+Y+j5+VLDzSLgPAnnIqF/ui2JQjefJxm/VLoYNaPAGdqoz/u/R0Tmz94bZUfLjgQaDoUpnxYywK2JGlf3mPZ3PNWjxJzuQTF5Ge5bz/TylnRYIyBT7KD7oaKHO62fhDbYPJ4f94iZN4B6nnTAeP34zFDlkUbX4AHudXU7bvxT5OUk9x9c2tj7xwxQHaEhq2+JsYW0EVw27RLhbymnBfLjVVUktNF0nQGvU2TEocw4pr2ZkDHQkSnlbNa4kujlL7VzbpnEgyOmi5er9GaIuVSVADovBu+pz/Ov1y/3jUe8hZ/KleQ==:iQGzBAABCAAdFiEE8/r2aOgu9RJNUYe67yb0aCND9pIFAmM9guIACgkQ7yb0aCND9pIEdwwAiLLqFlrKu0UsQebfuUP07cvGbYy9LfbCMsQj/3/pG/zl7q2mSl2YdXOalbaYD2uyGU/sm7J/+qQZXGyIjmDA7F53sNVAXTuYnrcKmYIzAmzW4lUAzfWA7yL55MtbR/eNUE1rqp/Gu/ejj1OedLyxi+tGFcXUHU0q8EnjQnzfHCJVzOa3PGMIX10NiXPjrF2pafAyE7q2ogwkKZdjJi+8tyAw0tviu4CRGOVlsNZlF+yxePZh55XdRZLCEt4n6mnJrccu0C22rM9R2dEReqGLAj8t/WhACI+UyNXtL+hICnu9y6wjk4spoMr0s0pqTQ76SMwfmRFzk11uZ+ge846hArcUxE27+AeBf9Q1IwT5Ypsc0Efm9ZPoJvA2ggcJv1Yyb58Ggfmd02xPW4EQ8MOEMLA/ZoAhOm3t3wATPNFG1ucm/o+NFNDpF7HNby+Savqv2NrbNwDMlWvFzRmER2+AIO0CIG2HVJScMEn7UkjF8jIm+ba3BIAXbz2FUZ3dytFF 543 &gt; Content-Length: 3 544 &gt; Content-Type: application/x-www-form-urlencoded 545 &gt; 546 } [3 bytes data] 547 * Mark bundle as not supporting multiuse 548 &lt; HTTP/1.1 200 OK 549 &lt; Server: tiny-http (Rust) 550 &lt; Date: Wed, 5 Oct 2022 13:15:37 GMT 551 &lt; Content-Type: text/plain; charset=UTF-8 552 &lt; Access-Control-Allow-Origin: * 553 &lt; Access-Control-Allow-Methods: OPTIONS, PUT, GET 554 &lt; Access-Control-Allow-Headers: Content-Type,Authorization,X-Filename 555 &lt; Content-Length: 64 556 &lt; 557 { [64 bytes data] 558 100 67 100 64 100 3 3548 166 --:--:-- --:--:-- --:--:-- 3722100 67 100 64 100 3 3534 165 --:--:-- --:--:-- --:--:-- 3722 559 * Connection #0 to host localhost left intact 560 32ea47ecc5a3ee2576aab00c2f30eaabc2592d56e19f5c82fe4f7cf5874632b2 561 </span><span class="gp">$ </span>curl<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>http://localhost:8000/32ea47ecc5a3ee2576aab00c2f30eaabc2592d56e19f5c82fe4f7cf5874632b2<span class="w"> 562 </span><span class="go">bar 563 </span><span class="gp">$ </span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span>bar<span class="w"> </span><span class="p">|</span><span class="w"> </span>sha256sum<span class="w"> 564 </span><span class="go">fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9 565 </span><span class="gp">$ </span>curl<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>http://localhost:8000/fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9<span class="w"> 566 </span><span class="go">bar</span> 567 </pre> 568 <p>Now, by changing the data and signature, the data under the mutable resource changes:</p> 569 <pre class="code console literal-block"> 570 <span class="gp">$&nbsp;</span><span class="nv">sig</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;xyzzy&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>gpg<span class="w"> </span>-b<span class="w"> </span>-u<span class="w"> </span>F3FAF668E82EF5124D5187BAEF26F4682343F692<span class="w"> </span><span class="p">|</span><span class="w"> </span>base64<span class="w"> </span>-w<span class="w"> </span><span class="m">0</span><span class="sb">`</span><span class="w"> 571 </span><span class="gp">$ </span>curl<span class="w"> </span>-X<span class="w"> </span>PUT<span class="w"> </span>http://localhost:8000/foo<span class="w"> </span>-H<span class="w"> </span><span class="s2">&quot;Authorization: PUBSIG:</span><span class="nv">$pub</span><span class="s2">:</span><span class="nv">$sig</span><span class="s2">&quot;</span><span class="w"> </span>--data<span class="w"> </span>xyzzy<span class="w"> 572 </span><span class="go">32ea47ecc5a3ee2576aab00c2f30eaabc2592d56e19f5c82fe4f7cf5874632b2 573 </span><span class="gp">$ </span>curl<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>http://localhost:8000/32ea47ecc5a3ee2576aab00c2f30eaabc2592d56e19f5c82fe4f7cf5874632b2<span class="w"> 574 </span><span class="go">xyzzy</span> 575 </pre> 576 </div> 577 </div> 578 <div class="section" id="building-your-identity"> 579 <h2>Building your identity</h2> 580 <p>We can also recreate the symbolic link hash using local tools:</p> 581 <pre class="code console literal-block"> 582 <span class="gp">$ </span><span class="nv">t</span><span class="o">=</span><span class="sb">`</span>mktemp<span class="sb">`</span><span class="w"> 583 </span><span class="gp">$ </span><span class="nv">d</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span>foo<span class="w"> </span><span class="p">|</span><span class="w"> </span>sha256sum<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">'{ printf &quot;%s&quot;,$1; }'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-e<span class="w"> </span><span class="s1">'s/../\\\\x&amp;/g'</span><span class="sb">`</span><span class="w"> 584 </span><span class="gp">$ </span><span class="nb">echo</span><span class="w"> </span>-ne<span class="w"> </span><span class="nv">$d</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$t</span><span class="w"> 585 </span><span class="gp">$ </span><span class="nv">k</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span>F3FAF668E82EF5124D5187BAEF26F4682343F692<span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-e<span class="w"> </span><span class="s1">'s/../\\\\x&amp;/g'</span><span class="sb">`</span><span class="w"> 586 </span><span class="gp">$ </span><span class="nb">echo</span><span class="w"> </span>-ne<span class="w"> </span><span class="nv">$k</span><span class="w"> </span>&gt;&gt;<span class="w"> </span><span class="nv">$t</span><span class="w"> 587 </span><span class="gp">$ </span>cat<span class="w"> </span><span class="nv">$t</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sha256sum<span class="w"> 588 </span><span class="go">32ea47ecc5a3ee2576aab00c2f30eaabc2592d56e19f5c82fe4f7cf5874632b2 -</span> 589 </pre> 590 <p>In other words, the mutable reference is constructed using the following recipe.</p> 591 <ol class="arabic simple"> 592 <li>Calculate binary value <code>R</code> of the SHA256 digest of the keyword (<code>sha256(foo) -&gt; 0x2C26B46B68FFC68FF99B453C1D30413413422D706483BFA0F98A5E886266E7AE</code>)</li> 593 <li>Calculate binary value <code>K</code> of the key fingerprint (<code>0xF3FAF668E82EF5124D5187BAEF26F4682343F692</code>).</li> 594 <li>Calculate binary value of SHA256 digest of <code>R | K</code> (<code>sha256(0x2C26B46B68FFC68FF99B453C1D30413413422D706483BFA0F98A5E886266E7AEF3FAF668E82EF5124D5187BAEF26F4682343F692) -&gt; 0x32EA47ECC5A3EE2576AAB00C2F30EAABC2592D56E19F5C82FE4F7CF5874632B2</code>)</li> 595 </ol> 596 <p>That means that anyone who knows the keyword and the public key of the uploader can calculate the mutable reference themselves, and retrieve the data behind it.</p> 597 <!-- --> 598 <blockquote> 599 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 600 <colgroup><col class="label" /><col /></colgroup> 601 <tbody valign="top"> 602 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Refer to <a class="reference external" href="https://www.ethswarm.org/The-Book-of-Swarm.pdf">section 4.3 of The Book of Swarm</a> for a description of &quot;feeds&quot; and &quot;single owner chunks.&quot; Swarm uses signatures to allow propagation of the data in the network. <code>wala</code> similarly uses signatures to accept update of resources &quot;owned&quot; by the holder of the private keys of an identity.</td></tr> 603 </tbody> 604 </table> 605 </blockquote> 606 <!-- --> 607 <blockquote> 608 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 609 <colgroup><col class="label" /><col /></colgroup> 610 <tbody valign="top"> 611 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>At the time of writing <code>wala</code> is at version <code>0.1.1</code></td></tr> 612 </tbody> 613 </table> 614 </blockquote> 615 <!-- --> 616 <blockquote> 617 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 618 <colgroup><col class="label" /><col /></colgroup> 619 <tbody valign="top"> 620 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>A custom authorization scheme <code>PUBSIG</code> has been invented for this purpose. The format is <code>Authorization: PUBSIG pgp:key-fingerprint-in-base64&gt;:&lt;signature-in-base64&gt;</code>. Currently this is strictly just authentication, as there is no feature in wala (yet) to use access control lists to determine which public keys to allow PUTs from.</td></tr> 621 </tbody> 622 </table> 623 </blockquote> 624 <!-- --> 625 <blockquote> 626 <table class="docutils footnote" frame="void" id="footnote-4" rules="none"> 627 <colgroup><col class="label" /><col /></colgroup> 628 <tbody valign="top"> 629 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-4">[4]</a></td><td>If you do not specify a keyword, the keyword value in the mutable resource reference will be the sha256 hash of an empty value (<code>0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855</code>)</td></tr> 630 </tbody> 631 </table> 632 </blockquote> 633 <!-- --> 634 <blockquote> 635 <table class="docutils footnote" frame="void" id="footnote-5" rules="none"> 636 <colgroup><col class="label" /><col /></colgroup> 637 <tbody valign="top"> 638 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-5">[5]</a></td><td><code>wala</code> comes with a binary tool <code>wala_send</code> that lets you specify a key fingerprint in your default pgp keyring and a keyword as arguments when you upload data. A lot less messy, but a lot less educational, too.</td></tr> 639 </tbody> 640 </table> 641 </blockquote> 642 </div> 643 </content><category term="Code"></category><category term="wala"></category><category term="pgp"></category><category term="crypto"></category><category term="storage"></category><category term="http"></category><category term="rust"></category></entry><entry><title>A portable book metadata exercise</title><link href="portable-book-metadata.html" rel="alternate"></link><published>2022-10-01T12:40:00+02:00</published><updated>2022-10-01T12:40:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2022-10-01:portable-book-metadata.html</id><summary type="html"><p class="first last">Structured approach to generate portable metadata files for bibliographies and literature files using cryptographic hash mapping.</p> 644 </summary><content type="html"><p>One of the things I have been working on the last few weeks is a rust application I have dubbed <a class="reference external" href="https://git.defalsify.net/kitab">kitab</a> <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>.</p> 645 <p>In short, the application makes it easy to extract literary metadata to a separate file structure.</p> 646 <p>The metadata can in turn be applied as <em>extended attributes</em> recursively on a directory for files that match.</p> 647 <p>The way it's accomplished it simple: The file name of the metadata is the hex representation of the digest of the file. The same digest is used to match files to metadata when applying it back to the file.</p> 648 <p>There are two advantages to this:</p> 649 <ol class="arabic simple"> 650 <li>The digest of the media file need not be affected by the metadata, i.e. by embedding metadata in the file itself.</li> 651 <li>You do not need to use the file name to keep record of what a file is.</li> 652 </ol> 653 <div class="section" id="yarr-ye-matey-data"> 654 <h2>Yarr, ye matey-data</h2> 655 <p>Let's demonstrate with an example.</p> 656 <p>The fabulous <a class="reference external" href="https://libgen.rs">Library Genesis</a> project has made available an endpoint to retrieve <tt class="docutils literal">bibtex</tt> entries based on the <tt class="docutils literal">md5</tt> hash of the book media file.</p> 657 <p>A version of the <a class="reference external" href="https://libgen.rs/book/index.php?md5=BCD99F1AB4155F2A2A362E5B7938A852">Bitcoin White Paper</a>, under the <code>md5</code> hash <code>bcd99f1ab4155f2a2a362e5b7938a852</code>, can be found there.</p> 658 <p>If you download this file using a synchronous download link, the browser will provide you with a filename to go with the download.</p> 659 <p>However, if you use the torrent alternative, the filename will be the <tt class="docutils literal">md5</tt> hash itself. If you are torrenting a bunch of those files, it quickly becomes a nuisance to distinguish them.</p> 660 <p>And, of course: In either case there is no guarantee the any metadata comes with the file.</p> 661 <div class="section" id="inside-the-book"> 662 <h3>Inside the book</h3> 663 <p>Kitab (v0.0.2) is able to read metadata from both a bibtex source and xattr entries on a file, as well as its native <a class="reference external" href="https://www.w3.org/TR/turtle/">rdf-turtle</a> format.</p> 664 <p>In kitab's data store, every media file entity in rdf-turtle is keyed with a <a class="reference external" href="https://www.rfc-editor.org/info/rfc8141">URN</a> specifying a digest for the file.</p> 665 <p>To see exactly what that looks like, let's download and import the bibtex metadata for the paper <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a>:</p> 666 <pre class="code bash literal-block"> 667 <span class="nv">bibtex_file</span><span class="o">=</span><span class="sb">`</span>mktemp<span class="sb">`</span><span class="w"> 668 </span><span class="nv">kitab_dir</span><span class="o">=</span><span class="sb">`</span>mktemp<span class="w"> </span>-d<span class="sb">`</span><span class="w"> 669 </span>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>https://libgen.rs/book/bibtex.php?md5<span class="o">=</span>BCD99F1AB4155F2A2A362E5B7938A852<span class="w"> </span>-o<span class="w"> </span><span class="nv">$bibtex_file</span><span class="w"> 670 </span>kitab<span class="w"> </span>--store<span class="w"> </span><span class="nv">$kitab_dir</span><span class="w"> </span>import<span class="w"> </span>--digest<span class="w"> </span>md5:BCD99F1AB4155F2A2A362E5B7938A852<span class="w"> </span><span class="nv">$bibtex_file</span><span class="w"> 671 </span>cat<span class="w"> </span><span class="nv">$kitab_dir</span>/* 672 </pre> 673 <p>The output of the above should be:</p> 674 <pre class="code turtle literal-block"> 675 <span class="nv">&lt;URN:md5:bcd99f1ab4155f2a2a362e5b7938a852&gt;</span> <span class="nv">&lt;https://purl.org/dc/terms/title&gt;</span> <span class="s">&quot;Bitcoin: A Peer-to-Peer Electronic Cash System&quot;</span> <span class="p">;</span> 676 <span class="nv">&lt;https://purl.org/dc/terms/creator&gt;</span> <span class="s">&quot;Satoshi Nakamoto&quot;</span> <span class="p">;</span> 677 <span class="nv">&lt;https://purl.org/dc/terms/type&gt;</span> <span class="s">&quot;book&quot;</span> <span class="p">.</span> 678 </pre> 679 <p>Now let's say the media file itself has been downloaded to <tt class="docutils literal"><span class="pre">~/.local/share/transmission</span></tt>. We can apply this metadata as extended attributes.</p> 680 <p>This time we turn on logging to see what's going on:</p> 681 <pre class="code console literal-block"> 682 <span class="gp">$ </span><span class="nv">RUST_LOG</span><span class="o">=</span>info<span class="w"> </span>kitab<span class="w"> </span>--store<span class="w"> </span><span class="nv">$kitab_dir</span><span class="w"> </span>apply<span class="w"> </span>--digest<span class="w"> </span>md5<span class="w"> </span>~/.local/share/transmission<span class="w"> 683 </span><span class="go">[2022-10-01T11:14:59Z INFO kitab] have index directory &quot;/tmp/tmp.r0jBm6q4hW&quot; 684 [2022-10-01T11:14:59Z INFO kitab] using digest type md5 685 [2022-10-01T11:14:59Z INFO kitab] apply from path &quot;/home/lash/.local/share/transmission/&quot; 686 [2022-10-01T11:14:59Z INFO kitab] apply DirEntry(&quot;/home/lash/.local/share/transmission/bcd99f1ab4155f2a2a362e5b7938a852&quot;) -&gt; title &quot;Bitcoin: A Peer-to-Peer Electronic Cash System&quot; author &quot;Satoshi Nakamoto&quot; digest md5:bcd99f1ab4155f2a2a362e5b7938a852 687 688 </span><span class="gp">$ </span>find<span class="w"> </span>~/.local/share/transmission<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span>-regextype<span class="w"> </span>sed<span class="w"> </span>-regex<span class="w"> </span><span class="s2">&quot;.*/[a-f0-9]\{32\}</span>$<span class="s2">&quot;</span><span class="w"> </span>-exec<span class="w"> </span>getfattr<span class="w"> </span>-d<span class="w"> </span><span class="o">{}</span><span class="w"> </span><span class="se">\;</span><span class="w"> 689 </span><span class="gp"># </span>file:<span class="w"> </span>.local/share/transmission/bcd99f1ab4155f2a2a362e5b7938a852<span class="w"> 690 </span><span class="go">user.dcterms:creator=&quot;Satoshi Nakamoto&quot; 691 user.dcterms:title=&quot;Bitcoin: A Peer-to-Peer Electronic Cash System&quot; 692 user.dcterms:type=&quot;book&quot;</span> 693 </pre> 694 </div> 695 <div class="section" id="let-the-right-one-in"> 696 <h3>Let the right one in</h3> 697 <p>Conversely, the metadata can be re-imported directly from the extended attributes. And this time, let's store it both under the <tt class="docutils literal">md5</tt> and the <tt class="docutils literal">sha512</tt> hash:</p> 698 <pre class="code bash literal-block"> 699 $<span class="w"> </span><span class="nv">kitab_dir_new</span><span class="o">=</span><span class="sb">`</span>mktemp<span class="w"> </span>-d<span class="sb">`</span><span class="w"> 700 </span>$<span class="w"> </span>kitab<span class="w"> </span>--store<span class="w"> </span><span class="nv">$kitab_dir_new</span><span class="w"> </span>import<span class="w"> </span>--digest<span class="w"> </span>md5<span class="w"> </span>--digest<span class="w"> </span>sha512<span class="w"> </span>.local/share/transmission/bcd99f1ab4155f2a2a362e5b7938a852<span class="w"> 701 </span>$<span class="w"> </span>find<span class="w"> </span><span class="nv">$kitab_dir_new</span><span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span>-exec<span class="w"> </span>cat<span class="w"> </span><span class="o">{}</span><span class="w"> </span><span class="se">\;</span><span class="w"> 702 </span>/tmp/tmp.B6j41YMmEM/493f2a720d63156d77187bcd5f0715e4e765a38d616ef47f24e0df817ee6b4f601d47a06ffae10ef1f6ba60bb5d2e99a26318f035f9cd56e30bfe7bcdf64a792<span class="w"> 703 </span>&lt;URN:sha512:493f2a720d63156d77187bcd5f0715e4e765a38d616ef47f24e0df817ee6b4f601d47a06ffae10ef1f6ba60bb5d2e99a26318f035f9cd56e30bfe7bcdf64a792&gt;<span class="w"> </span>&lt;https://purl.org/dc/terms/title&gt;<span class="w"> </span><span class="s2">&quot;Bitcoin: A Peer-to-Peer Electronic Cash System&quot;</span><span class="w"> </span><span class="p">;</span><span class="w"> 704 </span>&lt;https://purl.org/dc/terms/creator&gt;<span class="w"> </span><span class="s2">&quot;Satoshi Nakamoto&quot;</span><span class="w"> </span><span class="p">;</span><span class="w"> 705 </span>&lt;https://purl.org/dc/terms/type&gt;<span class="w"> </span><span class="s2">&quot;book&quot;</span><span class="w"> </span><span class="p">;</span><span class="w"> 706 </span>&lt;https://purl.org/dc/terms/MediaType&gt;<span class="w"> </span><span class="s2">&quot;application/epub+zip&quot;</span><span class="w"> </span>.<span class="w"> 707 </span>/tmp/tmp.B6j41YMmEM/bcd99f1ab4155f2a2a362e5b7938a852<span class="w"> 708 </span>&lt;URN:md5:bcd99f1ab4155f2a2a362e5b7938a852&gt;<span class="w"> </span>&lt;https://purl.org/dc/terms/title&gt;<span class="w"> </span><span class="s2">&quot;Bitcoin: A Peer-to-Peer Electronic Cash System&quot;</span><span class="w"> </span><span class="p">;</span><span class="w"> 709 </span>&lt;https://purl.org/dc/terms/creator&gt;<span class="w"> </span><span class="s2">&quot;Satoshi Nakamoto&quot;</span><span class="w"> </span><span class="p">;</span><span class="w"> 710 </span>&lt;https://purl.org/dc/terms/type&gt;<span class="w"> </span><span class="s2">&quot;book&quot;</span><span class="w"> </span><span class="p">;</span><span class="w"> 711 </span>&lt;https://purl.org/dc/terms/MediaType&gt;<span class="w"> </span><span class="s2">&quot;application/epub+zip&quot;</span><span class="w"> </span>. 712 </pre> 713 </div> 714 </div> 715 <div class="section" id="level-up"> 716 <h2>Level up</h2> 717 <p>Finally, a bash script <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a> example that lets you retrieve and apply metadata for a batch of files found in the directory given as the <em>first positional arg</em>.</p> 718 <p>This script even renames the files according to the metadata applied.</p> 719 <pre class="code bash literal-block"> 720 <span class="ln"> 0 </span><span class="c1"># NOTE! this will only work if your fs supports xattr. 721 </span><span class="ln"> 1 </span><span class="c1"># That's why we cannot use tmpfs (mktemp) here; tmpfs does not support xattr. 722 </span><span class="ln"> 2 </span><span class="c1"></span><span class="w"> 723 </span><span class="ln"> 3 </span><span class="w"></span><span class="c1"># directory to copy media files to 724 </span><span class="ln"> 4 </span><span class="c1"></span><span class="nv">outdir</span><span class="o">=</span>./<span class="k">$(</span>uuidgen<span class="k">)</span><span class="w"> 725 </span><span class="ln"> 5 </span><span class="w"></span>mkdir<span class="w"> </span>-vp<span class="w"> </span><span class="nv">$outdir</span><span class="w"> 726 </span><span class="ln"> 6 </span><span class="w"> 727 </span><span class="ln"> 7 </span><span class="w"></span><span class="c1"># Input dir is the first positional arg. 728 </span><span class="ln"> 8 </span><span class="c1"></span><span class="nv">indir</span><span class="o">=</span><span class="nv">$1</span><span class="w"> 729 </span><span class="ln"> 9 </span><span class="w"> 730 </span><span class="ln">10 </span><span class="w"></span><span class="nv">IFS</span><span class="o">=</span><span class="s1">$'\n'</span><span class="w"> 731 </span><span class="ln">11 </span><span class="w"> 732 </span><span class="ln">12 </span><span class="w"></span><span class="c1"># Retrieve metadata for each file and import it into the kitab store. 733 </span><span class="ln">13 </span><span class="c1"># Also copy the media file to the separate output directory. 734 </span><span class="ln">14 </span><span class="c1"></span><span class="k">for</span><span class="w"> </span>f<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">$(</span>find<span class="w"> </span><span class="nv">$indir</span><span class="w"> </span>-type<span class="w"> </span>f<span class="k">)</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 735 </span><span class="ln">15 </span><span class="w"> </span><span class="nv">sum</span><span class="o">=</span><span class="k">$(</span>md5sum<span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">'{print $1;}'</span><span class="k">)</span><span class="w"> 736 </span><span class="ln">16 </span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;downloading metadata for </span><span class="nv">$indir</span><span class="s2">/</span><span class="nv">$f</span><span class="s2">&quot;</span><span class="w"> 737 </span><span class="ln">17 </span><span class="w"> </span><span class="nv">srct</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span><span class="w"> 738 </span><span class="ln">18 </span><span class="w"> </span>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>https://libgen.rs/book/bibtex.php?md5<span class="o">=</span><span class="nv">$sum</span><span class="w"> </span>-o<span class="w"> </span><span class="nv">$srct</span><span class="w"> 739 </span><span class="ln">19 </span><span class="w"> </span><span class="nv">dstt</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span><span class="w"> 740 </span><span class="ln">20 </span><span class="w"> </span>xmllint<span class="w"> </span>--html<span class="w"> </span>--xpath<span class="w"> </span><span class="s1">'string(/html/body/textarea[&#64;id=&quot;bibtext&quot;])'</span><span class="w"> </span><span class="nv">$srct</span><span class="w"> </span>&gt;<span class="w"> </span><span class="nv">$dstt</span><span class="w"> 741 </span><span class="ln">21 </span><span class="w"> </span>kitab<span class="w"> </span>import<span class="w"> </span>--digest<span class="w"> </span>md5:<span class="nv">$sum</span><span class="w"> </span><span class="nv">$dstt</span><span class="w"> 742 </span><span class="ln">22 </span><span class="w"> </span>cp<span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="nv">$outdir</span>/<span class="w"> 743 </span><span class="ln">23 </span><span class="w"></span><span class="k">done</span><span class="w"> 744 </span><span class="ln">24 </span><span class="w"> 745 </span><span class="ln">25 </span><span class="w"></span><span class="c1"># Apply metadata imported from bibtex as xattr for the media files. 746 </span><span class="ln">26 </span><span class="c1"></span><span class="nv">RUST_LOG</span><span class="o">=</span>info<span class="w"> </span>kitab<span class="w"> </span>apply<span class="w"> </span>--digest<span class="w"> </span>md5<span class="w"> </span><span class="nv">$outdir</span>/<span class="w"> 747 </span><span class="ln">27 </span><span class="w"> 748 </span><span class="ln">28 </span><span class="w"></span><span class="c1"># Rename the files according to the metadata title and media type. 749 </span><span class="ln">29 </span><span class="c1"></span><span class="k">for</span><span class="w"> </span>f<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">$(</span>ls<span class="w"> </span><span class="nv">$outdir</span><span class="k">)</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 750 </span><span class="ln">30 </span><span class="w"> </span><span class="nv">title</span><span class="o">=</span><span class="k">$(</span>getfattr<span class="w"> </span>--only-values<span class="w"> </span>-n<span class="w"> </span>user.dcterms:title<span class="w"> </span><span class="nv">$outdir</span>/<span class="nv">$f</span><span class="k">)</span><span class="w"> 751 </span><span class="ln">31 </span><span class="w"> 752 </span><span class="ln">32 </span><span class="w"> </span><span class="nv">f_typ</span><span class="o">=</span><span class="k">$(</span>file<span class="w"> </span>-b<span class="w"> </span>--mime-type<span class="w"> </span><span class="nv">$outdir</span>/<span class="nv">$f</span><span class="k">)</span><span class="w"> 753 </span><span class="ln">33 </span><span class="w"> </span><span class="nv">f_ext</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="w"> 754 </span><span class="ln">34 </span><span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$f_typ</span><span class="s2">&quot;</span><span class="w"> </span><span class="k">in</span><span class="w"> 755 </span><span class="ln">35 </span><span class="w"> </span><span class="s2">&quot;application/pdf&quot;</span><span class="o">)</span><span class="w"> 756 </span><span class="ln">36 </span><span class="w"> </span><span class="nv">f_ext</span><span class="o">=</span><span class="s2">&quot;.pdf&quot;</span><span class="w"> 757 </span><span class="ln">37 </span><span class="w"> </span><span class="p">;;</span><span class="w"> 758 </span><span class="ln">38 </span><span class="w"> </span><span class="s2">&quot;application/epub+zip&quot;</span><span class="o">)</span><span class="w"> 759 </span><span class="ln">39 </span><span class="w"> </span><span class="nv">f_ext</span><span class="o">=</span><span class="s2">&quot;.epub&quot;</span><span class="w"> 760 </span><span class="ln">40 </span><span class="w"> </span><span class="p">;;</span><span class="w"> 761 </span><span class="ln">41 </span><span class="w"> </span><span class="s2">&quot;application/x-mobipocket-ebook&quot;</span><span class="o">)</span><span class="w"> 762 </span><span class="ln">42 </span><span class="w"> </span><span class="nv">f_ext</span><span class="o">=</span><span class="s2">&quot;.mobi&quot;</span><span class="w"> 763 </span><span class="ln">43 </span><span class="w"> </span><span class="p">;;</span><span class="w"> 764 </span><span class="ln">44 </span><span class="w"> </span><span class="s2">&quot;text/plain&quot;</span><span class="o">)</span><span class="w"> 765 </span><span class="ln">45 </span><span class="w"> </span><span class="nv">f_ext</span><span class="o">=</span><span class="s2">&quot;.txt&quot;</span><span class="w"> 766 </span><span class="ln">46 </span><span class="w"> </span><span class="p">;;</span><span class="w"> 767 </span><span class="ln">47 </span><span class="w"> </span><span class="s2">&quot;text/html&quot;</span><span class="o">)</span><span class="w"> 768 </span><span class="ln">48 </span><span class="w"> </span><span class="nv">f_ext</span><span class="o">=</span><span class="s2">&quot;.html&quot;</span><span class="w"> 769 </span><span class="ln">49 </span><span class="w"> </span><span class="p">;;</span><span class="w"> 770 </span><span class="ln">50 </span><span class="w"> </span>*<span class="o">)</span><span class="w"> 771 </span><span class="ln">51 </span><span class="w"> </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span>unhandled<span class="w"> </span>mime<span class="w"> </span><span class="nb">type</span><span class="w"> </span><span class="nv">$f_typ</span><span class="w"> 772 </span><span class="ln">52 </span><span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="w"> 773 </span><span class="ln">53 </span><span class="w"> </span><span class="k">esac</span><span class="w"> 774 </span><span class="ln">54 </span><span class="w"> </span>mv<span class="w"> </span>-v<span class="w"> </span><span class="nv">$outdir</span>/<span class="nv">$f</span><span class="w"> </span><span class="nv">$outdir</span>/<span class="si">${</span><span class="nv">title</span><span class="si">}${</span><span class="nv">f_ext</span><span class="si">}</span><span class="w"> 775 </span><span class="ln">55 </span><span class="w"></span><span class="k">done</span> 776 </pre> 777 <p>This last example will result in:</p> 778 <ul class="simple"> 779 <li>A media file named <tt class="docutils literal">$outdir/Bitcoin: A <span class="pre">Peer-to-Peer</span> Electronic Cash System.epub</tt></li> 780 <li>... with metadata applied as extended attributes</li> 781 <li>An rdf-turtle metadata entry in <tt class="docutils literal"><span class="pre">~/.local/share/kitab/idx/bcd99f1ab4155f2a2a362e5b7938a852</span></tt></li> 782 </ul> 783 <!-- --> 784 <blockquote> 785 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 786 <colgroup><col class="label" /><col /></colgroup> 787 <tbody valign="top"> 788 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>The relevant documentation for <tt class="docutils literal">kitab</tt> at the time of writing is <a class="reference external" href="https://defalsify.org/doc/crates/kitab/0.0.2/kitab/">here</a>. To build kitab, simply <em>clone</em> the repository and build with <code>cargo build --all-features</code>.</td></tr> 789 </tbody> 790 </table> 791 </blockquote> 792 <!-- --> 793 <blockquote> 794 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 795 <colgroup><col class="label" /><col /></colgroup> 796 <tbody valign="top"> 797 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>The <code>kitab</code> command in the script assumes you have built the <em>kitab binary</em> and made it available in your path.</td></tr> 798 </tbody> 799 </table> 800 </blockquote> 801 <!-- --> 802 <blockquote> 803 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 804 <colgroup><col class="label" /><col /></colgroup> 805 <tbody valign="top"> 806 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>the script uses <code>xmllint</code> which on archlinux is provided by the <tt class="docutils literal">libxml2</tt> package.</td></tr> 807 </tbody> 808 </table> 809 </blockquote> 810 </div> 811 </content><category term="Archiving"></category><category term="hash"></category><category term="kitab"></category><category term="literature"></category><category term="metadata"></category><category term="dublincore"></category><category term="libgen"></category></entry><entry><title>Combining duplicity and rsync</title><link href="backup-rsync-duplicity.html" rel="alternate"></link><published>2022-01-15T16:57:00+01:00</published><updated>2022-01-15T16:57:00+01:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2022-01-15:backup-rsync-duplicity.html</id><summary type="html"><p class="first last">An exercise in combining plain and encrypted backups on local and remote hosts</p> 812 </summary><content type="html"><p>There are two awesome, weathered tools out there that are all you really need for your personal backups. <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a> One is the <a class="reference external" href="https://rsync.samba.org/">rsync cli</a>, the other is <a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">duplicity</a>.</p> 813 <p>The former should need no introduction.</p> 814 <p>The latter operates more like tar. But it still works over ssh like rsync. In fact, it's based on <a class="reference external" href="http://librsync.sourcefrog.net/">librsync</a> which implements the <a class="reference external" href="https://rsync.samba.org/tech_report/">rsync protocol</a>. The special sauce however is, of course, <em>encryption</em>.</p> 815 <div class="section" id="backup-categories"> 816 <h2>Backup categories</h2> 817 <p>Let's for the sake of argument say that our personal backups can be divided in three categories:</p> 818 <div class="section" id="stuff-that-can-be-public"> 819 <h3>Stuff that can be public</h3> 820 <p>Code snippets, git repositories, public data store states (e.g. blockchain ledgers), copies of OS packages and any other assets assets without redistribution issues.</p> 821 <p>For this we will use <a class="reference external" href="https://rsync.samba.org/">rsync</a>.</p> 822 </div> 823 <div class="section" id="sensitive-stuff"> 824 <h3>Sensitive stuff</h3> 825 <p>Passwords, keys, contacts, calendars, contracts, invoices, task lists, databases, system configurations, application data.</p> 826 <p>For this we will use <a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">duplicity</a>.</p> 827 </div> 828 <div class="section" id="secret-stuff"> 829 <h3>Secret stuff</h3> 830 <p>Long-lived keys, password- and volume decryption keys, cryptocurrency keys and meta-information about the backups themselves.</p> 831 <p>This will not be addressed now.</p> 832 </div> 833 </div> 834 <div class="section" id="why-not-just-one-or-the-other"> 835 <h2>Why not just one or the other?</h2> 836 <p><a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">Duplicity</a> stores everything in an archive file format. That means that you must first authenticate, decrypt and unpack the archive in order to even browse the files inside.</p> 837 <p>If there is no reason to keep the files from prying eyes, then it's much more practical to be able to browse the files where they lie, with the regular filesystem tools. In such a case, <a class="reference external" href="https://rsync.samba.org/">rsync</a> will scratch your itch.</p> 838 <p>For the <strong>sensitive</strong> and <strong>secret stuff</strong>, there would be no real need to use <a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">duplicity</a> if you were only operating on your local host. You'd just use an encrypted volume <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a> and <a class="reference external" href="https://rsync.samba.org/">rsync</a> everything in there.</p> 839 <p>But half the point here is to keep remote copies aswell as your local ones. You know, in case of fire, hardware-eating locust swarms or some totalitarian minions nabbing all your electronics. Unless &quot;remote&quot; here means some box hidden in some moated leisure castle of yours, you'll want to encrypt everything <em>before</em> you ship it off. And that's where <a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">duplicity</a> comes in.</p> 840 </div> 841 <div class="section" id="vive-la-difference"> 842 <h2>Vive la difference</h2> 843 <p>Of course, it would be too much to hope for that <a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">duplicity</a> and <a class="reference external" href="https://rsync.samba.org/">rsync cli</a> have aligned the ways they parse their invocation parameters.</p> 844 <p>Here are some examples <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a> of how they do <em>not</em> match:</p> 845 <div class="section" id="local-to-local"> 846 <h3>local to local</h3> 847 <div class="highlight"><pre><span></span>$<span class="w"> </span>rsync<span class="w"> </span>-a<span class="w"> </span>src/<span class="w"> </span>/path/to/dst/ 848 849 $<span class="w"> </span>duplicity<span class="w"> </span>src/<span class="w"> </span>file:///path/to/dst/ 850 </pre></div> 851 </div> 852 <div class="section" id="local-to-remote-relative-path"> 853 <h3>local to remote, relative path</h3> 854 <div class="highlight"><pre><span></span>$<span class="w"> </span>rsync<span class="w"> </span>-a<span class="w"> </span>src/<span class="w"> </span>user@remotehost:path/to/dst/ 855 856 $<span class="w"> </span>duplicity<span class="w"> </span>src/<span class="w"> </span>scp://user@remotehost/path/to/dst 857 </pre></div> 858 </div> 859 <div class="section" id="toggle-dotfiles-from-current-path"> 860 <h3>toggle dotfiles from current path</h3> 861 <div class="highlight"><pre><span></span><span class="c1"># include only .foo/foo.txt given the current structure:</span> 862 $<span class="w"> </span>tree<span class="w"> </span>src/<span class="w"> </span>-a 863 src/ 864 ├──<span class="w"> </span>.bar 865 ├──<span class="w"> </span>baz 866 └──<span class="w"> </span>.foo 867 <span class="w"> </span>└──<span class="w"> </span>foo.txt 868 869 $<span class="w"> </span>rsync<span class="w"> </span>--exclude<span class="o">=</span><span class="s2">&quot;.b*&quot;</span><span class="w"> </span>--include<span class="o">=</span><span class="s2">&quot;.*/***&quot;</span><span class="w"> </span>--exclude<span class="o">=</span><span class="s2">&quot;*&quot;</span><span class="w"> </span>./<span class="w"> </span>../dst/ 870 871 $<span class="w"> </span>duplicity<span class="w"> </span>--exclude<span class="o">=</span><span class="s2">&quot;./.b*&quot;</span><span class="w"> </span>--include<span class="o">=</span><span class="s2">&quot;./.*/***&quot;</span><span class="w"> </span>--exclude<span class="o">=</span><span class="s2">&quot;*&quot;</span><span class="w"> </span>./<span class="w"> </span>file:///home/lash/tmp/dst/ 872 </pre></div> 873 </div> 874 <div class="section" id="logging"> 875 <h3>logging</h3> 876 <div class="highlight"><pre><span></span><span class="c1"># spill the beans</span> 877 $<span class="w"> </span>rsync<span class="w"> </span>-vv<span class="w"> </span>... 878 879 $<span class="w"> </span>duplicity<span class="w"> </span>-v<span class="w"> </span>debug 880 </pre></div> 881 </div> 882 </div> 883 <div class="section" id="batchin"> 884 <h2>Batchin'</h2> 885 <p>Since you will want to select up front which tool to use for which sensititivy category, you'll be writing the includes and excludes specifically for the tool anyway.</p> 886 <p>So the only real issue with the above is the way remote host is specified.</p> 887 <p>Let's say we choose to stick to the <a class="reference external" href="https://rsync.samba.org/">rsync cli</a> host format. That means we need to make the following translations:</p> 888 <table border="1" class="docutils"> 889 <colgroup> 890 <col width="50%" /> 891 <col width="50%" /> 892 </colgroup> 893 <thead valign="bottom"> 894 <tr><th class="head">rsync</th> 895 <th class="head">duplicity</th> 896 </tr> 897 </thead> 898 <tbody valign="top"> 899 <tr><td><tt class="docutils literal">foo/bar</tt></td> 900 <td><tt class="docutils literal"><span class="pre">file://foo/bar</span></tt></td> 901 </tr> 902 <tr><td><tt class="docutils literal">/foo/bar</tt></td> 903 <td><tt class="docutils literal"><span class="pre">file:///foo/bar</span></tt></td> 904 </tr> 905 <tr><td><tt class="docutils literal">user&#64;host:foo/bar</tt></td> 906 <td><tt class="docutils literal"><span class="pre">scp://user&#64;host/foo/bar</span></tt></td> 907 </tr> 908 <tr><td><tt class="docutils literal"><span class="pre">user&#64;host:/foo/bar</span></tt></td> 909 <td><tt class="docutils literal"><span class="pre">scp://user&#64;host//foo/bar</span></tt></td> 910 </tr> 911 </tbody> 912 </table> 913 <p>Expressed in <tt class="docutils literal">bash</tt> that could look like this:</p> 914 <pre class="code bash literal-block"> 915 to_duplicity_remote<span class="o">()</span><span class="w"> </span><span class="o">{</span><span class="w"> 916 917 </span><span class="c1"># remote host is defined in rsync format 918 </span><span class="w"> </span><span class="c1"># ... and we will only support scp 919 </span><span class="w"> </span><span class="c1"># $remote_base is the path we want to parse 920 </span><span class="w"> </span><span class="nv">remote_duplicity_base</span><span class="o">=</span><span class="w"> 921 </span><span class="nv">remote_base</span><span class="o">=</span><span class="nv">$1</span><span class="w"> 922 923 </span><span class="c1"># substring up until the first slash 924 </span><span class="w"> </span><span class="nv">s_firstslash</span><span class="o">=</span><span class="si">${</span><span class="nv">remote_base</span><span class="p">%%/*</span><span class="si">}</span><span class="w"> 925 926 </span><span class="c1"># substring up until the first colon 927 </span><span class="w"> </span><span class="nv">s_firstcolon</span><span class="o">=</span><span class="si">${</span><span class="nv">remote_base</span><span class="p">%%:*</span><span class="si">}</span><span class="w"> 928 929 </span><span class="c1"># string index of the first slash 930 </span><span class="w"> </span><span class="nv">i_firstslash</span><span class="o">=</span><span class="k">$((</span><span class="si">${#</span><span class="nv">s_firstslash</span><span class="si">}</span><span class="k">))</span><span class="w"> 931 932 </span><span class="c1"># string index of the first colon 933 </span><span class="w"> </span><span class="nv">i_firstcolon</span><span class="o">=</span><span class="k">$((</span><span class="si">${#</span><span class="nv">s_firstcolon</span><span class="si">}</span><span class="k">))</span><span class="w"> 934 935 </span><span class="c1"># if colon is before first slash that most likely means we have a remote host 936 </span><span class="w"> </span><span class="c1"># (Exception is if first directory of path happens to have &quot;:&quot; in it. Seriously, don't use &quot;:&quot; in filenames) 937 </span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$i_firstcolon</span><span class="s2">&quot;</span><span class="w"> </span>-gt<span class="w"> </span><span class="s2">&quot;0&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 938 939 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$i_firstcolon</span><span class="s2">&quot;</span><span class="w"> </span>-lt<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$i_firstslash</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 940 941 </span><span class="c1"># pexpect addition due to lack of implicit private key fetch, without pexpect works only with key wo pwd 942 </span><span class="w"> </span><span class="c1"># https://serverfault.com/questions/982591/duplicity-backup-fails-private-key-file-is-encrypted 943 </span><span class="w"> </span><span class="nv">remote_duplicity_base</span><span class="o">=</span><span class="s2">&quot;pexpect+scp://&quot;</span><span class="w"> 944 945 </span><span class="c1"># trim url so that colon after hostname is removed 946 </span><span class="w"> </span><span class="c1"># (no support here for setting an alternate port number) 947 </span><span class="w"> </span><span class="nv">remote_duplicity_base</span><span class="o">=</span><span class="si">${</span><span class="nv">remote_duplicity_base</span><span class="si">}${</span><span class="nv">remote_base</span><span class="p">:</span><span class="nv">0</span><span class="p">:</span><span class="nv">$i_firstcolon</span><span class="si">}</span><span class="w"> 948 </span><span class="nv">remote_duplicity_base</span><span class="o">=</span><span class="si">${</span><span class="nv">remote_duplicity_base</span><span class="si">}</span>/<span class="si">${</span><span class="nv">remote_base</span><span class="p">:((</span><span class="nv">$i_firstcolon</span><span class="p">+1))</span><span class="si">}</span><span class="w"> 949 950 </span><span class="c1"># indicate that we have a remote host 951 </span><span class="w"> </span><span class="nv">remote</span><span class="o">=</span><span class="m">1</span><span class="w"> 952 </span><span class="k">fi</span><span class="w"> 953 </span><span class="k">fi</span><span class="w"> 954 955 </span><span class="c1"># If it's not a remote host, treat it as a file 956 </span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="nv">$remote_duplicity_base</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 957 </span><span class="nv">remote_duplicity_base</span><span class="o">=</span><span class="s2">&quot;file://</span><span class="si">${</span><span class="nv">remote_base</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> 958 </span><span class="k">fi</span><span class="w"> 959 </span><span class="o">}</span><span class="w"> 960 961 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$BAK_TEST</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 962 </span><span class="nv">src</span><span class="o">=(</span>/foo/bar<span class="w"> </span>foo/bar<span class="w"> </span>localhost:foo/bar<span class="w"> </span>localhost:/foo/bar<span class="o">)</span><span class="w"> 963 </span><span class="nv">res</span><span class="o">=(</span>file:///foo/bar<span class="w"> </span>file://foo/bar<span class="w"> </span>pexpect+scp://localhost/foo/bar<span class="w"> </span>pexpect+scp://localhost//foo/bar<span class="o">)</span><span class="w"> 964 965 </span><span class="nv">i</span><span class="o">=</span><span class="m">0</span><span class="w"> 966 </span><span class="k">for</span><span class="w"> </span>case_src<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="si">${</span><span class="nv">src</span><span class="p">[&#64;]</span><span class="si">}</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 967 </span><span class="nv">case_res</span><span class="o">=</span><span class="si">${</span><span class="nv">res</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span><span class="si">}</span><span class="w"> 968 969 </span>to_duplicity_remote<span class="w"> </span><span class="nv">$case_src</span><span class="w"> 970 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$remote_duplicity_base</span><span class="s2">&quot;</span><span class="w"> </span>!<span class="o">=</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$case_res</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 971 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;expected </span><span class="nv">$case_res</span><span class="s2"> got </span><span class="nv">$remote_duplicity_base</span><span class="s2"> from </span><span class="nv">$case_src</span><span class="s2">&quot;</span><span class="w"> 972 </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="w"> 973 </span><span class="k">elif</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$remote_base</span><span class="s2">&quot;</span><span class="w"> </span>!<span class="o">=</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$case_src</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 974 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$case_src</span><span class="s2"> got mangled into </span><span class="nv">$remote_base</span><span class="s2">&quot;</span><span class="w"> 975 </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="w"> 976 </span><span class="k">fi</span><span class="w"> 977 </span><span class="nv">i</span><span class="o">=</span><span class="k">$((</span><span class="nv">i</span><span class="o">+</span><span class="m">1</span><span class="k">))</span><span class="w"> 978 </span><span class="k">done</span><span class="w"> 979 </span><span class="k">fi</span> 980 </pre> 981 <p>Let's behave and test our code:</p> 982 <pre class="code bash literal-block"> 983 <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$BAK_TEST</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 984 </span><span class="nv">src</span><span class="o">=(</span>/foo/bar<span class="w"> </span>foo/bar<span class="w"> </span>localhost:foo/bar<span class="w"> </span>localhost:/foo/bar<span class="o">)</span><span class="w"> 985 </span><span class="nv">res</span><span class="o">=(</span>file:///foo/bar<span class="w"> </span>file://foo/bar<span class="w"> </span>pexpect+scp://localhost/foo/bar<span class="w"> </span>pexpect+scp://localhost//foo/bar<span class="o">)</span><span class="w"> 986 987 </span><span class="nv">i</span><span class="o">=</span><span class="m">0</span><span class="w"> 988 </span><span class="k">for</span><span class="w"> </span>case_src<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="si">${</span><span class="nv">src</span><span class="p">[&#64;]</span><span class="si">}</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 989 </span><span class="nv">case_res</span><span class="o">=</span><span class="si">${</span><span class="nv">res</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span><span class="si">}</span><span class="w"> 990 991 </span>to_duplicity_remote<span class="w"> </span><span class="nv">$case_src</span><span class="w"> 992 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$remote_duplicity_base</span><span class="s2">&quot;</span><span class="w"> </span>!<span class="o">=</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$case_res</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 993 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;expected </span><span class="nv">$case_res</span><span class="s2"> got </span><span class="nv">$remote_duplicity_base</span><span class="s2"> from </span><span class="nv">$case_src</span><span class="s2">&quot;</span><span class="w"> 994 </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="w"> 995 </span><span class="k">elif</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$remote_base</span><span class="s2">&quot;</span><span class="w"> </span>!<span class="o">=</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$case_src</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 996 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$case_src</span><span class="s2"> got mangled into </span><span class="nv">$remote_base</span><span class="s2">&quot;</span><span class="w"> 997 </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="w"> 998 </span><span class="k">fi</span><span class="w"> 999 </span><span class="nv">i</span><span class="o">=</span><span class="k">$((</span><span class="nv">i</span><span class="o">+</span><span class="m">1</span><span class="k">))</span><span class="w"> 1000 </span><span class="k">done</span><span class="w"> 1001 </span><span class="k">fi</span> 1002 </pre> 1003 <div class="highlight"><pre><span></span><span class="c1"># 0 == good!</span> 1004 $<span class="w"> </span><span class="nv">BAK_TEST</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>bash<span class="w"> </span>remote.sh<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$?</span> 1005 <span class="m">0</span> 1006 </pre></div> 1007 <p>Now we can use the <a class="reference external" href="https://rsync.samba.org/">rsync cli</a> path input, and use that same input to a batch of single backup steps, each which may use <a class="reference external" href="https://rsync.samba.org/">rsync cli</a> or <a class="reference external" href="https://duplicity.gitlab.io/duplicity-web/">duplicity</a></p> 1008 <div class="highlight"><pre><span></span>to_duplicity_remote<span class="w"> </span>localhost:/foo/bar 1009 1010 rsync<span class="w"> </span>-avzP<span class="w"> </span>pub/<span class="w"> </span><span class="nv">$remote_base</span>:src/ 1011 1012 duplicity<span class="w"> </span>-v<span class="w"> </span>info<span class="w"> </span>secret/<span class="w"> </span><span class="nv">$remote_duplicity_base</span>:secret/ 1013 </pre></div> 1014 </div> 1015 <div class="section" id="see-also"> 1016 <h2>See also</h2> 1017 <ul class="simple"> 1018 <li><a class="reference external" href="https://git.defalsify.org/rsync-duplicity-backups">https://git.defalsify.org/rsync-duplicity-backups</a></li> 1019 </ul> 1020 <!-- --> 1021 <blockquote> 1022 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 1023 <colgroup><col class="label" /><col /></colgroup> 1024 <tbody valign="top"> 1025 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Ok, I know, I assuming that you are using <tt class="docutils literal">git</tt> in daily life, too.</td></tr> 1026 </tbody> 1027 </table> 1028 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 1029 <colgroup><col class="label" /><col /></colgroup> 1030 <tbody valign="top"> 1031 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>Provided, of course, that it's an encrypted volume that you don't keep unlocked all the time.</td></tr> 1032 </tbody> 1033 </table> 1034 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 1035 <colgroup><col class="label" /><col /></colgroup> 1036 <tbody valign="top"> 1037 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>Duplicity needs at a minimum a password for symmetric encryption, and will prompt for it unless it's set in the environment. Simply <tt class="docutils literal">export PASSPHRASE=test</tt> for these examples to relieve you of the annoyance.</td></tr> 1038 </tbody> 1039 </table> 1040 </blockquote> 1041 <!-- --> 1042 <blockquote> 1043 </blockquote> 1044 </div> 1045 </content><category term="Archiving"></category><category term="backup"></category><category term="rsync"></category><category term="duplicity"></category><category term="bash"></category></entry><entry><title>Homemade internet state monitor</title><link href="internet-up-monitor.html" rel="alternate"></link><published>2021-07-01T13:59:00+02:00</published><updated>2021-07-01T13:59:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-07-01:internet-up-monitor.html</id><summary type="html"><p class="first last">A small script to detect internet status</p> 1046 </summary><content type="html"><p>I use <a class="reference external" href="https://i3wm.org">i3wm</a>. I use it because it has a very low resource footprint, it has great documentation, and it's simple to configure.</p> 1047 <p>Complementing <em>i3wm</em> is the <em>i3status</em> status provider to the <em>i3bar</em>. It provides a good suite of preset components for things like temperatur measurement, disk capacity. It also provides a couple of generic ones, namely &quot;output the contents of a file&quot; or &quot;yes if file exists, no if not.&quot;</p> 1048 <div class="section" id="it-s-not-you-it-s-me"> 1049 <h2>It's not you, it's me?</h2> 1050 <div class="admonition warning"> 1051 <p class="first admonition-title">Warning</p> 1052 <p class="last"><strong>Always use a VPN! Always!!</strong></p> 1053 </div> 1054 <p>Now, I <em>always</em> use VPN <em>no matter what</em>, and I configure network manually. That is, I do not use a network manager. For this reason, when network applications start misbehaving, it's sometimes hard to tell whether it is because the host is gone, the local network is flaky, the internet route is flaky, or simply that the connection is gone for good.</p> 1055 <p>Evidently, it would be practical to have <strong>an item in the status bar telling me whether I have access to internet right now or not</strong>.</p> 1056 <p>The &quot;yes if file exists, no if not&quot; option seems to be the right one to go for. Basically, we need to stick an empty file in a runtime file store whenever we detect that internet is available.</p> 1057 </div> 1058 <div class="section" id="ping-rinse-repeat"> 1059 <h2>Ping, rinse, repeat</h2> 1060 <p>There's not much trickery involved here. The check is a ping to a remote location every now and then.</p> 1061 <p>Some caveats to cover to grant it some minimum of intelligence:</p> 1062 <ul class="simple"> 1063 <li>We need a few alternative destinations for the ping, that can take over if others happen to be down or take too long to repond.</li> 1064 <li>To make the script useful for general purpose use, we'd also want to be able to supply a custom list of hosts to ping.</li> 1065 <li>We also want to make sure that when the script is interrupted, it deletes the internet indicator file. That way, when it stops running, the internet indicator will be frozen in the &quot;no&quot; state.</li> 1066 <li>Lastly, it can be useful to log to syslog every time the state of the internet connection (or the script itself) changes.</li> 1067 </ul> 1068 <p>That can translate to a shell script like this;</p> 1069 <pre class="code bash literal-block"> 1070 <span class="ch">#!/bin/bash 1071 </span><span class="w"> 1072 </span><span class="c1"># this is our runtime file, if - which exists - tells us internet is up 1073 </span><span class="nv">f</span><span class="o">=</span><span class="s2">&quot;/run/user/</span><span class="nv">$UID</span><span class="s2">/probe_up&quot;</span><span class="w"> 1074 </span>rm<span class="w"> </span>-f<span class="w"> </span><span class="nv">$f</span><span class="w"> 1075 1076 </span>up<span class="o">()</span><span class="w"> </span><span class="o">{</span><span class="w"> 1077 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>!<span class="w"> </span>-f<span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 1078 </span>logger<span class="w"> </span>-p<span class="w"> </span>info<span class="w"> </span><span class="s2">&quot;iup up&quot;</span><span class="w"> 1079 </span><span class="k">fi</span><span class="w"> 1080 </span>touch<span class="w"> </span><span class="nv">$f</span><span class="w"> 1081 </span><span class="o">}</span><span class="w"> 1082 </span>down<span class="o">()</span><span class="w"> </span><span class="o">{</span><span class="w"> 1083 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-f<span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 1084 </span>logger<span class="w"> </span>-p<span class="w"> </span>info<span class="w"> </span><span class="s2">&quot;iup down&quot;</span><span class="w"> 1085 </span><span class="k">fi</span><span class="w"> 1086 </span>rm<span class="w"> </span>-f<span class="w"> </span><span class="nv">$f</span><span class="w"> 1087 </span><span class="o">}</span><span class="w"> 1088 1089 </span>argh<span class="o">()</span><span class="w"> </span><span class="o">{</span><span class="w"> 1090 </span>logger<span class="w"> </span>-p<span class="w"> </span>info<span class="w"> </span><span class="s2">&quot;iup die&quot;</span><span class="w"> 1091 </span>rm<span class="w"> </span>-f<span class="w"> </span><span class="nv">$f</span><span class="w"> 1092 </span><span class="nb">exit</span><span class="w"> </span><span class="m">0</span><span class="w"> 1093 </span><span class="o">}</span><span class="w"> 1094 1095 </span><span class="nv">IUP_DELAY</span><span class="o">=</span><span class="si">${</span><span class="nv">IUP_DELAY</span><span class="k">:-</span><span class="nv">$1</span><span class="si">}</span><span class="w"> 1096 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="nv">$IUP_DELAY</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 1097 </span><span class="nv">IUP_DELAY</span><span class="o">=</span><span class="m">5</span><span class="w"> 1098 </span><span class="k">fi</span><span class="w"> 1099 </span>logger<span class="w"> </span>-p<span class="w"> </span>debug<span class="w"> </span>iup<span class="w"> </span>started<span class="w"> </span>with<span class="w"> </span>delay<span class="w"> </span><span class="nv">$IUP_DELAY</span><span class="w"> 1100 1101 </span><span class="nv">hosts</span><span class="o">=()</span><span class="w"> 1102 </span><span class="k">while</span><span class="w"> </span><span class="nb">read</span><span class="w"> </span>h<span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 1103 </span><span class="nv">hosts</span><span class="o">+=(</span><span class="nv">$h</span><span class="o">)</span><span class="w"> 1104 </span><span class="k">done</span><span class="w"> </span>&lt;<span class="w"> </span><span class="nv">$HOME</span>/.config/iup<span class="w"> </span><span class="m">2</span>&gt;<span class="w"> </span>/dev/null<span class="w"> 1105 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">hosts</span><span class="p">[&#64;]</span><span class="si">}</span><span class="w"> </span>-eq<span class="w"> </span><span class="s2">&quot;0&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 1106 </span><span class="nv">hosts</span><span class="o">=(</span><span class="m">8</span>.8.8.8<span class="w"> </span><span class="m">151</span>.101.193.67<span class="o">)</span><span class="w"> </span><span class="c1"># google nameserver and cnn.com 1107 </span><span class="w"> </span>logger<span class="w"> </span>-p<span class="w"> </span>warn<span class="w"> </span>missing<span class="w"> </span>hosts<span class="w"> </span>input<span class="w"> </span>file,<span class="w"> </span>using<span class="w"> </span>evil<span class="w"> </span>default:<span class="w"> </span><span class="si">${</span><span class="nv">hosts</span><span class="p">[&#64;]</span><span class="si">}</span><span class="w"> 1108 </span><span class="k">fi</span><span class="w"> 1109 1110 </span><span class="nb">trap</span><span class="w"> </span>argh<span class="w"> </span>SIGINT<span class="w"> 1111 </span><span class="nb">trap</span><span class="w"> </span>argh<span class="w"> </span>SIGTERM<span class="w"> 1112 </span><span class="nb">trap</span><span class="w"> </span>argh<span class="w"> </span>SIGQUIT<span class="w"> 1113 1114 </span><span class="k">while</span><span class="w"> </span>true<span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 1115 </span><span class="nv">isup</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="w"> 1116 </span><span class="k">for</span><span class="w"> </span>h<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="si">${</span><span class="nv">hosts</span><span class="p">[&#64;]</span><span class="si">}</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 1117 </span>ping<span class="w"> </span>-c1<span class="w"> </span><span class="nv">$h</span><span class="w"> </span>-w1<span class="w"> </span>-q<span class="w"> </span><span class="p">&amp;</span>&gt;<span class="w"> </span>/dev/null<span class="w"> 1118 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="nv">$?</span><span class="w"> </span>-eq<span class="w"> </span><span class="s1">'0'</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 1119 </span>up<span class="w"> 1120 </span><span class="nv">isup</span><span class="o">=</span><span class="m">1</span><span class="w"> 1121 </span><span class="c1">#logger -p debug &quot;ping success $h&quot; 1122 </span><span class="w"> </span><span class="k">break</span><span class="w"> 1123 </span><span class="k">fi</span><span class="w"> 1124 </span><span class="k">done</span><span class="w"> 1125 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="nv">$isup</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 1126 </span>down<span class="w"> 1127 </span><span class="k">fi</span><span class="w"> 1128 </span>sleep<span class="w"> </span><span class="nv">$IUP_DELAY</span><span class="w"> 1129 </span><span class="k">done</span> 1130 </pre> 1131 <p>Since we'll want this to run when network interfaces allegedly are up, it makes sense to link it to systemd and the network target:</p> 1132 <pre class="code ini literal-block"> 1133 <span class="k">[Unit]</span><span class="w"> 1134 </span><span class="na">Description</span><span class="o">=</span><span class="s">Internet connection poller</span><span class="w"> 1135 </span><span class="na">After</span><span class="o">=</span><span class="s">multi-user.target</span><span class="w"> 1136 1137 </span><span class="k">[Service]</span><span class="w"> 1138 </span><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/local/bin/iup.sh</span><span class="w"> 1139 </span><span class="na">Restart</span><span class="o">=</span><span class="s">always</span><span class="w"> 1140 1141 </span><span class="k">[Install]</span><span class="w"> 1142 </span><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span> 1143 </pre> 1144 <p>Stick this in <code>~/.config/systemd/user/iup.service</code> and enable and start the service:</p> 1145 <div class="highlight"><pre><span></span>$<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>iup.service 1146 $<span class="w"> </span>systemctl<span class="w"> </span>--user<span class="w"> </span>start<span class="w"> </span>iup.service 1147 </pre></div> 1148 </div> 1149 <div class="section" id="checking-what-status-our-status-is-in"> 1150 <h2>Checking what status our status is in</h2> 1151 <p>Now, in the i3status configuration, assuming that your system uid is 1000 <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>, add:</p> 1152 <div class="highlight"><pre><span></span>order += &quot;run_watch INET&quot; 1153 1154 kj 1155 1156 run_watch INET { 1157 pidfile = &quot;/run/user/1000/probe_up&quot; 1158 } 1159 </pre></div> 1160 <p>To reload the config on the fly, press <code>mod+r</code> (that's <code>ctrl+r</code> on mine). The result should be a new item in your bar showing <code>INET: yes</code> (most likely yes, anyway, since you're currently reading this).</p> 1161 </div> 1162 <div class="section" id="safety-first-eh-second"> 1163 <h2>Safety first, eh ... second</h2> 1164 <div class="admonition warning"> 1165 <p class="first admonition-title">Warning</p> 1166 <p class="last"><strong>Did I mention that you should always use a VPN?</strong></p> 1167 </div> 1168 <p>The same <code>run_watch</code> trick can be used for VPN.</p> 1169 <p>I use <code>openvpn</code>, and it defines a flag <code>--writepid</code>. If you pass a file path to this parameter, either through the <code>writepid</code> config directiry or on the <code>--writepid</code> flag on the command line, it will write the openvpn pid to that file, and similarly remove it when the openvpn service ends.</p> 1170 <p>However, this raises another issue. Namely the fact that a location is needed for openvpn to write the file to, for which it also has access.</p> 1171 <p>If we want to use the <code>/run</code> directory again, now we also has to make sure that this directory exists.</p> 1172 <div class="section" id="got-the-runs"> 1173 <h3>Got the runs?</h3> 1174 <p>This is exactly what <code>systemd-tmpfiles</code> is for. You can add files to <code>/etc/tmpfiles.d</code> which describe what kind of temporary files or directories to add <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a>.</p> 1175 <p>Create a file <code>/etc/tmpfiles.d/openvpn.conf</code> and add a single line to it:</p> 1176 <div class="highlight"><pre><span></span>d /run/openvpn 0755 openvpn openvpn 1177 </pre></div> 1178 <p>This will ensure that the folder is created at startup.</p> 1179 <p>Now, to create one right away for the current session, run <code>systemd-tmpfiles --create</code>.</p> 1180 <p>Now we have everything we need to add the VPN status to <em>i3status</em> aswell. Provided that we chose <code>/run/openvpn/openvpn.pid</code> as our argument to <code>openvpn --writepid</code>, the setup is more or less the same as before:</p> 1181 <div class="highlight"><pre><span></span>order += &quot;run_watch VPN&quot; 1182 1183 kj 1184 1185 run_watch VPN { 1186 pidfile = &quot;/run/openvpn/openvpn.pid&quot; 1187 } 1188 </pre></div> 1189 <!-- --> 1190 <blockquote> 1191 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 1192 <colgroup><col class="label" /><col /></colgroup> 1193 <tbody valign="top"> 1194 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>The <code>/run/user/$UID</code> folder will be created on system startup, and will be writable by that user. It provides a tmpfs storage location for processes that run in early stages of boot, before the entire filesystem tree (which may include <code>/var/run</code> because <code>/var</code> may be on a separate partition) has been mounted. More in that here: <a class="reference external" href="https://lwn.net/Articles/436012/">https://lwn.net/Articles/436012/</a></td></tr> 1195 </tbody> 1196 </table> 1197 </blockquote> 1198 <!-- --> 1199 <blockquote> 1200 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 1201 <colgroup><col class="label" /><col /></colgroup> 1202 <tbody valign="top"> 1203 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>This service has an impressive amount of flexibility, which you can inspect yourself by visiting the <code>tmpfiles.d</code> (and <code>systemd-tmpfiles</code>) manpage.</td></tr> 1204 </tbody> 1205 </table> 1206 </blockquote> 1207 </div> 1208 </div> 1209 </content><category term="Hygiene"></category><category term="bash"></category><category term="systemd"></category><category term="network"></category><category term="i3wm"></category><category term="tmpfs"></category><category term="openvpn"></category></entry><entry><title>Making sense of Ethereum log bloom filters</title><link href="eth-log-bloom.html" rel="alternate"></link><published>2021-06-27T11:17:00+02:00</published><updated>2021-06-27T15:56:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-06-27:eth-log-bloom.html</id><summary type="html"><p class="first last">How to generate values to match the log bloom filters in Ethereum</p> 1210 </summary><content type="html"><p>Ethereum blocks and transaction receipts both carry a bloom filter. They provide a shortcut to check whether a particular event of a particular contract was triggered. The filter in the transaction indexes all events that occurred in the transaction. The filter in the block carries all the filter bits of the individual transactions within the block.</p> 1211 <p>Somewhat surprisingly, there are practically no friendly descriptions out there that tell you how to generate filters to match them with. In fact, the <a class="reference external" href="https://holbrook.no/doc/ethereum/yellowpaper.pdf">Ethereum Yellow Paper</a> was the only source I found.</p> 1212 <p><strong>So let's make a friendly description</strong></p> 1213 <p>Before we get to the friendliness, though, an ever so slight dose of brain melt is due:</p> 1214 <div class="section" id="the-formalities"> 1215 <h2>The formalities</h2> 1216 <p>The formal definition of the filter calculation in the <a class="reference external" href="https://holbrook.no/doc/ethereum/yellowpaper.pdf">Ethereum Yellow Paper section 4.3.1</a> is (here cited in painfully inadequate math formatting):</p> 1217 <blockquote> 1218 <div class="formula"> 1219 <i>M</i>(<i>O</i>) ≡ <span class="limits"><span class="limit"><span class="bigoperator">⋁</span></span></span><i>x</i> ∈ {<i>Oa</i>}∪<i>Ot</i>(<i>M</i><sub>3 : 2048</sub>(<i>x</i>)) 1220 </div> 1221 </blockquote> 1222 <p>Where <span class="formula"><i>Oa</i></span> is the contract address and <span class="formula"><i>Ot</i></span> is all of the topics.</p> 1223 <p>In the context of <em>Solidity</em>, &quot;all&quot; of the topics means the actual event signature, along with all the <em>indexed</em> parameters to the event.</p> 1224 <p>The <span class="formula"><i>M</i></span> is the filter bit generator. The filter 2048 bits long, and for each entry that is added, 3 bits need to be set.</p> 1225 <p>Reading further, we learn that the bits to set are the &quot;first three&quot; pairs of bytes of the keccak256 hash <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>, modulo the length of the filter - 2048.</p> 1226 <p>In the context of hashes, I usually assume &quot;first&quot; means from the left. That happens to be the case here, too. In other words, the &quot;first&quot; bytes are actually the bytes of most significance in big-endian order.</p> 1227 <p>What's less clear, however, is which bits to set in the filter. The paper says:</p> 1228 <blockquote> 1229 <span class="formula">ℬ</span> is the bit reference function such that <span class="formula">ℬ<sub><i>j</i></sub>(<i>x</i>)</span> equals the bit of index <span class="formula"><i>j</i></span> (indexed from 0) in the byte array <span class="formula"><i>x</i></span>.</blockquote> 1230 <p>Turns out after some trial and error that &quot;index 0&quot; means the <em>right</em> end of the byte array. So when setting the bits, we have to count from the right side.</p> 1231 </div> 1232 <div class="section" id="the-friendly-recipe"> 1233 <h2>The friendly recipe</h2> 1234 <p>Now let's try to bring this down to a mortal level:</p> 1235 <ol class="arabic simple"> 1236 <li>Take the keccak256 hash of the contract address</li> 1237 <li>Take the keccak256 hash of the first topic value</li> 1238 <li>Optionally, take the keccak256 hash of the remaining topic values (the indexed parameters of the event) <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a></li> 1239 <li>Instantiate a byte array with length 256</li> 1240 <li>Now, <em>for each</em> of the hashes:<ol class="arabic"> 1241 <li>Get the <em>big-endian</em> integer of the <em>first two</em> bytes of the hash</li> 1242 <li>Calculate the 2048-modulo of that integer <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a></li> 1243 <li><em>Bitwise or</em> the filter bit at the index corresponding to the modulated value, counting from <em>right to left</em> (i.e. the value <span class="formula">0</span> corresponds to the <em>rightmost bit</em>).</li> 1244 <li>Repeat 2 and 3 for the two next pairs of bytes of the hash.</li> 1245 </ol> 1246 </li> 1247 </ol> 1248 </div> 1249 <div class="section" id="the-code"> 1250 <h2>The code</h2> 1251 <p>All that taken into account, we can take a stab at implementing a filter generator:</p> 1252 <pre class="code python literal-block"> 1253 <span class="ln"> 0 </span><span class="c1"># external imports</span><span class="w"> 1254 </span><span class="ln"> 1 </span><span class="w"></span><span class="kn">import</span> <span class="nn">sha3</span><span class="w"> 1255 </span><span class="ln"> 2 </span><span class="w"> 1256 </span><span class="ln"> 3 </span><span class="w"> 1257 </span><span class="ln"> 4 </span><span class="w"></span><span class="k">class</span> <span class="nc">LogBloom</span><span class="p">:</span><span class="w"> 1258 </span><span class="ln"> 5 </span><span class="w"> 1259 </span><span class="ln"> 6 </span><span class="w"></span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w"> 1260 </span><span class="ln"> 7 </span><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">(</span><span class="mi">256</span><span class="p">)</span><span class="w"> 1261 </span><span class="ln"> 8 </span><span class="w"> 1262 </span><span class="ln"> 9 </span><span class="w"> 1263 </span><span class="ln">10 </span><span class="w"></span> <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">element</span><span class="p">):</span><span class="w"> 1264 </span><span class="ln">11 </span><span class="w"></span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">element</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">):</span><span class="w"> 1265 </span><span class="ln">12 </span><span class="w"></span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'element must be bytes'</span><span class="p">)</span><span class="w"> 1266 </span><span class="ln">13 </span><span class="w"></span> <span class="n">h</span> <span class="o">=</span> <span class="n">sha3</span><span class="o">.</span><span class="n">keccak_256</span><span class="p">()</span><span class="w"> 1267 </span><span class="ln">14 </span><span class="w"></span> <span class="n">h</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">element</span><span class="p">)</span><span class="w"> 1268 </span><span class="ln">15 </span><span class="w"></span> <span class="n">z</span> <span class="o">=</span> <span class="n">h</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span><span class="w"> 1269 </span><span class="ln">16 </span><span class="w"> 1270 </span><span class="ln">17 </span><span class="w"></span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span><span class="w"> 1271 </span><span class="ln">18 </span><span class="w"></span> <span class="n">c</span> <span class="o">=</span> <span class="n">j</span> <span class="o">*</span> <span class="mi">2</span><span class="w"> 1272 </span><span class="ln">19 </span><span class="w"></span> <span class="n">v</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">z</span><span class="p">[</span><span class="n">c</span><span class="p">:</span><span class="n">c</span><span class="o">+</span><span class="mi">2</span><span class="p">],</span> <span class="n">byteorder</span><span class="o">=</span><span class="s1">'big'</span><span class="p">)</span><span class="w"> 1273 </span><span class="ln">20 </span><span class="w"></span> <span class="n">v</span> <span class="o">&amp;=</span> <span class="mh">0x07ff</span><span class="w"> 1274 </span><span class="ln">21 </span><span class="w"></span> <span class="n">m</span> <span class="o">=</span> <span class="mi">255</span> <span class="o">-</span> <span class="nb">int</span><span class="p">(</span><span class="n">v</span> <span class="o">/</span> <span class="mi">8</span><span class="p">)</span><span class="w"> 1275 </span><span class="ln">22 </span><span class="w"></span> <span class="n">n</span> <span class="o">=</span> <span class="n">v</span> <span class="o">%</span> <span class="mi">8</span><span class="w"> 1276 </span><span class="ln">23 </span><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">content</span><span class="p">[</span><span class="n">m</span><span class="p">]</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">n</span><span class="p">)</span> 1277 </pre> 1278 <p>Let's say we'd want to check if an Solidity-defined event <code>Foo(uint256,bytes32)</code> emitted by a contract at address <code>0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee</code> exists in a filter.</p> 1279 <p>Using the class above, we would have to do something like this:</p> 1280 <pre class="code python literal-block"> 1281 <span class="c1"># external imports</span><span class="w"> 1282 </span><span class="kn">import</span> <span class="nn">sha3</span><span class="w"> 1283 </span><span class="kn">import</span> <span class="nn">some_block_getter</span><span class="w"> 1284 1285 </span><span class="c1"># local imports</span><span class="w"> 1286 </span><span class="kn">from</span> <span class="nn">the_above</span> <span class="kn">import</span> <span class="n">LogBloom</span><span class="w"> 1287 1288 </span><span class="c1"># all set bits in our filter must be set in theirs</span><span class="w"> 1289 </span><span class="k">def</span> <span class="nf">filter_match</span><span class="p">(</span><span class="n">theirs</span><span class="p">,</span> <span class="n">ours</span><span class="p">):</span><span class="w"> 1290 </span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">ours</span><span class="p">)):</span><span class="w"> 1291 </span> <span class="k">if</span> <span class="n">ours</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&amp;</span> <span class="n">theirs</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="n">ours</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span><span class="w"> 1292 </span> <span class="k">return</span> <span class="kc">False</span><span class="w"> 1293 </span> <span class="k">return</span> <span class="kc">True</span><span class="w"> 1294 1295 </span><span class="n">fltr</span> <span class="o">=</span> <span class="n">LogBloom</span><span class="p">()</span><span class="w"> 1296 1297 </span><span class="c1"># add the contract address to the filter</span><span class="w"> 1298 </span><span class="n">address</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s1">'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'</span><span class="p">)</span><span class="w"> 1299 </span><span class="n">h</span> <span class="o">=</span> <span class="n">sha3</span><span class="o">.</span><span class="n">keccak_256</span><span class="p">()</span><span class="w"> 1300 </span><span class="n">h</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">address</span><span class="p">)</span><span class="w"> 1301 </span><span class="n">address_element</span> <span class="o">=</span> <span class="n">h</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span><span class="w"> 1302 </span><span class="n">fltr</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">address_element</span><span class="p">)</span><span class="w"> 1303 1304 </span><span class="c1"># create the topic signature for the event</span><span class="w"> 1305 </span><span class="n">h</span> <span class="o">=</span> <span class="n">sha3</span><span class="o">.</span><span class="n">keccak_256</span><span class="p">()</span><span class="w"> 1306 </span><span class="n">h</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="sa">b</span><span class="s1">'Foo(uint256,bytes32)'</span><span class="p">)</span><span class="w"> 1307 </span><span class="n">topic</span> <span class="o">=</span> <span class="n">h</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span><span class="w"> 1308 1309 </span><span class="c1"># add the topic to the filter</span><span class="w"> 1310 </span><span class="n">h</span> <span class="o">=</span> <span class="n">sha3</span><span class="o">.</span><span class="n">keccak_256</span><span class="p">()</span><span class="w"> 1311 </span><span class="n">h</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">topic</span><span class="p">)</span><span class="w"> 1312 </span><span class="n">topic_element</span> <span class="o">=</span> <span class="n">h</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span><span class="w"> 1313 </span><span class="n">fltr</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">topic_element</span><span class="p">)</span><span class="w"> 1314 1315 </span><span class="c1"># get the filter from a block</span><span class="w"> 1316 </span><span class="n">block</span> <span class="o">=</span> <span class="n">some_block_getter</span><span class="p">()</span><span class="w"> 1317 </span><span class="n">block_bloom</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">block</span><span class="p">[</span><span class="s1">'logsBloom'</span><span class="p">][</span><span class="mi">2</span><span class="p">:])</span> <span class="c1"># assumes there is a 0x-prefix</span><span class="w"> 1318 1319 </span><span class="c1"># check if it matches</span><span class="w"> 1320 </span><span class="n">match</span> <span class="o">=</span> <span class="n">filter_match</span><span class="p">(</span><span class="n">block_bloom</span><span class="p">,</span> <span class="n">fltr</span><span class="o">.</span><span class="n">contents</span><span class="p">)</span><span class="w"> 1321 </span><span class="nb">print</span><span class="p">(</span><span class="n">match</span><span class="p">)</span> 1322 </pre> 1323 <!-- --> 1324 <blockquote> 1325 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 1326 <colgroup><col class="label" /><col /></colgroup> 1327 <tbody valign="top"> 1328 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Remember the keccak hash used in Ethereum/Bitcoin is _not_ the official <cite>sha3</cite> hash. The padding parameter is different. More on that <a class="reference external" href="nft-tokenid-content.html#id8">here</a></td></tr> 1329 </tbody> 1330 </table> 1331 </blockquote> 1332 <!-- --> 1333 <blockquote> 1334 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 1335 <colgroup><col class="label" /><col /></colgroup> 1336 <tbody valign="top"> 1337 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>Solidity supports up to 3 indexed parameters, which maps to the <em>evm</em> opcodes <code>LOGn</code> where <span class="formula"><i>n</i> ∈ {1, 2, 3, 4}</span></td></tr> 1338 </tbody> 1339 </table> 1340 </blockquote> 1341 <!-- --> 1342 <blockquote> 1343 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 1344 <colgroup><col class="label" /><col /></colgroup> 1345 <tbody valign="top"> 1346 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>Since <span class="formula">2<sup>11</sup> = 2048</span>, this is the same as zeroing out all bits above bit number 11; <span class="formula"><i>x</i>∧2048</span>. In other words, the result is an <em>11 bit integer</em>.</td></tr> 1347 </tbody> 1348 </table> 1349 </blockquote> 1350 </div> 1351 </content><category term="Code"></category><category term="crypto"></category><category term="ethereum"></category><category term="bloom filter"></category></entry><entry><title>Local npm repository</title><link href="docker-offline-3-npm.html" rel="alternate"></link><published>2021-05-25T11:47:00+02:00</published><updated>2021-05-25T11:47:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-05-25:docker-offline-3-npm.html</id><summary type="html"><p class="first last">Feeding npm packages to your offline Docker setup</p> 1352 </summary><content type="html"><p>As expected, serving a local <tt class="docutils literal">npm</tt> repository is a <em>lot</em> less straightforward than that for <tt class="docutils literal">pip</tt> and <tt class="docutils literal">python</tt>.</p> 1353 <p>The npm registry uses the <code>CouchDB</code> nosql server as backend to resolve dependencies and serve resource metadata. The <code>npm</code> CLI tool expects a corresponding json response to requests that cite the <em>name</em> (and not the path) of the package.</p> 1354 <p>Let's ask the <a class="reference external" href="https://registry.npmjs.org">registry</a> <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a> for the package <tt class="docutils literal">ftp</tt>, and have a look at the response (excerpt):</p> 1355 <div class="highlight"><pre><span></span><span class="gp">$ </span>curl<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span>https://registry.npmjs.org/ftp<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq 1356 <span class="go">{</span> 1357 <span class="go"> &quot;_id&quot;: &quot;ftp&quot;,</span> 1358 <span class="go"> &quot;_rev&quot;: &quot;113-89fe76508a7ece41b4c9a157114f966f&quot;,</span> 1359 <span class="go"> &quot;name&quot;: &quot;ftp&quot;,</span> 1360 <span class="go"> &quot;description&quot;: &quot;An FTP client module for node.js&quot;,</span> 1361 <span class="go"> &quot;dist-tags&quot;: {</span> 1362 <span class="go"> &quot;latest&quot;: &quot;0.3.10&quot;</span> 1363 <span class="go"> },</span> 1364 <span class="go"> &quot;versions&quot;: {</span> 1365 1366 1367 1368 <span class="go"> &quot;0.3.10&quot;: {</span> 1369 <span class="go"> &quot;name&quot;: &quot;ftp&quot;,</span> 1370 <span class="go"> &quot;version&quot;: &quot;0.3.10&quot;,</span> 1371 <span class="go"> &quot;author&quot;: {</span> 1372 <span class="go"> &quot;name&quot;: &quot;Brian White&quot;,</span> 1373 <span class="go"> &quot;email&quot;: &quot;mscdex@mscdex.net&quot;</span> 1374 <span class="go"> },</span> 1375 <span class="go"> &quot;description&quot;: &quot;An FTP client module for node.js&quot;,</span> 1376 <span class="go"> &quot;main&quot;: &quot;./lib/connection&quot;,</span> 1377 <span class="go"> &quot;engines&quot;: {</span> 1378 <span class="go"> &quot;node&quot;: &quot;&gt;=0.8.0&quot;</span> 1379 <span class="go"> },</span> 1380 <span class="go"> &quot;dependencies&quot;: {</span> 1381 <span class="go"> &quot;xregexp&quot;: &quot;2.0.0&quot;,</span> 1382 <span class="go"> &quot;readable-stream&quot;: &quot;1.1.x&quot;</span> 1383 <span class="go"> },</span> 1384 <span class="go"> &quot;scripts&quot;: {</span> 1385 <span class="go"> &quot;test&quot;: &quot;node test/test.js&quot;</span> 1386 <span class="go"> },</span> 1387 <span class="go"> &quot;keywords&quot;: [</span> 1388 <span class="go"> &quot;ftp&quot;,</span> 1389 <span class="go"> &quot;client&quot;,</span> 1390 <span class="go"> &quot;transfer&quot;</span> 1391 <span class="go"> ],</span> 1392 <span class="go"> &quot;licenses&quot;: [</span> 1393 <span class="go"> {</span> 1394 <span class="go"> &quot;type&quot;: &quot;MIT&quot;,</span> 1395 <span class="go"> &quot;url&quot;: &quot;http://github.com/mscdex/node-ftp/raw/master/LICENSE&quot;</span> 1396 <span class="go"> }</span> 1397 <span class="go"> ],</span> 1398 <span class="go"> &quot;repository&quot;: {</span> 1399 <span class="go"> &quot;type&quot;: &quot;git&quot;,</span> 1400 <span class="go"> &quot;url&quot;: &quot;http://github.com/mscdex/node-ftp.git&quot;</span> 1401 <span class="go"> },</span> 1402 <span class="go"> &quot;bugs&quot;: {</span> 1403 <span class="go"> &quot;url&quot;: &quot;https://github.com/mscdex/node-ftp/issues&quot;</span> 1404 <span class="go"> },</span> 1405 <span class="go"> &quot;homepage&quot;: &quot;https://github.com/mscdex/node-ftp&quot;,</span> 1406 <span class="go"> &quot;_id&quot;: &quot;ftp@0.3.10&quot;,</span> 1407 <span class="go"> &quot;_shasum&quot;: &quot;9197d861ad8142f3e63d5a83bfe4c59f7330885d&quot;,</span> 1408 <span class="go"> &quot;_from&quot;: &quot;https://github.com/mscdex/node-ftp/tarball/v0.3.10&quot;,</span> 1409 <span class="go"> &quot;_resolved&quot;: &quot;https://github.com/mscdex/node-ftp/tarball/v0.3.10&quot;,</span> 1410 <span class="go"> &quot;_npmVersion&quot;: &quot;1.4.28&quot;,</span> 1411 <span class="go"> &quot;_npmUser&quot;: {</span> 1412 <span class="go"> &quot;name&quot;: &quot;mscdex&quot;,</span> 1413 <span class="go"> &quot;email&quot;: &quot;mscdex@mscdex.net&quot;</span> 1414 <span class="go"> },</span> 1415 <span class="go"> &quot;maintainers&quot;: [</span> 1416 <span class="go"> {</span> 1417 <span class="go"> &quot;name&quot;: &quot;mscdex&quot;,</span> 1418 <span class="go"> &quot;email&quot;: &quot;mscdex@mscdex.net&quot;</span> 1419 <span class="go"> }</span> 1420 <span class="go"> ],</span> 1421 <span class="go"> &quot;dist&quot;: {</span> 1422 <span class="go"> &quot;shasum&quot;: &quot;9197d861ad8142f3e63d5a83bfe4c59f7330885d&quot;,</span> 1423 <span class="go"> &quot;tarball&quot;: &quot;https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz&quot;</span> 1424 <span class="go"> },</span> 1425 <span class="go"> &quot;directories&quot;: {}</span> 1426 <span class="go"> }</span> 1427 <span class="go"> },</span> 1428 </pre></div> 1429 <p>That looks a lot like embellished contents of the basic package.json set up by <code>npm init</code>, except it's wrapped in a versions array. It also explicitly defines an <em>absolute</em> url to a <cite>tarball</cite> under the <cite>dist</cite> key. <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a></p> 1430 <div class="section" id="how-low-can-you-go"> 1431 <h2>How low can you go</h2> 1432 <p>It seems that all that's needed is a json index page served for the package name subpath of repository path, together with the tarball itself. We check this by putting together an utterly pointless package <tt class="docutils literal">foobarbarbar</tt>:</p> 1433 <p><strong>foobarbarbar/index.js</strong></p> 1434 <pre class="code javascript literal-block" id="foobarbarbar-index-js"> 1435 <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> 1436 </span><span class="s1">'smth'</span><span class="o">:</span><span class="w"> </span><span class="nx">smth</span><span class="p">,</span><span class="w"> 1437 </span><span class="p">}</span><span class="w"> 1438 1439 </span><span class="kd">function</span><span class="w"> </span><span class="nx">smth</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> 1440 </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'foo'</span><span class="p">);</span><span class="w"> 1441 </span><span class="p">}</span> 1442 </pre> 1443 <p><strong>foobarbarbar/package.json</strong></p> 1444 <pre class="code json literal-block" id="foobarbarbar-package-json"> 1445 <span class="p">{</span><span class="w"> 1446 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;foobarbarbar&quot;</span><span class="p">,</span><span class="w"> 1447 </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;1.0.0&quot;</span><span class="p">,</span><span class="w"> 1448 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo repo&quot;</span><span class="p">,</span><span class="w"> 1449 </span><span class="nt">&quot;main&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;index.js&quot;</span><span class="p">,</span><span class="w"> 1450 </span><span class="nt">&quot;author&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo Bar&quot;</span><span class="p">,</span><span class="w"> 1451 </span><span class="nt">&quot;license&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;GPL3&quot;</span><span class="p">,</span><span class="w"> 1452 </span><span class="p">}</span> 1453 </pre> 1454 <p>Then make a tarball from those two files named <tt class="docutils literal"><span class="pre">foobarbarbar-1.0.0.tgz</span></tt>, take the sha1 sum of it,</p> 1455 <p>Remembering our <a class="reference external" href="docker-offline-2-python.html">virtual interface setup from the pip example</a> we stick it behind our webserver (here with npm sub-path) and add our minimal version json wrapper:</p> 1456 <p><strong>package.json</strong></p> 1457 <pre class="code json literal-block" id="package-json"> 1458 <span class="p">{</span><span class="w"> 1459 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;foobarbarbar&quot;</span><span class="p">,</span><span class="w"> 1460 </span><span class="nt">&quot;versions&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> 1461 </span><span class="nt">&quot;1.0.0&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> 1462 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;foobarbarbar&quot;</span><span class="p">,</span><span class="w"> 1463 </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;1.0.0&quot;</span><span class="p">,</span><span class="w"> 1464 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo repo&quot;</span><span class="p">,</span><span class="w"> 1465 </span><span class="nt">&quot;main&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;index.js&quot;</span><span class="p">,</span><span class="w"> 1466 </span><span class="nt">&quot;author&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo Bar&quot;</span><span class="p">,</span><span class="w"> 1467 </span><span class="nt">&quot;license&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;GPL3&quot;</span><span class="p">,</span><span class="w"> 1468 </span><span class="nt">&quot;dist&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> 1469 </span><span class="nt">&quot;shasum&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2ccd68498ef5f2bfa00f0e1e59f44686fdb296ee&quot;</span><span class="p">,</span><span class="w"> 1470 </span><span class="nt">&quot;tarball&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;http://10.1.2.1/npm/foobarbarbar-1.0.0.tgz&quot;</span><span class="w"> 1471 </span><span class="p">}</span><span class="w"> 1472 1473 </span><span class="p">}</span><span class="w"> 1474 </span><span class="p">}</span><span class="w"> 1475 </span><span class="p">}</span> 1476 </pre> 1477 </div> 1478 <div class="section" id="making-introductions"> 1479 <h2>Making introductions</h2> 1480 <p>The central trick here is to serve a json document as the &quot;directory index&quot; of the HTTP server. It's useful to remind ourselves at this point that we are <em>not</em> setting up a <em>registry</em> but a <em>repository</em> that contains provisions for a <em>locked</em> or <em>frozen</em> dependency graph. The former would surely need some of the <tt class="docutils literal">CouchDB</tt> magic to resolve dependencies. Our assumption is that the latter can theoretically be realized using static files.</p> 1481 <p>In other words; just as with the previous <a class="reference external" href="docker-offline-2-python.html">python repository example</a>, we don't try to handle dependency resolutions for pip, but merely serve the actual package files after dependences have been resolved.</p> 1482 <p>With Apache Web Server, using the <tt class="docutils literal">package.json</tt> as the directory index is as easy as:</p> 1483 <div class="highlight"><pre><span></span>&lt;Directory &quot;/srv/http/npm&quot;&gt; 1484 DirectoryIndex package.json 1485 &lt;/Directory&gt; 1486 </pre></div> 1487 <p>Of course adjusting the <cite>Directory</cite> path as needed to match the local setup.</p> 1488 <p>Make sure that the tarball and the latter <cite>package.json</cite> can be found in the <cite>foobarbarbar</cite> virtual subfolder of the above <cite>Directory</cite> directive path:</p> 1489 <div class="highlight"><pre><span></span><span class="gp">$ </span>ls<span class="w"> </span>/srv/http/npm/foobarbarbar/ 1490 <span class="go">-rw-r--r-- 1 root root 351 May 24 18:38 foobarbarbar-1.0.0.tgz</span> 1491 <span class="go">-rw-r--r-- 1 root root 380 May 25 09:36 package.json</span> 1492 </pre></div> 1493 <p>Set the registry entry in your <tt class="docutils literal"><span class="pre">~./npmrc</span></tt> as follows:</p> 1494 <div class="highlight"><pre><span></span><span class="na">registry</span><span class="o">=</span><span class="s">http://10.1.2.1/npm</span> 1495 </pre></div> 1496 <p>Then restart the Apache server and give it a go:</p> 1497 <div class="highlight"><pre><span></span><span class="gp">$ </span>npm<span class="w"> </span>install<span class="w"> </span>--verbose<span class="w"> </span>foobarbarbar 1498 <span class="go">npm verb cli [ &#39;/usr/bin/node&#39;, &#39;/usr/bin/npm&#39;, &#39;install&#39;, &#39;--verbose&#39;, &#39;foobarbarbar&#39; ]</span> 1499 <span class="go">npm info using npm@7.13.0</span> 1500 <span class="go">npm info using node@v16.1.0</span> 1501 <span class="go">[...]</span> 1502 <span class="go">npm http fetch GET 200 http://10.1.2.1/npm/foobarbarbar/ 6ms</span> 1503 <span class="go">[...]</span> 1504 <span class="go">npm http fetch GET 200 http://10.1.2.1/npm/foobarbarbar/foobarbarbar-1.0.0.tgz 25ms</span> 1505 <span class="go">[...]</span> 1506 <span class="go">added 2 packages in 545ms</span> 1507 <span class="go">npm timing command:install Completed in 70ms</span> 1508 <span class="go">[...]</span> 1509 </pre></div> 1510 </div> 1511 <div class="section" id="cache-dodging"> 1512 <h2>Cache dodging</h2> 1513 <p>Seriously, <tt class="docutils literal">npm <span class="pre">--help</span></tt> leaves a lot to be desired. However, the online <tt class="docutils literal">npm install</tt> documentation does not yield any more clues as to whether it gives you an option of ignoring local cache like you can with <tt class="docutils literal">pip <span class="pre">--no-cache</span></tt>. This is a major pain in the ass, as you have to remember to keep your <tt class="docutils literal"><span class="pre">~/.npm</span></tt> folder clean between each install attempt. Otherwise, changes you make to the repository won't be used by <tt class="docutils literal">npm install</tt>.</p> 1514 <p>While testing, it pays to use a folder fairly close to the fs root, like a subfolder in <cite>tmp</cite>. That way, you won't be thrown off by some stray <cite>node_modules</cite> folder somewhere down the tree.</p> 1515 </div> 1516 <div class="section" id="think-locally-act-locally"> 1517 <h2>Think locally, act locally</h2> 1518 <p>Serving packages globally apparently comes down to this:</p> 1519 <ol class="arabic simple"> 1520 <li>Pull the <tt class="docutils literal">package.json</tt> versions list from a proper registry.</li> 1521 <li>Get the tarball</li> 1522 <li>Transform the absolute package url to our host instead. (sigh)</li> 1523 <li>(optional) Prune all the versions and metadata which is not needed (anything that's not in our minimal <tt class="docutils literal">foobarbarbar</tt> example above).</li> 1524 </ol> 1525 <p>Obviously, this is an annoyingly large amount of work to code up parsing and retrieval for.</p> 1526 <p>Incidentally, there is a very nice tool called <a class="reference external" href="https://verdaccio.org">verdaccio</a> which gets us most of the way there. <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a> Its <em>storage</em> directory <a class="footnote-reference" href="#footnote-4" id="footnote-reference-4">[4]</a> uses exactly the directory structure we need. After retrieving a package collection using it as a proxy, getting the files in place is merely a case of copying <a class="footnote-reference" href="#footnote-5" id="footnote-reference-5">[5]</a> the files from the <em>storage</em> directory to the corresponding webserver directory.</p> 1527 <p>Looking at the <tt class="docutils literal">package.json</tt> versions wrapper saved by <tt class="docutils literal">verdaccio</tt>, however, we see that it still preserves the absolute path of the upstream registry. Sadly we are still left with doing the third task ourselves.</p> 1528 <p>As parsing json is one of my least favorite things in the world, I won't include the code for this last step here. For now it's sufficient that we understand the minimums required for manually setting up and serving a static, local, offline npm repository. Regardless of the pain involved.</p> 1529 </div> 1530 <div class="section" id="making-it-personal"> 1531 <h2>Making it personal</h2> 1532 <p>Whether we massage jsons ourselves or lazily resort to <tt class="docutils literal">verdaccio</tt>, the final step we need to take is the same: Setting the registry url in the npm configuration of the docker image.</p> 1533 <p>This is merely a case of setting the registry url in the <a class="reference external" href="https://docs.npmjs.com/cli/v6/configuring-npm/npmrc#files">npmrc</a> inside the container.</p> 1534 <div class="highlight"><pre><span></span><span class="gp">$ </span>docker<span class="w"> </span>run<span class="w"> </span>-it<span class="w"> </span><span class="o">[</span>...<span class="o">]</span><span class="w"> </span>npm<span class="w"> </span>config<span class="w"> </span>ls<span class="w"> </span>-l<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>etc/npmrc 1535 <span class="go">globalconfig = &quot;/usr/etc/npmrc&quot;</span> 1536 </pre></div> 1537 <p>To get at our manually provided <tt class="docutils literal">foobarbarbar</tt> package from before, the dockerfile will be (using archlinux):</p> 1538 <div class="highlight"><pre><span></span><span class="o">[</span>...<span class="o">]</span> 1539 <span class="k">RUN</span><span class="w"> </span>pacman<span class="w"> </span>-S<span class="w"> </span>nodejs<span class="w"> </span>npm 1540 <span class="k">RUN</span><span class="w"> </span>mkdir<span class="w"> </span>-vp<span class="w"> </span>/usr/etc 1541 <span class="k">RUN</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;registry=http:/10.1.2.1/npm&quot;</span><span class="w"> </span>&gt;<span class="w"> </span>/usr/etc/npmrc 1542 <span class="k">WORKDIR</span><span class="w"> </span><span class="s">/root</span> 1543 <span class="k">RUN</span><span class="w"> </span>npm<span class="w"> </span>install<span class="w"> </span>foobarbarbar 1544 </pre></div> 1545 <!-- --> 1546 <blockquote> 1547 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 1548 <colgroup><col class="label" /><col /></colgroup> 1549 <tbody valign="top"> 1550 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>This was the registry address I had in my <cite>~/.npmrc</cite> or whether I put it in myself.</td></tr> 1551 </tbody> 1552 </table> 1553 </blockquote> 1554 <!-- --> 1555 <blockquote> 1556 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 1557 <colgroup><col class="label" /><col /></colgroup> 1558 <tbody valign="top"> 1559 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>I have tried using a relative path instead, both with and without a leading <tt class="docutils literal">/</tt>, but in either case the install then errors our complaining that the lock file is &quot;corrupt.&quot; Whether the schema allows a base-url setting I do not know, as the schema documentation doesn't seem to be readily available.</td></tr> 1560 </tbody> 1561 </table> 1562 </blockquote> 1563 <!-- --> 1564 <blockquote> 1565 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 1566 <colgroup><col class="label" /><col /></colgroup> 1567 <tbody valign="top"> 1568 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>In fact, <tt class="docutils literal">verdaccio</tt> by itself solves the particular problem that we're trying to solve; providing an offline repository alternative. The task at hand, however, is to understand how to cope <em>without</em> using other tools than what can be considered base infrastructure (i.e. a web server).</td></tr> 1569 </tbody> 1570 </table> 1571 </blockquote> 1572 <!-- --> 1573 <blockquote> 1574 <table class="docutils footnote" frame="void" id="footnote-4" rules="none"> 1575 <colgroup><col class="label" /><col /></colgroup> 1576 <tbody valign="top"> 1577 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-4">[4]</a></td><td>Should be <tt class="docutils literal">/etc/verdaccio/storage</tt>; see <tt class="docutils literal">/etc/verdaccio/config.yaml</tt></td></tr> 1578 </tbody> 1579 </table> 1580 </blockquote> 1581 <!-- --> 1582 <blockquote> 1583 <table class="docutils footnote" frame="void" id="footnote-5" rules="none"> 1584 <colgroup><col class="label" /><col /></colgroup> 1585 <tbody valign="top"> 1586 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-5">[5]</a></td><td>An alternative approach would be to set the storage path to point to the same folder as the web server is serving. However, since we need to mess with the tarball paths</td></tr> 1587 </tbody> 1588 </table> 1589 </blockquote> 1590 </div> 1591 </content><category term="Offlining"></category><category term="docker"></category><category term="npm"></category><category term="nodejs"></category><category term="javascript"></category><category term="devops"></category></entry><entry><title>A village network protocol</title><link href="village-network-protocol.html" rel="alternate"></link><published>2021-05-19T10:40:00+02:00</published><updated>2021-05-19T10:40:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-05-19:village-network-protocol.html</id><summary type="html"><p class="first last">What would truly peer-to-peer CRM backend look like?</p> 1592 </summary><content type="html"><p>What would truly peer-to-peer CRM backend look like? And at that, once that respected the right of the &quot;customer&quot; to choose how it wants to represent itself in the context?</p> 1593 <p>Perhaps by Modeling the network on how villages treat strangers and their own, and how you would go about interacting with them.</p> 1594 <p>Here's an initial stab at describing what such a meta-protocol could look like.</p> 1595 <!-- There is no truth. There is only perspective and predictability. --> 1596 <div class="section" id="terms"> 1597 <h2>Terms</h2> 1598 <dl class="docutils"> 1599 <dt>Individual</dt> 1600 <dd>A subjective structure where the thing being described is the thing that described it.</dd> 1601 <dt>User</dt> 1602 <dd>A particular perception of an individual by an entity in the network.</dd> 1603 <dt>Userspec</dt> 1604 <dd>Data structure containing details about a user. May or may not be public.</dd> 1605 <dt>Locals</dt> 1606 <dd>A group of individuals acknowledging each other as part of a group, and who share views on users.</dd> 1607 <dt>Local</dt> 1608 <dd>A member of a group of locals, from the shared perspective of an individual and another local.</dd> 1609 <dt>Localslist</dt> 1610 <dd>The individuals making up a group of locals, from the perspective of a local.</dd> 1611 <dt>Stranger</dt> 1612 <dd>A user as percieved by a group of locals.</dd> 1613 <dt>Strangerlist</dt> 1614 <dd>All users that may speak to the locals, from the perspective of a local. To be shared among locals.</dd> 1615 <dt>Strangerspec</dt> 1616 <dd>Userspec created by a local describing a user. To be shared among locals.</dd> 1617 <dt>Entity</dt> 1618 <dd>An individual or any derived representation of it (user, stranger, local).</dd> 1619 <dt>Peer</dt> 1620 <dd>An entity coupled with connection information</dd> 1621 <dt>Content address</dt> 1622 <dd>SHA256 sum of arbitrary content</dd> 1623 <dt>Key setup</dt> 1624 <dd>May be an individual key, or a specification describing how to generate keys for a particular relation.</dd> 1625 </dl> 1626 </div> 1627 <div class="section" id="roles"> 1628 <h2>Roles</h2> 1629 <div class="section" id="the-individuals"> 1630 <h3>The individuals</h3> 1631 <p>Are free to have their own opinion of what they are.</p> 1632 <p>When interacting with others, they are free to choose what they say about what they are.</p> 1633 <p>An individual initially <em>defines itself</em> in the network.</p> 1634 </div> 1635 <div class="section" id="the-users"> 1636 <h3>The users</h3> 1637 <p>A user is a view of an individual as defined by one or more other individuals.</p> 1638 <p>It may be how an individual presents itself to an particular individual or a group.</p> 1639 <p>It may also be how an individual or a group presents another individual to itself.</p> 1640 </div> 1641 <div class="section" id="the-locals"> 1642 <h3>The locals</h3> 1643 <p>Locals are a group of individuals who wish to share a view of themselves and prejudice about others.</p> 1644 <p>An individual will not be treated as a local unless it and its local counterpart considers it a local.</p> 1645 <p>Locals may acknowledge strangers with or without proof.</p> 1646 <p>If one local changes opinion about a stranger, all locals should change their opinon to the same.</p> 1647 <p>If one local excludes a stranger, all locals should exclude the stranger.</p> 1648 <p>You can't keep secrets in a village. If someone says something to another local, it must assume all locals will get to know what was said.</p> 1649 </div> 1650 <div class="section" id="the-strangers"> 1651 <h3>The strangers</h3> 1652 <p>A stranger is a user that's not considered local by a group of locals.</p> 1653 <p>A stranger may not speak to a local until the local has decided it can.</p> 1654 <p>If a stranger may speak to one local, it may speak all locals.</p> 1655 </div> 1656 </div> 1657 <div class="section" id="identifiers"> 1658 <h2>Identifiers</h2> 1659 <p>The <code>individual_id</code> is the identifier of an individual consists of the content address of some basic public data about the entity registering itself. The <code>individual_id</code> is immutable.</p> 1660 <p>Any local will refer to another local by its <code>individual_id</code>.</p> 1661 <p>A <code>user_id</code> is the content address of an <code>individual_id</code> together with the content address of some basic data describing the perception of the individual.</p> 1662 <p>Locals describe stranger by a pair of <code>individual_id</code> and <code>user_id</code>. Let us call this <code>stranger_id</code>. The <code>stranger_id</code> is immutable.</p> 1663 <p>The data represented by the <code>user_id</code> in a <code>stranger_id</code> is visible to locals, and may or may not be public.</p> 1664 </div> 1665 <div class="section" id="state-description"> 1666 <h2>State description</h2> 1667 <p>A user keeps a state towards other entities consisting of the following content-addresses, in the given order:</p> 1668 <ol class="arabic simple"> 1669 <li>Previous state</li> 1670 <li>Userspec</li> 1671 </ol> 1672 <p>A local keeps a state towards its locals consisting of the following content-addresses, in the given order.</p> 1673 <ol class="arabic simple"> 1674 <li>Previous state</li> 1675 <li>Userspec</li> 1676 <li>Localslist</li> 1677 <li>Locals state</li> 1678 <li>Strangerslist</li> 1679 <li>Strangers state</li> 1680 </ol> 1681 <p>The state is represented by the content address of the concatenation of these content-addresses, in the given order.</p> 1682 <p>Any state change will be signed by the key resolvable through the <code>individual_id</code>.</p> 1683 <div class="section" id="previous-state"> 1684 <h3>Previous state</h3> 1685 <p>The content address of the previous state. Will be <code>0</code> in the first state.</p> 1686 </div> 1687 <div class="section" id="userspec"> 1688 <h3>Userspec</h3> 1689 <p>The individual as it wants to be perceived in the current context. There is no guarantee that the individual will be viewed in this way by others.</p> 1690 </div> 1691 <div class="section" id="localslist"> 1692 <h3>Localslist</h3> 1693 <p>A lexiographically ordered array of <code>individual_id</code> that define the individuals in a group of locals.</p> 1694 <p>All locals must keep this list in the same state.</p> 1695 </div> 1696 <div class="section" id="locals-state"> 1697 <h3>Locals state</h3> 1698 <p>A local's last recorded state of each of the locals, in the same order as <code>Localslist</code>.</p> 1699 <p>The recorded state is the same content address as in the state description section above.</p> 1700 <p>All locals must strive to keep this in the same state.</p> 1701 </div> 1702 <div class="section" id="strangerslist"> 1703 <h3>Strangerslist</h3> 1704 <p>A lexiographically ordered pointer array representing a local's opinion of which users that can communicate with locals. <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a></p> 1705 <p>Combination of <code>stranger_id</code> and a <em>vote</em> on the specific user.</p> 1706 <p>The vote can be in one of three states:</p> 1707 <ul class="simple"> 1708 <li>Value <code>0</code>, meaning local has acknowledged the user, explicitly or by following other locals. We call this state &quot;deferred.&quot;</li> 1709 <li>Value <code>-1</code>, meaning a local has vetoed the user. We call this state &quot;vetoed.&quot;</li> 1710 <li>Any other value, which is the content-address of the proof accepted by the local. <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a> We call this state &quot;accepted.&quot;</li> 1711 </ul> 1712 <p>Typically, the initial vote value is <code>0</code>. The value may change at any time, but may never change back to <code>0</code> once set to another value.</p> 1713 <p>A user that is part of the list, but not vetoed, is referred to as &quot;acknowledged.&quot;</p> 1714 <p>The Strangerslist state may be differ between locals, but they should strive to keep it the same.</p> 1715 </div> 1716 <div class="section" id="strangers-state"> 1717 <h3>Strangers state</h3> 1718 <p>The local's version of the current user state of the strangers, in the same order as the <code>Strangerlist</code>.</p> 1719 <p>The Strangers state may differ between locals, but they should strive to keep it the same.</p> 1720 </div> 1721 </div> 1722 <div class="section" id="behavior-of-locals"> 1723 <h2>Behavior of locals</h2> 1724 <p>A local may announce itself as <em>active</em> or <em>passive</em>. This should be part of the <code>Userspec</code> in the locals context.</p> 1725 <p>Specifically, this affects the way it &quot;votes&quot; on strangers. A passive local:</p> 1726 <ul class="simple"> 1727 <li>Will always <em>veto</em> a user if <em>at least one active local</em> has vetoed it.</li> 1728 <li>Will always <em>accept</em> a user if <em>all active locals</em> have accepted it. <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a></li> 1729 </ul> 1730 <p>Typically, a passive local will be a bot, for example an always-online node that guarantees availability of the locals so users can request state changes.</p> 1731 <p>There must be at least two active locals in any group of locals.</p> 1732 <div class="section" id="veto-dissent"> 1733 <h3>Veto dissent</h3> 1734 <p>If a local vetoes a stranger, then all locals should cut communication with that stranger.</p> 1735 <p>Vetoes should be adopted by all locals.</p> 1736 <p>If, however, other locals consider the veto to be issued in error, there are two possible outcomes:</p> 1737 <ol class="arabic simple"> 1738 <li>The vetoing local(s) abandon(s) the veto. <a class="footnote-reference" href="#footnote-4" id="footnote-reference-4">[4]</a></li> 1739 <li>The vetoing individual(s) is/are excluded by the locals.</li> 1740 </ol> 1741 </div> 1742 </div> 1743 <div class="section" id="network"> 1744 <h2>Network</h2> 1745 <p>Any entity needs to define and announce itself as a <code>peer</code> in order to be available on the network.</p> 1746 <p>A &quot;peer&quot; is the coupling of network connection information to an <code>individual_id</code>.</p> 1747 <div class="section" id="discoverability"> 1748 <h3>Discoverability</h3> 1749 <p>Peers are kept in a <a class="reference external" href="https://en.wikipedia.org/wiki/Distributed_hash_table">DHT</a>.</p> 1750 <p>Peers must be discoverable by their locals affiliation. <a class="footnote-reference" href="#footnote-5" id="footnote-reference-5">[5]</a></p> 1751 </div> 1752 <div class="section" id="users"> 1753 <h3>Users</h3> 1754 <p>Users open single-request connections to other users to annonunce, look up and prune peers.</p> 1755 <p>Users may request state changes for themselves, or relay such requests for other users.</p> 1756 </div> 1757 <div class="section" id="locals"> 1758 <h3>Locals</h3> 1759 <p>Locals keep open connections (streams) with each other.</p> 1760 <p>Locals continually exchange state changes.</p> 1761 <p>Locals should be able to audit the chain of state changes of other locals.</p> 1762 </div> 1763 <div class="section" id="between-locals-and-users"> 1764 <h3>Between locals and users</h3> 1765 <p>Strangers <em>acknowledged</em> by locals open single-request connections to a local to announce state changes.</p> 1766 <p>Locals may open single-request connections to a user to announce a change to the state of the <code>Userspec</code> of a stranger. <a class="footnote-reference" href="#footnote-6" id="footnote-reference-6">[6]</a></p> 1767 <p>Individuals are free to deny connections from non-locals, for example if the entity has been sending garbage requests. The implementation of such sanctions is deferred to the application layer.</p> 1768 </div> 1769 </div> 1770 <div class="section" id="encryption"> 1771 <h2>Encryption</h2> 1772 <p>An individual may keep content totally private on the network.</p> 1773 <p>A user may keep different encryption keys for different contexts.</p> 1774 <p>If a stranger shares a key setup with a local, that setup will be shared among all locals. A local will expect to be able to communicate with a strangerr using this key setup.</p> 1775 <p>The stranger must be able to change the key setup towards locals.</p> 1776 <p>The locals may be able to change the key setup towards a stranger.</p> 1777 <p>The protocol should allow anything from simple symmetric key exchanges to complex forward/backward secrecy schemes.</p> 1778 <!-- --> 1779 <blockquote> 1780 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 1781 <colgroup><col class="label" /><col /></colgroup> 1782 <tbody valign="top"> 1783 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Insert in this sorted list will face scaling issues when locals need to acknowledge a lot of users. The lists can be split into <a class="reference external" href="https://en.wikipedia.org/wiki/Bucket_%28computing%29">buckets</a>, either statically (for example two-byte prefixes) or dynamically after crossing a certain threshold. It should not be necessary to store empty buckets.</td></tr> 1784 </tbody> 1785 </table> 1786 </blockquote> 1787 <!-- --> 1788 <blockquote> 1789 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 1790 <colgroup><col class="label" /><col /></colgroup> 1791 <tbody valign="top"> 1792 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>This proof may map to what's commonly known as <em>kyc information</em>.</td></tr> 1793 </tbody> 1794 </table> 1795 </blockquote> 1796 <!-- --> 1797 <blockquote> 1798 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 1799 <colgroup><col class="label" /><col /></colgroup> 1800 <tbody valign="top"> 1801 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>Perhaps with the further limitation that all active users must have accepted with the <em>same</em> proof. Otherwise, how will the passive local choose which proof to accept.</td></tr> 1802 </tbody> 1803 </table> 1804 </blockquote> 1805 <!-- --> 1806 <blockquote> 1807 <table class="docutils footnote" frame="void" id="footnote-4" rules="none"> 1808 <colgroup><col class="label" /><col /></colgroup> 1809 <tbody valign="top"> 1810 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-4">[4]</a></td><td>Observe that since the vote state cannot change back to <code>0</code>, &quot;cancelling&quot; the veto can only be done by adding proof.</td></tr> 1811 </tbody> 1812 </table> 1813 </blockquote> 1814 <!-- --> 1815 <blockquote> 1816 <table class="docutils footnote" frame="void" id="footnote-5" rules="none"> 1817 <colgroup><col class="label" /><col /></colgroup> 1818 <tbody valign="top"> 1819 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-5">[5]</a></td><td>What exactly uniquely identifies a group of locals on the network in such a way that it cannot be hi-jacked or spoofed still needs to be thought through. Perhaps it can be a combination of a specific name with a signature from the &quot;founders;&quot; the first (two or more) locals in it.</td></tr> 1820 </tbody> 1821 </table> 1822 </blockquote> 1823 <!-- --> 1824 <blockquote> 1825 <table class="docutils footnote" frame="void" id="footnote-6" rules="none"> 1826 <colgroup><col class="label" /><col /></colgroup> 1827 <tbody valign="top"> 1828 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-6">[6]</a></td><td>A use-case here would be where the locals make a change for the user due to an out-of-band request from the user. This is <em>not</em> the <code>Strangerspec</code>; it is the user's current <code>Userspec</code> for the given context. Of course, it's up to the user to accept the change. It may in turn request a new state change to the locals to revert it.</td></tr> 1829 </tbody> 1830 </table> 1831 </blockquote> 1832 </div> 1833 </content><category term="Hygiene"></category><category term="p2p"></category><category term="protocol"></category><category term="network"></category><category term="crm"></category><category term="privacy"></category><category term="f2f"></category><category term="community"></category></entry><entry><title>The NFT token id as URI</title><link href="nft-tokenid-content.html" rel="alternate"></link><published>2021-05-08T19:14:00+02:00</published><updated>2021-05-10T09:18:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-05-08:nft-tokenid-content.html</id><summary type="html"><p class="first last">How to embed asset references for NFTs that are independent of providers in the current standard.</p> 1834 </summary><content type="html"><p>Let's consider an NFT that works like a badge for participating in development of a software project.</p> 1835 <p>This token is awarded as a proof that the task was completed.</p> 1836 <p>To make things more fun, each NFT should have some unique, immutable content attached to it.</p> 1837 <p>In other words, the properties of this token, once set, should never change.</p> 1838 <p>Nor should they disappear.</p> 1839 <p>So how do we refer to the artwork asset within the token standard?</p> 1840 <div class="section" id="it-was-acceptable-at-the-time"> 1841 <h2>It was acceptable at the time</h2> 1842 <p>The ERC721 standard is not explicit about where the assets that belong with the NFT can be discovered and resolved.</p> 1843 <p>At the time when the standard was adopted by the Ethereum community, there were multiple <em>&quot;[...] Alternatives considered: put all metadata for each asset on the blockchain (too expensive), use URL templates to query metadata parts (URL templates do not work with all URL schemes, especially P2P URLs), multiaddr network address (not mature enough).&quot;</em> Furthermore, they <em>&quot;[...] considered an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can naturally change.&quot;</em> <a class="citation-reference" href="#eip721" id="citation-reference-1">[EIP721]</a></p> 1844 <p>A &quot;changing house&quot; doesn't sound quite like what we need. And anyway; if we stick a good old web2 URI in there, then that will end up on the great bonfire of dead links before long.</p> 1845 </div> 1846 <div class="section" id="image-schmimage"> 1847 <h2>Image, schmimage</h2> 1848 <p>To be honest, I find the presumption in the optional EIP721 metadata structure to be surprisingly short-sighted. It <em>specifically</em> defines the asset as an image, and at the same time is presupposes that only a <em>single</em> asset file will be used.</p> 1849 <p>We may want to add <em>multiple</em> sources, so this is another obstacle for us.</p> 1850 <p>So how to get around this, while still playing nice with existing implementations out there? Two ideas come to mind:</p> 1851 <ul class="simple"> 1852 <li>Embed a <em>thumbnail</em> as a preview of the artwork using a <code>base64</code> <em>data URI</em> <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a> in the metadata. Stick <code>name</code> and <code>description</code> on it, and the schema is still fulfilled.</li> 1853 <li><em>Extend</em> the structure with a list of <em>attachments</em> that <em>our</em> application layer knows about. Of course, each of these can have the same format as above.</li> 1854 </ul> 1855 <p>In other words:</p> 1856 <pre class="code json literal-block"> 1857 <span class="p">{</span><span class="w"> 1858 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo&quot;</span><span class="p">,</span><span class="w"> 1859 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo image&quot;</span><span class="p">,</span><span class="w"> 1860 </span><span class="nt">&quot;image&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;data:image/gif;base64,R0lGODlhDgARAKEDAAAAAOjqAPP1APDw8CH+EUNyZWF0ZWQgd2l0aCBHSU1QACwAAAAADgARAAACO5wHqXdrClocodbUaGg34qoBHaJtl7VYIfqBZxepb5C6NBvbIlyucO6jPUIK1CMiSCYvDIVS4GhCJoYCADs=&quot;</span><span class="w"> 1861 </span><span class="nt">&quot;attachments&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> 1862 </span><span class="p">{</span><span class="w"> 1863 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Bar&quot;</span><span class="p">,</span><span class="w"> 1864 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Bar image&quot;</span><span class="p">,</span><span class="w"> 1865 </span><span class="nt">&quot;image&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;data:image/gif;base64,&quot;</span><span class="w"> 1866 </span><span class="p">},</span><span class="w"> 1867 </span><span class="p">{</span><span class="w"> 1868 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Baz&quot;</span><span class="p">,</span><span class="w"> 1869 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Baz image&quot;</span><span class="p">,</span><span class="w"> 1870 </span><span class="nt">&quot;image&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;data:image/gif;base64,&quot;</span><span class="w"> 1871 </span><span class="p">}</span><span class="w"> 1872 </span><span class="p">]</span><span class="w"> 1873 </span><span class="p">}</span> 1874 </pre> 1875 </div> 1876 <div class="section" id="mirror-mirror"> 1877 <h2>Mirror, mirror</h2> 1878 <p>Since the asset reference shouldn't change, we can refer to it by its fingerprint or <a class="reference external" href="https://en.wikipedia.org/wiki/Content-addressable_storage">content address</a>. If we define that the resource can be looked up over HTTP by that fingerprint as its basename, then we are free to define and modify whatever list of mirrors for that resource that's valid for any point in time. The application layer would simply try the endpoints one after another.</p> 1879 <p>We take the <code>sha2-256</code> <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a> of the asset reference (the json file above, free of evil whitespace and newlines):</p> 1880 <div class="highlight"><pre><span></span>$<span class="w"> </span>cat<span class="w"> </span>reference.json<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span>-c<span class="w"> </span>-j<span class="w"> </span><span class="p">|</span><span class="w"> </span>sha256sum<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{ print $1; }&#39;</span> 1881 3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551 1882 </pre></div> 1883 <p>Imagine we had a mirror list of <a class="reference external" href="https://foo.com">https://foo.com</a> and <a class="reference external" href="https://bar.com/baz/">https://bar.com/baz/</a>. Then our application would try these urls in sequence, stopping at the first that returns a valid result:</p> 1884 <div class="highlight"><pre><span></span>https://foo.com/3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551 1885 https://bar.com/baz/3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551 1886 </pre></div> 1887 <p>Once we receive the content, all we have to do is hash it ourselves and verify that the sum matches the basename of the URI. If it doesn't the result is of course not valid and we continue down the list, appropriately banning the mischievous server then throrougly harassing its admin.</p> 1888 </div> 1889 <div class="section" id="cast-away"> 1890 <h2>Cast away</h2> 1891 <p>Since our fingerprint is 32 bytes, it fits exactly inside the <code>tokenId</code> (<code>uint256</code>). Let's decide to big-endian numbers when converting (I find them easier to make sense of). In that case our hash from the reference turns into this modest number:</p> 1892 <div class="highlight"><pre><span></span><span class="c1"># python3</span> 1893 <span class="o">&gt;&gt;&gt;</span> <span class="n">hx</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="s1">&#39;3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551&#39;</span><span class="p">)</span> 1894 <span class="o">&gt;&gt;&gt;</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">hx</span><span class="p">,</span> <span class="n">byteorder</span><span class="o">=</span><span class="s1">&#39;big&#39;</span><span class="p">)</span> 1895 <span class="mi">28891040728719892888467057134569335350980764617882743994259054993630416573777</span> 1896 </pre></div> 1897 <p>As long as we're composing the <code>evm</code> inputs ourselves, we don't really have to worry about the integer representation in this particular case. But the interface is defined as an integer type, and other mortals may be using higher level interfaces, we have to be explicit about our choice.</p> 1898 </div> 1899 <div class="section" id="welcoming-mint"> 1900 <h2>Welcoming mint</h2> 1901 <p>Assume we have a method <code>mintTo(address _recipient, uint256 _tokenId)</code> on our NFT contract. The Solidity signature of that contract is <code>edb20b7e</code> <a class="footnote-reference" href="#footnote-3" id="footnote-reference-3">[3]</a>. If I were to mint to myself then the input to the contract would be:</p> 1902 <div class="highlight"><pre><span></span>edb20b7e000000000000000000000000185cbce7650ff7ad3b587e26b2877d95568805e33fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551 1903 </pre></div> 1904 <p>Broken down:</p> 1905 <div class="highlight"><pre><span></span>signature: edb20b7e 1906 address, zero-padded: 000000000000000000000000185cbce7650ff7ad3b587e26b2877d95568805e3 1907 token id: 3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551 1908 </pre></div> 1909 <p>The corresponding web3.js code would look like:</p> 1910 <div class="highlight"><pre><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">web3</span><span class="p">.</span><span class="nx">eth</span><span class="p">.</span><span class="nx">Contract</span><span class="p">([...],</span><span class="w"> </span><span class="s1">&#39;0x...&#39;</span><span class="p">);</span> 1911 <span class="nx">c</span><span class="p">.</span><span class="nx">methods</span><span class="p">.</span><span class="nx">mintTo</span><span class="p">(</span><span class="s1">&#39;0x185cbce7650ff7ad3b587e26b2877d95568805e3&#39;</span><span class="p">,</span><span class="w"> </span><span class="mf">28891040728719892888467057134569335350980764617882743994259054993630416573777</span><span class="p">).</span><span class="nx">call</span><span class="p">();</span> 1912 </pre></div> 1913 <p>To satisfy the <cite>tokenURI</cite> method, we can generate a string that's prefix with sha256 as a &quot;scheme&quot; <a class="footnote-reference" href="#footnote-4" id="footnote-reference-4">[4]</a>. A bit of (unoptimized) solidity helps us out here:</p> 1914 <pre class="code solidity literal-block"> 1915 <span class="k">contract</span><span class="w"> </span><span class="ni">NFT</span><span class="w"> </span><span class="p">{</span><span class="w"> 1916 1917 </span><span class="kt">uint256</span><span class="p">[]</span><span class="w"> </span>token<span class="p">;</span><span class="w"> 1918 </span><span class="kt">mapping</span><span class="p">(</span><span class="kt">uint256</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="kt">uint256</span><span class="p">)</span><span class="w"> </span>tokenIndex<span class="p">;</span><span class="w"> </span><span class="c1">// map token id to master token array index position 1919 </span><span class="w"> 1920 </span><span class="p">[...]</span><span class="w"> 1921 1922 </span><span class="kt">function</span><span class="w"> </span><span class="nv">tokenURI</span><span class="p">(</span><span class="kt">uint256</span><span class="w"> </span><span class="nv">_tokenId</span><span class="p">)</span><span class="w"> </span><span class="kt">public</span><span class="w"> </span>pure<span class="w"> </span><span class="kt">returns</span><span class="p">(</span><span class="kt">string</span><span class="w"> </span><span class="nv">memory</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> 1923 </span><span class="kt">bytes32</span><span class="w"> </span><span class="nv">token_bytes</span><span class="p">;</span><span class="w"> 1924 </span><span class="kt">bytes</span><span class="w"> </span><span class="nv">memory</span><span class="w"> </span>out<span class="p">;</span><span class="w"> 1925 </span><span class="kt">uint8</span><span class="w"> </span><span class="nv">t</span><span class="p">;</span><span class="w"> 1926 </span><span class="kt">uint256</span><span class="w"> </span><span class="nv">c</span><span class="p">;</span><span class="w"> 1927 1928 </span>token_bytes<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">bytes32</span><span class="p">(</span>token<span class="p">[</span>tokenIndex<span class="p">[</span>_tokenId<span class="p">]]);</span><span class="w"> 1929 1930 </span>out<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">new</span><span class="w"> </span><span class="kt">bytes</span><span class="p">(</span><span class="m-Decimal">64</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="m-Decimal">7</span><span class="p">);</span><span class="w"> 1931 </span>out<span class="p">[</span><span class="m-Decimal">0</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;s&quot;</span><span class="p">;</span><span class="w"> 1932 </span>out<span class="p">[</span><span class="m-Decimal">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;h&quot;</span><span class="p">;</span><span class="w"> 1933 </span>out<span class="p">[</span><span class="m-Decimal">2</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;a&quot;</span><span class="p">;</span><span class="w"> 1934 </span>out<span class="p">[</span><span class="m-Decimal">3</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;2&quot;</span><span class="p">;</span><span class="w"> 1935 </span>out<span class="p">[</span><span class="m-Decimal">4</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;5&quot;</span><span class="p">;</span><span class="w"> 1936 </span>out<span class="p">[</span><span class="m-Decimal">5</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;6&quot;</span><span class="p">;</span><span class="w"> 1937 </span>out<span class="p">[</span><span class="m-Decimal">6</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;:&quot;</span><span class="p">;</span><span class="w"> 1938 1939 </span>c<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m-Decimal">7</span><span class="p">;</span><span class="w"> 1940 </span><span class="kt">for</span><span class="w"> </span><span class="p">(</span><span class="kt">uint256</span><span class="w"> </span><span class="nv">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m-Decimal">0</span><span class="p">;</span><span class="w"> </span>i<span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="m-Decimal">32</span><span class="p">;</span><span class="w"> </span>i<span class="o">++</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> 1941 </span>t<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">uint8</span><span class="p">(</span>_data<span class="p">[</span>i<span class="p">])</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="mh">0xf0</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="m-Decimal">4</span><span class="p">;</span><span class="w"> 1942 </span><span class="kt">if</span><span class="w"> </span><span class="p">(</span>t<span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="m-Decimal">10</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> 1943 </span>out<span class="p">[</span>c<span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>bytes1<span class="p">(</span>t<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mh">0x30</span><span class="p">);</span><span class="w"> 1944 </span><span class="p">}</span><span class="w"> </span><span class="kt">else</span><span class="w"> </span><span class="p">{</span><span class="w"> 1945 </span>out<span class="p">[</span>c<span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>bytes1<span class="p">(</span>t<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mh">0x57</span><span class="p">);</span><span class="w"> 1946 </span><span class="p">}</span><span class="w"> 1947 </span>t<span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">uint8</span><span class="p">(</span>_data<span class="p">[</span>i<span class="p">])</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="mh">0x0f</span><span class="p">;</span><span class="w"> 1948 </span><span class="kt">if</span><span class="w"> </span><span class="p">(</span>t<span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="m-Decimal">10</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> 1949 </span>out<span class="p">[</span>c<span class="o">+</span><span class="m-Decimal">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>bytes1<span class="p">(</span>t<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mh">0x30</span><span class="p">);</span><span class="w"> 1950 </span><span class="p">}</span><span class="w"> </span><span class="kt">else</span><span class="w"> </span><span class="p">{</span><span class="w"> 1951 </span>out<span class="p">[</span>c<span class="o">+</span><span class="m-Decimal">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>bytes1<span class="p">(</span>t<span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mh">0x57</span><span class="p">);</span><span class="w"> 1952 </span><span class="p">}</span><span class="w"> 1953 </span>c<span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="m-Decimal">2</span><span class="p">;</span><span class="w"> 1954 </span><span class="p">}</span><span class="w"> 1955 </span><span class="kt">return</span><span class="w"> </span><span class="kt">string</span><span class="p">(</span>out<span class="p">);</span><span class="w"> 1956 </span><span class="p">}</span><span class="w"> 1957 </span><span class="p">}</span> 1958 </pre> 1959 <p>This will return <code>sha256:3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551</code> for <code>tokenId</code> <code>3fdfbfe3b510b69f90cd92618e4c1ec76cf8b9c330bc2da1922acda8f84f9551</code> as input, provided that the <code>tokenId</code> actually exists. That may seem a bit useless at first, but consider the scenario where we want to interface with other NFTs aswell. Or perhaps we are implementing a contract that optionally can support a static web2 URI in storage. By doing it this way, all bases are covered.</p> 1960 </div> 1961 <div class="section" id="decentralized-identifiers"> 1962 <h2>Decentralized identifiers</h2> 1963 <p>Even better would be to add redundancy with autonomous decentralized storage. However, networks like <a class="reference external" href="https://ethswarm.org">Swarm</a> and <a class="reference external" href="https://ipfs.io">IPFS</a> use their own hashing recipes. That means that for every network referenced, we'd have to define an <em>alternative</em> in our reference structure.</p> 1964 <p>Referencing the canonical <code>sha256</code> aswell as the <code>Swarmhash</code> for the same item could then look like this <a class="footnote-reference" href="#footnote-5" id="footnote-reference-5">[5]</a>:</p> 1965 <pre class="code json literal-block"> 1966 <span class="p">{</span><span class="w"> 1967 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo&quot;</span><span class="p">,</span><span class="w"> 1968 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo image&quot;</span><span class="p">,</span><span class="w"> 1969 </span><span class="nt">&quot;image&quot;</span><span class="p">:</span><span class="w"> </span><span class="nt">&quot;data:image/gif;base64,R0lGODlhDgARAKEDAAAAAOjqAPP1APDw8CH+EUNyZWF0ZWQgd2l0aCBHSU1QACwAAAAADgARAAACO5wHqXdrClocodbUaGg34qoBHaJtl7VYIfqBZxepb5C6NBvbIlyucO6jPUIK1CMiSCYvDIVS4GhCJoYCADs=&quot;</span><span class="w"> 1970 </span><span class="nt">&quot;alternatives&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> 1971 </span><span class="p">{</span><span class="w"> 1972 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo&quot;</span><span class="p">,</span><span class="w"> 1973 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo image&quot;</span><span class="p">,</span><span class="w"> 1974 </span><span class="nt">&quot;image&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;bzz:4b9149ee4550f2d786f9ba6584b79a30ee14ae05ff6e84a0f7c7561a14e3b779&quot;</span><span class="w"> 1975 </span><span class="p">},</span><span class="w"> 1976 </span><span class="p">{</span><span class="w"> 1977 </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo&quot;</span><span class="p">,</span><span class="w"> 1978 </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Foo image&quot;</span><span class="p">,</span><span class="w"> 1979 </span><span class="nt">&quot;image&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sha256:d036a4ce7f929b632256225b2bebd81bdd558d3bfe3d96faae61db664708c16f&quot;</span><span class="w"> 1980 </span><span class="p">}</span><span class="w"> 1981 </span><span class="p">]</span><span class="w"> 1982 </span><span class="p">}</span> 1983 </pre> 1984 <hr class="docutils" /> 1985 <!-- --> 1986 <blockquote> 1987 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 1988 <colgroup><col class="label" /><col /></colgroup> 1989 <tbody valign="top"> 1990 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Yes, they are valid URIs actually: <a class="reference external" href="https://www.rfc-archive.org/getrfc.php?rfc=2397">https://www.rfc-archive.org/getrfc.php?rfc=2397</a></td></tr> 1991 </tbody> 1992 </table> 1993 </blockquote> 1994 <!-- --> 1995 <blockquote> 1996 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 1997 <colgroup><col class="label" /><col /></colgroup> 1998 <tbody valign="top"> 1999 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>Likely it would be prudent to start using the official <code>sha3</code> instead of <code>sha2</code> these days, also because the <code>sha2</code> hash is not a builtin for <code>evm</code>. But neither is <code>sha3</code>. The <code>keccak256</code> Bitcoin uses, which EVM has inherited, is a pre-cursor to the <code>keccak</code> published as the <em>official</em> <code>sha3</code>. Still, <code>keccak256</code> and <code>sha3</code> is used interchangeably in opcode lists (and previously in <a class="reference external" href="https://docs.soliditylang.org/en/v0.8.0/050-breaking-changes.html#functions">Solidity</a> too). This has caused me quite a fair bit of confusion, I might add. Apart from it being ambiguous, the <code>keccak256</code> tooling is also less common in the wild. Therefore <code>sha2</code> seems like a safer bet for our experiments. It's not broken yet, after all.</td></tr> 2000 </tbody> 2001 </table> 2002 </blockquote> 2003 <!-- --> 2004 <blockquote> 2005 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 2006 <colgroup><col class="label" /><col /></colgroup> 2007 <tbody valign="top"> 2008 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[3]</a></td><td>The hex result of <code>keccak256(&quot;mintTo(address,uint256)&quot;)</code></td></tr> 2009 </tbody> 2010 </table> 2011 </blockquote> 2012 <!-- --> 2013 <blockquote> 2014 <table class="docutils footnote" frame="void" id="footnote-4" rules="none"> 2015 <colgroup><col class="label" /><col /></colgroup> 2016 <tbody valign="top"> 2017 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-4">[4]</a></td><td><em>Data URI</em> is of no use here, because the hash itself is just nondescript binary data. Luckily <code>&lt;scheme&gt;:&lt;path&gt;</code> is still a valid URI.</td></tr> 2018 </tbody> 2019 </table> 2020 </blockquote> 2021 <!-- --> 2022 <blockquote> 2023 <table class="docutils footnote" frame="void" id="footnote-5" rules="none"> 2024 <colgroup><col class="label" /><col /></colgroup> 2025 <tbody valign="top"> 2026 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-5">[5]</a></td><td>Here the hashes represent the media content itself, not the reference. That's why the <code>sha256</code> one is different than before.</td></tr> 2027 </tbody> 2028 </table> 2029 </blockquote> 2030 <!-- --> 2031 <blockquote> 2032 <table class="docutils citation" frame="void" id="eip721" rules="none"> 2033 <colgroup><col class="label" /><col /></colgroup> 2034 <tbody valign="top"> 2035 <tr><td class="label"><a class="fn-backref" href="#citation-reference-1">[EIP721]</a></td><td><a class="reference external" href="https://eips.ethereum.org/EIPS/eip-721">https://eips.ethereum.org/EIPS/eip-721</a></td></tr> 2036 </tbody> 2037 </table> 2038 </blockquote> 2039 </div> 2040 </content><category term="Code"></category><category term="nft"></category><category term="evm"></category><category term="hash"></category><category term="key-value store"></category><category term="decentralized storage"></category></entry><entry><title>wasm and C - The bare necessities</title><link href="wasm-c-bare.html" rel="alternate"></link><published>2021-05-05T06:15:00+02:00</published><updated>2021-05-05T06:15:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-05-05:wasm-c-bare.html</id><summary type="html"><p class="first last">The bare minimum needed for two-way communication between wasm and C</p> 2041 </summary><content type="html"><p>I am currently resuming my self-improvement task of learning Webassembly. Or, I should rather say, <em>using</em> Webassembly.</p> 2042 <p>Since I have some optimized small code chunks that I want to use in an embedded environment, it seemed sensible that the first step would be to establish two-way communication with C.</p> 2043 <p>In my last outing around three years ago, I was using <a class="reference external" href="https://emscripten.org/">Emscripten</a> to bridge the gap. That tool adds quite a few bells and whistles, and doesn't quite yield that warm, fuzzy bare-metal rush. <a class="reference external" href="https://emscripten.org/">Emscripten</a> relies on <a class="reference external" href="http://clang.org/">Clang</a> and <a class="reference external" href="https://llvm.org/">LLVM</a>, all of which seem to have gotten their <code>wasm</code> support built-in in the meantime (at least on my archlinux system). This it integrates nicely with <a class="reference external" href="https://github.com/WebAssembly/wabt">wabt</a> - the swiss-army knife of Webassembly.</p> 2044 <p>So how far do we get with just <a class="reference external" href="http://clang.org/">clang</a>, <a class="reference external" href="https://llvm.org/">LLVM</a> and <a class="reference external" href="https://github.com/WebAssembly/wabt">wabt</a> ? Let's see if we at least can set up a code snippet which simply writes <em>&quot;foobar&quot;</em> to memory. The host will write <em>&quot;foo&quot;</em>, and <code>wasm</code> will write <em>&quot;bar&quot;</em>.</p> 2045 <div class="section" id="without-libc"> 2046 <h2>Without libc</h2> 2047 <p>This excellent <a class="reference external" href="https://surma.dev/things/c-to-webassembly/">tutorial by Surma</a> provides a good starting point. Go ahead and <strong>read that first</strong>. This text is not a Webassembly primer, so the following will make a lot more sense if you do.</p> 2048 <p>That setup still adds some magic. Namely, the <em>memory</em> and <em>symbol table</em> are here added by the <em>wasm linker</em>. It would be even more fun to pass this from the host system instead.</p> 2049 <p>And so we start:&nbsp;<a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a></p> 2050 <pre class="code c literal-block"> 2051 <span class="ln"> 0 </span><span class="cp">#include</span><span class="w"> </span><span class="cpf">&quot;string.h&quot;</span><span class="cp"> 2052 </span><span class="ln"> 1 </span><span class="cp"></span><span class="w"> 2053 </span><span class="ln"> 2 </span><span class="w"></span><span class="k">extern</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">__heap_base</span><span class="p">;</span><span class="w"> 2054 </span><span class="ln"> 3 </span><span class="w"></span><span class="k">extern</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">call_me_sometime</span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">b</span><span class="p">);</span><span class="w"> 2055 </span><span class="ln"> 4 </span><span class="w"> 2056 </span><span class="ln"> 5 </span><span class="w"></span><span class="kt">void</span><span class="w"> </span><span class="nf">foo</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> 2057 </span><span class="ln"> 6 </span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">__heap_base</span><span class="p">;</span><span class="w"> 2058 </span><span class="ln"> 7 </span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">'b'</span><span class="p">;</span><span class="w"> 2059 </span><span class="ln"> 8 </span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">4</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">'a'</span><span class="p">;</span><span class="w"> 2060 </span><span class="ln"> 9 </span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">'r'</span><span class="p">;</span><span class="w"> 2061 </span><span class="ln">10 </span><span class="w"> </span><span class="n">call_me_sometime</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span><span class="w"> 2062 </span><span class="ln">11 </span><span class="w"></span><span class="p">}</span> 2063 </pre> 2064 <p>Compiling this without linking gives us a hint on what needs to be defined.</p> 2065 <div class="highlight"><pre><span></span>$<span class="w"> </span>clang<span class="w"> </span>--target<span class="o">=</span>wasm32<span class="w"> </span>-nostdlib<span class="w"> </span>-nostartfiles<span class="w"> </span>-o<span class="w"> </span>bare.wasm<span class="w"> </span>-c<span class="w"> </span>bare.c 2066 $<span class="w"> </span>wasm-objdump<span class="w"> </span>-x<span class="w"> </span>bare.wasm 2067 bare.wasm:<span class="w"> </span>file<span class="w"> </span>format<span class="w"> </span>wasm<span class="w"> </span>0x1 2068 2069 Section<span class="w"> </span>Details: 2070 2071 Type<span class="o">[</span><span class="m">2</span><span class="o">]</span>: 2072 <span class="w"> </span>-<span class="w"> </span>type<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="o">()</span><span class="w"> </span>-&gt;<span class="w"> </span>nil 2073 <span class="w"> </span>-<span class="w"> </span>type<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="o">(</span>i32<span class="o">)</span><span class="w"> </span>-&gt;<span class="w"> </span>nil 2074 Import<span class="o">[</span><span class="m">4</span><span class="o">]</span>: 2075 <span class="w"> </span>-<span class="w"> </span>memory<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>pages:<span class="w"> </span><span class="nv">initial</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;-<span class="w"> </span>env.__linear_memory 2076 <span class="w"> </span>-<span class="w"> </span>table<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="nv">type</span><span class="o">=</span>funcref<span class="w"> </span><span class="nv">initial</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;-<span class="w"> </span>env.__indirect_function_table 2077 <span class="w"> </span>-<span class="w"> </span>global<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>i32<span class="w"> </span><span class="nv">mutable</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;-<span class="w"> </span>env.__stack_pointer 2078 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;env.call_me_sometime&gt;<span class="w"> </span>&lt;-<span class="w"> </span>env.call_me_sometime 2079 Function<span class="o">[</span><span class="m">1</span><span class="o">]</span>: 2080 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;foo&gt; 2081 Code<span class="o">[</span><span class="m">1</span><span class="o">]</span>: 2082 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">138</span><span class="w"> </span>&lt;foo&gt; 2083 Custom: 2084 <span class="w"> </span>-<span class="w"> </span>name:<span class="w"> </span><span class="s2">&quot;linking&quot;</span> 2085 <span class="w"> </span>-<span class="w"> </span>symbol<span class="w"> </span>table<span class="w"> </span><span class="o">[</span><span class="nv">count</span><span class="o">=</span><span class="m">4</span><span class="o">]</span> 2086 <span class="w"> </span>-<span class="w"> </span><span class="m">0</span>:<span class="w"> </span>F<span class="w"> </span>&lt;foo&gt;<span class="w"> </span><span class="nv">func</span><span class="o">=</span><span class="m">1</span><span class="w"> </span><span class="nv">binding</span><span class="o">=</span>global<span class="w"> </span><span class="nv">vis</span><span class="o">=</span>hidden 2087 <span class="w"> </span>-<span class="w"> </span><span class="m">1</span>:<span class="w"> </span>G<span class="w"> </span>&lt;env.__stack_pointer&gt;<span class="w"> </span><span class="nv">global</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>undefined<span class="w"> </span><span class="nv">binding</span><span class="o">=</span>global<span class="w"> </span><span class="nv">vis</span><span class="o">=</span>default 2088 <span class="w"> </span>-<span class="w"> </span><span class="m">2</span>:<span class="w"> </span>D<span class="w"> </span>&lt;__heap_base&gt;<span class="w"> </span>undefined<span class="w"> </span><span class="nv">binding</span><span class="o">=</span>global<span class="w"> </span><span class="nv">vis</span><span class="o">=</span>default 2089 <span class="w"> </span>-<span class="w"> </span><span class="m">3</span>:<span class="w"> </span>F<span class="w"> </span>&lt;env.call_me_sometime&gt;<span class="w"> </span><span class="nv">func</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>undefined<span class="w"> </span><span class="nv">binding</span><span class="o">=</span>global<span class="w"> </span><span class="nv">vis</span><span class="o">=</span>default 2090 Custom: 2091 <span class="w"> </span>-<span class="w"> </span>name:<span class="w"> </span><span class="s2">&quot;reloc.CODE&quot;</span> 2092 <span class="w"> </span>-<span class="w"> </span>relocations<span class="w"> </span><span class="k">for</span><span class="w"> </span>section:<span class="w"> </span><span class="m">3</span><span class="w"> </span><span class="o">(</span>Code<span class="o">)</span><span class="w"> </span><span class="o">[</span><span class="m">5</span><span class="o">]</span> 2093 <span class="w"> </span>-<span class="w"> </span>R_wasm_GLOBAL_INDEX_LEB<span class="w"> </span><span class="nv">offset</span><span class="o">=</span>0x000007<span class="o">(</span><span class="nv">file</span><span class="o">=</span>0x000099<span class="o">)</span><span class="w"> </span><span class="nv">symbol</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;env.__stack_pointer&gt; 2094 <span class="w"> </span>-<span class="w"> </span>R_wasm_GLOBAL_INDEX_LEB<span class="w"> </span><span class="nv">offset</span><span class="o">=</span>0x00001c<span class="o">(</span><span class="nv">file</span><span class="o">=</span>0x0000ae<span class="o">)</span><span class="w"> </span><span class="nv">symbol</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;env.__stack_pointer&gt; 2095 <span class="w"> </span>-<span class="w"> </span>R_wasm_MEMORY_ADDR_SLEB<span class="w"> </span><span class="nv">offset</span><span class="o">=</span>0x000031<span class="o">(</span><span class="nv">file</span><span class="o">=</span>0x0000c3<span class="o">)</span><span class="w"> </span><span class="nv">symbol</span><span class="o">=</span><span class="m">2</span><span class="w"> </span>&lt;__heap_base&gt; 2096 <span class="w"> </span>-<span class="w"> </span>R_wasm_FUNCTION_INDEX_LEB<span class="w"> </span><span class="nv">offset</span><span class="o">=</span>0x000073<span class="o">(</span><span class="nv">file</span><span class="o">=</span>0x000105<span class="o">)</span><span class="w"> </span><span class="nv">symbol</span><span class="o">=</span><span class="m">3</span><span class="w"> </span>&lt;env.call_me_sometime&gt; 2097 <span class="w"> </span>-<span class="w"> </span>R_wasm_GLOBAL_INDEX_LEB<span class="w"> </span><span class="nv">offset</span><span class="o">=</span>0x000086<span class="o">(</span><span class="nv">file</span><span class="o">=</span>0x000118<span class="o">)</span><span class="w"> </span><span class="nv">symbol</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;env.__stack_pointer&gt; 2098 Custom: 2099 <span class="w"> </span>-<span class="w"> </span>name:<span class="w"> </span><span class="s2">&quot;producers&quot;</span> 2100 </pre></div> 2101 <p>Using nodejs as the host, we check if we can instantiate a <code>WebAssembly</code> object</p> 2102 <pre class="code javascript literal-block"> 2103 <span class="ln"> 0 </span><span class="kd">const</span><span class="w"> </span><span class="nx">fs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">'fs'</span><span class="p">);</span><span class="w"> 2104 </span><span class="ln"> 1 </span><span class="w"> 2105 </span><span class="ln"> 2 </span><span class="w"></span><span class="kd">const</span><span class="w"> </span><span class="nx">imports</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{}</span><span class="w"> 2106 </span><span class="ln"> 3 </span><span class="w"> 2107 </span><span class="ln"> 4 </span><span class="w"></span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">init</span><span class="p">()</span><span class="w">&nbsp;</span><span class="p">{</span><span class="w"> 2108 </span><span class="ln"> 5 </span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">code</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="s1">'./bare.wasm'</span><span class="p">);</span><span class="w"> 2109 </span><span class="ln"> 6 </span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Module</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span><span class="w"> 2110 </span><span class="ln"> 7 </span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Instance</span><span class="p">(</span><span class="nx">m</span><span class="p">,</span><span class="w"> </span><span class="nx">imports</span><span class="p">);</span><span class="w"> 2111 </span><span class="ln"> 8 </span><span class="w"></span><span class="p">}</span><span class="w"> 2112 </span><span class="ln"> 9 </span><span class="w"></span><span class="nx">init</span><span class="p">();</span> 2113 </pre> 2114 <p>Running this tells us we are apparently missing a property <code>env</code> in the imports object.</p> 2115 <div class="highlight"><pre><span></span>$<span class="w"> </span>node<span class="w"> </span>bare_naive.js 2116 /home/lash/src/tests/wasm/bare/bare_naive.js:8 2117 const<span class="w"> </span><span class="nv">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span>new<span class="w"> </span>WebAssembly.Instance<span class="o">(</span>m,<span class="w"> </span>imports<span class="o">)</span><span class="p">;</span> 2118 <span class="w"> </span>^ 2119 2120 TypeError:<span class="w"> </span>WebAssembly.Instance<span class="o">()</span>:<span class="w"> </span>Import<span class="w"> </span><span class="c1">#0 module=&quot;env&quot; error: module is not an object or function</span> 2121 </pre></div> 2122 <p>That seems to match with the <code>Import</code> section in the <code>objdump</code> output above. Let's stick the <em>memory</em> and <em>table</em> in there. <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a></p> 2123 <p>And let's make a bold guess that the callback function <code>call_me_sometime</code> needs to go in there aswell.</p> 2124 <pre class="code javascript literal-block"> 2125 <span class="ln"> 0 </span><span class="kd">const</span><span class="w"> </span><span class="nx">fs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">require</span><span class="p">(</span><span class="s1">'fs'</span><span class="p">);</span><span class="w"> 2126 </span><span class="ln"> 1 </span><span class="w"> 2127 </span><span class="ln"> 2 </span><span class="w"></span><span class="kd">const</span><span class="w"> </span><span class="nx">memory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Memory</span><span class="p">({</span><span class="nx">initial</span><span class="o">:</span><span class="w"> </span><span class="mf">2</span><span class="p">});</span><span class="w"> 2128 </span><span class="ln"> 3 </span><span class="w"></span><span class="kd">const</span><span class="w"> </span><span class="nx">table</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Table</span><span class="p">({</span><span class="nx">initial</span><span class="o">:</span><span class="w"> </span><span class="mf">3</span><span class="p">,</span><span class="w"> </span><span class="nx">element</span><span class="o">:</span><span class="w"> </span><span class="s1">'anyfunc'</span><span class="p">});</span><span class="w"> 2129 </span><span class="ln"> 4 </span><span class="w"></span><span class="kd">const</span><span class="w"> </span><span class="nx">importsObj</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> 2130 </span><span class="ln"> 5 </span><span class="w"> </span><span class="nx">env</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> 2131 </span><span class="ln"> 6 </span><span class="w"> </span><span class="nx">memory</span><span class="o">:</span><span class="w"> </span><span class="nx">memory</span><span class="p">,</span><span class="w"> 2132 </span><span class="ln"> 7 </span><span class="w"> </span><span class="nx">__linear_memory</span><span class="o">:</span><span class="w"> </span><span class="nx">memory</span><span class="p">,</span><span class="w"> 2133 </span><span class="ln"> 8 </span><span class="w"> </span><span class="nx">__indirect_function_table</span><span class="o">:</span><span class="w"> </span><span class="nx">table</span><span class="p">,</span><span class="w"> 2134 </span><span class="ln"> 9 </span><span class="w"> </span><span class="nx">call_me_sometime</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> 2135 </span><span class="ln">10 </span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">memory</span><span class="p">.</span><span class="nx">buffer</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="mf">9</span><span class="p">)</span><span class="w"> 2136 </span><span class="ln">11 </span><span class="w"> </span><span class="nx">a</span><span class="p">.</span><span class="nx">set</span><span class="p">([</span><span class="mh">0x66</span><span class="p">,</span><span class="w"> </span><span class="mh">0x6f</span><span class="p">,</span><span class="w"> </span><span class="mh">0x6f</span><span class="p">],</span><span class="w"> </span><span class="mf">0</span><span class="p">);</span><span class="w"> 2137 </span><span class="ln">12 </span><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s1">'heap is at: '</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">n</span><span class="p">);</span><span class="w"> 2138 </span><span class="ln">13 </span><span class="w"> </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'heap contains: '</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">TextDecoder</span><span class="p">().</span><span class="nx">decode</span><span class="p">(</span><span class="nx">a</span><span class="p">));</span><span class="w"> 2139 </span><span class="ln">14 </span><span class="w"> </span><span class="p">},</span><span class="w"> 2140 </span><span class="ln">15 </span><span class="w"> </span><span class="p">},</span><span class="w"> 2141 </span><span class="ln">16 </span><span class="w"></span><span class="p">}</span><span class="w"> 2142 </span><span class="ln">17 </span><span class="w"> 2143 </span><span class="ln">18 </span><span class="w"></span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">init</span><span class="p">()</span><span class="w">&nbsp;</span><span class="p">{</span><span class="w"> 2144 </span><span class="ln">19 </span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">code</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="s1">'./bare.wasm'</span><span class="p">);</span><span class="w"> 2145 </span><span class="ln">20 </span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Module</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span><span class="w"> 2146 </span><span class="ln">21 </span><span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">WebAssembly</span><span class="p">.</span><span class="nx">Instance</span><span class="p">(</span><span class="nx">m</span><span class="p">,</span><span class="w"> </span><span class="nx">importsObj</span><span class="p">);</span><span class="w"> 2147 </span><span class="ln">22 </span><span class="w"> </span><span class="nx">i</span><span class="p">.</span><span class="nx">exports</span><span class="p">.</span><span class="nx">foo</span><span class="p">();</span><span class="w"> 2148 </span><span class="ln">23 </span><span class="w"></span><span class="p">}</span><span class="w"> 2149 </span><span class="ln">24 </span><span class="w"></span><span class="nx">init</span><span class="p">();</span> 2150 </pre> 2151 <p>The linker needs a little help from us for this:</p> 2152 <ul class="simple"> 2153 <li>Our callback function will not be available at link time, so we have to <code>--allow-undefined</code> to promise that the host has got this covered.</li> 2154 <li><code>--import-memory</code> and <code>--import-table</code> to enable us to get memory and symbol table from the host.</li> 2155 <li><code>--export=&quot;foo&quot;</code> to make sure we only export exactly what we intend to from our <code>wasm</code>.</li> 2156 </ul> 2157 <div class="highlight"><pre><span></span>$<span class="w"> </span>clang<span class="w"> </span>--target<span class="o">=</span>wasm32<span class="w"> </span>-nostdlib<span class="w"> </span>-nostartfiles<span class="w"> </span>-Wl,--no-entry<span class="w"> </span>-Wl,--export<span class="o">=</span><span class="s2">&quot;foo&quot;</span><span class="w"> </span>-Wl,--import-memory<span class="w"> </span>-Wl,--import-table<span class="w"> </span>-Wl,--allow-undefined<span class="w"> </span>-o<span class="w"> </span>bare.wasm<span class="w"> </span>bare.c 2158 </pre></div> 2159 <p>And that should give us:</p> 2160 <div class="highlight"><pre><span></span>$<span class="w"> </span>node<span class="w"> </span>bare.js 2161 heap<span class="w"> </span>is<span class="w"> </span>at:<span class="w"> </span><span class="m">66560</span> 2162 heap<span class="w"> </span>contains:<span class="w"> </span>foobar 2163 </pre></div> 2164 <p>This way of pointing to memory is of course grossly inadequate <em>and</em> unsafe <em>and</em> ridiculous for any purpose more advanced that this one. So some proper memory management would not be a bad thing.</p> 2165 </div> 2166 <div class="section" id="adding-libc"> 2167 <h2>Adding libc</h2> 2168 <p>And what do you know. In other news since last time I looked at this is the addition of &quot;a libc for WebAssembly programs built on top of WASI system calls.&quot; <a class="citation-reference" href="#wasi-libc" id="citation-reference-1">[wasi-libc]</a>. Let's see if we can add a slightly less manual way of handling memory with <code>malloc</code> and <code>memcpy</code></p> 2169 <pre class="code c literal-block"> 2170 <span class="ln"> 0 </span><span class="cp">#ifdef HAVE_LIBC 2171 </span><span class="ln"> 1 </span><span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;string.h&gt;</span><span class="cp"> 2172 </span><span class="ln"> 2 </span><span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;stdlib.h&gt;</span><span class="cp"> 2173 </span><span class="ln"> 3 </span><span class="cp">#endif 2174 </span><span class="ln"> 4 </span><span class="cp"></span><span class="w"> 2175 </span><span class="ln"> 5 </span><span class="w"></span><span class="k">extern</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">__heap_base</span><span class="p">;</span><span class="w"> 2176 </span><span class="ln"> 6 </span><span class="w"></span><span class="k">extern</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="nf">call_me_sometime</span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">b</span><span class="p">);</span><span class="w"> 2177 </span><span class="ln"> 7 </span><span class="w"> 2178 </span><span class="ln"> 8 </span><span class="w"></span><span class="kt">void</span><span class="w"> </span><span class="nf">foo</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> 2179 </span><span class="ln"> 9 </span><span class="w"> 2180 </span><span class="ln">10 </span><span class="w"></span><span class="cp">#ifdef HAVE_LIBC 2181 </span><span class="ln">11 </span><span class="cp"></span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="p">;</span><span class="w"> 2182 </span><span class="ln">12 </span><span class="w"> </span><span class="n">buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">malloc</span><span class="p">(</span><span class="mi">9</span><span class="p">);</span><span class="w"> 2183 </span><span class="ln">13 </span><span class="w"> </span><span class="n">memcpy</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;bazbar&quot;</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">);</span><span class="w"> 2184 </span><span class="ln">14 </span><span class="w"></span><span class="cp">#else 2185 </span><span class="ln">15 </span><span class="cp"></span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">__heap_base</span><span class="p">;</span><span class="w"> 2186 </span><span class="ln">16 </span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">'b'</span><span class="p">;</span><span class="w"> 2187 </span><span class="ln">17 </span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">4</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">'a'</span><span class="p">;</span><span class="w"> 2188 </span><span class="ln">18 </span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="n">buf</span><span class="o">+</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sc">'r'</span><span class="p">;</span><span class="w"> 2189 </span><span class="ln">19 </span><span class="w"></span><span class="cp">#endif 2190 </span><span class="ln">20 </span><span class="cp"></span><span class="w"> </span><span class="n">call_me_sometime</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span><span class="w"> 2191 </span><span class="ln">21 </span><span class="w"> 2192 </span><span class="ln">22 </span><span class="w"></span><span class="cp">#ifdef HAVE_LIBC 2193 </span><span class="ln">23 </span><span class="cp"></span><span class="w"> </span><span class="n">free</span><span class="p">(</span><span class="n">buf</span><span class="p">);</span><span class="w"> 2194 </span><span class="ln">24 </span><span class="w"></span><span class="cp">#endif 2195 </span><span class="ln">25 </span><span class="cp"></span><span class="w"> 2196 </span><span class="ln">26 </span><span class="w"></span><span class="p">}</span> 2197 </pre> 2198 <p>As you see, we need a few more parameters for the compiler and linker at this point. The <code>--target=wasm32-unknown-wasi --sysroot /opt/wasi-libc .. /opt/wasi-libc/lib/wasm32-wasi/libc.a</code> is needed to hook us up with headers and symbols for the libc.</p> 2199 <p>My archlinux puts that sysroot in <code>/opt/wasi-libc</code>, that may of course not be the case elsewhere.</p> 2200 <div class="highlight"><pre><span></span>$<span class="w"> </span>clang<span class="w"> </span>-DHAVE_LIBC<span class="o">=</span><span class="m">1</span><span class="w"> </span>--target<span class="o">=</span>wasm32-unknown-wasi<span class="w"> </span>--sysroot<span class="w"> </span>/opt/wasi-libc<span class="w"> </span>-nostdlib<span class="w"> </span>-nostartfiles<span class="w"> </span>-Wl,--no-entry<span class="w"> </span>-Wl,--export<span class="o">=</span><span class="s2">&quot;foo&quot;</span><span class="w"> </span>-Wl,--import-memory<span class="w"> </span>-Wl,--import-table<span class="w"> </span>-Wl,--allow-undefined<span class="w"> </span>-o<span class="w"> </span>bare.wasm<span class="w"> </span>bare.c<span class="w"> </span>/opt/wasi-libc/lib/wasm32-wasi/libc.a 2201 $<span class="w"> </span>wasm-objdump<span class="w"> </span>-x<span class="w"> </span>bare.wasm 2202 2203 bare.wasm:<span class="w"> </span>file<span class="w"> </span>format<span class="w"> </span>wasm<span class="w"> </span>0x1 2204 2205 Section<span class="w"> </span>Details: 2206 2207 Type<span class="o">[</span><span class="m">3</span><span class="o">]</span>: 2208 <span class="w"> </span>-<span class="w"> </span>type<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="o">(</span>i32<span class="o">)</span><span class="w"> </span>-&gt;<span class="w"> </span>nil 2209 <span class="w"> </span>-<span class="w"> </span>type<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="o">()</span><span class="w"> </span>-&gt;<span class="w"> </span>nil 2210 <span class="w"> </span>-<span class="w"> </span>type<span class="o">[</span><span class="m">2</span><span class="o">]</span><span class="w"> </span><span class="o">(</span>i32<span class="o">)</span><span class="w"> </span>-&gt;<span class="w"> </span>i32 2211 Import<span class="o">[</span><span class="m">3</span><span class="o">]</span>: 2212 <span class="w"> </span>-<span class="w"> </span>memory<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>pages:<span class="w"> </span><span class="nv">initial</span><span class="o">=</span><span class="m">2</span><span class="w"> </span>&lt;-<span class="w"> </span>env.memory 2213 <span class="w"> </span>-<span class="w"> </span>table<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="nv">type</span><span class="o">=</span>funcref<span class="w"> </span><span class="nv">initial</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;-<span class="w"> </span>env.__indirect_function_table 2214 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;call_me_sometime&gt;<span class="w"> </span>&lt;-<span class="w"> </span>env.call_me_sometime 2215 Function<span class="o">[</span><span class="m">7</span><span class="o">]</span>: 2216 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;foo&gt; 2217 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">2</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">2</span><span class="w"> </span>&lt;malloc&gt; 2218 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">3</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">2</span><span class="w"> </span>&lt;dlmalloc&gt; 2219 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">4</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;free&gt; 2220 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">5</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;dlfree&gt; 2221 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">6</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>&lt;abort&gt; 2222 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">7</span><span class="o">]</span><span class="w"> </span><span class="nv">sig</span><span class="o">=</span><span class="m">2</span><span class="w"> </span>&lt;sbrk&gt; 2223 Global<span class="o">[</span><span class="m">2</span><span class="o">]</span>: 2224 <span class="w"> </span>-<span class="w"> </span>global<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>i32<span class="w"> </span><span class="nv">mutable</span><span class="o">=</span><span class="m">1</span><span class="w"> </span>-<span class="w"> </span>init<span class="w"> </span><span class="nv">i32</span><span class="o">=</span><span class="m">67072</span> 2225 <span class="w"> </span>-<span class="w"> </span>global<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span>i32<span class="w"> </span><span class="nv">mutable</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>&lt;__heap_base&gt;<span class="w"> </span>-<span class="w"> </span>init<span class="w"> </span><span class="nv">i32</span><span class="o">=</span><span class="m">67072</span> 2226 Export<span class="o">[</span><span class="m">2</span><span class="o">]</span>: 2227 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span>&lt;foo&gt;<span class="w"> </span>-&gt;<span class="w"> </span><span class="s2">&quot;foo&quot;</span> 2228 <span class="w"> </span>-<span class="w"> </span>global<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span>-&gt;<span class="w"> </span><span class="s2">&quot;__heap_base&quot;</span> 2229 Code<span class="o">[</span><span class="m">7</span><span class="o">]</span>: 2230 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">171</span><span class="w"> </span>&lt;foo&gt; 2231 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">2</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">10</span><span class="w"> </span>&lt;malloc&gt; 2232 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">3</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">6984</span><span class="w"> </span>&lt;dlmalloc&gt; 2233 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">4</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">10</span><span class="w"> </span>&lt;free&gt; 2234 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">5</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">1908</span><span class="w"> </span>&lt;dlfree&gt; 2235 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">6</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">4</span><span class="w"> </span>&lt;abort&gt; 2236 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">7</span><span class="o">]</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">78</span><span class="w"> </span>&lt;sbrk&gt; 2237 Data<span class="o">[</span><span class="m">2</span><span class="o">]</span>: 2238 <span class="w"> </span>-<span class="w"> </span>segment<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span><span class="nv">memory</span><span class="o">=</span><span class="m">0</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">7</span><span class="w"> </span>-<span class="w"> </span>init<span class="w"> </span><span class="nv">i32</span><span class="o">=</span><span class="m">1024</span> 2239 <span class="w"> </span>-<span class="w"> </span><span class="m">0000400</span>:<span class="w"> </span><span class="m">6261</span><span class="w"> </span>7a62<span class="w"> </span><span class="m">6172</span><span class="w"> </span><span class="m">00</span><span class="w"> </span>bazbar. 2240 <span class="w"> </span>-<span class="w"> </span>segment<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span><span class="nv">memory</span><span class="o">=</span><span class="m">0</span><span class="w"> </span><span class="nv">size</span><span class="o">=</span><span class="m">500</span><span class="w"> </span>-<span class="w"> </span>init<span class="w"> </span><span class="nv">i32</span><span class="o">=</span><span class="m">1032</span> 2241 <span class="w"> </span>-<span class="w"> </span><span class="m">0000408</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2242 <span class="w"> </span>-<span class="w"> </span><span class="m">0000418</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2243 <span class="w"> </span>-<span class="w"> </span><span class="m">0000428</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2244 <span class="w"> </span>-<span class="w"> </span><span class="m">0000438</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2245 <span class="w"> </span>-<span class="w"> </span><span class="m">0000448</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2246 <span class="w"> </span>-<span class="w"> </span><span class="m">0000458</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2247 <span class="w"> </span>-<span class="w"> </span><span class="m">0000468</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2248 <span class="w"> </span>-<span class="w"> </span><span class="m">0000478</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2249 <span class="w"> </span>-<span class="w"> </span><span class="m">0000488</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2250 <span class="w"> </span>-<span class="w"> </span><span class="m">0000498</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2251 <span class="w"> </span>-<span class="w"> </span>00004a8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2252 <span class="w"> </span>-<span class="w"> </span>00004b8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2253 <span class="w"> </span>-<span class="w"> </span>00004c8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2254 <span class="w"> </span>-<span class="w"> </span>00004d8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2255 <span class="w"> </span>-<span class="w"> </span>00004e8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2256 <span class="w"> </span>-<span class="w"> </span>00004f8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2257 <span class="w"> </span>-<span class="w"> </span><span class="m">0000508</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2258 <span class="w"> </span>-<span class="w"> </span><span class="m">0000518</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2259 <span class="w"> </span>-<span class="w"> </span><span class="m">0000528</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2260 <span class="w"> </span>-<span class="w"> </span><span class="m">0000538</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2261 <span class="w"> </span>-<span class="w"> </span><span class="m">0000548</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2262 <span class="w"> </span>-<span class="w"> </span><span class="m">0000558</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2263 <span class="w"> </span>-<span class="w"> </span><span class="m">0000568</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2264 <span class="w"> </span>-<span class="w"> </span><span class="m">0000578</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2265 <span class="w"> </span>-<span class="w"> </span><span class="m">0000588</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2266 <span class="w"> </span>-<span class="w"> </span><span class="m">0000598</span>:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2267 <span class="w"> </span>-<span class="w"> </span>00005a8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2268 <span class="w"> </span>-<span class="w"> </span>00005b8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2269 <span class="w"> </span>-<span class="w"> </span>00005c8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2270 <span class="w"> </span>-<span class="w"> </span>00005d8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2271 <span class="w"> </span>-<span class="w"> </span>00005e8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>................ 2272 <span class="w"> </span>-<span class="w"> </span>00005f8:<span class="w"> </span><span class="m">0000</span><span class="w"> </span><span class="m">0000</span><span class="w"> </span>.... 2273 Custom: 2274 <span class="w"> </span>-<span class="w"> </span>name:<span class="w"> </span><span class="s2">&quot;name&quot;</span> 2275 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">0</span><span class="o">]</span><span class="w"> </span>&lt;call_me_sometime&gt; 2276 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">1</span><span class="o">]</span><span class="w"> </span>&lt;foo&gt; 2277 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">2</span><span class="o">]</span><span class="w"> </span>&lt;malloc&gt; 2278 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">3</span><span class="o">]</span><span class="w"> </span>&lt;dlmalloc&gt; 2279 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">4</span><span class="o">]</span><span class="w"> </span>&lt;free&gt; 2280 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">5</span><span class="o">]</span><span class="w"> </span>&lt;dlfree&gt; 2281 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">6</span><span class="o">]</span><span class="w"> </span>&lt;abort&gt; 2282 <span class="w"> </span>-<span class="w"> </span>func<span class="o">[</span><span class="m">7</span><span class="o">]</span><span class="w"> </span>&lt;sbrk&gt; 2283 Custom: 2284 <span class="w"> </span>-<span class="w"> </span>name:<span class="w"> </span><span class="s2">&quot;producers&quot;</span> 2285 </pre></div> 2286 <p>What luxury. And of course, our <code>bare.wasm</code> file just grew from 350 bytes to 10k...</p> 2287 <p>We don't have to change our <code>javascript</code> code at this point. Simply run again, and get:</p> 2288 <div class="highlight"><pre><span></span>$<span class="w"> </span>node<span class="w"> </span>bare.js 2289 heap<span class="w"> </span>is<span class="w"> </span>at:<span class="w"> </span><span class="m">67088</span> 2290 heap<span class="w"> </span>contains:<span class="w"> </span>foobazbar 2291 </pre></div> 2292 <!-- --> 2293 <blockquote> 2294 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 2295 <colgroup><col class="label" /><col /></colgroup> 2296 <tbody valign="top"> 2297 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td><code>__heap_base</code> will be set by default by the wasm environment, and is thus available as an external symbol.</td></tr> 2298 </tbody> 2299 </table> 2300 </blockquote> 2301 <!-- --> 2302 <blockquote> 2303 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 2304 <colgroup><col class="label" /><col /></colgroup> 2305 <tbody valign="top"> 2306 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>After linking the memory symbol meeds to be called <code>memory</code> instead of <code>__linear_memory</code> for some reason. Thus we add both here for clarity.</td></tr> 2307 </tbody> 2308 </table> 2309 </blockquote> 2310 <!-- --> 2311 <blockquote> 2312 <table class="docutils citation" frame="void" id="wasi-libc" rules="none"> 2313 <colgroup><col class="label" /><col /></colgroup> 2314 <tbody valign="top"> 2315 <tr><td class="label"><a class="fn-backref" href="#citation-reference-1">[wasi-libc]</a></td><td><a class="reference external" href="https://github.com/WebAssembly/wasi-libc">https://github.com/WebAssembly/wasi-libc</a></td></tr> 2316 </tbody> 2317 </table> 2318 </blockquote> 2319 </div> 2320 </content><category term="Code"></category><category term="wasm"></category><category term="c"></category><category term="clang"></category><category term="llvm"></category></entry><entry><title>Proving what you link to</title><link href="web-snapshot.html" rel="alternate"></link><published>2021-05-03T14:22:00+02:00</published><updated>2024-06-19T16:21:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-05-03:web-snapshot.html</id><summary type="html"><p class="first last">Generating proof of a web resource when you read and share</p> 2321 </summary><content type="html"><p>When we send a link to someone, we are trusting that whoever is behind that link will serve that someone the content we actually saw. Give or take an ad or two.</p> 2322 <p>Also, when we bookmark the link for later retrieval. We are trusting that the entity will be serving that content at any time in the future that you may want it.</p> 2323 <p>This may not be a huge problem if the page is merely a list of <a class="reference external" href="https://thoughtcatalog.com/clint-conway/2016/08/50-of-the-funniest-dead-baby-jokes-of-all-time/">dead baby jokes</a> <a class="footnote-reference" href="#footnote-6" id="footnote-reference-1">[6]</a>. They are objectively funny, of course. But you can also get along without them.</p> 2324 <p>But what of the case of formal, scientific texts we may depend on, and that use citations from the web as part of their source material? Usually, they refer to a source by <em>link</em> and <em>date of retrieval</em>. This is not of much use unless the actual source and/or render they saw at that time also is available.</p> 2325 <p>That may not always be the case.</p> 2326 <div class="section" id="take-care-of-your-shelf"> 2327 <h2>Take care of your shelf</h2> 2328 <blockquote> 2329 &quot;No worries, the <a class="reference external" href="https://archive.org">Wayback Machine</a> has me covered.&quot;</blockquote> 2330 <p>Yes. But no. The Wayback Machine is a (thus far) centralized entity that depend on a few idealists and donations to keep going. If they stop to keep going, they depend on passing the buck to someone else. If that someone is evil, they may take it and rewrite history to suit themselves. If that someone else cannot be found, it becomes a garbage collection blip on Bezos' <a class="reference external" href="https://gizmodo.com/i-tried-to-block-amazon-from-my-life-it-was-impossible-1830565336">infrastructure monopoly</a> dashboard. <a class="footnote-reference" href="#footnote-1" id="footnote-reference-2">[1]</a></p> 2331 <p>That aside, sources like Wayback Machine are like libraries. Libraries are, of course, essential. Not only because they serve as one of the pillars of democracy, providing free access to knowledge for everyone. They are also essential because it's simply not very practical for you to pre-emptively own all the books that you may at some point want to read. Let alone ask around in your neighborhood if they happen to have a copy (although a crowd-sourced library app sounds like a fun decentralization project to explore).</p> 2332 <p>You may however want to keep a copy of the books you <em>depend</em> on, and the ones you <em>really like</em>. Just to make really sure you have it available when you want it. Then, if some New Public Management clowns get the chance to gut public infrastructure where you live, or someone starts a good old-fashioned fascist book burning, you have yourself covered.</p> 2333 </div> 2334 <div class="section" id="a-lack-of-friction"> 2335 <h2>A lack of friction</h2> 2336 <p>Yes, stuff may disappear on the web. Just as books may.</p> 2337 <p>On the web that stuff can get <em>rewritten</em>. Books may be rewritten, too. <a class="footnote-reference" href="#footnote-2" id="footnote-reference-3">[2]</a> The previous editions of the books will still exist as independent physical objects until they degrade, as long as something is not actively done to them. But so too with data on storage media. If it is not renewed or copied during that lifetime it may degrade until it becomes illegible. And of course, it may also simply be deleted. And without the smell of smoke at that.</p> 2338 <p>That's the difference that seems to leap out when using this imagery. How <em>easy</em> it is to change and destroy stuff at scale on the web compared with the real world of books. And how inconspicuously it can happen, without <em>anyone</em> noticing. And for those who notice, it is very hard to prove what has changed, unless you have a copy. <a class="footnote-reference" href="#footnote-5" id="footnote-reference-4">[5]</a></p> 2339 <p>So what can we do? Copies and proofs are definitely keywords here. Fortunately, copying is what computers are all about. And making a cryptographical proof of what you see is easy enough these days, too. The tricky bit is to build <em>credibility</em> around that proof. But let's stick our head in the sand and start with the easy part and see where it takes us.</p> 2340 </div> 2341 <div class="section" id="look-no-head"> 2342 <h2>Look, no head</h2> 2343 <!-- .. WARNING:: 2344 2345 Nerdy zone ahead --> 2346 <p>A good start is to dump the document source to disk, then calculating and storing the sum of it.</p> 2347 <p>In many cases this will be insufficient, though, as many sites populates the DOM through scripts, either in part or in full. As the use-case here is humans voting on what they see is what they get, human aids will be needed. In other words, we need to render the page to store what we actually see.</p> 2348 <p>Printing to PDF from the browser is an option, but that is really difficult to automate. Fortunately, modern browser engines provide command line access to rendering. Since I mostly use <a class="reference external" href="https://brave.com/">Brave Browser</a> these days, we'll use <em>headless Chromium</em> here.</p> 2349 <p>In addition to source, sum and rendering, we should also include a copy of the request headers for good measure.</p> 2350 <p>Thus, we end up with something like this.</p> 2351 <pre class="code bash literal-block"> 2352 <span class="ch">#!/bin/bash 2353 </span><span class="w"> 2354 </span><span class="nv">f</span><span class="o">=</span><span class="si">${</span><span class="nv">WEBSHOT_OUTPUT_DIR</span><span class="k">:-</span><span class="p">/tmp</span><span class="si">}</span><span class="w"> 2355 </span><span class="nv">url</span><span class="o">=</span><span class="nv">$1</span><span class="w"> 2356 </span><span class="nv">title</span><span class="o">=</span><span class="nv">$2</span><span class="w"> 2357 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span>using<span class="w"> </span>outdir<span class="w"> </span><span class="nv">$f</span><span class="w"> 2358 2359 </span><span class="nb">set</span><span class="w"> </span>+e<span class="w"> 2360 2361 </span><span class="c1"># prepare 2362 </span><span class="nv">d</span><span class="o">=</span><span class="sb">`</span><span class="nv">TZ</span><span class="o">=</span>UTC<span class="w"> </span>date<span class="w"> </span>+%Y%m%d%H%M<span class="sb">`</span><span class="w"> 2363 </span><span class="nv">t</span><span class="o">=</span><span class="sb">`</span>mktemp<span class="w"> </span>-d<span class="sb">`</span><span class="w"> 2364 </span><span class="nb">pushd</span><span class="w"> </span><span class="nv">$t</span><span class="w"> 2365 2366 </span><span class="c1"># store raw outputs 2367 </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span>url.txt<span class="w"> 2368 </span>curl<span class="w"> </span>-s<span class="w"> </span>-I<span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span>headers.txt<span class="w"> 2369 </span>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>GET<span class="w"> </span><span class="nv">$1</span><span class="w"> </span>&gt;<span class="w"> </span>contents.txt<span class="w"> 2370 </span><span class="nv">z</span><span class="o">=</span><span class="sb">`</span>sha256sum<span class="w"> </span>contents.txt<span class="sb">`</span><span class="w"> 2371 </span><span class="nb">echo</span><span class="w"> </span><span class="nv">$z</span><span class="w"> </span>&gt;<span class="w"> </span>contents.txt.sha256<span class="w"> 2372 </span><span class="nv">h</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span>-n<span class="w"> </span><span class="nv">$z</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">'{ print $1; }'</span><span class="sb">`</span><span class="w"> 2373 2374 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$title</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 2375 </span><span class="nv">title</span><span class="o">=</span><span class="nv">$h</span><span class="w"> 2376 </span><span class="k">fi</span><span class="w"> 2377 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span>using<span class="w"> </span>title<span class="w"> </span><span class="nv">$title</span><span class="w"> 2378 2379 </span><span class="c1"># rendered snapshot 2380 </span>chromium<span class="w"> </span>--headless<span class="w"> </span>--print-to-pdf<span class="w"> </span><span class="nv">$url</span><span class="w"> 2381 </span><span class="nv">n</span><span class="o">=</span><span class="si">${</span><span class="nv">d</span><span class="si">}</span>_<span class="si">${</span><span class="nv">h</span><span class="si">}</span><span class="w"> 2382 </span>mv<span class="w"> </span>output.pdf<span class="w"> </span><span class="nv">$n</span>.pdf<span class="w"> 2383 2384 </span><span class="c1"># store result 2385 </span>mkdir<span class="w"> </span>-p<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$f</span><span class="s2">/</span><span class="nv">$title</span><span class="s2">&quot;</span><span class="w"> 2386 </span>tar<span class="w"> </span>-zcvf<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$f</span><span class="s2">/</span><span class="nv">$title</span><span class="s2">/</span><span class="nv">$n</span><span class="s2">.tar.gz&quot;</span><span class="w"> </span>*<span class="w"> 2387 2388 </span><span class="c1"># clean up 2389 </span><span class="nb">popd</span><span class="w"> 2390 2391 </span><span class="nb">set</span><span class="w"> </span>-e 2392 </pre> 2393 </div> 2394 <div class="section" id="what-does-this-mean"> 2395 <h2>What does this mean</h2> 2396 <p>Let's sum up what information we've managed to store with this operation.</p> 2397 <ul class="simple"> 2398 <li>We have a copy of the unrendered source. It may or may not include all the information we want to store.</li> 2399 <li>We have a fingerprint of that source.</li> 2400 <li>We have a copy of the headers we were served when retrieving the document. <a class="footnote-reference" href="#footnote-3" id="footnote-reference-5">[3]</a></li> 2401 <li>We have a image copy of what we actually saw when visiting the page.</li> 2402 <li>We have a date and time for retrieval (file attributes).</li> 2403 </ul> 2404 <p>To link the headers together with the visual copy, we could sum the header file and image file aswell, put those sums together with the content sum in a deterministic order, and calculate the sum of those sums. E.g. <a class="footnote-reference" href="#footnote-4" id="footnote-reference-6">[4]</a></p> 2405 <div class="highlight"><pre><span></span>$<span class="w"> </span>cp<span class="w"> </span>contents.txt.sha256<span class="w"> </span>sums.txt 2406 $<span class="w"> </span>sha256sum<span class="w"> </span>headers.txt<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{ print $1; }&#39;</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>sums.txt 2407 $<span class="w"> </span>sha256sum<span class="w"> </span>&lt;pdf<span class="w"> </span>file&gt;<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{ print $1; }&#39;</span><span class="w"> </span>&gt;&gt;<span class="w"> </span>sums.txt 2408 $<span class="w"> </span>sha256sum<span class="w"> </span>sums.txt<span class="w"> </span><span class="p">|</span><span class="w"> </span>awk<span class="w"> </span><span class="s1">&#39;{ print $1; }&#39;</span><span class="w"> </span>&gt;<span class="w"> </span>topsum.txt 2409 </pre></div> 2410 <p>If we now sign <em>this</em> sum, we are confirming that for this particuar resource:</p> 2411 <blockquote> 2412 &quot;This was the source. These were the headers for that source. This is how that source served in that manner looked for <strong>ME</strong> at the time&quot;</blockquote> 2413 </div> 2414 <div class="section" id="proving-links-in-this-post"> 2415 <h2>Proving links in this post</h2> 2416 <p>This post makes use of several external links to articles. So as a final step, let's eat our own dogfood and add proofs for them.</p> 2417 <table border="1" class="docutils"> 2418 <caption>Article retrieval proofs</caption> 2419 <colgroup> 2420 <col width="34%" /> 2421 <col width="22%" /> 2422 <col width="22%" /> 2423 <col width="22%" /> 2424 </colgroup> 2425 <thead valign="bottom"> 2426 <tr><th class="head">Link</th> 2427 <th class="head">Content</th> 2428 <th class="head">Header</th> 2429 <th class="head">Image</th> 2430 </tr> 2431 </thead> 2432 <tbody valign="top"> 2433 <tr><td><a class="reference external" href="https://gizmodo.com/i-tried-to-block-amazon-from-my-life-it-was-impossible-1830565336">https://gizmodo.com/i-tried-to-block-amazon-from-my-life-it-was-impossible-1830565336</a></td> 2434 <td><a class="reference external" href="misc/web_snapshot/0c657ec25e55e72702b0473f62e6d632dece992485f67c407ed1f748b3f40bc2.txt">0c657ec2</a></td> 2435 <td><a class="reference external" href="misc/web_snapshot/360125fa513b8db8380eb2b62c4479164baa5b48d8544b2242bcc7305bad0de4.txt">360125fa</a></td> 2436 <td><a class="reference external" href="misc/web_snapshot/a2584ca07ba16b15b9fad315a767cbb0c4b7124abdfd578cab2afb4dce9d1971.pdf">a2584ca0</a></td> 2437 </tr> 2438 <tr><td><a class="reference external" href="https://www.thelocal.se/20111109/37244/">https://www.thelocal.se/20111109/37244/</a></td> 2439 <td><a class="reference external" href="misc/web_snapshot/be8741ff91c6e3eac760a3a4652b57f47bce92fa9b617662296c2ab5b3c5fe31.txt">be8741ff</a></td> 2440 <td><a class="reference external" href="misc/web_snapshot/6c20e7678a0235467687425b9818718ab143fd477afee2087d7f79c933abdc75.txt">6c20e767</a></td> 2441 <td><a class="reference external" href="misc/web_snapshot/f1c557b9555149dde570976ed956ffb17d17b99cea5f2651020f66408dacf301.pdf">f1c557b9</a></td> 2442 </tr> 2443 <tr><td><a class="reference external" href="https://www.statista.com/chart/18819/worldwide-market-share-of-leading-cloud-infrastructure-service-providers/">https://www.statista.com/chart/18819/worldwide-market-share-of-leading-cloud-infrastructure-service-providers/</a></td> 2444 <td><a class="reference external" href="misc/web_snapshot/595a07b85e6b75a41fe0880c8538f15c4b6da5770db230d986efa2e080ca479a.txt">595a07b8</a></td> 2445 <td><a class="reference external" href="misc/web_snapshot/f2aa95b42c5420cb11ccbf1cb084d5e5ccc3fc6cd575c51e9ee473b9df19c890.txt">f2aa95b4</a></td> 2446 <td><a class="reference external" href="misc/web_snapshot/6a610fa69d4909cc9aa1dcb7105203cc50c1bcf26733411964f95cbcbdf37eb5.pdf">6a610fa6</a></td> 2447 </tr> 2448 <tr><td><a class="reference external" href="https://web.archive.org/web/20170710185409/https://www.botkyrka.se/arkiv/nyhetsarkiv/nyheter-startsida/2017-07-10-angaende-uttalanden-av-journalisten-janne-josefsson-om-bibliotek-botkyrka.html">https://web.archive.org/web/20170710185409/https://www.botkyrka.se/arkiv/nyhetsarkiv/nyheter-startsida/2017-07-10-angaende-uttalanden-av-journalisten-janne-josefsson-om-bibliotek-botkyrka.html</a></td> 2449 <td><a class="reference external" href="misc/web_snapshot/5a806be410da82986a85f68658279234c1a5cf3bb6dc55da137d5274dc722f26.txt">5a806be4</a></td> 2450 <td><a class="reference external" href="misc/web_snapshot/078d9fe6be070de0d378a6e903f8558a7da4917cba5c95ca453ae1936541e4f6.txt">078d9fe6</a></td> 2451 <td><a class="reference external" href="misc/web_snapshot/2d0f0e69e7c8b6ffe9ff8ffc8702a78d6a2d46ab1edd4123e84eb211171d6cde.pdf">2d0f0e69</a></td> 2452 </tr> 2453 <tr><td><a class="reference external" href="https://www.breitbart.com/europe/2017/07/19/swedens-libraries-pulp-traditional-children-books-racist-phrases/">https://www.breitbart.com/europe/2017/07/19/swedens-libraries-pulp-traditional-children-books-racist-phrases/</a></td> 2454 <td><a class="reference external" href="misc/web_snapshot/a8c6b61cec2cce1a58bcf7a65091a6c2e8510ca5fa17f2d242d286f087d95cd5.txt">a8c6b61c</a></td> 2455 <td><a class="reference external" href="misc/web_snapshot/3ba9094b295a898cfe8cba256f4ebf65ef98ff05bb3034e048e517d52fc13d33.txt">3ba9094b</a></td> 2456 <td><a class="reference external" href="misc/web_snapshot/eb76a87f224b0e4f4e5d1673b1505aaeee28d8ad03ce544656b6f5ac7c4d9983.pdf">eb76a87f</a></td> 2457 </tr> 2458 </tbody> 2459 </table> 2460 <p>Clicking on the &quot;image&quot; links, we see that thanks to the recent ubiquity of cookie nag boxes screaming &quot;accept all&quot; at you, those very boxes are now blocking the content we want to get at. So we will need more work to get to where we want by automation. But it's s start.</p> 2461 <!-- --> 2462 <blockquote> 2463 <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> 2464 <colgroup><col class="label" /><col /></colgroup> 2465 <tbody valign="top"> 2466 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[1]</a></td><td>Early 2021 survey puts Amazon at one-third of the global market share. <a class="reference external" href="https://www.statista.com/chart/18819/worldwide-market-share-of-leading-cloud-infrastructure-service-providers/">https://www.statista.com/chart/18819/worldwide-market-share-of-leading-cloud-infrastructure-service-providers/</a></td></tr> 2467 </tbody> 2468 </table> 2469 </blockquote> 2470 <!-- --> 2471 <blockquote> 2472 <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> 2473 <colgroup><col class="label" /><col /></colgroup> 2474 <tbody valign="top"> 2475 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-3">[2]</a></td><td>2011 sparked a <a class="reference external" href="https://www.thelocal.se/20111109/37244/">controversy</a> around Astrid Lindgren's Pippi Longstockings. Echoing those viewpoints, the books were edited in 2015 during which some alleged &quot;racist&quot; content was altered. The rabid rightwing media later spun a <a class="reference external" href="https://www.breitbart.com/europe/2017/07/19/swedens-libraries-pulp-traditional-children-books-racist-phrases/">false tale of mass purges</a> of Pippi books around one single swedish library's decision to throw out copies of the originial &quot;racist&quot; versions. Case-in-point, a public statement in which the library tries to justify its actions is no longer available on their website, and has to be retrieved by the Wayback Machine. <a class="reference external" href="https://web.archive.org/web/20170710185409/https://www.botkyrka.se/arkiv/nyhetsarkiv/nyheter-startsida/2017-07-10-angaende-uttalanden-av-journalisten-janne-josefsson-om-bibliotek-botkyrka.html">https://web.archive.org/web/20170710185409/https://www.botkyrka.se/arkiv/nyhetsarkiv/nyheter-startsida/2017-07-10-angaende-uttalanden-av-journalisten-janne-josefsson-om-bibliotek-botkyrka.html</a> (in swedish)</td></tr> 2476 </tbody> 2477 </table> 2478 </blockquote> 2479 <!-- --> 2480 <blockquote> 2481 <table class="docutils footnote" frame="void" id="footnote-3" rules="none"> 2482 <colgroup><col class="label" /><col /></colgroup> 2483 <tbody valign="top"> 2484 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-5">[3]</a></td><td>Well actually, not quite. We did the same request twice, but they were two separate requests. Using a single request would improve the script.</td></tr> 2485 </tbody> 2486 </table> 2487 </blockquote> 2488 <!-- --> 2489 <blockquote> 2490 <table class="docutils footnote" frame="void" id="footnote-4" rules="none"> 2491 <colgroup><col class="label" /><col /></colgroup> 2492 <tbody valign="top"> 2493 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-6">[4]</a></td><td>We use the hex representation for clarity here. A proper tool would convert the hex values to bytes before calculating sum on them.</td></tr> 2494 </tbody> 2495 </table> 2496 </blockquote> 2497 <!-- --> 2498 <blockquote> 2499 <table class="docutils footnote" frame="void" id="footnote-5" rules="none"> 2500 <colgroup><col class="label" /><col /></colgroup> 2501 <tbody valign="top"> 2502 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-4">[5]</a></td><td>Another important difference is that the book does not need to be <em>interpreted</em> by a <em>machine</em> in order to make sense for a human. Availability of tooling is definitely an equally important topic in this discussion. However this post limits focus to the actual data itself.</td></tr> 2503 </tbody> 2504 </table> 2505 </blockquote> 2506 <!-- --> 2507 <blockquote> 2508 <table class="docutils footnote" frame="void" id="footnote-6" rules="none"> 2509 <colgroup><col class="label" /><col /></colgroup> 2510 <tbody valign="top"> 2511 <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[6]</a></td><td>How ironic; that site is gone. And I don't have a snapshot. What a shame. I've changed the link to some different baby jokes. The original url was <a class="reference external" href="https://dead-baby-joke.com">https://dead-baby-joke.com</a>.</td></tr> 2512 </tbody> 2513 </table> 2514 </blockquote> 2515 <!-- .. _data availability: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.433.3480 --> 2516 <!-- https://web.archive.org/web/20170710185409/https://www.botkyrka.se/arkiv/nyhetsarkiv/nyheter-startsida/2017-07-10-angaende-uttalanden-av-journalisten-janne-josefsson-om-bibliotek-botkyrka.html <- not available on link, no result on search https://www.mhpbooks.com/the-trouble-with-pippi/ --> 2517 </div> 2518 </content><category term="Archiving"></category><category term="web"></category><category term="hash"></category><category term="chromium"></category></entry><entry><title>Local python repository</title><link href="docker-offline-2-python.html" rel="alternate"></link><published>2021-04-26T07:55:00+02:00</published><updated>2021-04-26T07:55:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-04-26:docker-offline-2-python.html</id><summary type="html"><p class="first last">Feeding python packages to your offline Docker setup</p> 2519 </summary><content type="html"><!-- .. CAUTION:: 2520 2521 This is a purely technical article. You should probably be `this geeky <https://g33k.holbrook.no/c18beedd>`_ to continue reading. --> 2522 <p>In the previous part of this series we were able to connect a Docker network to a virtual interface on our host, neither of which have access to the internet. That means we are ready to host content for the container locally. And we will start out with creating a local Python repository.</p> 2523 <div class="section" id="packaging-the-packages"> 2524 <h2>Packaging the packages</h2> 2525 <p>I'll be so bold as to assume that you are using <tt class="docutils literal">pip</tt> to manage your packages. It gives you not only the option to <em>install</em> packages, but merely <em>download</em> them to storage aswell. So let's do that and try to serve the packages.</p> 2526 <div class="highlight"><pre><span></span>$<span class="w"> </span>pip<span class="w"> </span>download<span class="w"> </span>faker 2527 <span class="o">[</span>...<span class="o">]</span> 2528 Successfully<span class="w"> </span>downloaded<span class="w"> </span>faker<span class="w"> </span>python-dateutil<span class="w"> </span>six<span class="w"> </span>text-unidecode 2529 $<span class="w"> </span>ls 2530 Faker-8.1.0-py3-none-any.whl<span class="w"> </span>python_dateutil-2.8.1-py2.py3-none-any.whl<span class="w"> </span>six-1.15.0-py2.py3-none-any.whl<span class="w"> </span>text_unidecode-1.3-py2.py3-none-any.whl 2531 $<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>RangeHTTPServer 2532 </pre></div> 2533 <div class="highlight"><pre><span></span>$<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>venv<span class="w"> </span>.venv 2534 $<span class="w"> </span>.<span class="w"> </span>.venv/bin/activate 2535 <span class="o">(</span>.venv<span class="o">)</span><span class="w"> </span>$<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--index<span class="w"> </span>http://localhost:8000<span class="w"> </span>faker 2536 Looking<span class="w"> </span><span class="k">in</span><span class="w"> </span>indexes:<span class="w"> </span>http://localhost:8000 2537 ERROR:<span class="w"> </span>Could<span class="w"> </span>not<span class="w"> </span>find<span class="w"> </span>a<span class="w"> </span>version<span class="w"> </span>that<span class="w"> </span>satisfies<span class="w"> </span>the<span class="w"> </span>requirement<span class="w"> </span>faker<span class="w"> </span><span class="o">(</span>from<span class="w"> </span>versions:<span class="w"> </span>none<span class="o">)</span> 2538 ERROR:<span class="w"> </span>No<span class="w"> </span>matching<span class="w"> </span>distribution<span class="w"> </span>found<span class="w"> </span><span class="k">for</span><span class="w"> </span>faker 2539 </pre></div> 2540 <p>Dag nabbit. Apparently not that simple. And indeed, if we read up on the spec for <a class="reference external" href="https://www.python.org/dev/peps/pep-0503">PEP 503 spec for Simple Repository API</a>, we learn that we are going to stick those package files under directories named after the packages.</p> 2541 <p>Well, nothing that a bit of bash scripting can't sort out:</p> 2542 <pre class="code bash literal-block"> 2543 <span class="ch">#!/usr/bin/env 2544 </span><span class="w"> 2545 </span><span class="nv">s</span><span class="o">=</span><span class="nv">$1</span><span class="w"> 2546 </span><span class="nv">d</span><span class="o">=</span><span class="nv">$2</span><span class="w"> 2547 </span><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="nv">$d</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 2548 </span>&gt;<span class="p">&amp;</span><span class="m">2</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">&quot;usage: pep503.sh &lt;source_dir&gt; &lt;dest_dir&gt;&quot;</span><span class="w"> 2549 </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span><span class="w"> 2550 </span><span class="k">fi</span><span class="w"> 2551 2552 </span><span class="k">for</span><span class="w"> </span>df<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="sb">`</span>find<span class="w"> </span><span class="nv">$s</span><span class="w"> </span>-name<span class="w"> </span><span class="s2">&quot;*.whl&quot;</span><span class="w"> </span>-type<span class="w"> </span>f<span class="sb">`</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 2553 </span><span class="nv">f</span><span class="o">=</span><span class="sb">`</span>basename<span class="w"> </span><span class="nv">$df</span><span class="sb">`</span><span class="w"> 2554 </span><span class="nv">pd</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;s/^\(.*\)-[[:digit:]]*\.[[:digit:]].*</span>$<span class="s2">/\1/g&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s2">&quot;[:upper:]&quot;</span><span class="w"> </span><span class="s2">&quot;[:lower:]&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s2">&quot;_&quot;</span><span class="w"> </span><span class="s2">&quot;-&quot;</span><span class="sb">`</span><span class="w"> 2555 </span>mkdir<span class="w"> </span>-v<span class="w"> </span><span class="nv">$d</span>/<span class="nv">$pd</span><span class="w"> 2556 </span>cp<span class="w"> </span>-v<span class="w"> </span><span class="nv">$df</span><span class="w"> </span><span class="nv">$d</span>/<span class="nv">$pd</span>/<span class="w"> 2557 </span><span class="k">done</span><span class="w"> 2558 </span><span class="k">for</span><span class="w"> </span>df<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="sb">`</span>find<span class="w"> </span><span class="nv">$s</span><span class="w"> </span>-name<span class="w"> </span><span class="s2">&quot;*.tar.gz&quot;</span><span class="w"> </span>-type<span class="w"> </span>f<span class="sb">`</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 2559 </span><span class="nv">f</span><span class="o">=</span><span class="sb">`</span>basename<span class="w"> </span><span class="nv">$df</span><span class="sb">`</span><span class="w"> 2560 </span><span class="nv">pd</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;s/^\(.*\)-[[:digit:]]*\.[[:digit:]].*</span>$<span class="s2">/\1/g&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s2">&quot;[:upper:]&quot;</span><span class="w"> </span><span class="s2">&quot;[:lower:]&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s2">&quot;_&quot;</span><span class="w"> </span><span class="s2">&quot;-&quot;</span><span class="sb">`</span><span class="w"> 2561 </span>mkdir<span class="w"> </span>-v<span class="w"> </span><span class="nv">$d</span>/<span class="nv">$pd</span><span class="w"> 2562 </span>cp<span class="w"> </span>-v<span class="w"> </span><span class="nv">$df</span><span class="w"> </span><span class="nv">$d</span>/<span class="nv">$pd</span>/<span class="w"> 2563 </span><span class="k">done</span><span class="w"> 2564 </span><span class="k">for</span><span class="w"> </span>df<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="sb">`</span>find<span class="w"> </span><span class="nv">$s</span><span class="w"> </span>-name<span class="w"> </span><span class="s2">&quot;*.zip&quot;</span><span class="w"> </span>-type<span class="w"> </span>f<span class="sb">`</span><span class="p">;</span><span class="w"> </span><span class="k">do</span><span class="w"> 2565 </span><span class="nv">f</span><span class="o">=</span><span class="sb">`</span>basename<span class="w"> </span><span class="nv">$df</span><span class="sb">`</span><span class="w"> 2566 </span><span class="nv">pd</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span><span class="w"> </span><span class="nv">$f</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;s/^\(.*\)-[[:digit:]]*\.[[:digit:]].*</span>$<span class="s2">/\1/g&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s2">&quot;[:upper:]&quot;</span><span class="w"> </span><span class="s2">&quot;[:lower:]&quot;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>tr<span class="w"> </span><span class="s2">&quot;_&quot;</span><span class="w"> </span><span class="s2">&quot;-&quot;</span><span class="sb">`</span><span class="w"> 2567 </span>mkdir<span class="w"> </span>-v<span class="w"> </span><span class="nv">$d</span>/<span class="nv">$pd</span><span class="w"> 2568 </span>cp<span class="w"> </span>-v<span class="w"> </span><span class="nv">$df</span><span class="w"> </span><span class="nv">$d</span>/<span class="nv">$pd</span>/<span class="w"> 2569 </span><span class="k">done</span> 2570 </pre> 2571 <p>Armed with this, let's try again:</p> 2572 <div class="highlight"><pre><span></span>$<span class="w"> </span>sh<span class="w"> </span>/home/lash/bin/shell/pep503.sh<span class="w"> </span>.<span class="w"> </span>packages 2573 $<span class="w"> </span>mkdir<span class="w"> </span>packages 2574 $<span class="w"> </span>bash<span class="w"> </span>/home/lash/bin/shell/pep503.sh<span class="w"> </span>.<span class="w"> </span>packages 2575 mkdir:<span class="w"> </span>created<span class="w"> </span>directory<span class="w"> </span><span class="s1">&#39;packages/text-unidecode&#39;</span> 2576 <span class="s1">&#39;./text_unidecode-1.3-py2.py3-none-any.whl&#39;</span><span class="w"> </span>-&gt;<span class="w"> </span><span class="s1">&#39;packages/text-unidecode/text_unidecode-1.3-py2.py3-none-any.whl&#39;</span> 2577 mkdir:<span class="w"> </span>created<span class="w"> </span>directory<span class="w"> </span><span class="s1">&#39;packages/six&#39;</span> 2578 <span class="s1">&#39;./six-1.15.0-py2.py3-none-any.whl&#39;</span><span class="w"> </span>-&gt;<span class="w"> </span><span class="s1">&#39;packages/six/six-1.15.0-py2.py3-none-any.whl&#39;</span> 2579 mkdir:<span class="w"> </span>created<span class="w"> </span>directory<span class="w"> </span><span class="s1">&#39;packages/python-dateutil&#39;</span> 2580 <span class="s1">&#39;./python_dateutil-2.8.1-py2.py3-none-any.whl&#39;</span><span class="w"> </span>-&gt;<span class="w"> </span><span class="s1">&#39;packages/python-dateutil/python_dateutil-2.8.1-py2.py3-none-any.whl&#39;</span> 2581 mkdir:<span class="w"> </span>created<span class="w"> </span>directory<span class="w"> </span><span class="s1">&#39;packages/faker&#39;</span> 2582 <span class="s1">&#39;./Faker-8.1.0-py3-none-any.whl&#39;</span><span class="w"> </span>-&gt;<span class="w"> </span><span class="s1">&#39;packages/faker/Faker-8.1.0-py3-none-any.whl&#39;</span> 2583 $<span class="w"> </span>find<span class="w"> </span>packages/<span class="w"> </span>-type<span class="w"> </span>f 2584 packages/faker/Faker-8.1.0-py3-none-any.whl 2585 packages/python-dateutil/python_dateutil-2.8.1-py2.py3-none-any.whl 2586 packages/six/six-1.15.0-py2.py3-none-any.whl 2587 packages/text-unidecode/text_unidecode-1.3-py2.py3-none-any.whl 2588 k<span class="w"> </span>$<span class="w"> </span>python<span class="w"> </span>-m<span class="w"> </span>RangeHTTPServer 2589 </pre></div> 2590 <div class="highlight"><pre><span></span>$<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--index<span class="w"> </span>http://localhost:8000/packages<span class="w"> </span>faker 2591 Looking<span class="w"> </span><span class="k">in</span><span class="w"> </span>indexes:<span class="w"> </span>http://localhost:8000/packages 2592 Collecting<span class="w"> </span>faker 2593 <span class="o">[</span>...<span class="o">]</span> 2594 Successfully<span class="w"> </span>installed<span class="w"> </span>faker-8.1.0<span class="w"> </span>python-dateutil-2.8.1<span class="w"> </span>six-1.15.0<span class="w"> </span>text-unidecode-1.3 2595 </pre></div> 2596 </div> 2597 <div class="section" id="extra-prepping"> 2598 <h2>Extra prepping</h2> 2599 <p>There are some basic packages you will most always need, and which <tt class="docutils literal">pip</tt> often will expect to find in at least one of its available repositories, even regardless of whether its installed or not. If you don't have this on your local offline repository when the internet goes <em>poof</em>, then that will block any build you are trying to do.</p> 2600 <p>Let's make sure we have the packages around all the time:</p> 2601 <div class="highlight"><pre><span></span>$<span class="w"> </span>pip<span class="w"> </span>download<span class="w"> </span>pip<span class="w"> </span>setuptools<span class="w"> </span>setuptools-markdown<span class="w"> </span>wheel 2602 <span class="o">[</span>...<span class="o">]</span> 2603 $<span class="w"> </span>bash<span class="w"> </span>/home/lash/bin/shell/pep503.sh<span class="w"> </span>.<span class="w"> </span>packages 2604 <span class="o">[</span>...<span class="o">]</span> 2605 </pre></div> 2606 </div> 2607 <div class="section" id="choosing-a-server"> 2608 <h2>Choosing a server</h2> 2609 <p>As I tend to favor the classics, I still use <tt class="docutils literal">Apache Web Server</tt> to host stuff to my local environment. One practical (if not all too safe) thing about it is that it will automatically bind to all interfaces. So to make the repository available, we simply link or add the <tt class="docutils literal">packages</tt> directory to the virtual root, and restart the server e.g. with</p> 2610 <div class="highlight"><pre><span></span>systemctl<span class="w"> </span>restart<span class="w"> </span>httpd 2611 </pre></div> 2612 <p>Of course you can use any HTTP server you like, as long as you know how to bind it to the virtual interface.</p> 2613 <p>To verify that the service is providing what's needed, simply point your web browser to the location, e.g.</p> 2614 <div class="highlight"><pre><span></span>lynx<span class="w"> </span>http://10.1.2.1/packages/ 2615 </pre></div> 2616 </div> 2617 <div class="section" id="serving-the-packages"> 2618 <h2>Serving the packages</h2> 2619 <p>Now that we are all prepped, the next step is to do install packages from within a Docker container.</p> 2620 <p>Let's start with an Archlinux base image with basic python provisions.</p> 2621 <pre class="code docker literal-block"> 2622 <span class="k">FROM</span><span class="w"> </span><span class="s">archlinux:latest</span><span class="w"> 2623 2624 </span><span class="k">RUN</span><span class="w"> </span>pacman<span class="w"> </span>-Sy<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="se">\ 2625 </span><span class="w"> </span>pacman<span class="w"> </span>-S<span class="w"> </span>--noconfirm<span class="w"> </span>python<span class="w"> </span>python-pip 2626 </pre> 2627 <p>Build and tag it with <tt class="docutils literal">pythonbase</tt>, and then let's add the Dockerfile to test the repository:</p> 2628 <div class="highlight"><pre><span></span><span class="o">[</span>...<span class="o">]</span> 2629 2630 <span class="k">RUN</span><span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>--index<span class="w"> </span>http://10.1.2.1/packages<span class="w"> </span>--trusted-host<span class="w"> </span><span class="m">10</span>.1.2.1<span class="w"> </span>faker 2631 </pre></div> 2632 <p>Now, turn internet off (and lights, too, if you'd like some extra suspense), and build the second file.</p> 2633 <div class="highlight"><pre><span></span>$<span class="w"> </span>docker<span class="w"> </span>build<span class="w"> </span>. 2634 Sending<span class="w"> </span>build<span class="w"> </span>context<span class="w"> </span>to<span class="w"> </span>Docker<span class="w"> </span>daemon<span class="w"> </span><span class="m">3</span>.072kB 2635 <span class="o">[</span>...<span class="o">]</span> 2636 Successfully<span class="w"> </span>installed<span class="w"> </span>faker-8.1.0<span class="w"> </span>python-dateutil-2.8.1<span class="w"> </span>six-1.15.0<span class="w"> </span>text-unidecode-1.3 2637 Removing<span class="w"> </span>intermediate<span class="w"> </span>container<span class="w"> </span>2c10ffcdf3ad 2638 ---&gt;<span class="w"> </span>1ba83bb8e111 2639 Successfully<span class="w"> </span>built<span class="w"> </span>1ba83bb8e111 2640 </pre></div> 2641 <!-- --> 2642 <blockquote> 2643 </blockquote> 2644 </div> 2645 </content><category term="Offlining"></category><category term="docker"></category><category term="python"></category><category term="devops"></category></entry><entry><title>The routing to freedom</title><link href="docker-offline-1-routing.html" rel="alternate"></link><published>2021-04-26T07:54:00+02:00</published><updated>2021-04-26T07:54:00+02:00</updated><author><name>Louis Holbrook</name></author><id>tag:None,2021-04-26:docker-offline-1-routing.html</id><summary type="html"><p class="first last">How to not be forced being online when forced to use docker</p> 2646 </summary><content type="html"><!-- .. CAUTION:: 2647 2648 This is a purely technical article. You should probably be `this geeky <https://g33k.holbrook.no/8319a926>`_ to continue reading. --> 2649 <p>Five years ago I decided that I wanted to be able to work from anywhere, anytime. Four years ago, that promise was kept. In part.</p> 2650 <p>I do not need to go to an office somewhere. I can work outside in a park if I want to. I can ride on to a new town every day. I only ever need to bring my trusty old <a class="reference external" href="https://www.tuxedocomputers.com/en">Tuxedo Laptop</a> whereever I go.</p> 2651 <p>All of this as true, as long as there is internet available. And, as it turns out, <em>good</em> internet available.</p> 2652 <p>This has become especially obvious to me once I started to work with a project that involves a collection of microservices contained in a Docker environment, which also makes extensive use of custom packages that change frequently alongside the development process. Turns out, every time I want to rebuild my cluster of containers when sitting in the sun in a park, I need my LTE modem to play along. If it doesn't, then a single package that can't be reached will thwart the build.</p> 2653 <p>This does not feel much like freedom after all. So let's see how we can serve all of these locally from our host instead.</p> 2654 <p>First of all, we have to be able to reach our local host from the Docker containers. This is less straightforward than it may seem at first. The most obvious solution is to use the <tt class="docutils literal">host</tt> network driver, but this exposes your <em>whole</em> localhost interface and routes to internet, too. Aside from the security issues that raises, it can also also trick you into assuming that some resources are available when they in fact will not be when you move on to a different environment. What we want is to <em>block</em> access to internet, while <em>choosing</em> which services to let the Docker container use.</p> 2655 <p>Once we have this in place, we want to create local repositories for all the stuff we otherwise need to download. In this particular case, that means a <strong>Docker</strong> repository, a <strong>Python</strong> repository, a <strong>nodejs</strong> repository and a <strong>linux</strong> repository. We'll use <a class="reference external" href="https://archlinux.org">Archlinux</a> for this exercise, because that's been my home environment for the last four years.</p> 2656 <p>In fact, having your own mirror of all these and anything else you base most of your work on is not only a good idea for the purpose of <em>offlining</em> in itself, but wasting on bandwidth for items you've already downloaded hundreds of times is not exactly a nod to climate awareness either. And even more importantly, ensuring <em>availability</em> of software is something we should all participate in, and not merely defer to a couple of git repository giants.</p> 2657 <blockquote> 2658 </blockquote> 2659 <!-- --> 2660 <blockquote> 2661 </blockquote> 2662 <div class="section" id="reaching-the-local-host"> 2663 <h2>Reaching the local host</h2> 2664 <p><em>Local host</em> not <em>localhost</em>, mind you. Which means we need a different interface to connect to. And since we are not wiring up in any physical sense, a virtual interface seems to be the reasonable way to go.</p> 2665 <p>First, let's prepare a base Docker layer with some tools that you should never leave home without.</p> 2666 <div class="section" id="prepare-the-docker-image"> 2667 <h3>Prepare the docker image</h3> 2668 <pre class="code docker literal-block"> 2669 <span class="k">FROM</span><span class="w"> </span><span class="s">archlinux:latest</span><span class="w"> 2670 2671 </span><span class="k">RUN</span><span class="w"> </span>pacman<span class="w"> </span>-Sy<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="se">\ 2672 </span><span class="w"> </span>pacman<span class="w"> </span>-S<span class="w"> </span>--noconfirm<span class="w"> </span>gnu-netcat<span class="w"> </span>socat<span class="w"> </span>inetutils<span class="w"> </span>iproute2 2673 </pre> 2674 <p>Let's build this as an image called <tt class="docutils literal">archbase</tt>. Provided the content above is a file called <tt class="docutils literal">Dockerfile.archbase</tt> in your current directory:</p> 2675 <div class="highlight"><pre><span></span>$<span class="w"> </span>docker<span class="w"> </span>build<span class="w"> </span>-t<span class="w"> </span>archbase<span class="w"> </span>-f<span class="w"> </span>Dockerfile.archbase<span class="w"> </span>. 2676 </pre></div> 2677 </div> 2678 <div class="section" id="set-up-network-interfaces"> 2679 <h3>Set up network interfaces</h3> 2680 <p>Bring up a virtual interface</p> 2681 <div class="highlight"><pre><span></span>$<span class="w"> </span>ip<span class="w"> </span>link<span class="w"> </span>add<span class="w"> </span>foo<span class="w"> </span><span class="nb">type</span><span class="w"> </span>dummy 2682 </pre></div> 2683 <p>Find the subnet of the <tt class="docutils literal"><span class="pre">no-internet</span></tt> Docker network. This network driver is a builtin that provides exactly what it advertises.</p> 2684 <div class="highlight"><pre><span></span>$<span class="w"> </span>docker<span class="w"> </span>network<span class="w"> </span>inspect<span class="w"> </span>no-internet 2685 <span class="o">[</span>...<span class="o">]</span> 2686 <span class="s2">&quot;Config&quot;</span>:<span class="w"> </span><span class="o">[</span> 2687 <span class="w"> </span><span class="o">{</span> 2688 <span class="w"> </span><span class="s2">&quot;Subnet&quot;</span>:<span class="w"> </span><span class="s2">&quot;10.1.1.0/24&quot;</span>, 2689 <span class="w"> </span><span class="s2">&quot;Gateway&quot;</span>:<span class="w"> </span><span class="s2">&quot;10.1.1.1&quot;</span> 2690 <span class="w"> </span><span class="o">}</span> 2691 <span class="o">]</span> 2692 </pre></div> 2693 <p>Assign an IP address to the dummy interface in a <em>different</em> subnet than the one the Docker network uses.</p> 2694 <div class="highlight"><pre><span></span>$<span class="w"> </span>ip<span class="w"> </span>addr<span class="w"> </span>add<span class="w"> </span><span class="m">10</span>.1.2.1/24<span class="w"> </span>dev<span class="w"> </span>foo 2695 </pre></div> 2696 </div> 2697 <div class="section" id="traverse-the-firewall"> 2698 <h3>Traverse the firewall</h3> 2699 <p>Find the bridge used by the Docker container. Look for an ip address that matches the gateway of the docker network config.</p> 2700 <div class="highlight"><pre><span></span>$<span class="w"> </span>ip<span class="w"> </span>addr<span class="w"> </span>ls 2701 <span class="m">17</span>:<span class="w"> </span>br-d4ddb68f9938:<span class="w"> </span>&lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt;<span class="w"> </span>mtu<span class="w"> </span><span class="m">1500</span><span class="w"> </span>qdisc<span class="w"> </span>noqueue<span class="w"> </span>state<span class="w"> </span>UP<span class="w"> </span>group<span class="w"> </span>default 2702 link/ether<span class="w"> </span><span class="m">02</span>:42:9c:1a:58:d2<span class="w"> </span>brd<span class="w"> </span>ff:ff:ff:ff:ff:ff 2703 inet<span class="w"> </span><span class="m">10</span>.1.1.1/24<span class="w"> </span>brd<span class="w"> </span><span class="m">10</span>.1.1.255<span class="w"> </span>scope<span class="w"> </span>global<span class="w"> </span>br-d4ddb68f9938 2704 valid_lft<span class="w"> </span>forever<span class="w"> </span>preferred_lft<span class="w"> </span>forever 2705 inet6<span class="w"> </span>fe80::42:9cff:fe1a:58d2/64<span class="w"> </span>scope<span class="w"> </span>link 2706 valid_lft<span class="w"> </span>forever<span class="w"> </span>preferred_lft<span class="w"> </span>forever 2707 <span class="o">[</span>...<span class="o">]</span> 2708 <span class="m">7614</span>:<span class="w"> </span>foo:<span class="w"> </span>&lt;BROADCAST,NOARP,UP,LOWER_UP&gt;<span class="w"> </span>mtu<span class="w"> </span><span class="m">1500</span><span class="w"> </span>qdisc<span class="w"> </span>noqueue<span class="w"> </span>master<span class="w"> </span>br-496e528b928c<span class="w"> </span>state<span class="w"> </span>UNKNOWN<span class="w"> </span>group<span class="w"> </span>default<span class="w"> </span>qlen<span class="w"> </span><span class="m">1000</span> 2709 link/ether<span class="w"> </span>1a:50:53:2b:96:98<span class="w"> </span>brd<span class="w"> </span>ff:ff:ff:ff:ff:ff 2710 inet<span class="w"> </span><span class="m">10</span>.1.3.1/24<span class="w"> </span>scope<span class="w"> </span>global<span class="w"> </span>foo 2711 valid_lft<span class="w"> </span>forever<span class="w"> </span>preferred_lft<span class="w"> </span>forever 2712 inet6<span class="w"> </span>fe80::1850:53ff:fe2b:9698/64<span class="w"> </span>scope<span class="w"> </span>link 2713 valid_lft<span class="w"> </span>forever<span class="w"> </span>preferred_lft<span class="w"> </span>forever 2714 </pre></div> 2715 <p>Add the virtual interface to the bridge.</p> 2716 <div class="highlight"><pre><span></span>$<span class="w"> </span>ip<span class="w"> </span>link<span class="w"> </span><span class="nb">set</span><span class="w"> </span>foo<span class="w"> </span>master<span class="w"> </span>br-d4ddb68f9938 2717 </pre></div> 2718 <p>Long story short, the previous step will make the traffic from the container reach the <tt class="docutils literal">INPUT</tt> chain in <tt class="docutils literal">iptables</tt>. Now we can make an exception for incoming traffic from the <tt class="docutils literal"><span class="pre">no-internet</span></tt> Docker bridge.</p> 2719 <div class="highlight"><pre><span></span>$<span class="w"> </span>iptables<span class="w"> </span>-I<span class="w"> </span>INPUT<span class="w"> </span><span class="m">1</span><span class="w"> </span>--source<span class="w"> </span>br-d4ddb68f9938<span class="w"> </span>--destination<span class="w"> </span><span class="m">10</span>.1.2.1/24<span class="w"> </span>-j<span class="w"> </span>ACCEPT 2720 </pre></div> 2721 </div> 2722 <div class="section" id="verify"> 2723 <h3>Verify</h3> 2724 <p>Provided you don't have any other hurdles in your local <tt class="docutils literal">ìptables</tt> setup, a port on device <tt class="docutils literal">foo</tt> should be reachable from the docker container. We can use socat to check.</p> 2725 <p>On local host:</p> 2726 <div class="highlight"><pre><span></span>$<span class="w"> </span>socat<span class="w"> </span>TCP4-LISTEN:8000,bind<span class="o">=</span><span class="m">10</span>.1.2.1,reuseaddr<span class="w"> </span>- 2727 </pre></div> 2728 <p>Start the docker container with shell prompt</p> 2729 <div class="highlight"><pre><span></span>$<span class="w"> </span>docker<span class="w"> </span>run<span class="w"> </span>--network<span class="w"> </span>no-internet<span class="w"> </span>-it<span class="w"> </span>archbase<span class="w"> </span>/bin/bash 2730 </pre></div> 2731 <p>The moment of truth</p> 2732 <div class="highlight"><pre><span></span>$<span class="w"> </span><span class="nb">echo</span><span class="w"> </span>bar<span class="w"> </span><span class="p">|</span><span class="w"> </span>socat<span class="w"> </span>-<span class="w"> </span>TCP4:10.1.2.1:8000 2733 </pre></div> 2734 <p>Spoiler: <tt class="docutils literal">bar</tt> should pop up on the local host side.</p> 2735 </div> 2736 </div> 2737 </content><category term="Offlining"></category><category term="docker"></category><category term="networking"></category><category term="iptables"></category><category term="iproute"></category></entry></feed>