<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Posts on kistu</title>
  <link rel="alternate" href="/posts/" />
  <link rel="self" href="/posts/index.xml" />
  <subtitle>Recent content in Posts on kistu</subtitle>
  <id>/posts/</id>
  <generator uri="http://gohugo.io" version="0.124.0">Hugo</generator>
  <language>en</language>
  <updated>2026-07-06T15:03:37+02:00</updated>
  <author>
    
    
  </author>
  
      <entry>
        <title>Automatically Generated Images</title>
        <link rel="alternate" href="/posts/011-automatically-generated-images/" />
        <id>/posts/011-automatically-generated-images/</id>
        <published>2026-07-06T15:03:37+02:00</published>
        <updated>2026-07-06T15:03:37+02:00</updated>
        <summary type="html">Images can broadly be classified by their source:
Camera Software The aspect that makes an image taken from a camera valued is that it is a represention of the real world.
The aspect that makes digitally created images valued is that is is a deliberate representation of an image in the artist&amp;rsquo;s mind.
Both of these are therefore valued because there is an element that makes it - in some sense - real.</summary>
          <content type="html"><![CDATA[<p>Images can broadly be classified by their source:</p>
<ul>
<li>Camera</li>
<li>Software</li>
</ul>
<p>



    
    <input type="checkbox" id="zoomCheck-9f62e" hidden>
    <label for="zoomCheck-9f62e">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="image-sources.svg" alt="image sources" 
             />
    </label>

</p>
<p>The aspect that makes an image taken from a camera valued is that it is a represention of the real world.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-2180c" hidden>
    <label for="zoomCheck-2180c">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="camera.svg" alt="camera" 
             />
    </label>

</p>
<p>The aspect that makes digitally created images valued is that is is a deliberate representation of an image in the artist&rsquo;s mind.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-76617" hidden>
    <label for="zoomCheck-76617">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="software.svg" alt="software" 
             />
    </label>

</p>
<p>Both of these are therefore valued because there is an element that makes it - in some sense - <strong>real</strong>.</p>
<p>Automatically generated images do not share this element. These images are not representations of the real world, nor are they a deliberate representation of an image in the artist&rsquo;s mind. Generated images are loosely based on patterns from such representations - <strong>this is not sufficient</strong>.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-9a4c6" hidden>
    <label for="zoomCheck-9a4c6">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="auto-generation.svg" alt="auto generation" 
             />
    </label>

</p>
<p>Of course, these are not clear boundaries. And the appeal of automatically generated images cannot be ignored. It is an accelerant - imitating at a moments notice that which previously took much time and resources. It provides to the masses an ability they previously couldn&rsquo;t achieve. However, these imitations mock that which we value in an image. If there are no clear boundaries, we must at least define some scale or varying degree.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-a8fca" hidden>
    <label for="zoomCheck-a8fca">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="scale.svg" alt="scale" 
             />
    </label>

</p>
<p>Images taken from a camera require some signal processing and filtering to convert readings from a sensor to a digital image. This can be considered a minimum necessity. However, further modifications are done using software to change how the image appears - this is not particularly desirable (unless it is deliberate and explicit). Worse, some modern cameras use automatic generation to enchance the image!</p>
<p>



    
    <input type="checkbox" id="zoomCheck-e199a" hidden>
    <label for="zoomCheck-e199a">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="camera-scale.svg" alt="camera scale" 
             />
    </label>

</p>
<p>Digital images are created using software. The artist has an image in mind, and the software is simply a medium through which it is expressed. Parts of this process can be delegated to automatic generation and classified by the control exerted:</p>
<ul>
<li>Auxillary (how to do X in Y)</li>
<li>Partial (do X in Y)</li>
<li>Unconstrained (create Z)</li>
</ul>
<p>How different is the result from the image that the artist had in mind?</p>
<ol>
<li>Auxillary control is very beneficial - learning &ldquo;how to do X in Y&rdquo; (typically through text generation) doesn&rsquo;t change the end result and can assist in reaching it more quickly.</li>
<li>Partial control is more complex . To &ldquo;do X in Y&rdquo;, the classification depends on what X is. If X is precise and restrictive, it may be considered fair use.</li>
<li>To &ldquo;create Z&rdquo; is generally a massive deviation simply because there is no way of precisely capturing the artists intent.</li>
</ol>
<p>



    
    <input type="checkbox" id="zoomCheck-3af62" hidden>
    <label for="zoomCheck-3af62">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="software-scale.svg" alt="software scale" 
             />
    </label>

</p>
<p>The problem is that all these delegation types may eventually produce a comparable result. It is the artists responsibility to be deliberate and transparent, and it is the patrons responsibility to demand it.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-30767" hidden>
    <label for="zoomCheck-30767">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="the-result.svg" alt="the result" 
             />
    </label>

</p>
<p>What if the artist does not have a particular image in mind? It may be vague; it is not inconceivable that the process of creation influences and directs the final form. For such creations, the influence may be subject to the same scrutiny as the creation. Is the influence a true representation of the real world, or a deliberate representation of an artist?</p>
<p>



    
    <input type="checkbox" id="zoomCheck-cd84a" hidden>
    <label for="zoomCheck-cd84a">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="influences.svg" alt="influences" 
             />
    </label>

</p>
<p>Ultimately, this is a preference. It is possible to define the boundary concretely, to draw a line seperating that which is acceptable and that which is not - but this is subject to change and scrutiny. The artist must decide.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-66bca" hidden>
    <label for="zoomCheck-66bca">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="boundary.svg" alt="boundary" 
             />
    </label>

</p>
<p>Summary:</p>
<ul>
<li>The value of an image from a camera is it&rsquo;s accurate representation of the real world.</li>
<li>The value of an image created or modified using software is the deliberate representation of an image in the artist&rsquo;s mind.</li>
<li>Automatically generated images are based on patterns in these representations - this is insufficient to serve as an accurate or deliberate representation.</li>
<li>This is a distinction of degree - there are various scales of influence.</li>
<li>It is the artists repsonsibility to be deliberate and transparent.</li>
</ul>
]]></content>
      </entry>
      <entry>
        <title>Moderation is Necessary</title>
        <link rel="alternate" href="/posts/010-moderation-is-necessary/" />
        <id>/posts/010-moderation-is-necessary/</id>
        <published>2026-05-12T13:20:31+02:00</published>
        <updated>2026-05-12T13:20:31+02:00</updated>
        <summary type="html">Moderation is necessary.
Too little is not good.
Too much is not good.
These extremes are not great.
You must first realize this balance. If you listen carefully, too much and too little with guide you there.
And then you will realize your potential.</summary>
          <content type="html"><![CDATA[<p>Moderation is necessary.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-ff862" hidden>
    <label for="zoomCheck-ff862">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="moderation.svg" alt="moderation" 
             />
    </label>

</p>
<p>Too little is not good.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-e626d" hidden>
    <label for="zoomCheck-e626d">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="too-little.svg" alt="too little" 
             />
    </label>

</p>
<p>Too much is not good.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-4d8a7" hidden>
    <label for="zoomCheck-4d8a7">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="too-much.svg" alt="too much" 
             />
    </label>

</p>
<p>These extremes are not great.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-d268a" hidden>
    <label for="zoomCheck-d268a">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="extremes.svg" alt="extremes" 
             />
    </label>

</p>
<p>You must first realize this balance. If you listen carefully, too much and too little with guide you there.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-e6008" hidden>
    <label for="zoomCheck-e6008">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="balance.svg" alt="balance" 
             />
    </label>

</p>
<p>And then you will realize your potential.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-cf19e" hidden>
    <label for="zoomCheck-cf19e">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="potential.svg" alt="potential" 
             />
    </label>

</p>
]]></content>
      </entry>
      <entry>
        <title>Reset Windows 10/11 Password</title>
        <link rel="alternate" href="/posts/006-reset-windows-1011-password/" />
        <id>/posts/006-reset-windows-1011-password/</id>
        <published>2026-05-10T10:55:13+02:00</published>
        <updated>2026-05-10T10:55:13+02:00</updated>
        <summary type="html">To reset a Windows 10/11 password (if you cannot login to your local user account), the Accessibility utility can be exploited.
This method does not require an external installation media, but only works for local accounts. If the drive is encrypted with BitLocker, it&amp;rsquo;s key will be required (this method does not reset the key).
Source: https://dev.to/jackscott7/reset-windows-1011-password-without-usb-or-reinstall-43m6
Online Microsoft accounts require internet access to login; passwords are tied to this account and cannot be reset locally.</summary>
          <content type="html"><![CDATA[<p>To reset a Windows 10/11 password (if you cannot login to your local user account), the Accessibility utility can be exploited.</p>
<p>This method does not require an external installation media, but only works for local accounts. If the drive is encrypted with BitLocker, it&rsquo;s key will be required (this method does not reset the key).</p>
<p>Source: <a href="https://dev.to/jackscott7/reset-windows-1011-password-without-usb-or-reinstall-43m6">https://dev.to/jackscott7/reset-windows-1011-password-without-usb-or-reinstall-43m6</a></p>
<p>Online Microsoft accounts require internet access to login; passwords are tied to this account and cannot be reset locally.</p>
<h1 id="boot-into-windows-recovery">Boot into Windows Recovery</h1>
<ul>
<li>Press the power button once the Windows/Computer logo appears on startup</li>
<li>Repeat 3 times; the next boot should show a &ldquo;Recovery&rdquo; screen</li>
<li>Click on &ldquo;See advanced repair options&rdquo;</li>
</ul>
<p>Some sources recommend pressing F11 on startup to enter &ldquo;Recovery&rdquo;, but this is not reliable; some machines may use F10 or F12 instead. Use the force shutdown method above instead.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-8ae5a" hidden>
    <label for="zoomCheck-8ae5a">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="01-windows-recovery.png" alt="windows recovery" 
             />
    </label>

</p>
<h1 id="open-cmd-session">Open cmd session</h1>
<p>Select &ldquo;Troubleshoot&rdquo;




    
    <input type="checkbox" id="zoomCheck-e5a52" hidden>
    <label for="zoomCheck-e5a52">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="02-choose-an-option.png" alt="choose an option" 
             />
    </label>

</p>
<p>Select &ldquo;Advanced options&rdquo;




    
    <input type="checkbox" id="zoomCheck-1e488" hidden>
    <label for="zoomCheck-1e488">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="03-troubleshoot.png" alt="troubleshoot" 
             />
    </label>

</p>
<p>Select &ldquo;Command Prompt&rdquo;




    
    <input type="checkbox" id="zoomCheck-3fbec" hidden>
    <label for="zoomCheck-3fbec">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="04-advanced-options.png" alt="advanced options" 
             />
    </label>

</p>
<p>This will open a command prompt window.




    
    <input type="checkbox" id="zoomCheck-d643f" hidden>
    <label for="zoomCheck-d643f">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="05-cmd.png" alt="cmd" 
             />
    </label>

</p>
<h1 id="overwrite-utilman-with-cmd">Overwrite utilman with cmd</h1>
<p>Identify the operating system partition (value for &ldquo;osdevice&rdquo;). This is typically &ldquo;C:\&rdquo;, but it may be different.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bcdedit
</span></span></code></pre></div><p>



    
    <input type="checkbox" id="zoomCheck-e9a72" hidden>
    <label for="zoomCheck-e9a72">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="06-bcdedit.png" alt="bcdedit" 
             />
    </label>

</p>
<p>Create a backup of &ldquo;Utilman.exe&rdquo;, and then replace it with &ldquo;cmd.exe&rdquo;:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl"><span class="k">copy</span> C:\Windows\System32\Utilman.exe C:\Windows\System32\Utilman.bak
</span></span><span class="line"><span class="cl"><span class="k">copy</span> C:\Windows\System32\cmd.exe     C:\Windows\System32\Utilman.exe
</span></span></code></pre></div><p>If prompted to overwrite, accept by typing &ldquo;Yes&rdquo;.</p>
<blockquote>
<p>Windows is not case sensitive. If you experience difficulty due to keyboard layout issues, special symbols (like &ldquo;\&rdquo;) can be copied from the terminal prompt.</p>
</blockquote>
<p>



    
    <input type="checkbox" id="zoomCheck-7c8f3" hidden>
    <label for="zoomCheck-7c8f3">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="07-copy.png" alt="copy" 
             />
    </label>

</p>
<p>The command prompt window can be closed and the system can now be rebooted.</p>
<h1 id="reset-password">Reset Password</h1>
<p>After startup, select the &ldquo;Accessibility&rdquo; icon at the bottom right of the screen. This will open a command prompt window.




    
    <input type="checkbox" id="zoomCheck-a25ab" hidden>
    <label for="zoomCheck-a25ab">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="08-accessibility.png" alt="accessibility" 
             />
    </label>

</p>
<p>List the users (to verify username) and change the password:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">net user
</span></span><span class="line"><span class="cl">net user <span class="s2">&#34;UserNameHere&#34;</span> <span class="s2">&#34;NewPasswordHere&#34;</span>
</span></span></code></pre></div><p>



    
    <input type="checkbox" id="zoomCheck-e50ae" hidden>
    <label for="zoomCheck-e50ae">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="09-password-reset.png" alt="password reset" 
             />
    </label>

</p>
<p>Although the next step is not necessary, it it recommended for security. Once logged in, open a command prompt and restore the Accessibility utility:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl"><span class="k">copy</span> C:\Windows\System32\Utilman.bak C:\Windows\System32\Utilman.exe
</span></span></code></pre></div><p>



    
    <input type="checkbox" id="zoomCheck-c7846" hidden>
    <label for="zoomCheck-c7846">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="10-restore.png" alt="restore" 
             />
    </label>

</p>
<p>If this returns access denied errors like above (even with admin privileges), boot back into recovery mode to restore.</p>
]]></content>
      </entry>
      <entry>
        <title>The Black Box</title>
        <link rel="alternate" href="/posts/008-the-blackbox/" />
        <id>/posts/008-the-blackbox/</id>
        <published>2026-04-26T15:01:49+02:00</published>
        <updated>2026-04-26T15:01:49+02:00</updated>
        <summary type="html">The black box is a representation of a system.
No knowledge about the internal workings of the black box is expected. It may not be known, or you may not know it. The system is only known in terms of inputs and associated outputs.
All inputs are not the same. Some inputs control what the system does.
Other inputs control what the system operates on.
The output can be used to drive inputs - this is feedback.</summary>
          <content type="html"><![CDATA[<p>The black box is a representation of a system.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-b3269" hidden>
    <label for="zoomCheck-b3269">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="blackbox.svg" alt="blackbox" 
             />
    </label>

</p>
<p>No knowledge about the internal workings of the black box is expected. It may not be known, or you may not know it. The system is only known in terms of inputs and associated outputs.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-f9775" hidden>
    <label for="zoomCheck-f9775">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="inputs-and-outputs.svg" alt="inputs and outputs" 
             />
    </label>

</p>
<p>All inputs are not the same. Some inputs control what the system does.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-9c54b" hidden>
    <label for="zoomCheck-9c54b">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="control-inputs.svg" alt="control inputs" 
             />
    </label>

</p>
<p>Other inputs control what the system operates on.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-9971c" hidden>
    <label for="zoomCheck-9971c">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="data-inputs.svg" alt="data inputs" 
             />
    </label>

</p>
<p>The output can be used to drive inputs - this is feedback.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-561d2" hidden>
    <label for="zoomCheck-561d2">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="feedback.svg" alt="feedback" 
             />
    </label>

</p>
<p>To know the black box is to know the relation between it&rsquo;s inputs and outputs; to contruct a model of how it might work.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-b5e2f" hidden>
    <label for="zoomCheck-b5e2f">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="model.svg" alt="model" 
             />
    </label>

</p>
<p>A model may reveal the internal workings of the black box.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-cc008" hidden>
    <label for="zoomCheck-cc008">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="reveal.svg" alt="reveal" 
             />
    </label>

</p>
<p>Does the black box make you uncomfortable? Does it leave you with the feeling that your knowledge is superficial or incomplete? Do you perhaps seek to know everything? If so, there is one more thing you should know.</p>
<p>All systems have black boxes. Complete knowledge of any system is not possible. Uncovering how a system works - and constructing more models - reveals more black boxes.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-a5072" hidden>
    <label for="zoomCheck-a5072">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="everything.svg" alt="everything" 
             />
    </label>

</p>
<hr>
<p>Some elements were inspired by <a href="https://en.wikipedia.org/wiki/Black_box">https://en.wikipedia.org/wiki/Black_box</a>.</p>
]]></content>
      </entry>
      <entry>
        <title>The Imbalance</title>
        <link rel="alternate" href="/posts/007-the-imbalance/" />
        <id>/posts/007-the-imbalance/</id>
        <published>2026-03-28T23:37:05+02:00</published>
        <updated>2026-03-29T18:32:27+02:00</updated>
        <summary type="html">First, there is simplicity.
Over billions of years, that simplicity becomes slightly more complex.
Complexity is accelerated by dependency.
That complexity can only persist because it gives enough time for the environment to support it - for it&amp;rsquo;s dependencies to persist. This is a delicate balance.
All complexities are not the same. The Earth has evolved to deal with much of it. However, some complexities - the ones we create - are very difficult to undo.</summary>
          <content type="html"><![CDATA[<p>First, there is simplicity.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-5a435" hidden>
    <label for="zoomCheck-5a435">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="simplicity.svg" alt="simplicity" 
             />
    </label>

</p>
<p>Over billions of years, that simplicity becomes slightly more complex.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-21adb" hidden>
    <label for="zoomCheck-21adb">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="complexity.svg" alt="complexity" 
             />
    </label>

</p>
<p>Complexity is accelerated by dependency.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-3f08a" hidden>
    <label for="zoomCheck-3f08a">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="dependency.svg" alt="dependency" 
             />
    </label>

</p>
<p>That complexity can only persist because it gives enough time for the environment to support it - for it&rsquo;s dependencies to persist. This is a delicate balance.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-a8fca" hidden>
    <label for="zoomCheck-a8fca">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="scale.svg" alt="scale" 
             />
    </label>

</p>
<p>All complexities are not the same. The Earth has evolved to deal with much of it. However, some complexities - the ones we create - are very difficult to undo.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-fc1eb" hidden>
    <label for="zoomCheck-fc1eb">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="different-complexity.svg" alt="different complexity" 
             />
    </label>

</p>
<p>When that complexity far exceeds what the environment is capable of handling, it makes me sad.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-f6802" hidden>
    <label for="zoomCheck-f6802">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="plastic.svg" alt="plastic" 
             />
    </label>

</p>
<p>Because that means one of two things:</p>
<ul>
<li>we must find some way of balancing this complexity</li>
<li>the scale must tip, the environment must adapt</li>
</ul>
<p>



    
    <input type="checkbox" id="zoomCheck-fee98" hidden>
    <label for="zoomCheck-fee98">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="imbalance.svg" alt="imbalance" 
             />
    </label>

</p>
<p>This may not be inherently bad, but the future of life then becomes an uncertainty.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-11dff" hidden>
    <label for="zoomCheck-11dff">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="earth.svg" alt="earth" 
             />
    </label>

</p>
]]></content>
      </entry>
      <entry>
        <title>Servers on Android</title>
        <link rel="alternate" href="/posts/009-servers-on-android/" />
        <id>/posts/009-servers-on-android/</id>
        <published>2026-03-28T08:53:29+02:00</published>
        <updated>2026-04-17T11:49:46+02:00</updated>
        <summary type="html">Android is a powerful operating system. To fully utilize it&amp;rsquo;s capabilities, a specific set of tools are required. Termux is a terminal emulator application enhanced with a large set of command line utilities ported to Android 1. It is used to install a server for remote access (sshd) and for serving a static website (httpd). These are exposed to the internet using Tor.
The Terminal A terminal emulator provides an interface to the system using text commands.</summary>
          <content type="html"><![CDATA[<p>Android is a powerful operating system. To fully utilize it&rsquo;s capabilities, a specific set of tools are required. Termux is a terminal emulator application enhanced with a large set of command line utilities ported to Android <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. It is used to install a server for remote access (sshd) and for serving a static website (httpd). These are exposed to the internet using Tor.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-d6d88" hidden>
    <label for="zoomCheck-d6d88">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="servers-on-android.svg" alt="servers on android" 
             />
    </label>

</p>
<h1 id="the-terminal">The Terminal</h1>
<p>A terminal emulator provides an interface to the system using text commands. A core set of utilites are provided by default, and more can be installed and upgraded with pkg - the Termux package manager <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-99e2b" hidden>
    <label for="zoomCheck-99e2b">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="termux-terminal.jpg" alt="termux terminal|10x30" 
             />
    </label>

</p>
<p>Refer to the official documentation to learn more about Termux: <a href="https://wiki.termux.dev/wiki/Main_Page">https://wiki.termux.dev/wiki/Main_Page</a>.</p>
<h2 id="installation">Installation</h2>
<p>The official source for installation is F-Droid, although builds are published on Github as well <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. Google PlayStore is not recommended. Do not mix applications between sources. Applications are installed by downloading and opening the Android Package Kit (APK) file.</p>
<blockquote>
<p>To install applications from APK files, &ldquo;Install from Unknown Sources&rdquo; must be temporarily enabled in Settings for the file manager. For security, disable it once installation is completed.</p>
</blockquote>
<p>The following applications need to be installed:</p>
<ul>
<li><a href="https://f-droid.org/en/packages/com.termux/">termux</a></li>
<li><a href="https://f-droid.org/en/packages/com.termux.api/">termux-api</a></li>
<li><a href="https://f-droid.org/en/packages/com.termux.boot/">termux-boot</a></li>
</ul>
<blockquote>
<p>F-Droid does not need to be downloded; search for the &ldquo;Download APK&rdquo; link under the latest version.</p>
</blockquote>
<p><code>termux</code> is the main application. <code>termux-api</code> exposes device functionality like vibrate, clipboard, torch and fingerprint <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>. <code>termux-boot</code> allows Termux to automatically start after device is restarted <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<h1 id="servers">Servers</h1>
<h2 id="sshd">sshd</h2>
<blockquote>
<p>Remote acceses via Secure SHell (ssh).</p>
</blockquote>
<p>Install and start <code>sshd</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pkg install openssh
</span></span><span class="line"><span class="cl">sshd
</span></span></code></pre></div><p>To use ssh, the hostname of the server and a username/password combination is required. Run <code>whoami</code> to determine the username (typically in the format u0_aXXX), and set the password with <code>passwd</code>.</p>
<p>The local IP address of the phone can be used to test the connection. The local IP address can be found under WiFi settings by expanding the currently connected network. Run <code>ssh</code> from a different machine on the same network:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh -p <span class="m">8022</span> u0_aXXX@192.168.0.1
</span></span></code></pre></div><blockquote>
<p>What follows is configuring key based authentication. While not strictly necessary, it is recommended for security. It can be configured later if required.</p>
</blockquote>
<p>The password will be prompted when connecting. This is not very secure since attackers could try and guess the username/password. To increase security, switch to key-based authentication by generating a key with <code>ssh-keygen</code> on the machine you will be connecting to the phone from:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">ssh-keygen -t rsa
</span></span><span class="line"><span class="cl">Enter file in which to save the key (/home/username/.ssh/id_rsa): /home/username/.ssh/keyname
</span></span><span class="line"><span class="cl">Enter passphrase for &#34;key-filename&#34; (empty for no passphrase): 
</span></span></code></pre></div><p>This will create two files under <code>/home/username/.ssh</code>:</p>
<ul>
<li><code>keyname</code>: a passphrase protected private key</li>
<li><code>keyname.pub</code>: a public key</li>
</ul>
<p>The public key&rsquo;s content must be copied to the file <code>/data/data/com.termux/files/home/.ssh/known_hosts</code>. This can be done using USB, by copying the text to clipboard and pasting it over <code>ssh</code>, or by using <code>ssh-copy-id</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh-copy-id -p <span class="m">8022</span> -i /home/username/.ssh/keyname.pub u0_aXXX@192.168.0.1
</span></span></code></pre></div><p>The private key can now be used to connect to the phone without a password:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh -p <span class="m">8022</span> -i /home/username/.ssh/keyname u0_aXXX@192.168.0.1
</span></span></code></pre></div><p>Once the key-based authentication has been tested, disable password authentication by editing <code>/data/data/com.termux/files/usr/etc/ssh/sshd_config</code> and setting <code>PasswordAuthentication no</code>. Restart <code>sshd</code> for the changes to take effect:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pkill sshd
</span></span><span class="line"><span class="cl">sshd
</span></span></code></pre></div><p>Try connecting from a different machine without the key; this should now fail:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh -o <span class="nv">PasswordAuthentication</span><span class="o">=</span>yes <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -o <span class="nv">PreferredAuthentications</span><span class="o">=</span>keyboard-interactive,password <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -o <span class="nv">PubkeyAuthentication</span><span class="o">=</span>no <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    u0_aXXX@192.168.0.1
</span></span></code></pre></div><p>Keep the private key at <code>/home/username/.ssh/keyname</code> secure - you cannot login remotely without it if &ldquo;PasswordAuthentication&rdquo; is disabled.</p>
<h2 id="httpd">httpd</h2>
<blockquote>
<p>Web server to host a website.</p>
</blockquote>
<p>Install <code>httpd</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pkg install apache2
</span></span></code></pre></div><p>Edit <code>/data/data/com.termux/files/usr/etc/apache2/httpd.conf</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-apache" data-lang="apache"><span class="line"><span class="cl"><span class="c"># Set server hostname and path to executables</span>
</span></span><span class="line"><span class="cl"><span class="nb">ServerName</span> <span class="m">127.0.0.1</span>
</span></span><span class="line"><span class="cl"><span class="nb">ServerRoot</span> <span class="s2">&#34;/data/data/com.termux/files/usr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Set port which server accepts connections over</span>
</span></span><span class="line"><span class="cl"><span class="nb">Listen</span> <span class="m">8080</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Set directory from which Apache will serve files</span>
</span></span><span class="line"><span class="cl"><span class="nb">DocumentRoot</span> <span class="s2">&#34;/data/data/com.termux/files/home/folderToServe&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Set the file to save errors to</span>
</span></span><span class="line"><span class="cl"><span class="nb">ErrorLog</span> <span class="s2">&#34;/data/data/com.termux/files/home/httpd.log&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Handle concurrent connections with Worker Multi-Processing Module</span>
</span></span><span class="line"><span class="cl"><span class="nb">LoadModule</span> mpm_worker_module libexec/apache2/mod_mpm_worker.so
</span></span><span class="line"><span class="cl"><span class="c"># Maps file extensions to content types</span>
</span></span><span class="line"><span class="cl"><span class="nb">LoadModule</span> mime_module libexec/apache2/mod_mime.so
</span></span><span class="line"><span class="cl"><span class="c"># Ensures proper privilege handling on Unix systems</span>
</span></span><span class="line"><span class="cl"><span class="nb">LoadModule</span> unixd_module libexec/apache2/mod_unixd.so
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Set default webpage to return when client connects to the server</span>
</span></span><span class="line"><span class="cl"><span class="nb">LoadModule</span> dir_module libexec/apache2/mod_dir.so
</span></span><span class="line"><span class="cl"><span class="nb">DirectoryIndex</span> index.html
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Prevent arbitrary file requests (lock down filesystem)</span>
</span></span><span class="line"><span class="cl"><span class="nb">LoadModule</span> authz_core_module libexec/apache2/mod_authz_core.so
</span></span><span class="line"><span class="cl"><span class="nt">&lt;Directory</span> <span class="s">&#34;/&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Require</span> <span class="k">all</span> denied
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Directory&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Allow access only from local connections (127.0.0.1)</span>
</span></span><span class="line"><span class="cl"><span class="nb">LoadModule</span> authz_host_module libexec/apache2/mod_authz_host.so
</span></span><span class="line"><span class="cl"><span class="nt">&lt;Directory</span> <span class="s">&#34;/data/data/com.termux/files/home/folderToServe&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="nb">Require</span> local
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Directory&gt;</span>
</span></span></code></pre></div><p>Replace <code>folderToServer</code> with the path your website is saved at. Modules are available for more functionality - refer to the Apache http server documentation: <a href="https://httpd.apache.org/docs/current/mod/">https://httpd.apache.org/docs/current/mod/</a>.</p>
<p>A folder from a different machine can be copied to the phone over <code>ssh</code> using <code>rsync</code> (without the <code>--dry-run</code> flag):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">rsync -havP --checksum --delete --dry-run <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -e <span class="s1">&#39;ssh -i /home/username/.ssh/keyname&#39;</span>
</span></span><span class="line"><span class="cl">    /local/path/to/folder/in/machine <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    u0_aXXX@192.168.0.1:/data/data/com.termux/files/home/folderToServe
</span></span></code></pre></div><p>Test to ensure the server starts without errors by running <code>httpd</code>. If the command doesn&rsquo;t fail due to invalid configuration, errors will be logged to the file specified above by <code>ErrorLog</code>. The files under <code>DocumentRoot</code> should now be served - visit <code>127.0.0.1:8080</code> on the phone&rsquo;s browser.</p>
<h1 id="tor">Tor</h1>
<blockquote>
<p>The proxy to the Internet.</p>
</blockquote>
<p>To access a server over the internet, the server needs a public IP address. A Virtual Private Server (VPS) is a publicly accessible machine hosted by a cloud provider. Since the phone itself does not have a public IP address, it must use a reverse proxy like <a href="https://github.com/fatedier/frp">frp</a> or <a href="https://nginx.org/">nginx</a> running on a VPS to be accessible on the internet. An alternative is to use the Tor network.</p>
<p>The Tor network is a group of volunteer-operated servers <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>. It uses a special protocol for privacy and anonymity. A Tor service can be set up to make the local services accessible to the internet.</p>
<p>A Tor service address is different from public domains; this is the link for the Tor Project homepage:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/
</span></span></code></pre></div><p>It can be accessed using the <a href="https://www.torproject.org/download/">Tor Browser</a>, and will not work with browsers that do not route traffic through the Tor network.</p>
<!-- raw HTML omitted -->
<p>To setup the Tor service, edit <strong>tor</strong> <strong>r</strong>un <strong>c</strong>onfig <code>/data/data/com.termux/files/usr/etc/tor/torrc</code> and append:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Service for sshd</span>
</span></span><span class="line"><span class="cl">HiddenServiceDir /data/data/com.termux/files/home/.tor/ssh/
</span></span><span class="line"><span class="cl">HiddenServicePort <span class="m">22</span> 127.0.0.1:8022
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Service for httpd</span>
</span></span><span class="line"><span class="cl">HiddenServiceDir /data/data/com.termux/files/home/.tor/webserver/
</span></span><span class="line"><span class="cl">HiddenServicePort <span class="m">80</span> 127.0.0.1:8080
</span></span></code></pre></div><p><code>HiddenServiceDir</code> defines where the identity files for the service must be stored. <code>HiddenServicePort</code> maps a virtual port to a local port. Clients will target the virtual port (22 or 80) and <code>tor</code> will forward this traffic to the specified local port (8022 or 8080).</p>
<p>Execute tor and get the hostame of the service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">tor
</span></span><span class="line"><span class="cl">cat /data/data/com.termux/files/home/.tor/ssh/hostname
</span></span></code></pre></div><p>Use <a href="https://gitlab.torproject.org/tpo/core/torsocks/">torsocks</a> to proxy ssh connections:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">torsocks ssh -p <span class="m">22</span> -i /home/username/.ssh/keyname.pub <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    u0_aXXX@abcde23456abcde23456abcde23456abcde23456abcde23456abcdef.onion
</span></span></code></pre></div><blockquote>
<p>The Termux documentation recommends <code>proxychains-ng</code> over <code>torsocks</code> as <code>torsocks</code> may not work since Android 8 <sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.</p>
</blockquote>
<p>Use the Tor Browser to visit the website. The services hosted on Android with Termux can now be accessed from anywhere in the world with an internet connection and Tor.</p>
<h1 id="autorun-on-boot">Autorun on boot</h1>
<blockquote>
<p>Keeping the services alive.</p>
</blockquote>
<p>When the phone is rebooted, the services will stop. To automatically start up, the service must be enabled and Termux must be started on boot.</p>
<h2 id="enable-services">Enable services</h2>
<p>Services run in the background and restart when certain conditions are triggered.</p>
<p>Install the service manager:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pkg install termux-services
</span></span></code></pre></div><p>Start the services and enable it to autostart when Termux starts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sv up sshd
</span></span><span class="line"><span class="cl">sv up httpd
</span></span><span class="line"><span class="cl">sv up tor
</span></span><span class="line"><span class="cl">sv-enable sshd
</span></span><span class="line"><span class="cl">sv-enable httpd
</span></span><span class="line"><span class="cl">sv-enable tor
</span></span></code></pre></div><p>The number of services supported is limited; refer to the documentation to learn more: <a href="https://wiki.termux.dev/wiki/Termux-services">https://wiki.termux.dev/wiki/Termux-services</a>.</p>
<p>The logs for running services are stored at <code>/data/data/com.termux/files/usr/var/log/sv/</code>.</p>
<p>To ensure that the serivce works, restart the service after killing all currently running processes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sv down httpd
</span></span><span class="line"><span class="cl">pgrep httpd
</span></span><span class="line"><span class="cl">pkill -f httpd
</span></span><span class="line"><span class="cl">sv up httpd
</span></span><span class="line"><span class="cl">cat /data/data/com.termux/files/usr/var/log/sv/httpd/current
</span></span></code></pre></div><h2 id="start-termux-on-boot">Start Termux on boot</h2>
<p>Create the directory for boot scripts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mkdir -p /data/data/com.termux/files/home/.termux/boot
</span></span></code></pre></div><p>Create a file (<code>nvim start-services</code>) under the boot directory:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/data/data/com.termux/files/usr/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>termux-wake-lock
</span></span><span class="line"><span class="cl">. <span class="nv">$PREFIX</span>/etc/profile
</span></span></code></pre></div><p><code>termux-wake-lock</code> stops Android from going into deep sleep - this prevents Android&rsquo;s aggressive power management from suspending background processes. The second line sources <code>/data/data/com.termux/files/usr/etc/profile</code> - this configures the environment.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://wiki.termux.dev/wiki/Getting_started">https://wiki.termux.dev/wiki/Getting_started</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://wiki.termux.dev/wiki/Package_Management">https://wiki.termux.dev/wiki/Package_Management</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://wiki.termux.dev/wiki/Main_Page#Installation">https://wiki.termux.dev/wiki/Main_Page#Installation</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://wiki.termux.dev/wiki/Termux:API">https://wiki.termux.dev/wiki/Termux:API</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://wiki.termux.dev/wiki/Termux:Boot">https://wiki.termux.dev/wiki/Termux:Boot</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://support.torproject.org/about-tor/how-tor-works/overview">https://support.torproject.org/about-tor/how-tor-works/overview</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://wiki.termux.dev/wiki/Bypassing_NAT">https://wiki.termux.dev/wiki/Bypassing_NAT</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
      </entry>
      <entry>
        <title>Is happiness a state?</title>
        <link rel="alternate" href="/posts/005-is-happiness-a-state/" />
        <id>/posts/005-is-happiness-a-state/</id>
        <published>2026-03-09T19:55:32+02:00</published>
        <updated>2026-03-09T19:55:32+02:00</updated>
        <summary type="html">Is happiness a state?
It matters because we wish to be happy. To be happy as long as we can. To never be unhappy - if at all possible. Is it possible?
That largely depends on our the measure of happiness.
Happiness could be:
a particular position a particular direction a state; some combination of position and direction What is happiness for you?
How would you measure it?</summary>
          <content type="html"><![CDATA[<p>Is happiness a state?</p>
<p>It matters because we wish to be happy. To be happy as long as we can. To never be unhappy - if at all possible. Is it possible?</p>
<p>That largely depends on our the measure of happiness.</p>
<p>Happiness could be:</p>
<ul>
<li>a particular position</li>
<li>a particular direction</li>
<li>a state; some combination of position and direction</li>
</ul>
<p>



    
    <input type="checkbox" id="zoomCheck-da512" hidden>
    <label for="zoomCheck-da512">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="curve-of-happiness.svg" alt="curve of happiness" 
             />
    </label>

</p>
<p>What is happiness for you?<br>
How would you measure it?</p>
<hr>
]]></content>
      </entry>
      <entry>
        <title>Audio Engines in C&#43;&#43;</title>
        <link rel="alternate" href="/posts/004-audio-engines-in-c&#43;&#43;/" />
        <id>/posts/004-audio-engines-in-c&#43;&#43;/</id>
        <published>2026-02-25T22:24:26+02:00</published>
        <updated>2026-03-26T21:18:27+02:00</updated>
        <summary type="html">Context Rhythmbox-Reloaded is a C++ application that needs to play audio files. There are libraries that handle this - they simply need to be included, initialized and used. miniaudio is easy to integrate, but has file format limitations. libvlc can support almost any format, but bundling it with the application impacts size and performance. Dynamic linking addresses this issue, but creates a requirement for external dependencies. As a compromise, the application may try to use libvlc, but fall back to miniaudio.</summary>
          <content type="html"><![CDATA[<h1 id="context">Context</h1>
<p><a href="https://git.kistu.dev/kistu/rhythmbox-reloaded-desktop">Rhythmbox-Reloaded</a> is a C++ application that needs to play audio files. There are libraries that handle this - they simply need to be included, initialized and used. <code>miniaudio</code> is easy to integrate, but has file format limitations. <code>libvlc</code> can support almost any format, but bundling it with the application impacts size and performance. Dynamic linking addresses this issue, but creates a requirement for external dependencies. As a compromise, the application may try to use <code>libvlc</code>, but fall back to <code>miniaudio</code>.</p>
<h1 id="miniaudio">Miniaudio</h1>
<p><a href="https://miniaud.io/">Miniaudio</a> is an audio playback library for C and C++; it is made up of a single source file without external dependencies.</p>
<p>To use the library, download the header file <a href="https://raw.githubusercontent.com/mackron/miniaudio/master/miniaudio.h">miniaudio.h</a>.</p>
<p>To test the library by playing a file, create <code>ma-test.cpp</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#define MINIAUDIO_IMPLEMENTATION
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;miniaudio.h&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// Miniaudio variables
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="n">ma_engine</span> <span class="n">engine</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">ma_result</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">ma_sound</span> <span class="n">sound</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Initialize engine
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="n">result</span> <span class="o">=</span> <span class="n">ma_engine_init</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">engine</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="n">MA_SUCCESS</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Load file
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="n">result</span> <span class="o">=</span> <span class="n">ma_sound_init_from_file</span><span class="p">(</span><span class="o">&amp;</span><span class="n">engine</span><span class="p">,</span> <span class="s">&#34;file.mp3&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sound</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="n">MA_SUCCESS</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// Start playback
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="n">ma_sound_start</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sound</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Playing file.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// Wait until completion
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>			<span class="k">while</span><span class="p">(</span><span class="o">!</span><span class="n">ma_sound_at_end</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sound</span><span class="p">)){</span>
</span></span><span class="line"><span class="cl">				<span class="n">ma_sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="n">ma_sound_uninit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sound</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Playback completed.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Failed to load file.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Failed to initialize file.</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Compile and run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">g++ ma-test.cpp -o ma-test.o
</span></span><span class="line"><span class="cl">./ma-test.o
</span></span></code></pre></div><blockquote>
<p>Ensure the header file <code>miniaudio.h</code> and audio file <code>file.mp3</code> are in the same folder as the above source code file.</p>
</blockquote>
<p>The high level API has built in support for WAV, MP3 and FLAC - other formats require more complex configuration with the low level API. <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> If support for an extensive list of audio formats is required, <code>libvlc</code> may be better suited.</p>
<h1 id="libvlc">libvlc</h1>
<p><a href="https://code.videolan.org/videolan/libvlc">libvlc</a> is a C library that is the core engine of the VLC media player. VideoLAN provides official C++ bindings <a href="https://code.videolan.org/videolan/libvlcpp">(libvlcpp)</a> that acts as a wrapper for libvlc. The bindings consist of header files; only the <code>vlcpp</code> folder - containing several .hpp files - is required from the repository: <a href="https://code.videolan.org/videolan/libvlcpp">https://code.videolan.org/videolan/libvlcpp</a>.</p>
<blockquote>
<p>VLC and libvlc are required dependencies. Some sources of VLC may not expose libvlc as a library. Refer to the VLC installation guides for your particular system.</p>
</blockquote>
<p>Once libvlc is accessible and the <code>vlcpp</code> folder has been copied to the current folder, the engine can be tested; create <code>vlc-test.cpp</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;vlcpp/vlc.hpp&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(){</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// VLC variables
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">VLC</span><span class="o">::</span><span class="n">Instance</span> <span class="n">m_instance</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">VLC</span><span class="o">::</span><span class="n">MediaPlayer</span> <span class="n">m_player</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">VLC</span><span class="o">::</span><span class="n">Media</span> <span class="n">m_media</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Initialize with video disabled
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="k">const</span> <span class="n">vlc_args</span><span class="p">[]</span> <span class="p">{</span><span class="s">&#34;--no-video&#34;</span><span class="p">,</span> <span class="s">&#34;--vout=dummy&#34;</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_instance</span> <span class="o">=</span> <span class="n">VLC</span><span class="o">::</span><span class="n">Instance</span><span class="p">(</span><span class="kt">int</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">vlc_args</span><span class="p">)</span><span class="o">/</span><span class="k">sizeof</span><span class="p">(</span><span class="n">vlc_args</span><span class="p">[</span><span class="mi">0</span><span class="p">])),</span> <span class="n">vlc_args</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">m_player</span> <span class="o">=</span> <span class="n">VLC</span><span class="o">::</span><span class="n">MediaPlayer</span><span class="p">(</span><span class="n">m_instance</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_media</span> <span class="o">=</span> <span class="n">VLC</span><span class="o">::</span><span class="n">Media</span><span class="p">(</span><span class="n">m_instance</span><span class="p">,</span> <span class="s">&#34;file.mp3&#34;</span><span class="p">,</span> <span class="n">VLC</span><span class="o">::</span><span class="n">Media</span><span class="o">::</span><span class="n">FromPath</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_player</span><span class="p">.</span><span class="n">setMedia</span><span class="p">(</span><span class="n">m_media</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Start playback
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="p">(</span><span class="n">m_player</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_player</span><span class="p">.</span><span class="n">play</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Wait for playback to start, then finish
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">m_player</span><span class="p">.</span><span class="n">isPlaying</span><span class="p">()){</span> <span class="n">usleep</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="n">m_player</span><span class="p">.</span><span class="n">isPlaying</span><span class="p">())</span> <span class="p">{</span> <span class="n">usleep</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>libvlc must be linked when compiling:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">g++ -lvlc vlc-test.cpp -o vlc-test.o
</span></span><span class="line"><span class="cl">./vlc-test.o
</span></span></code></pre></div><blockquote>
<p>If you need to package the application as an AppImage, you must exclude libvlc when building. VLC initialization unexpectedly fails with partial vlc installations (missing some plugins) and including the full installation would be quite inefficient - it is better to dynamically link.</p>
</blockquote>
<h1 id="fallback">Fallback</h1>
<p>It would be beneficial if the application could retain some functionality even if <code>libvlc</code> is unavailable. When using <code>libvlc</code> by <code>#include &lt;vlcpp/vlc.hpp&gt;</code>, the application does not start without libvlc.</p>
<p>Using preprocessor directives only works for compilation. The developer machine can be required to have vlc installed, but this doesn&rsquo;t solve the runtime issue.</p>
<p>Another option is to load the library at runtime using <code>dlopen</code> or <code>LoadLibrary</code>, but since <code>libvlcpp</code> is being used as a binding, this doesn&rsquo;t seem easily possible.</p>
<p>Currently, fallback is not supported. Two build version may be offered - one using <code>miniaudio</code> and the other <code>libvlc</code>. An integrated version would be prefered.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://miniaud.io/docs/manual/index.html#Decoding">https://miniaud.io/docs/manual/index.html#Decoding</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
      </entry>
      <entry>
        <title>PostgreSQL with a REST API</title>
        <link rel="alternate" href="/posts/003-postgresql-with-a-rest-api/" />
        <id>/posts/003-postgresql-with-a-rest-api/</id>
        <published>2026-02-16T19:41:13+02:00</published>
        <updated>2026-04-21T12:16:04+02:00</updated>
        <summary type="html">Context The requirement is to implement a new database design for managing a variety of digital assets. Each asset has an associated identifier (hash) and metadata. The hash is calculated using a tool like sha512sum or phash. Metadata is extracted in JSON format using tools like ffprobe or exiftool. Ideally, only specific required metadata fields will be stored in the database, but that makes future changes more complex and requires making certain assumptions about the type of digital asset.</summary>
          <content type="html"><![CDATA[<h1 id="context">Context</h1>
<p>The requirement is to implement a new database design for managing a variety of digital assets. Each asset has an associated identifier (hash) and metadata. The hash is calculated using a tool like <code>sha512sum</code> or <code>phash</code>. Metadata is extracted in JSON format using tools like <code>ffprobe</code> or <code>exiftool</code>. Ideally, only specific required metadata fields will be stored in the database, but that makes future changes more complex and requires making certain assumptions about the type of digital asset. To make the database as general purpose as possible, the database management system (DBMS) needs to support JSON documents.</p>
<h1 id="postgresql">PostgreSQL</h1>
<p><a href="https://www.postgresql.org">PostgreSQL</a> is an open source relational database that supports JSON types. To better understand the configuration of this database, it is beneficial to briefly cover some terminology and hierarchies.</p>
<p>PostgreSQL uses a client-server model. The server accepts client connections at a specified port (5432 by default) and stores data at a specified location (typically <code>/usr/local/pgsql/data</code> or <code>/var/lib/postgresql/data</code>). The server can manage any number of databases stored in this data directory; this forms a single database cluster. The database itself consists of schemas; schemas act as a namespace for organizing database objects like tables and functions (SQL queries).</p>
<p>



    
    <input type="checkbox" id="zoomCheck-bb973" hidden>
    <label for="zoomCheck-bb973">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="postgresql-organization-hierarchy.svg" alt="postgresql organization hierarchy" 
             />
    </label>

</p>
<p>PostgreSQL uses roles and privileges for access control. Roles are created and assigned permissions to perform specific actions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">create role role_name password <span class="s1">&#39;role_password&#39;</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">grant usage on schema schema_name to role_name<span class="p">;</span>  <span class="c1"># allows accessing objects under specified schema</span>
</span></span><span class="line"><span class="cl">grant INSERT on schema.table to role_name<span class="p">;</span>       <span class="c1"># allows action on specified object</span>
</span></span></code></pre></div><p>The specified action can be ALL, or INSERT, SELECT, UPDATE, DELETE and so on.</p>
<h1 id="implementation">Implementation</h1>
<p>Docker is used to deploy the services. Three services are used:</p>
<ul>
<li><code>db</code>: the PostgreSQL server and database cluster</li>
<li><a href="https://www.adminer.org/en/"><code>adminer</code></a>: webpage that allows running SQL commands</li>
<li><code>api</code>: <a href="https://docs.postgrest.org">Postg<strong>REST</strong></a> web server that exposes a REST API</li>
</ul>
<p>



    
    <input type="checkbox" id="zoomCheck-779ce" hidden>
    <label for="zoomCheck-779ce">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="services-connection.svg" alt="services connection" 
             />
    </label>

</p>
<p><code>psql</code> is a terminal based client interface for the PostgreSQL server. <code>pgAdmin</code> and <code>phpMyAdmin</code> are graphical interfaces similar to <code>adminer</code>; GUIs make running SQL commands easier by eliminating the need for terminal access. Adminer can be used to create schemas, perform CRUD operations on tables, create roles, assign permissions and execute other SQL commands.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-b31bc" hidden>
    <label for="zoomCheck-b31bc">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="adminer-interface.png" alt="adminer interface" 
             />
    </label>

</p>
<p>The <code>api</code> is used to enable clients to perform actions without specialized tools for connecting to the database; only the ability to send HTTP requests is required.</p>
<h2 id="docker">Docker</h2>
<p>Create <code>docker-compose.yml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:18.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_USER</span><span class="p">:</span><span class="w"> </span><span class="l">${ADMIN_USERNAME}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">${ADMIN_PASSWORD}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_DB</span><span class="p">:</span><span class="w"> </span><span class="l">${DATABASE_NAME}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">pgdata:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adminer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">adminer:5.4.2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">8080</span><span class="p">:</span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgrest/postgrest:v14.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">3000</span><span class="p">:</span><span class="m">3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGRST_SERVER_HOST</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGRST_DB_URI</span><span class="p">:</span><span class="w"> </span><span class="l">postgres://${AUTH_ROLE_USERNAME}:${AUTH_ROLE_PASSWORD}@db:5432/${DATABASE_NAME}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGRST_OPENAPI_SERVER_PROXY_URI</span><span class="p">:</span><span class="w"> </span><span class="l">http://127.0.0.1:3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PGRST_JWT_SECRET</span><span class="p">:</span><span class="w"> </span><span class="l">${JWT_SECRET}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pgdata</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>Define the variables in <code>.env</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-env" data-lang="env"><span class="line"><span class="cl"><span class="nv">ADMIN_USERNAME</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">ADMIN_PASSWORD</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DATABASE_NAME</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">DATABASE_SCHEMAS</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">AUTH_ROLE_USERNAME</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="nv">AUTH_ROLE_PASSWORD</span><span class="o">=</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">JWT_SECRET</span><span class="o">=</span>
</span></span></code></pre></div><blockquote>
<p>Use <code>echo $(&lt; /dev/urandom tr -dc A-Za-z0-9 | head -c32)</code> to generate secure alphanumeric passwords; change length (to <code>-c64</code>, <code>c128</code>, &hellip;) as required. The JWT secret must be 32 characters.</p>
</blockquote>
<p>Start the services with <code>docker compose up -d</code>.</p>
<h2 id="database-roles">Database roles</h2>
<p>Two roles are used to separate authentication (verifying identity) from authorization (verifying permissions). Clients connect to the <code>authenticator</code> role by providing the correct password, but this role does not have any permissions. The client can then switch to another role (<code>SET ROLE role_name</code>) which has the required permissions.</p>
<p>The following can be executed using a client interface (like <code>psql</code> or <code>Adminer</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">create role role_name nologin<span class="p">;</span> 
</span></span><span class="line"><span class="cl">create role authenticator noinherit login password <span class="s1">&#39;example_authenticator_password&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">grant role_name to authenticator<span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">create schema schema_name<span class="p">;</span>
</span></span><span class="line"><span class="cl">grant usage on schema schema_name to role_name<span class="p">;</span>
</span></span><span class="line"><span class="cl">grant all on schema_name.table_name to role_name<span class="p">;</span>
</span></span></code></pre></div><blockquote>
<p>The nologin rolename and authenticator rolename and password should match that defined in <code>.env</code>.</p>
</blockquote>
<ul>
<li><code>nologin</code> disables connecting directly as the specified role</li>
<li><code>noinherit</code> prevents default permissions from being automatically assigned</li>
</ul>
<p>The containers need to be restarted for <code>postgrest</code> to start without errors:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">docker compose down
</span></span><span class="line"><span class="cl">docker compose up -d
</span></span></code></pre></div><p>Check for keywords <code>error</code> or <code>fatal</code> in the logs (<code>docker compose logs</code>).</p>
<h2 id="api-token-generation">API token generation</h2>
<p>The following steps are from the PostgREST documentation <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Use the JWT secret defined in <code>.env</code>. Create a signed token using bash and openssl, replacing role_name with the nologin role:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="nb">set</span> -e
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">JWT_SECRET</span><span class="o">=</span><span class="s1">&#39;test_secret_that_is_at_least_32_characters_long&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">_base64 <span class="o">()</span> <span class="o">{</span> openssl base64 -e -A <span class="p">|</span> tr <span class="s1">&#39;+/&#39;</span> <span class="s1">&#39;-_&#39;</span> <span class="p">|</span> tr -d <span class="s1">&#39;=&#39;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">header</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> -n <span class="s1">&#39;{&#34;alg&#34;:&#34;HS256&#34;,&#34;typ&#34;:&#34;JWT&#34;}&#39;</span> <span class="p">|</span> _base64<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">payload</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> -n <span class="s2">&#34;{\&#34;role\&#34;:\&#34;role_name\&#34;}&#34;</span> <span class="p">|</span> _base64<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">signature</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="nv">$header</span><span class="s2">.</span><span class="nv">$payload</span><span class="s2">&#34;</span> <span class="p">|</span> openssl dgst -sha256 -hmac <span class="s2">&#34;</span><span class="nv">$JWT_SECRET</span><span class="s2">&#34;</span> -binary <span class="p">|</span> _base64<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="nv">$header</span><span class="s2">.</span><span class="nv">$payload</span><span class="s2">.</span><span class="nv">$signature</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Run this script (<code>./script_name.sh</code>) to create the token.</p>
<h2 id="api-usage">API usage</h2>
<p>Set your token value (this needs to be repeated when a new shell is opened):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">read</span> TOKEN
</span></span></code></pre></div><p>To retrieve records:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl https://domain_name.org/table_name -X GET <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -H <span class="s2">&#34;Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -H <span class="s2">&#34;Accept-Profile: schema_name&#34;</span>
</span></span></code></pre></div><blockquote>
<p>Note, the schema must be created manually (using Adminer) and defined in the environment (<code>DATABASE_SCHEMAS</code>) with the containers restarted after updates for the schema to be accessible. <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
</blockquote>
<p>To insert new data:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl https://domain_name.org/table_name -X POST <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -H <span class="s2">&#34;Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -d <span class="s2">&#34;{&#34;</span>column1<span class="s2">&#34;: &#34;</span>value<span class="s2">&#34;, &#34;</span>column2<span class="s2">&#34;: {key1: value1, key2: value2}, &#34;</span>column3<span class="s2">&#34;: 3 }&#34;</span>
</span></span></code></pre></div><h2 id="troubleshooting">Troubleshooting</h2>
<p>There are two different ways of approaching troubleshooting - by starting with something that doesn&rsquo;t work and removing/modifying components until does, or by starting with something that does work and adding components until it doesn&rsquo;t. The second approach is easier when there are many things that could go wrong.</p>
<p>A common error that may be faced is that the binding port is already used. The syntax for port binding is <code>- host_port:container_port</code>. The host post may need to be changed if there is a conflict.</p>
<p>When modifying the <code>docker-compose.yml</code>, if you mount the <code>pgdata</code> volume to <code>/var/lib/postgresql/data</code> and try to access the API, it will state that the <code>JWT secret is not set</code>. If you have a look at logs of the PostgreSQL service, you will notice that it is actually not running - the error references a pull request which requires that the volume be mounted at <code>/var/lib/postgresql</code> instead for PostgreSQL v18+ <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. Determining this true cause by troubleshooting the API would have been tedious and time consuming.</p>
<p>The PostgREST REST API requires cache invalidation; if faced with errors of the format <code>Could not find xxx in the schema cache</code> and after verification that xxx indeed exists in the specified schema, simply stop and start the service to update cache.</p>
<p>Note, one PostgREST instance can only connect to one database instance. If API access is required for multiple databases in the same database cluster, each database requires a PostgREST instance <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>. Depending on your requirements, using multiple schemas may be a better option.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://docs.postgrest.org/en/v14/tutorials/tut1.html#step-3-sign-a-token">https://docs.postgrest.org/en/v14/tutorials/tut1.html#step-3-sign-a-token</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://docs.postgrest.org/en/v14/references/api/schemas.html">https://docs.postgrest.org/en/v14/references/api/schemas.html</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://github.com/docker-library/postgres/pull/1259">https://github.com/docker-library/postgres/pull/1259</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://github.com/PostgREST/postgrest/issues/1090">https://github.com/PostgREST/postgrest/issues/1090</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content>
      </entry>
      <entry>
        <title>Packaging Qt Applications for Linux</title>
        <link rel="alternate" href="/posts/002-packaging-qt-applications/" />
        <id>/posts/002-packaging-qt-applications/</id>
        <published>2025-11-28T00:15:11+02:00</published>
        <updated>2026-03-26T20:51:04+02:00</updated>
        <summary type="html">Rhythmbox-Reloaded is a C++ application that uses Qt for the user interface. An executable that anyone can use to run the application needs to be created. For Windows and Apple systems, there is little variation in operating system structure so simply building the application on that platform is sufficient. This is not the case for Linux with its many distributions and variations.
Single Distribution The first step is to get an executable for the developer machine.</summary>
          <content type="html"><![CDATA[<p><a href="https://git.kistu.dev/kistu/rhythmbox-reloaded-desktop">Rhythmbox-Reloaded</a> is a C++ application that uses Qt for the user interface. An executable that anyone can use to run the application needs to be created. For Windows and Apple systems, there is little variation in operating system structure so simply building the application on that platform is sufficient. This is not the case for Linux with its many distributions and variations.</p>
<h1 id="single-distribution">Single Distribution</h1>
<p>The first step is to get an executable for the developer machine. This is quite simple - it can be done in the Qt Creator IDE or using the terminal with <code>qmake</code> and <code>make</code>. <code>qmake</code> generates a Makefile based on the contents of a project file.</p>
<p>The specifics of the project file aren&rsquo;t important; this file is created automatically by QtCreator and defines a few configurations (compiler versions and required Qt modules) as well as files to include and compile. Here is a minimal example of a project file <code>Application.pro</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">QT       += core gui
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">CONFIG += c++17
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">SOURCES += \
</span></span><span class="line"><span class="cl">    application-src/main.cpp \
</span></span><span class="line"><span class="cl">    application-src/mainwindow.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">HEADERS += \
</span></span><span class="line"><span class="cl">    application-headers/mainwindow.h
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">FORMS += \
</span></span><span class="line"><span class="cl">    application-forms/mainwindow.ui
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">INCLUDEPATH += application-headers/
</span></span><span class="line"><span class="cl">INCLUDEPATH += application-src/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">RESOURCES += \
</span></span><span class="line"><span class="cl">    application-styling/application-styling.qrc
</span></span></code></pre></div><p><code>qmake</code> reads this file and generates a Makefile:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="c">#############################################################################
</span></span></span><span class="line"><span class="cl"><span class="c"># Generated by qmake (3.1) (Qt 6.2.4)
</span></span></span><span class="line"><span class="cl"><span class="c">#############################################################################
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="c">####### Compiler, tools and options
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nv">CC</span>            <span class="o">=</span> gcc
</span></span><span class="line"><span class="cl"><span class="nv">CXX</span>           <span class="o">=</span> g++
</span></span><span class="line"><span class="cl"><span class="nv">MKDIR</span>         <span class="o">=</span> mkdir -p
</span></span><span class="line"><span class="cl"><span class="nv">COPY</span>          <span class="o">=</span> cp -f
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Output directory
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nv">OBJECTS_DIR</span>   <span class="o">=</span> ./
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Files
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nv">SOURCES</span>       <span class="o">=</span> ../application-src/main.cpp <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		../application-src/mainwindow.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">OBJECTS</span>       <span class="o">=</span> main.o <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		mainwindow.o 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DIST</span>          <span class="o">=</span> /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/spec_pre.prf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		/usr/lib/x86_64-linux-gnu/qt6/mkspecs/common/unix.conf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		/usr/lib/x86_64-linux-gnu/qt6/mkspecs/common/linux.conf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		../application-src/main.cpp <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		../application-src/mainwindow.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Build rules
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">Application</span><span class="o">:</span> <span class="n">ui_mainwindow</span>.<span class="n">h</span> <span class="k">$(</span><span class="nv">OBJECTS</span><span class="k">)</span>  
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>LINK<span class="k">)</span> <span class="k">$(</span>LFLAGS<span class="k">)</span> -o <span class="k">$(</span>TARGET<span class="k">)</span> <span class="k">$(</span>OBJECTS<span class="k">)</span> <span class="k">$(</span>OBJCOMP<span class="k">)</span> <span class="k">$(</span>LIBS<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Sub-libraries
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">mocclean</span><span class="o">:</span> <span class="n">compiler_moc_header_clean</span> <span class="n">compiler_moc_objc_header_clean</span> <span class="n">compiler_moc_source_clean</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Compile
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">main.o</span><span class="o">:</span> ../<span class="n">application</span>-<span class="n">src</span>/<span class="n">main</span>.<span class="n">cpp</span> ../<span class="n">application</span>-<span class="n">headers</span>/<span class="n">mainwindow</span>.<span class="n">h</span>
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>CXX<span class="k">)</span> -c <span class="k">$(</span>CXXFLAGS<span class="k">)</span> <span class="k">$(</span>INCPATH<span class="k">)</span> -o main.o ../application-src/main.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Install
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">install_target</span><span class="o">:</span> <span class="n">first</span> <span class="n">FORCE</span>
</span></span><span class="line"><span class="cl">	@test -d <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin <span class="o">||</span> mkdir -p <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>QINSTALL_PROGRAM<span class="k">)</span> <span class="k">$(</span>QMAKE_TARGET<span class="k">)</span> <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin/<span class="k">$(</span>QMAKE_TARGET<span class="k">)</span>
</span></span><span class="line"><span class="cl">	-<span class="k">$(</span>STRIP<span class="k">)</span> <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin/<span class="k">$(</span>QMAKE_TARGET<span class="k">)</span>
</span></span></code></pre></div><p>Running <code>make</code> uses the GNU Compiler Collection to generate an <code>elf</code> binary that can be executed.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-0aea2" hidden>
    <label for="zoomCheck-0aea2">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="compilation-chain.svg" alt="compilation-chain" 
             />
    </label>

</p>
<h1 id="multiple-distributions">Multiple Distributions</h1>
<p>A critical limitation of the executable generated above is that it depends on certain libraries in a way that makes it distribution dependent.</p>
<h2 id="discovering-the-issue">Discovering the Issue</h2>
<p>The most likely way this issue would be discovered is by running the application on different distributions - typically a given with even with a small user base. However, for this particular project, the issue was intrestingly identified in the CI/CD pipeline.</p>
<p>You may wish to automate the building and publishing of the application when the source code is pushed to remote git repository. Git hosting services often include an &ldquo;Actions&rdquo; feature that can use docker to achieve this. A minimal image that is often chosen is Alpine Linux. Required packages are installed with the Alpine package manager <code>apk</code> and the process for building the application outlined above is followed.</p>
<p>To test the generated binary, run it on the development machine. In this case, it happens to be Arch Linux - an operating system that is organised differently to Alpine Linux. Running the binary results in an error: <code>Error: file not found</code>.</p>
<p>To understand the cause of this error, a better understanding is required of how Make and the GNU compiler works.</p>
<h2 id="compilation-and-linking-process">Compilation and Linking Process</h2>
<p>The compiler&rsquo;s actions can be divided into 4 stages handled by different components.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-179d2" hidden>
    <label for="zoomCheck-179d2">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="compiler-components.svg" alt="compiler components" 
             />
    </label>

</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td>Preprocessor</td>
<td>expand or resolve lines starting with the directive (&rsquo;#')</td>
</tr>
<tr>
<td>Compiler</td>
<td>translates source code to assembly code</td>
</tr>
<tr>
<td>Assembler</td>
<td>translates assembly code to object code</td>
</tr>
<tr>
<td>Linker</td>
<td>combines multiple object files and links external libraries</td>
</tr>
</tbody>
</table>
<p>Examining the output of each stage gives a better insight into the process.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-ec13b" hidden>
    <label for="zoomCheck-ec13b">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="compiler-stages-with-output.svg" alt="compiler stages with output" 
             />
    </label>

</p>
<p>The component of importance here is the linker.</p>
<h3 id="dynamic-linking">Dynamic Linking</h3>
<p>Dynamic link object files are identified by the <code>.so</code> for &ldquo;shared object&rdquo; extension on Linux and <code>.dll</code> for &ldquo;dynamically linked library&rdquo; on Windows.</p>
<p>When linking, the symbols of the object file is added instead of the entire file itself.</p>
<p>The following command lists all the symbols in the libcurl library:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">objdump -T /usr/lib/libcurl.so
</span></span></code></pre></div><p>During runtime, missing symbols are resolved by the system. This makes the application more resource efficient and means changes to shared objects can be easily propagated. The comes at the cost of comptatibility since more assumptions are made about the system - such as having the shared object in an accessible path.</p>
<h3 id="static-linking">Static Linking</h3>
<p>All object files are stored directly in the resulting executable. Statically linked libraries are identified by an <code>.a</code> for &ldquo;archive&rdquo; extension.</p>
<p>Static linking results in better compatibility since it minimizes external dependencies. However, is increases resource consumption and makes it more difficult to update.</p>
<p>Static linking Qt is not trivial. The official installation only includes dynamic libraries and static linking may potentially face licensing restrictions. Static linking requires building Qt from source and additional configuration.</p>
<h2 id="packaging-with-appimage">Packaging with AppImage</h2>
<p>AppImages takes a different approach. The executable binary is generated using the same process outlined before, but this is then packaged with most required files to run with minimal external dependencies.</p>
<p>It should be noted that AppImages do still have some dependencies, but these are likely fulfilled by default by your distribution. There are some exceptions; Filesystem in Userspace (FUSE), which allows non-root users to mount filesystems and is required for the AppImage to work, is not bundled with older AppImages and may need to be installed explicitly.</p>
<p>If this is your first time packaging an application with AppImage, getting it to work may take time. After much trial and error, it seems that packaging with AppImage has two requirements: a compliant <code>AppDir</code> and a specific version of <code>glibc</code>.</p>
<h3 id="appdir-structure">AppDir Structure</h3>
<p>Creating the <code>AppDir</code> is quite simple and can be done manually; this is how it is organized:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">└── AppDir
</span></span><span class="line"><span class="cl">    └── usr
</span></span><span class="line"><span class="cl">        ├── bin
</span></span><span class="line"><span class="cl">        │   └── app-name.elf
</span></span><span class="line"><span class="cl">        └── share
</span></span><span class="line"><span class="cl">            ├── applications
</span></span><span class="line"><span class="cl">            │   └── app-name.desktop
</span></span><span class="line"><span class="cl">            └── icons
</span></span><span class="line"><span class="cl">                └── hicolor
</span></span><span class="line"><span class="cl">                    └── 256x256
</span></span><span class="line"><span class="cl">                        └── apps
</span></span><span class="line"><span class="cl">                            └── app-name.svg 
</span></span></code></pre></div><p><code>app-name.svg</code> is an image file that will be displayed as the application&rsquo;s icon. <code>app-name.elf</code> is the binary that was generated.</p>
<p><code>app-name.desktop</code> is a desktop entry file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Type=Application
</span></span><span class="line"><span class="cl">Exec=app-name
</span></span><span class="line"><span class="cl">Name=app-name
</span></span><span class="line"><span class="cl">Icon=app-name
</span></span><span class="line"><span class="cl">Categories=Utility;
</span></span></code></pre></div><blockquote>
<p>Note that the file extension is excluded in the entries for <code>Exec</code> and <code>Icon</code>.</p>
</blockquote>
<h3 id="glibc">glibc</h3>
<p><code>glibc</code> is a standard C library that provides many common and important functions. The <code>glibc</code> contraint is a little more nuanced.</p>
<blockquote>
<p>To produce binaries that are compatible with many target systems, build on the oldest still-supported build system.</p>
</blockquote>
<p><a href="https://github.com/probonopd/linuxdeployqt">linuxdeployqt</a> - the chosen packaging tool - recommends Ubuntu and enforces this system constraint quite strictly to encourage developers to support the most platforms.</p>
<p>The initial attempt on Alpine Linux failed because the glibc version was too new. From the documentation, it wasn&rsquo;t entirely clear if &ldquo;still-supported&rdquo; referred to the &ldquo;end of standard support&rdquo; or &ldquo;end of life&rdquo;. Ubuntu 14.04 was the oldest system that had not reached end of life. Not only did the system have network issues (there was a bug with libcurl3 that made TLS connections unusable), but the application didn&rsquo;t even compile. Sections of the source code needed to be modified because the <code>glibc</code> version was too old and didn&rsquo;t recognise some of the newer syntax.</p>
<p>At this stage, the packaging tool was abandoned and alternatives were tested - this time on Arch Linux, a rolling distribution with the latest versions of all packages. After much frustration, the issue was narrowed down to the <code>glibc</code> version again (this was not as obvious as it is in hindsight). Arch Linux does not support rolling back to a specific version, so the correct version was manually downloaded from <code>https://archive.archlinux.org/packages/g/glibc/</code> and forcefully installed. This promptly broke the system, causing a blue screen of death on shutdown (a first time on Linux). Luckily this was inside a virtual machine. Before shutdown, new terminals could not be opened, but some commands in an already open terminal worked and was used to copy some important data to a thumbdrive. The thumbdrive later failed due to an unrelated issue.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-c17e7" hidden>
    <label for="zoomCheck-c17e7">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="linux-bsod.png" alt="Linux&amp;rsquo;s blue screen of death" 
             />
    </label>

</p>
<p>Ubuntu 22.04 - the oldest system that still had standard support - was used next. Everything went smoothly once the format for the AppDir was established and application was successfully packaged and run on different distributions.</p>
<p>A example of a command that might be used to create an AppImage on Ubuntu 22.04:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./linuxdeploy.AppImage -appimage <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    AppDir/usr/share/applications/app-name.desktop <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -qmake<span class="o">=</span>/usr/bin/qmake6 <span class="nv">qmldir</span><span class="o">=</span>application-styling/ <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -qmldir-importscanner<span class="o">=</span>/usr/lib/qt6/libexec/qmlimportscanner
</span></span></code></pre></div><blockquote>
<p>The <code>qmldir</code> and <code>importscanner</code> are required to include styling and image assets defined as Qt Resources.</p>
</blockquote>
<p>It is intresting to see the lifespan of the software we build, and the chain of dependencies it requires.</p>
]]></content>
      </entry>
      <entry>
        <title>Jupyter Notebook on Docker</title>
        <link rel="alternate" href="/posts/001-jupyter-on-docker/" />
        <id>/posts/001-jupyter-on-docker/</id>
        <published>2025-11-11T09:10:59+02:00</published>
        <updated>2026-04-17T09:37:59+02:00</updated>
        <summary type="html">The design described here is for a very specific set of requirements; to create a reproducable and easy to configure environment to use Jupyter notebooks - one that is suitable for quickly experimenting and sharing ideas. Multiple virtual environments will be required for use across different projects.
Install Jupyter in a virtual environment Create and setup the virtual environment:
python3 -m virtualenv env-name source env-name/bin/activate pip3 install jupyter Run Jupyter Notebook:</summary>
          <content type="html"><![CDATA[<p>The design described here is for a very specific set of requirements; to create a reproducable and easy to configure environment to use Jupyter notebooks - one that is suitable for quickly experimenting and sharing ideas. Multiple virtual environments will be required for use across different projects.</p>
<h2 id="install-jupyter-in-a-virtual-environment">Install Jupyter in a virtual environment</h2>
<p>



    
    <input type="checkbox" id="zoomCheck-8a28a" hidden>
    <label for="zoomCheck-8a28a">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="install-jupyter-in-virtualenv.svg" alt="install jupyter in virtualenv" 
             />
    </label>

</p>
<p>Create and setup the virtual environment:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python3 -m virtualenv env-name
</span></span><span class="line"><span class="cl"><span class="nb">source</span> env-name/bin/activate
</span></span><span class="line"><span class="cl">pip3 install jupyter
</span></span></code></pre></div><p>Run Jupyter Notebook:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">jupyter notebook
</span></span></code></pre></div><p>While it is very easy to install modules by sourcing the correct virtualenv and using <code>pip</code>, there are a few drawbacks to this approach. Jupyter Notebook needs to be installed for each new environment created. If used as a server, this would become quite tedious and difficult to maintain - particularly if there is a need to use multiple environemnts at once with different users.</p>
<h2 id="install-jupyter-as-global-system-package">Install Jupyter as global system package</h2>
<p>



    
    <input type="checkbox" id="zoomCheck-f536f" hidden>
    <label for="zoomCheck-f536f">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="install-jupyter-as-system-package.svg" alt="install jupyter as system package" 
             />
    </label>

</p>
<p>The Jupyter Notebook installation can be centralized using the system package manager. Virtual environments are created separately.</p>
<p>To install Jupyter Notebook on Debian based systems, use <code>apt</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt install python3 python3-virtualenv jupyter
</span></span><span class="line"><span class="cl">jupyter notebook
</span></span></code></pre></div><p>This is convenient because there is only one Jupyter Notebook installation - it can be accessed on localhost:8888. Next, virtual environments must created, and modules need to be installed in different virtual environments.</p>
<h3 id="commands">Commands</h3>
<p>Commands can be run from within a notebook. A better idea of how Jupyter Notebook works is needed to understand this. Internally, it has a component called the IPython Kernel. This is responsible for communicating with the Python interpreter on the system. The <code>ipykernel</code> is included by default when Jupyter Notebook is installed, but it can also be installed independently.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-28a4e" hidden>
    <label for="zoomCheck-28a4e">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="how-ipython-kernel-integrates.svg" alt="how ipython kernel integrates" 
             />
    </label>

</p>
<p>Shell commands are provided by the operating system. They can be run by prefixing a line with an exclaimation mark:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="err">!</span><span class="n">ls</span> <span class="p">;</span> <span class="n">cd</span> <span class="n">example_dir</span>
</span></span></code></pre></div><p>This spawns a subprocess that exits upon completion; it is equivalent (on Linux) to:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">([</span><span class="s2">&#34;bash&#34;</span><span class="p">,</span> <span class="s2">&#34;-c&#34;</span><span class="p">,</span> <span class="s2">&#34;ls ; cd example_dir&#34;</span><span class="p">])</span>
</span></span></code></pre></div><p>In this case, the <code>ls</code> will list the files in the current directory and <code>cd</code> will change the directory. The change in directory will not persist since the process exists after completion.</p>
<p>Magic commands (prefixed with a percentage sign) are provided by the IPython kernel. State changes persist because these commands are run inside the kernel process itself (and apply to the virtual environment in which the kernel was installed):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">cd</span> <span class="n">example_dir</span>
</span></span><span class="line"><span class="cl"><span class="o">%</span><span class="n">page</span> <span class="n">my_file</span><span class="o">.</span><span class="n">txt</span>
</span></span><span class="line"><span class="cl"><span class="o">%</span><span class="n">pip</span> <span class="n">install</span> <span class="n">bs4</span>
</span></span></code></pre></div><p>This prints <code>example_dir/my_file.txt</code> and installs BeautifulSoup (a module for HTML parsing) in the current virtual environment.</p>
<p>For all available magic commands, refer to the <a href="https://ipython.readthedocs.io/en/stable/interactive/magics.html">IPython documentation</a> or run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">lsmagic</span>
</span></span></code></pre></div><p>To install a module in the current virtual environment:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">pip</span> <span class="n">install</span> <span class="n">module_name</span>
</span></span></code></pre></div><p>It is not possible to create and use a virtual environment directly within a notebook. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">!python -m virtualenv myvenv
</span></span><span class="line"><span class="cl">!source myvenv/bin/activate
</span></span><span class="line"><span class="cl">!pip install bs4
</span></span></code></pre></div><p>The first two commands succeed but the third fails with an <code>externally-managed-environment</code> error. This is because a new subprocess is created for each command - the sourced environment is not actually activated when pip is used. This can be resolved by chaining the command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">!python -m virtualenv myvenv<span class="p">;</span> <span class="nb">source</span> myvenv/bin/activate<span class="p">;</span> pip install bs4
</span></span></code></pre></div><p>BeautifulSoup now installs in the <code>myvenv</code> virtual environment, but the notebook does not use this environment. The virtual environment that the IPython kernel is running on is different to the one created - virtual environments cannot be nested!</p>
<h3 id="multiple-virtual-environments">Multiple Virtual Environments</h3>
<p>To make a virtual environments available to Jupyter Notebook, the ipykernel module should be installed inside the virtual environment:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">python3 -m virtualenv --system-site-packages env-name
</span></span><span class="line"><span class="cl"><span class="nb">source</span> env-name/bin/activate
</span></span><span class="line"><span class="cl">pip install ipykernel
</span></span><span class="line"><span class="cl">python3 -m ipykernel install --user --name<span class="o">=</span>env-name --display-name <span class="s2">&#34;Python (env-name)&#34;</span>
</span></span></code></pre></div><blockquote>
<p>The <code>--system-site-packages</code> flag is particularly important if you depend on globally installed modules. For example, <code>matplotlib</code> has to be specially packaged for ARM platforms like Raspberry Pi and installed using the system package manager. This flag ensures that these modules can be found.</p>
</blockquote>
<p>Since the Jupyter Notebook installation is global, the above commands can be easily run from the interface by creating a new terminal.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-50c75" hidden>
    <label for="zoomCheck-50c75">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="jupyter-new-menu.png" alt="jupyter new menu" 
             />
    </label>

</p>
<p>When creating a new notebook, you can select <code>Python (env-name)</code> to use a particular virtual environment.</p>
<p>To list all kernels and remove a particular kernel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">jupyter-kernelspec list
</span></span><span class="line"><span class="cl">jupyter-kernelspec uninstall env-name
</span></span></code></pre></div><p>There is still a problem - this is running directly on the server. If something goes wrong, it might affect other services. This is also not very easily portable.</p>
<h2 id="install-jupyter-on-docker">Install Jupyter on Docker</h2>
<p>Using docker, this can be containerized!</p>
<p>



    
    <input type="checkbox" id="zoomCheck-aeca6" hidden>
    <label for="zoomCheck-aeca6">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="install-jupyter-in-docker.svg" alt="install jupyter in docker" 
             />
    </label>

</p>
<p>This Dockerfile uses Alpine linux as the base image and installs <code>python</code>, <code>pip</code>, <code>virtualenv</code> and <code>jupyter-notebook</code> using the Alpine package manager <code>apk</code>.
The last command starts the notebook. This probably isn&rsquo;t very secure - you might want to modify the flags based on your security model.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="s"> alpine:3.22</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> apk add python3 py3-pip py3-virtualenv jupyter-notebook<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">EXPOSE</span><span class="s"> 8888</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;jupyter-notebook&#34;</span><span class="p">,</span> <span class="s2">&#34;--ip=0.0.0.0&#34;</span><span class="p">,</span> <span class="s2">&#34;--no-browser&#34;</span><span class="p">,</span> <span class="s2">&#34;--allow-root&#34;</span><span class="p">,</span> <span class="s2">&#34;--NotebookApp.token=&#39;your-very-long-and-secure-passphrase&#39;&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>Now, creating the following docker-compose.yaml file and running <code>docker compose up</code> will build the image and expose Jupyter Notebook on localhost:4444. To manage the virtual environments, use the terminal and follow the steps in the previous section.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">services:
</span></span><span class="line"><span class="cl">  jupyter:
</span></span><span class="line"><span class="cl">    container_name: jupyter
</span></span><span class="line"><span class="cl">    build: .
</span></span><span class="line"><span class="cl">    ports:
</span></span><span class="line"><span class="cl">      - 4444:8888
</span></span></code></pre></div><p>To reproduce this cionfiguration on a different service, just the <code>Dockerfile</code> and <code>docker-compose.yaml</code> need to be copied.</p>
]]></content>
      </entry>

</feed>


