<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>smhk</title><link>https://smhk.net/</link><description>Recent content on smhk</description><generator>Hugo</generator><language>en-GB</language><lastBuildDate>Wed, 29 Apr 2026 16:00:00 +0000</lastBuildDate><atom:link href="https://smhk.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Using Dear ImGui from C</title><link>https://smhk.net/note/2026/04/dear-imgui-c/</link><pubDate>Wed, 29 Apr 2026 16:00:00 +0000</pubDate><guid>https://smhk.net/note/2026/04/dear-imgui-c/</guid><description>&lt;p>These notes cover how to add &lt;em>Dear ImGui&lt;/em> into an existing C project using the SDL3_Renderer backend. Since &lt;em>Dear ImGui&lt;/em> is C++, we use the C bindings provided by &lt;em>Dear Bindings&lt;/em>. The &lt;a href="https://smhk.net/note/2026/04/dear-imgui-c/#end-result">end result&lt;/a> is a C application with a couple of example GUI windows.&lt;/p>
&lt;h2 id="bindings">Bindings&lt;/h2>
&lt;p>Since Dear ImGui is written in C++, we need some bindings to enable us to use it from C.&lt;/p>
&lt;p>Two popular options are:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/dearimgui/dear_bindings">Dear Bindings&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/cimgui/cimgui">cimgui&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Both of these take the &lt;em>Dear ImGui&lt;/em> C++ source as input, and perform some processing to output C bindings.&lt;/p></description></item><item><title>Firefox Profile Nomenclature: Traditional vs Fancy</title><link>https://smhk.net/note/2025/11/firefox-profile-traditional-vs-fancy/</link><pubDate>Fri, 28 Nov 2025 18:00:00 +0000</pubDate><guid>https://smhk.net/note/2025/11/firefox-profile-traditional-vs-fancy/</guid><description>&lt;p>As someone who has been using Firefox Profiles for years, I was initially excited by &lt;a href="https://blog.mozilla.org/en/firefox/profile-management/">the announcement&lt;/a> of the new &lt;em>profile management feature&lt;/em>. It sounded like this was promoting the existing feature with a UI refresh.&lt;/p>
&lt;p>&lt;strong>Confusingly, it&amp;rsquo;s a completely separate feature that shares the exact same name!&lt;/strong>&lt;/p>
&lt;p>Let&amp;rsquo;s invent some terminology so that we can be clear:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Traditional Profiles&lt;/em>
&lt;ul>
&lt;li>These have been around for years.&lt;/li>
&lt;li>They are &lt;a href="https://support.mozilla.org/en-US/kb/profile-manager-create-remove-switch-firefox-profiles">managed&lt;/a> through &lt;code>about:profiles&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;em>Fancy Profiles&lt;/em>:
&lt;ul>
&lt;li>These began a gradual rollout&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> on 14th October 2025. If not yet enabled on your Firefox, go to &lt;code>about:config&lt;/code> and enable &lt;code>browser.profiles.enabled&lt;/code>.&lt;/li>
&lt;li>Once enabled, these are managed through &lt;code>about:profilemanager&lt;/code> and &lt;code>about:editprofile&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>What do these two profiles look like?&lt;/p></description></item><item><title>Pixel Perfect Rendering in SDL2</title><link>https://smhk.net/note/2025/10/pixel-perfect-rendering-in-sdl2/</link><pubDate>Fri, 10 Oct 2025 17:45:00 +0100</pubDate><guid>https://smhk.net/note/2025/10/pixel-perfect-rendering-in-sdl2/</guid><description>&lt;p>&lt;em>TL;DR: To ensure SDL2 rendering is pixel perfect, you must use &amp;ldquo;nearest pixel sampling&amp;rdquo; for the render quality, and mark the process as &amp;ldquo;DPI aware&amp;rdquo; on Windows.&lt;/em>&lt;/p>
&lt;h2 id="enable-nearest-pixel-sampling">Enable nearest pixel sampling&lt;/h2>
&lt;p>The &lt;a href="https://wiki.libsdl.org/SDL2/SDL_HINT_RENDER_SCALE_QUALITY">scale quality&lt;/a> must be set to &lt;code>&amp;quot;0&amp;quot;&lt;/code>, which is &lt;em>nearest pixel sampling&lt;/em>.&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="c">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-c" data-lang="c">&lt;span class="line">&lt;span class="cl">&lt;span class="nf">SDL_SetHint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">SDL_HINT_RENDER_SCALE_QUALITY&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;0&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>If set to &lt;code>&amp;quot;1&amp;quot;&lt;/code> (&lt;code>&amp;quot;linear&amp;quot;&lt;/code>) or &lt;code>&amp;quot;2&amp;quot;&lt;/code> (&lt;code>&amp;quot;best&amp;quot;&lt;/code>), SDL2 will use linear filtering when scaling textures, &lt;a href="https://stackoverflow.com/questions/19859891/sdl2-antialiasing">resulting in antialiasing&lt;/a>.&lt;/p>
&lt;h2 id="set-dpi-aware-on-windows">Set DPI aware on Windows&lt;/h2>
&lt;p>Windows 10 may automatically apply &lt;a href="https://stackoverflow.com/a/52028160/3190803">a scaling of 125%&lt;/a> to processes which do not declare themselves &amp;ldquo;DPI aware&amp;rdquo;. This will break any attempt at pixel perfect rendering.&lt;/p></description></item><item><title>Some useful PlantUML architecture diagrams</title><link>https://smhk.net/note/2025/04/some-useful-plantuml-architecture-diagrams/</link><pubDate>Tue, 15 Apr 2025 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2025/04/some-useful-plantuml-architecture-diagrams/</guid><description>&lt;p>PlantUML is very useful for creating architecture diagrams using text. However, the official documentation is so big (&lt;a href="https://pdf.plantuml.net/1.2025.0/PlantUML_Language_Reference_Guide_en.pdf">a 606 page PDF&lt;/a> as of v1.2025.0) that it&amp;rsquo;s not always easy to find what I&amp;rsquo;m looking for. Yet, despite being so big, not everything is documented. For example, there are 45 &amp;ldquo;TODO&amp;quot;s, and &lt;code>norank&lt;/code> is not mentioned anywhere (but is mentioned in the unofficial but extremely helpful &lt;a href="https://crashedmind.github.io/PlantUMLHitchhikersGuide/layout/layout.html">&lt;em>The Hitchhiker’s Guide to PlantUML&lt;/em>&lt;/a>).&lt;/p>
&lt;p>So, following is a small collection of useful example architecture diagrams:&lt;/p></description></item><item><title>5G: Frames, Subframes, Slots, Minislots, Resource Blocks &amp; Resource Elements</title><link>https://smhk.net/note/2025/03/5g-frames-subframes-slots-and-resources/</link><pubDate>Fri, 28 Mar 2025 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2025/03/5g-frames-subframes-slots-and-resources/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;h3 id="subcarriers">Subcarriers&lt;/h3>
&lt;p>In order to understand &lt;strong>frames&lt;/strong> (and their component parts), it is helpful to first have a refresher on &lt;strong>subcarriers&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>A &lt;strong>carrier&lt;/strong> is formed of multiple smaller &lt;strong>subcarriers&lt;/strong>:
&lt;ul>
&lt;li>The &lt;strong>subcarrier&lt;/strong>: the building blocks of a &lt;strong>carrier&lt;/strong>; a smaller frequency channel within the larger &lt;strong>carrier&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The &lt;strong>subcarrier spacing&lt;/strong> (or SCS): the frequency separation between adjacent carriers.
&lt;ul>
&lt;li>e.g. 15kHz, 30kHz, 60kHz, 120kHz.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The &lt;strong>frequency band&lt;/strong>: defines a duplex mode (TDD/FDD), one frequency range (if TDD, or two if FDD), and a range of supported &lt;strong>channel bandwidths&lt;/strong>.
&lt;ul>
&lt;li>For example, band n78 (very common in 5G):
&lt;ul>
&lt;li>The uplink/downlink frequency is 3300MHz-3800MHz (n78 is TDD, so uplink and downlink share the same frequency range).&lt;/li>
&lt;li>This provides a total available bandwidth of 500MHz&amp;hellip;&lt;/li>
&lt;li>&amp;hellip;however, of this 500MHz, the supported &lt;strong>channel bandwidths&lt;/strong> are &lt;a href="https://en.wikipedia.org/wiki/5G_NR_frequency_bands">10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100MHz&lt;/a>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The &lt;strong>channel bandwidth&lt;/strong>: the total frequency range occupied by a &lt;strong>carrier&lt;/strong>.&lt;/li>
&lt;li>To calculate the approximate number of &lt;strong>subcarriers&lt;/strong> (ignoring guard bands): &lt;code>num_subcarriers = bandwidth / subcarrier_spacing&lt;/code>
&lt;ul>
&lt;li>e.g. For a 5MHz bandwidth with 15kHz SCS: &lt;code>5MHz / 15kHz = 5,000,000 / 15,000 = ~333 subcarriers&lt;/code>&lt;/li>
&lt;li>Multiple &lt;strong>subcarriers&lt;/strong> are sent/received in parallel using &lt;a href="https://smhk.net/note/2024/10/fdm-ofdm-and-ofdma/#ofdm">OFDM&lt;/a>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="frames-and-their-component-parts">Frames (and their component parts)&lt;/h3>
&lt;p>Now we can dig into &lt;strong>frames&lt;/strong>, their component parts (&lt;strong>subframes&lt;/strong>, &lt;strong>slots&lt;/strong>, &lt;strong>resource blocks&lt;/strong>, &lt;strong>resource elements&lt;/strong>) and how they relate to &lt;strong>subcarriers&lt;/strong>:&lt;/p></description></item><item><title>C stack trace in Windows</title><link>https://smhk.net/note/2025/03/c-stack-trace-in-windows/</link><pubDate>Tue, 25 Mar 2025 18:15:00 +0000</pubDate><guid>https://smhk.net/note/2025/03/c-stack-trace-in-windows/</guid><description>&lt;p>&lt;em>TL;DR: It&amp;rsquo;s possible to generate a C stack trace in Windows with GCC, but it&amp;rsquo;s easier with MSVC or LLVM/Clang. Either way, you need to install the Windows SDK and then compile and link against &lt;em>DbgHelp&lt;/em>. With GCC, you also need to convert the DWARF debug information to PDB, which can be done with a tool called &lt;em>cv2pdb&lt;/em>, but that depends upon installing the Visual Studio build tools.&lt;/em>&lt;/p>
&lt;p>These notes cover how to print a stack trace from a C application built using Mingw-w64 (with GCC or LLVM/Clang)&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> in CMake on Windows 11.&lt;/p></description></item><item><title>Enable British English language in Firefox</title><link>https://smhk.net/note/2025/03/enable-british-english-language-in-firefox/</link><pubDate>Fri, 14 Mar 2025 17:00:00 +0000</pubDate><guid>https://smhk.net/note/2025/03/enable-british-english-language-in-firefox/</guid><description>&lt;p>&lt;em>TL;DR: To add a British English dictionary for spellchecking in Firefox, first install the &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/english-gb-language-pack/">British English &lt;strong>Language Pack&lt;/strong>&lt;/a>, then install a British English &lt;strong>Dictionary&lt;/strong> &lt;a href="https://addons.mozilla.org/en-GB/firefox/addon/british-english-dictionary-gb/">such as this one&lt;/a>. Finally, go to &lt;code>about:preferences&lt;/code> and set the language to &amp;ldquo;English (GB)&amp;rdquo;; this sets both the default &lt;strong>Language Pack&lt;/strong> &lt;em>and&lt;/em> &lt;strong>Dictionary&lt;/strong>.&lt;/em>&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>By default, Firefox provides the &lt;em>English (United States)&lt;/em> dictionary:&lt;/p>


&lt;figure class="single">
 &lt;div class="single-inset">
 










 
 
 
 &lt;a href="https://smhk.net/images/screenshot/firefox/firefox-add-dictionaries.png">
 
 &lt;img src="https://smhk.net/images/screenshot/firefox/firefox-add-dictionaries_hu4574953951791442881.png" alt="Screenshot of Firefox.">
 
 &lt;/a>
 


 &lt;/div>
&lt;/figure>
&lt;p>These notes cover how to add the &lt;em>English (United Kingdom)&lt;/em> dictionary and make it the default:&lt;/p></description></item><item><title>Open5GS: Change WebUI IP and port</title><link>https://smhk.net/note/2025/02/open5gs-change-webui-ip-and-port/</link><pubDate>Fri, 28 Feb 2025 17:00:00 +0000</pubDate><guid>https://smhk.net/note/2025/02/open5gs-change-webui-ip-and-port/</guid><description>&lt;p>&lt;em>TL;DR: To change the IP and port used by Open5GS&amp;rsquo;s WebUI, modify the service file to set &lt;code>HOSTNAME=x.x.x.x&lt;/code> and &lt;code>PORT=yyyy&lt;/code> for the &lt;code>Environment&lt;/code> field.&lt;/em>&lt;/p>
&lt;h2 id="situation">Situation&lt;/h2>
&lt;ul>
&lt;li>The Open5GS WebUI binds to &lt;code>localhost:9999&lt;/code> by default.&lt;/li>
&lt;li>In general, &lt;a href="https://open5gs.org/open5gs/docs/guide/01-quickstart/">the Open5GS documentation&lt;/a> &lt;a href="https://open5gs.org/open5gs/docs/troubleshoot/01-simple-issues/">is excellent&lt;/a>, however I could not find documentation on how to change the IP and port used by the WebUI.&lt;/li>
&lt;li>I needed to change the bind IP from &lt;code>localhost&lt;/code> to &lt;code>0.0.0.0&lt;/code> so that the WebUI is accessible from outside of the core.&lt;/li>
&lt;/ul>
&lt;h2 id="solution">Solution&lt;/h2>
&lt;p>The Open5GS WebUI IP and port can be changed by modifying the &lt;code>open5gs-webgui.service&lt;/code> file as follows&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>:&lt;/p></description></item><item><title>Setting up a test 5G network</title><link>https://smhk.net/note/2025/03/setting-up-a-test-5g-network/</link><pubDate>Fri, 28 Feb 2025 17:00:00 +0000</pubDate><guid>https://smhk.net/note/2025/03/setting-up-a-test-5g-network/</guid><description>&lt;p>&lt;em>TL;DR: &lt;strong>Beware that Android phones may silently ignore your configured MCC/MNC/slice!&lt;/strong> Monitor their network requests (e.g. via tcpdump or gNB/core logs) to verify what settings the UE is actually using.&lt;/em>&lt;/p>
&lt;p>Following are various notes from setting up a test 5G network.&lt;/p>
&lt;p class="note">Operating a private 5G network on cellular frequency bands may be tightly regulated in your jurisdiction.&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="configuration">Configuration&lt;/h2>
&lt;p>Here are some critical things to be aware of when configuring the core for a UE to attach.&lt;/p></description></item><item><title>The Important Files (part 11): Fixing Windows 11 Samba share</title><link>https://smhk.net/note/2025/01/the-important-files-part-11/</link><pubDate>Sat, 11 Jan 2025 16:45:00 +0000</pubDate><guid>https://smhk.net/note/2025/01/the-important-files-part-11/</guid><description>&lt;p>&lt;em>TL;DR: The &lt;a href="https://www.truenas.com/docs/core/13.0/coretutorials/sharing/smb/smbshare/">Samba share permissions&lt;/a> are determined by the TrueNAS user. By default, Windows will user the Windows user to try and authenticate, which may result in a read-only user if this does not match with the corresponding Samba user. A solution is to check &amp;ldquo;Connect using different credentials&amp;rdquo; when creating the share and use the correct user.&lt;/em>&lt;/p>
&lt;p>After upgrading from Windows 10 to Windows 11, the Samba share with my TrueNAS had become read-only. These notes document how I re-created the share to ensure it had the correct read and write permissions.&lt;/p></description></item><item><title>Configure Poetry to use a virtualenv</title><link>https://smhk.net/note/2024/12/configure-poetry-to-use-a-virtualenv/</link><pubDate>Tue, 17 Dec 2024 16:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/12/configure-poetry-to-use-a-virtualenv/</guid><description>&lt;p>&lt;em>TL;DR: If you want to configure Poetry to use a local virtualenv (e.g. &lt;code>.venv/&lt;/code>) instead of storing the virtualenv in the cache, &lt;strong>first&lt;/strong> you must set &lt;code>virtualenvs.in-project = true&lt;/code>, &lt;strong>second&lt;/strong> you must then re-create the virtualenv with &lt;code>poetry env remove &amp;lt;...&amp;gt;&lt;/code> and &lt;code>poetry install&lt;/code>.&lt;/em>&lt;/p></description></item><item><title>Valgrind 'Invalid read' with SDL2</title><link>https://smhk.net/note/2024/12/valgrind-invalid-read/</link><pubDate>Sat, 14 Dec 2024 13:30:00 +0000</pubDate><guid>https://smhk.net/note/2024/12/valgrind-invalid-read/</guid><description>&lt;p>&lt;em>TL;DR: Old versions of Valgrind detect false positive memory issues in SDL2 applications. This was fixed in Valgrind v3.20, but unfortunately v3.19 is the latest available version on Debian stable &lt;code>apt&lt;/code>. A solution is to &lt;a href="https://smhk.net/note/2024/12/valgrind-invalid-read/#build--install-valgrind-from-source">build &amp;amp; install Valgrind from source&lt;/a>.&lt;/em>&lt;/p></description></item><item><title>Pylint: Use source-roots instead of init-hook to fix E0401 'import-error'</title><link>https://smhk.net/note/2024/12/pylint-source-roots/</link><pubDate>Fri, 13 Dec 2024 13:30:00 +0000</pubDate><guid>https://smhk.net/note/2024/12/pylint-source-roots/</guid><description>&lt;p>&lt;em>TL;DR: Instead of using &lt;code>sys.path.append(&amp;lt;path&amp;gt;)&lt;/code> in &lt;code>init-hook&lt;/code> to configure Pylint, use &lt;code>source-roots = &amp;quot;&amp;lt;path&amp;gt;&amp;quot;&lt;/code>.&lt;/em>&lt;/p></description></item><item><title>Convert a Python script into a Poetry package</title><link>https://smhk.net/note/2024/12/convert-a-python-script-into-a-poetry-package/</link><pubDate>Fri, 13 Dec 2024 09:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/12/convert-a-python-script-into-a-poetry-package/</guid><description>&lt;p>&lt;em>Tl;DR: Run &lt;code>poetry init&lt;/code> convert a Python script into a Poetry package, then edit the resulting &lt;code>pyproject.toml&lt;/code> to define the &lt;code>package&lt;/code>, entry points (&lt;code>scripts&lt;/code>), dependencies and more.&lt;/em>&lt;/p>
&lt;p>A new Python project can easily begin as a single &lt;code>script.py&lt;/code>, but there soon becomes a point where it&amp;rsquo;s worth packaging up the script. That way you can benefit from proper dependency management, entry points, installation and much more.&lt;/p>
&lt;p>Here is how to go from a &lt;code>script.py&lt;/code> to a Python package using Poetry.&lt;/p></description></item><item><title>CMake and coverage with gcov</title><link>https://smhk.net/note/2024/11/cmake-and-coverage-with-gcov/</link><pubDate>Mon, 25 Nov 2024 17:30:00 +0000</pubDate><guid>https://smhk.net/note/2024/11/cmake-and-coverage-with-gcov/</guid><description>&lt;p>&lt;em>TL;DR: To generate coverage data in CMake: enable CTest, enable debug, build and link all relevant targets with &lt;code>--coverage&lt;/code>, then run &lt;code>ctest --test-dir build -T Coverage&lt;/code>. Use &lt;code>gcov&lt;/code> or &lt;code>lcov&lt;/code> to generate a CLI or HTML coverage report respectively.&lt;/em>&lt;/p></description></item><item><title>Setting up Unity test framework for CMake</title><link>https://smhk.net/note/2024/11/setting-up-unity-test-framework-for-cmake/</link><pubDate>Wed, 20 Nov 2024 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/11/setting-up-unity-test-framework-for-cmake/</guid><description>&lt;p>&lt;a href="https://www.throwtheswitch.org/unity">Unity&lt;/a> is a test framework written in C, which is commonly used for unit testing embedded software. It can be integrated with CMake, but official support is &lt;a href="https://www.throwtheswitch.org/build/cmake">&amp;ldquo;a work in progress and not suitable for use yet&amp;rdquo;&lt;/a>. However, as shown by &lt;a href="https://honeytreelabs.com/posts/cmake-unity-integration/">this blog post from November 2022&lt;/a> and its corresponding &lt;a href="https://github.com/rpoisel/cmake-unity-tutorial">example project&lt;/a>, Unity can integrate well with CMake.&lt;/p>
&lt;p>It&amp;rsquo;s been two years since that blog post was published, and both Unity and CMake have changed a little, so I decided to share the steps I took today in November 2024. The main differences between my steps and the aforementioned are:&lt;/p>
&lt;ul>
&lt;li>Fixing a minor issue (that arose due to changes in Unity).&lt;/li>
&lt;li>Updating the usage of CMake (due to changes in CMake).&lt;/li>
&lt;li>Aligning some things with my personal preference.&lt;/li>
&lt;/ul></description></item><item><title>Installing Docker Desktop using local admin</title><link>https://smhk.net/note/2024/11/docker-desktop-local-admin/</link><pubDate>Tue, 12 Nov 2024 16:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/11/docker-desktop-local-admin/</guid><description>&lt;p>&lt;em>TL;DR: If you installed Docker Desktop with a &lt;strong>local admin&lt;/strong> account, you must manually add your (non-admin) &lt;strong>user account&lt;/strong> to the &lt;code>docker-users&lt;/code> group. This is mentioned in &lt;a href="https://docs.docker.com/desktop/setup/install/windows-install/#install-docker-desktop-on-windows">the official docs&lt;/a>, but is easy to miss.&lt;/em>&lt;/p></description></item><item><title>Zig: First impressions</title><link>https://smhk.net/note/2024/11/zig-first-impressions/</link><pubDate>Mon, 04 Nov 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/11/zig-first-impressions/</guid><description>&lt;p>I have been working through the excellent &lt;a href="https://craftinginterpreters.com/">Crafting Interpreters&lt;/a> by Robert Nystrom. Specifically, &amp;ldquo;part III: A Bytecode Virtual Machine&amp;rdquo;. For this part the book uses C, but I decided to use Zig. I&amp;rsquo;ve never used Zig before. Following are some rough notes from my first experience of using Zig.&lt;/p></description></item><item><title>C memory layout</title><link>https://smhk.net/note/2024/11/c-memory-layout/</link><pubDate>Mon, 04 Nov 2024 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/11/c-memory-layout/</guid><description>&lt;p>The memory used by a C program is laid out in &lt;a href="https://en.wikipedia.org/wiki/Data_segment">various segments&lt;/a>:&lt;/p></description></item><item><title>Switching from Netlify to Uberspace</title><link>https://smhk.net/note/2024/10/setting-up-uberspace/</link><pubDate>Wed, 30 Oct 2024 20:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/10/setting-up-uberspace/</guid><description>&lt;p>&lt;em>TL;DR: Some rough notes on how I switched hosting this website from Netlify to Uberspace, and analytics from Plausible to Goatcounter.&lt;/em>&lt;/p>
&lt;h2 id="background">Background&lt;/h2>
&lt;p>I have been hosting this website with Netlify since January 2018, which has in general been a great experience! It was easy, convenient and free to host a static website. While some features were quite useful (e.g. branches automatically creating website previews on hidden subdomains), in general Netlify was far more featureful than I needed (e.g. severless functions, team management).&lt;/p></description></item><item><title>Hugo: How to create a post series</title><link>https://smhk.net/note/2024/10/hugo-post-series/</link><pubDate>Tue, 29 Oct 2024 17:30:00 +0000</pubDate><guid>https://smhk.net/note/2024/10/hugo-post-series/</guid><description>&lt;p>&lt;em>TL;DR: Follow these steps to implement a post series in Hugo, just like the one at the top of this page!&lt;/em>&lt;/p>
&lt;p>A few years back I followed &lt;a href="https://digitaldrummerj.me/hugo-post-series/">this blog post&lt;/a> to implement support for post series in Hugo. However, when I upgraded to Hugo 0.136.2 recently, this broke, and the series was just blank. I found &lt;a href="https://brainbaking.com/post/2024/01/displaying-series-of-posts-in-hugo/">this more recent blog post&lt;/a> helpful in coming up with the following solution.&lt;/p></description></item><item><title>FDM, OFDM &amp; OFDMA</title><link>https://smhk.net/note/2024/10/fdm-ofdm-and-ofdma/</link><pubDate>Tue, 29 Oct 2024 09:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/10/fdm-ofdm-and-ofdma/</guid><description>&lt;p>Following is an interactive overview of &lt;a href="https://en.wikipedia.org/wiki/Orthogonal_frequency-division_multiple_access">OFDMA&lt;/a>, which is used many wireless standards, including 5G NR, to enable efficient use of bandwidth for multiple users.&lt;/p></description></item><item><title>Debian: Upgrading Dropbox</title><link>https://smhk.net/note/2024/10/debian-upgrading-dropbox/</link><pubDate>Mon, 21 Oct 2024 21:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/10/debian-upgrading-dropbox/</guid><description>&lt;p>&lt;em>TL;DR: Dropbox on my Debian machine had stopped syncing. Turns out it had not been updated in 4 years. Fix was to update Dropbox.&lt;/em>&lt;/p></description></item><item><title>Upgrading Node, Go and Hugo for this website</title><link>https://smhk.net/note/2024/10/upgrading-node-go-and-hugo-for-this-website/</link><pubDate>Sun, 20 Oct 2024 21:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/10/upgrading-node-go-and-hugo-for-this-website/</guid><description>&lt;p>Time for some routine maintenance. Need to upgrade the dependencies for this website and fix any resulting issues.&lt;/p></description></item><item><title>Ubuntu: allow a port through the firewall</title><link>https://smhk.net/note/2024/10/ubuntu-allow-port-through-firewall/</link><pubDate>Tue, 08 Oct 2024 15:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/10/ubuntu-allow-port-through-firewall/</guid><description>&lt;p>To allow TCP port &lt;code>12345&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="console">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> sudo ufw allow 12345/tcp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Setting up Zig on Windows for VS Code</title><link>https://smhk.net/note/2024/10/setting-up-zig-on-windows/</link><pubDate>Tue, 01 Oct 2024 09:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/10/setting-up-zig-on-windows/</guid><description>&lt;p>&lt;em>TL;DR: On Windows, I recommend installing Zig via Scoop, then setting up the ZLS (Zig Language Server) with VS Code.&lt;/em>&lt;/p></description></item><item><title>The Important Files (part 10): Fixing borg backup</title><link>https://smhk.net/note/2024/09/the-important-files-part-10/</link><pubDate>Mon, 30 Sep 2024 18:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/09/the-important-files-part-10/</guid><description>&lt;p>&lt;em>TL;DR: Tried to run backup, it failed because the rsync.net username and hostname have changed. Updated the script and re-ran the backup, and it completed successfully after 3.5 hours. It took so long because this is the first time it has been run (successfully) in a few months.&lt;/em>&lt;/p></description></item><item><title>Git: display current branch in Bash</title><link>https://smhk.net/note/2024/09/git-display-current-branch-in-bash/</link><pubDate>Tue, 03 Sep 2024 20:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/09/git-display-current-branch-in-bash/</guid><description>&lt;p>To show the current Git branch in Bash, e.g.:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">user@host:/path/to/directory &lt;span class="o">(&lt;/span>branch_name&lt;span class="o">)&lt;/span>$&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Jupyter: plot multiple layers on a single map</title><link>https://smhk.net/note/2024/08/jupyter-plot-multiple-layers-on-a-single-map/</link><pubDate>Wed, 14 Aug 2024 21:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/jupyter-plot-multiple-layers-on-a-single-map/</guid><description>&lt;p>&lt;em>TL;DR: Create a map (&lt;code>m&lt;/code>) in a Jupyter Notebook by calling &lt;code>m = explore(...)&lt;/code> on a &lt;a href="https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.html">&lt;code>geopandas.GeoDataFrame&lt;/code>&lt;/a>. Add multiple layers to the map by passing the &lt;code>m&lt;/code> object back in for each layer, e.g. &lt;code>m = explore(m=m, ...)&lt;/code>.&lt;/em>&lt;/p></description></item><item><title>Box2D v3 is released</title><link>https://smhk.net/note/2024/08/box2d-v3/</link><pubDate>Mon, 12 Aug 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/box2d-v3/</guid><description>&lt;p>I was excited today to see that &lt;a href="https://box2d.org/posts/2024/08/releasing-box2d-3.0/">Box2D v3.0 has been released&lt;/a>.&lt;/p></description></item><item><title>Docker: understanding it</title><link>https://smhk.net/note/2024/08/docker-understanding-it/</link><pubDate>Fri, 09 Aug 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/docker-understanding-it/</guid><description>&lt;p>It&amp;rsquo;s common to see &lt;code>docker run -it&lt;/code> or &lt;code>docker run -itd&lt;/code>, but what do those arguments actually mean?&lt;/p></description></item><item><title>Setting up Rye on Windows</title><link>https://smhk.net/note/2024/08/setting-up-rye-on-windows/</link><pubDate>Fri, 09 Aug 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/setting-up-rye-on-windows/</guid><description>&lt;p>&lt;em>TL;DR: Do not run &lt;code>pipx install rye&lt;/code>. Instead, first &lt;a href="https://rye.astral.sh/guide/faq/#windows-developer-mode">enable &lt;strong>Developer Mode&lt;/strong> in Windows&lt;/a>, and then use &lt;a href="https://rye.astral.sh/guide/installation/">the official Rye installer&lt;/a>.&lt;/em>&lt;/p></description></item><item><title>Recursively update Git submodules</title><link>https://smhk.net/note/2024/08/2024-08-02-recursively-update-git-submodules/</link><pubDate>Fri, 02 Aug 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/2024-08-02-recursively-update-git-submodules/</guid><description>&lt;p>To recursively update Git submodules:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git submodule update --init --recursive&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Python forward reference</title><link>https://smhk.net/note/2024/08/2024-08-02-python-forward-reference/</link><pubDate>Fri, 02 Aug 2024 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/2024-08-02-python-forward-reference/</guid><description>&lt;p>&lt;a href="https://stackoverflow.com/a/53845083">Prior to Python 3.12&lt;/a>, recursive type require &lt;code>from __future__ import annotations&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">&lt;figcaption class="" style="height: 1.5em;" >Example of a recursive type&lt;/figcaption>
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma has-caption">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">__future__&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">annotations&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Something&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">parent&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Something&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Something&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parent&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Python argparse</title><link>https://smhk.net/note/2024/08/2024-08-01-python-argparse/</link><pubDate>Thu, 01 Aug 2024 15:30:00 +0100</pubDate><guid>https://smhk.net/note/2024/08/2024-08-01-python-argparse/</guid><description>&lt;p>In Python, there is no need to write arguments &lt;code>--like_this&lt;/code>.&lt;/p>
&lt;p>You can do arguments &lt;code>--like-this&lt;/code>, and &lt;a href="https://docs.python.org/3/library/argparse.html#dest">argparse will convert&lt;/a> the &lt;code>-&lt;/code> characters to &lt;code>_&lt;/code>.&lt;/p>
&lt;p>For example:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="nn">argparse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">argparse&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ArgumentParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_argument&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--like-this&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">args&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse_args&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--like-this=123&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">args&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Namespace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">like_this&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">like_this&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">123&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>What is O-RAN?</title><link>https://smhk.net/note/2024/07/what-is-o-ran/</link><pubDate>Wed, 31 Jul 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/07/what-is-o-ran/</guid><description>&lt;p>The terms &lt;em>Open RAN&lt;/em> and &lt;em>OpenRAN&lt;/em> are often used interchangeably, but can have different meanings depending upon the context&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Open RAN&lt;/em> (&lt;strong>with&lt;/strong> a space): Refers to the general concept of an open Radio Access Network (RAN) architecture, which can be disaggregated into component parts, with openly defined interfaces between them.&lt;/li>
&lt;li>&lt;em>OpenRAN&lt;/em> (&lt;strong>without&lt;/strong> a space): Refers to the specific implementation of an &amp;ldquo;Open RAN&amp;rdquo; as defined by the O-RAN Alliance.&lt;/li>
&lt;/ul>
&lt;p>To avoid ambiguity, this note will use the term &lt;em>O-RAN&lt;/em> to refer to the specific implementation of &lt;em>OpenRAN&lt;/em> by the O-RAN Alliance.&lt;/p></description></item><item><title>Getting started with clang-format</title><link>https://smhk.net/note/2024/07/getting-started-with-clang-format/</link><pubDate>Mon, 29 Jul 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/07/getting-started-with-clang-format/</guid><description>&lt;p>Just as &lt;a href="https://smhk.net/note/2023/09/poetry-pre-commit-hooks/">Black&lt;/a> and &lt;a href="https://smhk.net/note/2023/09/python-using-isort/">isort&lt;/a> can be used to format Python code, &lt;code>clang-format&lt;/code> can be used to format C/C++/C# code (and more&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>).&lt;/p></description></item><item><title>pytest: Selectively mark values as xfail in parametrize</title><link>https://smhk.net/note/2024/06/pytest-selectively-mark-values-in-parametrize/</link><pubDate>Tue, 11 Jun 2024 20:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/06/pytest-selectively-mark-values-in-parametrize/</guid><description>&lt;p>In these notes, we use the pytest &lt;code>parametrize&lt;/code> decorator to apply marks to &lt;em>some&lt;/em> values for that parameter, rather than marking the whole test.&lt;/p></description></item><item><title>Xming: bind to IP</title><link>https://smhk.net/note/2024/06/xming-bind-to-ip/</link><pubDate>Thu, 06 Jun 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/06/xming-bind-to-ip/</guid><description>&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Xming">Xming&lt;/a> is a server&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> that can be used to display &lt;a href="https://www.x.org/releases/X11R7.7/doc/man/man7/X.7.xhtml">X11&lt;/a> applications on a Windows machine. The &lt;a href="https://sourceforge.net/projects/xming/">last release of Xming was on 9th August 2016&lt;/a> (almost 8 years ago), but still works just fine on Windows 10. For comparison, the &lt;a href="https://www.x.org/releases/X11R7.7/doc/">last release of X11 was on 6th June 2012&lt;/a>&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> (12 years ago today!).&lt;/p></description></item><item><title>Directory specific environment variables with direnv</title><link>https://smhk.net/note/2024/05/directory-specific-environment-variables-with-direnv/</link><pubDate>Thu, 30 May 2024 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/05/directory-specific-environment-variables-with-direnv/</guid><description>&lt;p>&lt;a href="https://direnv.net/">direnv&lt;/a> is a very simple but useful tool. Create a &lt;code>.envrc&lt;/code> file that defines some environment variables, e.g.:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">MY_VAR&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">123&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">SOMETHING_ELSE&lt;/span>&lt;span class="o">=&lt;/span>blah&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>When you &lt;code>cd&lt;/code> into that directory, the environment variables are loaded. When you &lt;code>cd&lt;/code> out, they are unloaded.&lt;/p></description></item><item><title>VirtualBox VM freezes at 'Loading essential drivers'</title><link>https://smhk.net/note/2024/05/vm-freezing-at-loading-essential-drivers/</link><pubDate>Wed, 08 May 2024 20:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/05/vm-freezing-at-loading-essential-drivers/</guid><description>&lt;p>&lt;em>TL;DR: If a VM gets stuck during boot, simply pausing and resuming can get it unstuck.&lt;/em>&lt;/p></description></item><item><title>Hugo: Global resources</title><link>https://smhk.net/note/2024/05/hugo-global-resources/</link><pubDate>Wed, 01 May 2024 19:30:00 +0100</pubDate><guid>https://smhk.net/note/2024/05/hugo-global-resources/</guid><description>&lt;p>In February 2018, I used &lt;a href="https://smhk.net/note/2018/02/automatic-image-thumbnails-in-hugo-from-static-directory/">a bit of a hack&lt;/a> to create a global resources object by using Hugo v0.32 page resources, i.e. &lt;a href="https://gohugo.io/methods/page/resources/">&lt;code>.Resources.Get&lt;/code>&lt;/a>.&lt;/p>
&lt;p>In July 2018, &lt;a href="https://github.com/gohugoio/hugo/releases/tag/v0.43">Hugo v0.43&lt;/a> was released, which added &lt;a href="https://gohugo.io/functions/resources/get/">&lt;code>resources.Get&lt;/code>&lt;/a>. This uses the global &lt;code>resources&lt;/code> object rather than a page-specific &lt;code>.Resources&lt;/code>.&lt;/p>
&lt;p>Using &lt;code>resources.Get&lt;/code> instead of &lt;code>.Resources.Get&lt;/code> makes it trivial to use global resources. As the docs say:&lt;/p>
&lt;blockquote>
&lt;p>This function [&lt;code>resources.Get&lt;/code>] operates on global resources. A global resource is a file within the assets directory, or within any directory mounted to the assets directory.&lt;/p>
&lt;/blockquote>
&lt;p>These notes how to switch from my hacky solution to the built-in global resources.&lt;/p></description></item><item><title>Pure CSS dark mode support for code highlighting</title><link>https://smhk.net/note/2024/05/pure-css-dark-mode-support-for-code-highlighting/</link><pubDate>Wed, 01 May 2024 08:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/05/pure-css-dark-mode-support-for-code-highlighting/</guid><description>&lt;p>These notes cover how to add support for code highlighting that automatically respects the user&amp;rsquo;s light☀️/dark🌑 mode configuration using pure CSS. No JavaScript!&lt;/p></description></item><item><title>Upgrade version of pkginfo used by Poetry</title><link>https://smhk.net/note/2024/04/upgrade-version-of-pkginfo-used-by-poetry/</link><pubDate>Tue, 30 Apr 2024 18:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/04/upgrade-version-of-pkginfo-used-by-poetry/</guid><description>&lt;p>Some Poetry issues&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> are fixed by upgrading pkginfo. However, the method for upgrading the verison of pkginfo used by Poetry depends upon how you installed Poetry:&lt;/p></description></item><item><title>Interactive geodetic coordinates</title><link>https://smhk.net/note/2024/04/interactive-geodetic-coordinates/</link><pubDate>Mon, 29 Apr 2024 18:10:00 +0100</pubDate><guid>https://smhk.net/note/2024/04/interactive-geodetic-coordinates/</guid><description>&lt;p>For some applications of &lt;a href="https://en.wikipedia.org/wiki/Geodesy">geodesy&lt;/a>, the Earth is modelled as an ellipsoid, since the planet &lt;a href="https://en.wikipedia.org/wiki/Equatorial_bulge">bulges at the equator&lt;/a>.&lt;/p></description></item><item><title>Python: mock reading and writing files</title><link>https://smhk.net/note/2024/04/python-mock-reading-and-writing-files/</link><pubDate>Sat, 20 Apr 2024 16:45:00 +0100</pubDate><guid>https://smhk.net/note/2024/04/python-mock-reading-and-writing-files/</guid><description>&lt;p>Since Python 3.3, the &lt;a href="https://docs.python.org/3/library/unittest.mock.html">standard library has included &lt;code>unitest.mock&lt;/code>&lt;/a>, which provides utilities for mocking out parts of the system for testing.&lt;/p>
&lt;p>These notes cover how to use &lt;code>unittest.mock&lt;/code> when reading from one or more files, or writing to a single file.&lt;/p></description></item><item><title>Pure CSS dark mode support</title><link>https://smhk.net/note/2024/04/pure-css-dark-mode-support/</link><pubDate>Thu, 11 Apr 2024 08:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/04/pure-css-dark-mode-support/</guid><description>&lt;p>It is easy to support dark mode with pure CSS. No JavaScript required.&lt;/p></description></item><item><title>Hugo: Migrating from Pygments to Chroma</title><link>https://smhk.net/note/2024/04/hugo-migrating-from-pygments-to-chroma/</link><pubDate>Wed, 10 Apr 2024 17:20:00 +0100</pubDate><guid>https://smhk.net/note/2024/04/hugo-migrating-from-pygments-to-chroma/</guid><description>&lt;p>These notes cover how this website switched from Pygments to Chroma for code syntax highlighting.&lt;/p></description></item><item><title>The Important Files (part 9): Updating rsync.net account</title><link>https://smhk.net/note/2024/04/the-important-files-part-9/</link><pubDate>Mon, 08 Apr 2024 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2024/04/the-important-files-part-9/</guid><description>&lt;p>&lt;em>TL;DR: Backup status was showing as &amp;ldquo;Idle&amp;rdquo; even though logs indicate backups had been succeeding. Decided to accept the rsync.net request to update my account to the new format in case that was (in part) responsible.&lt;/em>&lt;/p></description></item><item><title>Trying out Coolify</title><link>https://smhk.net/note/2024/03/trying-out-coolify/</link><pubDate>Sun, 24 Mar 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/03/trying-out-coolify/</guid><description>&lt;p>&lt;em>TL;DR: I attempt installing Coolify on Raspberry Pi OS, but it fails because my OS turns out to be 32-bit, and Coolify requires 64-bit. In future, I would use an OS such as Ubuntu Server instead of Raspberry Pi OS.&lt;/em>&lt;/p>
&lt;p>I like the idea of self-hosting, but since 2018 I&amp;rsquo;ve been using Netlify. Some features of Netlify I find useful are:&lt;/p>
&lt;ul>
&lt;li>Upon commit to &lt;code>main&lt;/code> the website is automatically re-built and published.&lt;/li>
&lt;li>Upon build failure the website remains at the previous working build.&lt;/li>
&lt;li>Branches (with Merge Requests) automatically deploy build previews.&lt;/li>
&lt;/ul>
&lt;p>You cannot self-host Netlify, but &lt;a href="https://coolify.io/self-hosted">you can self-host Coolify&lt;/a>, which looks like it has the same key features.&lt;/p>
&lt;p>In these notes, I plan to try out using Coolify to replace Netlify by self-hosting this website.&lt;/p></description></item><item><title>Replacing Tox with Poetry and pre-commit for CI linting</title><link>https://smhk.net/note/2024/03/replacing-tox-with-poetry-and-pre-commit-for-ci-linting/</link><pubDate>Fri, 22 Mar 2024 19:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/03/replacing-tox-with-poetry-and-pre-commit-for-ci-linting/</guid><description>&lt;p>&lt;em>TL;DR: These notes meander a little. The initial problem was &lt;a href="https://smhk.net/note/2024/03/replacing-tox-with-poetry-and-pre-commit-for-ci-linting/#improving-the-tox-approach">Tox being an inefficient way to do linting with my setup&lt;/a>. I arrive at the solution of &lt;a href="https://smhk.net/note/2024/03/replacing-tox-with-poetry-and-pre-commit-for-ci-linting/#replacing-tox-with-pre-commit">replacing Tox with pre-commit&lt;/a> for running linting tools, both locally and in CI.&lt;/em>&lt;/p></description></item><item><title>Useful symbols</title><link>https://smhk.net/note/2024/03/useful-symbols/</link><pubDate>Wed, 20 Mar 2024 08:30:00 +0000</pubDate><guid>https://smhk.net/note/2024/03/useful-symbols/</guid><description>&lt;p>Some useful symbols.&lt;/p>
&lt;h2 id="degrees">Degrees&lt;/h2>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">°&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;h2 id="arrows">Arrows&lt;/h2>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">↑ ↓ → ←
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↔ ↕ ↨
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">▲ ▼ ► ◄
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">« »&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;h2 id="maths">Maths&lt;/h2>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">× ≈ ≠ ≥ ≤&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;h2 id="footnotes">Footnotes&lt;/h2>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Note_(typography)">Traditional order&lt;/a>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">* † ‡ § ‖ ¶&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Additional:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># Δ ◊ ↓ ☞&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Setting up PowerShell</title><link>https://smhk.net/note/2024/03/setting-up-powershell/</link><pubDate>Fri, 08 Mar 2024 11:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/03/setting-up-powershell/</guid><description>&lt;p>These steps improve PowerShell by:&lt;/p>
&lt;ul>
&lt;li>Adding tabs.&lt;/li>
&lt;li>Changing the tab completion to be more bash like.&lt;/li>
&lt;li>Displaying the current Git branch (if applicable).&lt;/li>
&lt;/ul></description></item><item><title>Git typo hamming distance</title><link>https://smhk.net/note/2024/03/2024-03-07-git-hamming-distance/</link><pubDate>Thu, 07 Mar 2024 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/03/2024-03-07-git-hamming-distance/</guid><description>&lt;p>A typo of &lt;code>git&lt;/code> is just one &lt;a href="https://en.wikipedia.org/wiki/Hamming_distance">Hamming distance&lt;/a> away from &lt;strong>twelve&lt;/strong> other commands:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="console">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> gti commit -m &lt;span class="s2">&amp;#34;FIXUP&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">Command &amp;#39;gti&amp;#39; not found, did you mean:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gtg&amp;#39; from snap getting-things-gnome (0.6)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gsi&amp;#39; from deb gambc (4.9.3-1.2)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gtf&amp;#39; from deb xserver-xorg-core (2:21.1.4-2ubuntu1.7~22.04.2)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;git&amp;#39; from deb git (1:2.34.1-1ubuntu1.10)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;ti&amp;#39; from deb ticgit (1.0.2.17-2.1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gt&amp;#39; from deb genometools (1.6.2+ds-2)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gli&amp;#39; from deb ruby-gli (2.14.0-1.1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;ghi&amp;#39; from deb ghi (1.2.0-1.1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;bti&amp;#39; from deb bti (034-6)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gt5&amp;#39; from deb gt5 (1.5.0~20111220+bzr29-4)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gmi&amp;#39; from deb lieer (1.3-6)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go"> command &amp;#39;gth&amp;#39; from deb genomethreader (1.7.3+dfsg-6)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">See &amp;#39;snap info &amp;lt;snapname&amp;gt;&amp;#39; for additional versions.
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>NiceGUI with async classes</title><link>https://smhk.net/note/2024/03/nicegui-with-async-classes/</link><pubDate>Thu, 07 Mar 2024 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/03/nicegui-with-async-classes/</guid><description>&lt;p>These notes build upon &lt;a href="https://smhk.net/note/2023/09/nicegui-with-click-poetry-auto-reload-and-classes/#bonus-explicit-index-page">the class based NiceGUI app written about here&lt;/a>, and modify it to be async. It may help to read those notes first.&lt;/p></description></item><item><title>Python: how (not) to reset a dataclass</title><link>https://smhk.net/note/2024/02/python-how-not-to-reset-a-dataclass/</link><pubDate>Wed, 21 Feb 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/02/python-how-not-to-reset-a-dataclass/</guid><description>&lt;p>In Python, it can be useful to reset a dataclass back to its initial values, but this can be easy to get subtly wrong.&lt;/p></description></item><item><title>Using gprof with POSIX FreeRTOS</title><link>https://smhk.net/note/2024/02/using-gprof-with-posix-freertos/</link><pubDate>Thu, 15 Feb 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/02/using-gprof-with-posix-freertos/</guid><description>&lt;p>&lt;em>TL;DR: To profile a C program with &lt;code>gprof&lt;/code>, build and link with &lt;code>-pg&lt;/code>, and ensure that the program terminates cleanly (e.g. handles &lt;code>SIGINT&lt;/code> and &lt;code>SIGTERM&lt;/code>). Then it will generate a &lt;code>gmon.out&lt;/code> file that you can analyze with &lt;code>gprof&lt;/code>. If running the program under systemd, be sure to set &lt;code>GMON_OUT_PREFIX&lt;/code> and &lt;code>WorkingDirectory=&lt;/code>, so that it writes &lt;code>gmon.out.&amp;lt;pid&amp;gt;&lt;/code> to a known location.&lt;/em>&lt;/p></description></item><item><title>shm_open not working under systemd</title><link>https://smhk.net/note/2024/02/shm-open-not-working-under-systemd/</link><pubDate>Mon, 05 Feb 2024 19:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/02/shm-open-not-working-under-systemd/</guid><description>&lt;p>&lt;em>TL;DR: &lt;code>shm_open&lt;/code>, which opens shared memory, will fail with &lt;code>fd != -1&lt;/code> if it does not have permission to read/write to &lt;code>/dev/shm/&amp;lt;name&amp;gt;&lt;/code>.&lt;/em>&lt;/p></description></item><item><title>Sphinx: Link to HTML from MyST Markdown</title><link>https://smhk.net/note/2024/01/sphinx-link-to-html-from-myst-markdown/</link><pubDate>Mon, 22 Jan 2024 19:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/01/sphinx-link-to-html-from-myst-markdown/</guid><description>&lt;p>&lt;em>TL;DR: If you try to create a link in Sphinx from MyST Markdown to an HTML file in the &lt;code>_static&lt;/code> directory, Sphinx seems to only include the individual file you link to, and places it in the &lt;code>_downloads&lt;/code> directory. If you want to include a &lt;strong>group&lt;/strong> of HTML files, use &lt;code>{.external}&lt;/code> as shown below.&lt;/em>&lt;/p></description></item><item><title>New Year's Resolution</title><link>https://smhk.net/note/2024/01/new-years-resolution/</link><pubDate>Fri, 19 Jan 2024 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/01/new-years-resolution/</guid><description>&lt;p>&lt;em>Tl;DR: After replacing the capacitors twice during 16+ years of regular use, I am finally getting a new monitor.&lt;/em>&lt;/p></description></item><item><title>Append to .bashrc using Ansible</title><link>https://smhk.net/note/2024/01/append-to-bashrc-using-ansible/</link><pubDate>Wed, 17 Jan 2024 18:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/01/append-to-bashrc-using-ansible/</guid><description>&lt;p>You can append to &lt;code>.bashrc&lt;/code> using the Ansible &lt;code>blockinfile&lt;/code> module.&lt;/p></description></item><item><title>Using Kerberos from an Ansible playbook</title><link>https://smhk.net/note/2024/01/using-kerberos-from-an-ansible-playbook/</link><pubDate>Mon, 15 Jan 2024 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/01/using-kerberos-from-an-ansible-playbook/</guid><description>&lt;p>&lt;em>TL;DR: To run &lt;code>kinit&lt;/code> under Ansible, use &lt;code>ktutil&lt;/code> to create a &lt;code>ticket.keytab&lt;/code>, and then Ansible can authenticate without prompting.&lt;/em>&lt;/p></description></item><item><title>Poetry: install alpha builds</title><link>https://smhk.net/note/2024/01/poetry-install-alpha-builds/</link><pubDate>Tue, 09 Jan 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/01/poetry-install-alpha-builds/</guid><description>&lt;p>&lt;em>Tl;DR: Use &lt;code>allow-prereleases = true&lt;/code> to tell Poetry to consider alpha/beta/dev builds.&lt;/em>&lt;/p></description></item><item><title>Detect if Ansible is running under Vagrant</title><link>https://smhk.net/note/2024/01/detect-if-ansible-is-running-under-vagrant/</link><pubDate>Sat, 06 Jan 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2024/01/detect-if-ansible-is-running-under-vagrant/</guid><description>&lt;p>&lt;em>TL;DR: To tell if Ansible was invoked by Vagrant, define an &lt;code>is_vagrant&lt;/code> flag as &lt;code>false&lt;/code> in the playbook, but set it to &lt;code>true&lt;/code> using &lt;code>extra_vars&lt;/code> in the &lt;code>Vagrantfile&lt;/code>.&lt;/em>&lt;/p></description></item><item><title>Reflections on 10 years as a professional Software Engineer</title><link>https://smhk.net/10-years/</link><pubDate>Mon, 01 Jan 2024 20:00:00 +0000</pubDate><guid>https://smhk.net/10-years/</guid><description>&lt;p>As we begin 2024, I&amp;rsquo;ve now been working full time as a Software Engineer for over 10 years&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. These past few years in particular have been significant for several reasons, so I thought now would be a good time to take a moment to reflect. Thinking back, there&amp;rsquo;s a lot I could talk about: I&amp;rsquo;ve worked on many different projects, for a range clients, with numerous technologies, in various locations across the globe. However, I thought I&amp;rsquo;d try and focus on the general life lessons, and draw out some underlying points that will hopefully better stand the test of time. We shall see! Here are a few of the most important things I have learnt:&lt;/p></description></item><item><title>GitLab CI and poetry-dynamic-versioning</title><link>https://smhk.net/note/2023/12/gitlab-ci-and-poetry-dynamic-versioning/</link><pubDate>Wed, 20 Dec 2023 18:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/12/gitlab-ci-and-poetry-dynamic-versioning/</guid><description>&lt;p>The &lt;a href="https://github.com/mtkennerly/poetry-dynamic-versioning">poetry-dynamic-versioning&lt;/a> plugin enables configuring &lt;a href="https://python-poetry.org/">Poetry&lt;/a> to automatically generate the package version number from your VCS (e.g. Git), rather than from the &lt;code>version&lt;/code> field in &lt;code>pyproject.toml&lt;/code>.&lt;/p>
&lt;p>For example, you can tag a commit in Git, and the release built from that commit will use the name of the tag.&lt;/p></description></item><item><title>How to set up Ansible and Vagrant in WSL</title><link>https://smhk.net/note/2023/12/ansible-and-vagrant-in-wsl/</link><pubDate>Fri, 15 Dec 2023 22:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/12/ansible-and-vagrant-in-wsl/</guid><description>&lt;p>These notes walk step-by-step through setting up Ansible and Vagrant in WSL, so that you can run &lt;code>vagrant up&lt;/code> &lt;em>from within WSL&lt;/em> to bring up a VirtualBox VM, &lt;em>and use Ansible to provision that VM!&lt;/em>&lt;/p>
&lt;p>Why might you want to do this? Primarily because Ansible does not support Windows, but it does work under WSL.&lt;/p>
&lt;p>There are lots of scattered pieces of information across the Internet about how to do all this, but support appears to have changed over the years, which should be expected given that this is &lt;a href="https://smhk.net/note/2023/12/ansible-and-vagrant-in-wsl/#a-note-about-support">not officially supported by Ansible or Vagrant&lt;/a>.&lt;/p></description></item><item><title>Testing Vector and Clickhouse</title><link>https://smhk.net/note/2023/12/testing-vector-and-clickhouse/</link><pubDate>Tue, 05 Dec 2023 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/12/testing-vector-and-clickhouse/</guid><description>&lt;p>I have a &lt;a href="https://smhk.net/note/2023/11/run-poetry-command-as-systemd-service/#add-a-basic-main-loop">basic Python program&lt;/a> which logs to journald.&lt;/p>
&lt;p>Using &lt;a href="https://smhk.net/note/2023/12/python-log-storage/#collector">a collector&lt;/a>, such as &lt;a href="https://opentelemetry.io/docs/collector/">OpenTelemetry Collector&lt;/a> or &lt;a href="https://vector.dev/">Vector&lt;/a>, I want to get these Python logs into &lt;a href="https://clickhouse.com/">ClickHouse&lt;/a>.&lt;/p>
&lt;p>Following are notes on trying this out with a VM.&lt;/p></description></item><item><title>Python log storage</title><link>https://smhk.net/note/2023/12/python-log-storage/</link><pubDate>Mon, 04 Dec 2023 17:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/12/python-log-storage/</guid><description>&lt;p>&lt;a href="https://opentelemetry.io/">OpenTelemetry&lt;/a> is for collecting telemetry data (metrics, logs and traces), and forwarding them to storage and analysis tools. &lt;a href="https://opentelemetry.io/docs/what-is-opentelemetry/">Here are more details on what it is&lt;/a>.&lt;/p></description></item><item><title>Using structlog and journald</title><link>https://smhk.net/note/2023/11/structlog-and-journald/</link><pubDate>Thu, 30 Nov 2023 17:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/structlog-and-journald/</guid><description>&lt;p>I was surprised to find practically nothing online regarding using &lt;a href="https://www.structlog.org">structlog&lt;/a> with &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd-journald.service.html">journald&lt;/a>. This seemed odd given how both are strong proponents of structured logging.&lt;/p>
&lt;p>&lt;em>Update 2023-12-08: After writing this up, I did find &lt;a href="https://github.com/hynek/structlog/issues/2">this structlog issue&lt;/a>, which seems to indicate there is interest in journald integration.&lt;/em>&lt;/p>
&lt;p>&lt;strong>To skip straight to my example implementation, &lt;a href="https://smhk.net/note/2023/11/structlog-and-journald/#third-attempt-implementing-a-custom-journald-logger">jump to the last section of these notes&lt;/a>.&lt;/strong>&lt;/p>
&lt;p>If you want to see how I got there, carry on reading. In the following notes I experiment with connecting structlog up to journald before converging onto a good solution. The end result is a Python application that uses structlog, and can be configured to send logs either to the console or journald. This allows the application to be run as a command line tool, or as a service under systemd.&lt;/p></description></item><item><title>Run Poetry command as systemd service</title><link>https://smhk.net/note/2023/11/run-poetry-command-as-systemd-service/</link><pubDate>Wed, 29 Nov 2023 13:30:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/run-poetry-command-as-systemd-service/</guid><description>&lt;p>These notes cover how to run a Python Poetry command as a systemd service. I&amp;rsquo;m using Ubuntu 22.04, but the steps should be the same for systemd on other OSes.&lt;/p>
&lt;h2 id="create-an-example-poetry-python-package">Create an example Poetry Python package&lt;/h2>
&lt;p>Run &lt;code>poetry new example_service&lt;/code> to create a new Poetry Python package named &lt;code>example_service&lt;/code>.&lt;/p>
&lt;p>Add &lt;code>systemd-python&lt;/code> so that we can write to journald (see &lt;a href="https://smhk.net/note/2023/11/installing-systemd-python-on-ubuntu/">these notes&lt;/a> for more details on installing &lt;code>systemd-python&lt;/code>):&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">poetry add systemd-python&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;h2 id="add-a-basic-main-loop">Add a basic main loop&lt;/h2>
&lt;p>Create an &lt;code>example_service/service.py&lt;/code> file with a basic main loop, which just writes to journald every second:&lt;/p></description></item><item><title>Reading from and writing to journald in Python</title><link>https://smhk.net/note/2023/11/reading-from-and-writing-to-journald-in-python/</link><pubDate>Wed, 29 Nov 2023 13:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/reading-from-and-writing-to-journald-in-python/</guid><description>&lt;p>These notes cover how to do the following with journald in Python:&lt;/p>
&lt;ul>
&lt;li>Writing to journald
&lt;ul>
&lt;li>Writing custom fields&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Reading from journald
&lt;ul>
&lt;li>Reading all recent entries&lt;/li>
&lt;li>Reading custom fields&lt;/li>
&lt;li>Filtering by the executable&lt;/li>
&lt;li>Filtering by the systemd service (or &amp;ldquo;unit&amp;rdquo;)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;m using Ubuntu 22.04 running inside a Virtual Machine.&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>To demonstrate this, we&amp;rsquo;ll create a new Python package imaginatively named &lt;code>py_sysd_pkg&lt;/code> (Python systemd package). Inside that we&amp;rsquo;ll add some functions for reading/writing journald entries, and hook them up to entry points using Poetry.&lt;/p></description></item><item><title>Installing systemd-python on Ubuntu</title><link>https://smhk.net/note/2023/11/installing-systemd-python-on-ubuntu/</link><pubDate>Wed, 29 Nov 2023 11:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/installing-systemd-python-on-ubuntu/</guid><description>&lt;p>Following are steps for installing &lt;a href="https://github.com/systemd/python-systemd">the official systemd Python package&lt;/a> on Python using Poetry, on Ubuntu 22.04:&lt;/p>
&lt;h2 id="steps">Steps&lt;/h2>
&lt;h3 id="install-dependencies">Install dependencies&lt;/h3>
&lt;p>Before installing the Python package, you must install the &lt;code>libsystemd-dev&lt;/code> and &lt;code>pkg-config&lt;/code> packages for Ubuntu:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">apt-get install libsystemd-dev pkg-config&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>You&amp;rsquo;ll probably also need a compiler such as &lt;code>gcc&lt;/code>.&lt;/p>
&lt;p>Some documentation (such as &lt;a href="https://github.com/systemd/python-systemd#to-build-from-source">the &lt;code>README.md&lt;/code>&lt;/a>) mentions needing to install &lt;code>libsystemd-journal-dev&lt;/code> and various other &lt;code>libsystemd-*&lt;/code> packages. However, as far as I can tell, these have all been combined into a single &lt;code>libsystemd-dev&lt;/code> package. I can&amp;rsquo;t find a good reference for this, but the &lt;a href="https://lists.debian.org/debian-devel-changes/2015/04/msg00691.html">2015 Debian mailing list&lt;/a> mentions a bunch of these &lt;code>libsystemd-*&lt;/code> packages as being either &amp;ldquo;deprecated&amp;rdquo; or a &amp;ldquo;transitional package&amp;rdquo;.&lt;/p></description></item><item><title>python-systemd != python-systemd</title><link>https://smhk.net/note/2023/11/systemd-python-vs-python-systemd/</link><pubDate>Fri, 24 Nov 2023 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/systemd-python-vs-python-systemd/</guid><description>&lt;p>&lt;em>tl;dr: To install &lt;a href="https://github.com/systemd/python-systemd">the official systemd Python package&lt;/a>, use &lt;code>pip install systemd-python&lt;/code>, even though &lt;code>python-systemd&lt;/code> is the name of the repository on GitHub.&lt;/em>&lt;/p>
&lt;h1 id="systemd-python-">&lt;code>systemd-python&lt;/code> ✔️&lt;/h1>
&lt;p>&lt;a href="https://pypi.org/project/systemd-python/">The &lt;code>systemd-python&lt;/code> package on PyPI&lt;/a> comes from &lt;a href="https://github.com/systemd/python-systemd">the &lt;code>systemd/python-systemd&lt;/code> repository on GitHub&lt;/a>. To install this package:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install systemd-python &lt;span class="c1"># Correct.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install python-systemd &lt;span class="c1"># Wrong!! This will install the archived package described below.&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>This package is the official Python wrapper for systemd provided by &lt;a href="https://en.wikipedia.org/wiki/Freedesktop.org">freedesktop.org&lt;/a>, and &lt;strong>is&lt;/strong> currently maintained&lt;/p></description></item><item><title>PlantUML in Sphinx (using MyST Markdown) and GitLab</title><link>https://smhk.net/note/2023/11/plantuml-in-sphinx-using-myst-and-gitlab/</link><pubDate>Wed, 22 Nov 2023 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/plantuml-in-sphinx-using-myst-and-gitlab/</guid><description>&lt;p>&lt;em>tl;dr: PlantUML in Sphinx cannot be written in a way that is compatible with both MyST Markdown and the GitLab UI preview. You need to either use &lt;a href="https://smhk.net/note/2023/11/plantuml-in-sphinx-using-myst-and-gitlab/#just-use-restructuredtext">reStructuredText instead of Markdown&lt;/a> or &lt;a href="https://smhk.net/note/2023/11/plantuml-in-sphinx-using-myst-and-gitlab/#forget-about-gitlab-ui-support-and-just-use-gitlab-pages">stick with Markdown and forget about GitLab UI preview support&lt;/a>).&lt;/em>&lt;/p>
&lt;h2 id="context">Context&lt;/h2>
&lt;p>I have some documentation written in Markdown, and use &lt;a href="https://www.sphinx-doc.org/en/master/">Sphinx&lt;/a> to convert it into HTML. By default, Sphinx uses &lt;a href="https://en.wikipedia.org/wiki/ReStructuredText">reStructuredText&lt;/a>, but I have used &lt;a href="https://myst-parser.readthedocs.io/en/latest/intro.html">MyST&lt;/a> to get Markdown support.&lt;/p>
&lt;p>With this existing setup, my goal was to do the following:&lt;/p></description></item><item><title>Managing multiple remotes in Git</title><link>https://smhk.net/note/2023/11/managing-multiple-remotes-in-git/</link><pubDate>Fri, 17 Nov 2023 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/managing-multiple-remotes-in-git/</guid><description>&lt;p>Following are some quick notes on how to manage using multiple remotes in Git.&lt;/p>
&lt;h2 id="what-are-remotes">What are remotes?&lt;/h2>
&lt;p>Remotes specify other repositories to which we can push or pull commits. Before pushing to or pulling from another repository, we must add it as a remote, identified by a name of our choice. By convention, &lt;code>origin&lt;/code> is typically the name for the default remote. Multiple remotes can be defined.&lt;/p>
&lt;h3 id="add-a-new-remote">Add a new remote&lt;/h3>
&lt;p>Create a new remote named &lt;code>some_org_repo&lt;/code> which identifies the specified repo:&lt;/p></description></item><item><title>Renaming Git branches</title><link>https://smhk.net/note/2023/11/renaming-git-branches/</link><pubDate>Thu, 16 Nov 2023 12:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/renaming-git-branches/</guid><description>&lt;p>Following are some quick notes on how to rename a branch in Git, both remotely and locally. In the following examples we are renaming the branch &lt;code>old-name&lt;/code> to &lt;code>new-name&lt;/code> by copying the branch and then deleting the original. These notes assume you have one remote called &lt;code>origin&lt;/code>.&lt;/p></description></item><item><title>Poetry: Offline installation of packages</title><link>https://smhk.net/note/2023/11/poetry-offline-installation-of-packages/</link><pubDate>Tue, 14 Nov 2023 17:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/11/poetry-offline-installation-of-packages/</guid><description>&lt;p>These notes are about how to perform an offline installation of a Python application using &lt;code>poetry install&lt;/code>, &lt;strong>not&lt;/strong> about how to install Poetry itself offline.&lt;/p></description></item><item><title>Configuring a Brocade FastIron switch</title><link>https://smhk.net/note/2023/11/configuring-a-brocade-fastiron-switch/</link><pubDate>Wed, 01 Nov 2023 20:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/11/configuring-a-brocade-fastiron-switch/</guid><description>&lt;p>Following are some notes on configuring a Brocade FastIron FCX 624S network switch.&lt;/p></description></item><item><title>NiceGUI: Change threshold for binding propagation warning</title><link>https://smhk.net/note/2023/10/nicegui-binding-propagation-warning/</link><pubDate>Wed, 11 Oct 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/10/nicegui-binding-propagation-warning/</guid><description>&lt;p>In NiceGUI you may start to see a &lt;code>binding propagation&lt;/code> warning logged repeatedly if you have lots of binding.&lt;/p></description></item><item><title>NiceGUI: Bind visibility to arbitrary value</title><link>https://smhk.net/note/2023/10/nicegui-bind-visibility-to-arbitrary-value/</link><pubDate>Mon, 09 Oct 2023 09:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/10/nicegui-bind-visibility-to-arbitrary-value/</guid><description>&lt;p>NiceGUI gives &lt;a href="https://nicegui.io/documentation/bindings">lots of examples&lt;/a> of using &lt;code>bind_visibility_from&lt;/code> to show/hide an element based upon the value of some attribute.&lt;/p></description></item><item><title>NiceGUI: tkinter error when updating pyplot</title><link>https://smhk.net/note/2023/10/nicegui-tkinter-error-when-updating-pyplot/</link><pubDate>Wed, 04 Oct 2023 13:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/10/nicegui-tkinter-error-when-updating-pyplot/</guid><description>&lt;p>&lt;em>TL;DR: if you get the error &lt;code>can't delete Tcl command&lt;/code> when updating a pyplot within NiceGUI, add &lt;code>close=False&lt;/code> to &lt;code>ui.pyplot()&lt;/code>.&lt;/em>&lt;/p></description></item><item><title>The Important Files (part 8): Setting up NAS after shipping across the Atlantic</title><link>https://smhk.net/note/2023/10/the-important-files-part-8/</link><pubDate>Tue, 03 Oct 2023 18:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/10/the-important-files-part-8/</guid><description>&lt;p>At the end of May I returned to the UK from Seattle with just few suitcases. Ten weeks later my shipping container turned up, which included my NAS, which has now completed a round-trip from the UK to Seattle and back. Two months has passed, and now it&amp;rsquo;s time to set up the NAS again. Let&amp;rsquo;s plug it in and get started.&lt;/p>
&lt;p>An overview of this post is:&lt;/p>
&lt;ul>
&lt;li>Powered on NAS (which was uneventful, fortunately - &lt;a href="https://smhk.net/note/2021/08/the-important-files-part-7/">last time&lt;/a> I had some errors to fix)&lt;/li>
&lt;li>Updated network settings (network is now &lt;code>192.168.1.x&lt;/code> instead of &lt;code>192.168.0.x&lt;/code>)&lt;/li>
&lt;li>Got backup running again&lt;/li>
&lt;li>Updated jails&lt;/li>
&lt;li>Re-added network drive&lt;/li>
&lt;/ul>
&lt;h2 id="upgrading-transmission-jail">Upgrading transmission-jail&lt;/h2>
&lt;p>Got an alert saying that the transmission-jail plugin has an update available:&lt;/p></description></item><item><title>Django REST framework performance (part 2: techniques)</title><link>https://smhk.net/note/2023/09/django-rest-framework-performance-part-2/</link><pubDate>Fri, 29 Sep 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/django-rest-framework-performance-part-2/</guid><description>&lt;p>This is part 2 in a series of notes about how to improve the performance of &lt;a href="https://www.django-rest-framework.org/">&lt;em>Django REST framework&lt;/em>&lt;/a>.&lt;/p></description></item><item><title>NiceGUI with Click, Poetry, auto-reload and classes</title><link>https://smhk.net/note/2023/09/nicegui-with-click-poetry-auto-reload-and-classes/</link><pubDate>Wed, 27 Sep 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/nicegui-with-click-poetry-auto-reload-and-classes/</guid><description>&lt;p>It&amp;rsquo;s very easy to get up and running with NiceGUI, and the development cycle is fast because it has an auto-reload feature, where the web page automatically updates whenever you save a change to your project&amp;rsquo;s Python code.&lt;/p>
&lt;p>However, almost all the official NiceGUI examples show UI code being written at the top-level, outside of a class, and outside of a main guard.&lt;/p></description></item><item><title>FastAPI: Pretty print JSON</title><link>https://smhk.net/note/2023/09/fastapi-pretty-print-json/</link><pubDate>Mon, 25 Sep 2023 15:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/fastapi-pretty-print-json/</guid><description>&lt;p>&lt;a href="https://fastapi.tiangolo.com/">FastAPI&lt;/a> is a Python web framework for building APIs. The default response class is &lt;a href="https://fastapi.tiangolo.com/advanced/custom-response/#custom-response-html-stream-file-others">&lt;code>JSONResponse&lt;/code>&lt;/a>, but to improve performance you can install &lt;a href="https://github.com/ijl/orjson">&lt;code>orjson&lt;/code>&lt;/a> &lt;a href="https://fastapi.tiangolo.com/advanced/custom-response/#use-orjsonresponse">and switch to &lt;code>ORJSONResponse&lt;/code>&lt;/a>.&lt;/p>
&lt;p>The steps below cover how to enable pretty printing with &lt;code>ORJSONResponse&lt;/code> in both FastAPI and NiceGUI.&lt;/p></description></item><item><title>Django REST framework performance (part 1: profiling)</title><link>https://smhk.net/note/2023/09/django-rest-framework-performance-part-1/</link><pubDate>Fri, 22 Sep 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/django-rest-framework-performance-part-1/</guid><description>&lt;p>This is part 1 in a series about how to improve the performance of &lt;a href="https://www.django-rest-framework.org/">&lt;em>Django REST framework&lt;/em>&lt;/a>.&lt;/p></description></item><item><title>NiceGUI: File upload and download</title><link>https://smhk.net/note/2023/09/nicegui-file-upload-and-download/</link><pubDate>Thu, 21 Sep 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/nicegui-file-upload-and-download/</guid><description>&lt;p>If your NiceGUI application has some sort of state or configuration, it can be useful to provide a way to save and load that state. We can do that through serializing the state into JSON, and providing a way to download and upload that JSON file. Following is a simple example.&lt;/p></description></item><item><title>NiceGUI: Show a confirmation popup</title><link>https://smhk.net/note/2023/09/nicegui-show-a-confirmation-popup/</link><pubDate>Tue, 19 Sep 2023 11:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/nicegui-show-a-confirmation-popup/</guid><description>&lt;p>The NiceGUI &lt;a href="https://nicegui.io/documentation/dialog">dialog&lt;/a> element can be used to create a confirmation popup or modal. With an awaitable dialog, it is easy to create an &amp;ldquo;Are you sure?&amp;rdquo; popup, to let a user confirm before doing an action. The official documentation provides &lt;a href="https://nicegui.io/documentation/dialog#awaitable_dialog">a good example&lt;/a> of a &lt;em>single&lt;/em> dialog, but if you were to follow the same approach for &lt;em>multiple&lt;/em> dialogs then the boilerplate code quickly multiplies.&lt;/p>
&lt;p>To minimise the boilerplate code, you can re-use the same popup in multiple places.&lt;/p></description></item><item><title>NiceGUI: Always show main scrollbar</title><link>https://smhk.net/note/2023/09/nicegui-always-show-main-scrollbar/</link><pubDate>Fri, 15 Sep 2023 08:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/nicegui-always-show-main-scrollbar/</guid><description>&lt;p>In a NiceGUI application, it can be annoying if the main vertical scrollbar appears and disappears depending upon whether there is more content than fits in the screen. This issue is of course not unique to NiceGUI, but just part of how HTML styling works by default.&lt;/p></description></item><item><title>Poetry: Fixing permission error when upgrading dulwich</title><link>https://smhk.net/note/2023/09/poetry-fixing-permission-error/</link><pubDate>Wed, 13 Sep 2023 09:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/poetry-fixing-permission-error/</guid><description>&lt;p>When attempting to upgrade from Poetry v1.5.1 to v1.6.1 by running &lt;code>poetry self update&lt;/code> I got the following error:&lt;/p></description></item><item><title>Python: Using isort with Black to improve your code base</title><link>https://smhk.net/note/2023/09/python-using-isort/</link><pubDate>Thu, 07 Sep 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/python-using-isort/</guid><description>&lt;p>&lt;a href="https://pycqa.github.io/isort/">isort&lt;/a> is a Python package that sorts the order of your imports, and groups them into sections by type. So while a code formatter like &lt;a href="https://github.com/psf/black">Black&lt;/a> will ensure the spacing and line length of your imports is correct, it will not modify their order.&lt;/p>
&lt;p>So why bother ordering your imports?&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p></description></item><item><title>Poetry: Running Black and isort with pre-commit hooks</title><link>https://smhk.net/note/2023/09/poetry-pre-commit-hooks/</link><pubDate>Mon, 04 Sep 2023 14:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/09/poetry-pre-commit-hooks/</guid><description>&lt;p>&lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">Git Hooks&lt;/a> provide &amp;ldquo;a way to fire off custom scripts when certain important actions occur&amp;rdquo;, and are typically stored in &lt;code>.git/hooks&lt;/code> within your Git repository. For example, the Git &lt;code>pre-commit&lt;/code> hook runs just before files are committed (e.g. when you run &lt;code>git commit&lt;/code>), and so is a useful time to perform linting.&lt;/p></description></item><item><title>Poetry: Fix warning about sources</title><link>https://smhk.net/note/2023/08/poetry-fix-warning-about-sources/</link><pubDate>Thu, 24 Aug 2023 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/08/poetry-fix-warning-about-sources/</guid><description>&lt;p>In Poetry, if you have defined custom sources but have not set a default source, you will get the following warning:&lt;/p></description></item><item><title>GitLab: Enable coverage reporting with pytest</title><link>https://smhk.net/note/2023/08/gitlab-enable-coverage-reporting-with-pytest/</link><pubDate>Wed, 23 Aug 2023 21:59:00 +0700</pubDate><guid>https://smhk.net/note/2023/08/gitlab-enable-coverage-reporting-with-pytest/</guid><description>&lt;p>GitLab supports coverage reporting. There are essential two types of coverage:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.gitlab.com/ee/ci/testing/code_coverage.html">Total coverage&lt;/a>, which is a single percentage displayed next to a job (or in Merge Requests, or badges).&lt;/li>
&lt;li>&lt;a href="https://docs.gitlab.com/ee/ci/testing/test_coverage_visualization/">Detailed coverage&lt;/a>, which is a line-by-line coverage visualisation.&lt;/li>
&lt;/ul>
&lt;p>Following are the steps for configuring pytest to generate coverage statistics, and hook &lt;em>both&lt;/em> of these coverage types up to GitLab.&lt;/p></description></item><item><title>Sphinx: Cross-referencing with MyST Markdown</title><link>https://smhk.net/note/2023/08/sphinx-cross-referencing-with-myst-markdown/</link><pubDate>Mon, 21 Aug 2023 11:30:00 +0100</pubDate><guid>https://smhk.net/note/2023/08/sphinx-cross-referencing-with-myst-markdown/</guid><description>&lt;p>In &lt;a href="https://www.sphinx-doc.org/en/master/">Sphinx&lt;/a>, using &lt;a href="https://myst-parser.readthedocs.io/en/latest/intro.html">the MyST extension&lt;/a> enables using Markdown (instead of ReStructuredText). To create a cross-reference, first you must &lt;a href="https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#auto-generated-header-anchors">enable auto-generated header anchors&lt;/a> by adding this line to your &lt;code>conf.py&lt;/code>:&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p></description></item><item><title>Poetry: Automatically generate package version from git commit</title><link>https://smhk.net/note/2023/08/poetry-automatically-generated-package-version-from-git-commit/</link><pubDate>Wed, 09 Aug 2023 11:30:00 +0100</pubDate><guid>https://smhk.net/note/2023/08/poetry-automatically-generated-package-version-from-git-commit/</guid><description>&lt;p>When building a Python package that is stored in git, it can be convenient to automatically generate the package version from the git commit and/or tag. That way there is no need to manually &amp;ldquo;bump&amp;rdquo; the version number in your Python code. Instead, each commit in git automatically generates a version such as:&lt;/p></description></item><item><title>Poetry: build.py example</title><link>https://smhk.net/note/2023/08/poetry-build-py-example/</link><pubDate>Mon, 07 Aug 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/08/poetry-build-py-example/</guid><description>&lt;p>&lt;a href="https://python-poetry.org/">Poetry&lt;/a> contains an undocumented feature which allows running custom code when &lt;code>poetry build&lt;/code> is executed. This can be particularly useful to, for example, compile a C extension, or perform some sort of pre-processing. This is done via a &lt;code>build.py&lt;/code> and some configuration.&lt;/p>
&lt;p>As can be seen by reading &lt;a href="https://github.com/python-poetry/poetry/issues/2740">this thread&lt;/a>, there has been a lot of churn over how to use this feature&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Given that it&amp;rsquo;s undocumented, this comes with all the usual caveats regarding stability. But for now it appears to be the only way to call custom code during &lt;code>poetry build&lt;/code>, which for some cases is a necessity.&lt;/p>
&lt;p>So, without further delay, following is an example of using this feature, as it works today.&lt;/p></description></item><item><title>Poetry: Fixing dubious ownership error</title><link>https://smhk.net/note/2023/08/poetry-fixing-dubious-ownership-error/</link><pubDate>Thu, 03 Aug 2023 12:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/08/poetry-fixing-dubious-ownership-error/</guid><description>&lt;p>On a Python project using &lt;a href="https://pypi.org/project/poetry/">poetry&lt;/a>, when running &lt;code>poetry build&lt;/code> I got the error:&lt;/p></description></item><item><title>Debugging missing UDP packets with Wireshark</title><link>https://smhk.net/note/2023/08/debugging-missing-udp-packets-with-wireshark/</link><pubDate>Thu, 03 Aug 2023 07:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/08/debugging-missing-udp-packets-with-wireshark/</guid><description>&lt;p>I had a device connected via Ethernet to a Windows 10 PC. The device was sending UDP packets to the PC, where a Python application was listening for them. Python could see no packets arriving, however, if I ran Wireshark on the PC I could see the packets arriving.&lt;/p></description></item><item><title>GitLab CI: Using a private project's container in a Dockerfile</title><link>https://smhk.net/note/2023/07/gitlab-ci-using-a-private-projects-container-in-a-dockerfile/</link><pubDate>Sun, 23 Jul 2023 19:10:00 +0100</pubDate><guid>https://smhk.net/note/2023/07/gitlab-ci-using-a-private-projects-container-in-a-dockerfile/</guid><description>&lt;p>In GitLab, if you have a Container Registry set up for a private project (&amp;ldquo;Project A&amp;rdquo;), and you wish to use one of those containers in the &lt;code>FROM &amp;lt;image&amp;gt;&lt;/code> field of a &lt;code>Dockerfile&lt;/code> in another project (&amp;ldquo;Project B&amp;rdquo;) to create a new container, which you will then push to Project B&amp;rsquo;s Container Registry, e.g.:&lt;/p></description></item><item><title>GitLab CI: Using a private project's container as an image</title><link>https://smhk.net/note/2023/07/gitlab-ci-using-a-private-projects-container-as-an-image/</link><pubDate>Sun, 23 Jul 2023 19:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/07/gitlab-ci-using-a-private-projects-container-as-an-image/</guid><description>&lt;p>In GitLab, if you have a Container Registry set up for a &lt;em>private&lt;/em> project (&amp;ldquo;Project A&amp;rdquo;), and you wish to use one of those containers in the &lt;code>image:&lt;/code> field of &lt;code>.gitlab-ci.yml&lt;/code> in another project (&amp;ldquo;Project B&amp;rdquo;), e.g.:&lt;/p></description></item><item><title>Installing Cartopy on Ubuntu</title><link>https://smhk.net/note/2023/07/installing-cartopy-on-ubuntu/</link><pubDate>Tue, 18 Jul 2023 09:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/07/installing-cartopy-on-ubuntu/</guid><description>&lt;p>The Python package &lt;a href="https://scitools.org.uk/cartopy/docs/latest/">Cartopy&lt;/a> has some external dependencies, meaning installation is more complicated than just &lt;code>pip install cartopy&lt;/code>. The &lt;a href="https://scitools.org.uk/cartopy/docs/latest/installing.html#ubuntu-debian">official documentation is a great starting point for Ubuntu&lt;/a>, but you may need to &lt;a href="https://scitools.org.uk/cartopy/docs/latest/installing.html#other">follow additional steps for the external dependencies&lt;/a>.&lt;/p></description></item><item><title>Hugo: Setting up Plausible analytics</title><link>https://smhk.net/note/2023/07/hugo-setting-up-plausible-analytics/</link><pubDate>Tue, 04 Jul 2023 20:00:00 +0100</pubDate><guid>https://smhk.net/note/2023/07/hugo-setting-up-plausible-analytics/</guid><description>&lt;h2 id="add-plausible-analytics-to-hugo">Add Plausible analytics to Hugo&lt;/h2>
&lt;p>Follow the &lt;a href="https://github.com/divinerites/plausible-hugo?tab=readme-ov-file#hugo-module">official installation instructions for installing Plausible &lt;strong>as a Hugo Module&lt;/strong>&lt;/a>.&lt;/p>
&lt;h3 id="convert-website-to-a-hugo-module">Convert website to a Hugo Module&lt;/h3>
&lt;p>Run &lt;code>hugo mod init&lt;/code>. This creates &lt;code>go.mod&lt;/code> and &lt;code>go.sum&lt;/code>.&lt;/p>
&lt;p>For more details &lt;a href="https://nachtimwald.com/2024/08/03/using-hugo-modules/">see this blog&lt;/a>.&lt;/p>
&lt;h3 id="add-plausible-as-hugo-module">Add Plausible as Hugo Module&lt;/h3>
&lt;p>Update &lt;code>config.toml&lt;/code> with:&lt;/p>
&lt;figure class="code-fig ">&lt;figcaption class="mono" style="height: 1.5em;" >config.toml&lt;/figcaption>
 &lt;div class="code-type" data-descr="toml">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma has-caption">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">module&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">imports&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;github.com/divinerites/plausible-hugo&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Then run &lt;code>hugo mod get -u&lt;/code>.&lt;/p>
&lt;h3 id="add-plausible-params-section-to-configtoml">Add Plausible params section to &lt;code>config.toml&lt;/code>&lt;/h3>
&lt;p>Add a new &lt;code>[params.plausible]&lt;/code> section to &lt;code>config.toml&lt;/code>:&lt;/p></description></item><item><title>Memory allocation error in WAV files created by arecord</title><link>https://smhk.net/note/2023/06/memory-allocation-error-in-wav-files-created-by-arecord/</link><pubDate>Mon, 19 Jun 2023 16:00:00 +0000</pubDate><guid>https://smhk.net/note/2023/06/memory-allocation-error-in-wav-files-created-by-arecord/</guid><description>&lt;p>The &lt;a href="https://alsa-project.org/wiki/Main_Page">ALSA&lt;/a> tool &lt;code>arecord&lt;/code> provides a very easy way to record audio. You can run it in the terminal to start recording audio, which is progressively written to a WAV file. You then press Ctrl+C to stop recording. The resulting WAV file can then be played back or edited in a variety of programs.&lt;/p></description></item><item><title>Normalizing a Django model</title><link>https://smhk.net/note/2023/04/normalizing-a-django-model/</link><pubDate>Wed, 19 Apr 2023 20:00:00 +0700</pubDate><guid>https://smhk.net/note/2023/04/normalizing-a-django-model/</guid><description>&lt;p>As your Django database schema changes over time, inevitably one day you will need to &lt;a href="https://en.wikipedia.org/wiki/Database_normalization">normalize a model&lt;/a>. Typically this consists of pulling out a column into its own table, and using foreign keys to store the relationship.&lt;/p>
&lt;p>Applying this change involves writing a migration which does two main things: updates the schema, and migrates the data. In Django it&amp;rsquo;s quite easy to make the schema change, but it&amp;rsquo;s also quite easy to write a very inefficient data migration.&lt;/p>
&lt;p>These notes focus on the problem of writing an efficient data migration.&lt;/p></description></item><item><title>Celery and systemd: how to avoid a restart loop</title><link>https://smhk.net/note/2023/01/celery-and-systemd/</link><pubDate>Fri, 03 Feb 2023 17:30:00 +0700</pubDate><guid>https://smhk.net/note/2023/01/celery-and-systemd/</guid><description>&lt;p>&lt;em>TL;DR: If using Celery as a systemd service with just one worker, I would suggest using &lt;code>Type=simple&lt;/code> and &lt;code>celery worker&lt;/code>, rather than &lt;code>Type=forking&lt;/code> and &lt;code>celery multi&lt;/code>, to avoid a potential race condition where systemd repeatedly restarts Celery.&lt;/em>&lt;/p></description></item><item><title>Handle FreeRTOS tick rollover with 64-bit ticks</title><link>https://smhk.net/note/2023/01/handle-freertos-tick-rollover-with-64-bit-ticks/</link><pubDate>Thu, 26 Jan 2023 20:00:00 +0700</pubDate><guid>https://smhk.net/note/2023/01/handle-freertos-tick-rollover-with-64-bit-ticks/</guid><description>&lt;p>In FreeRTOS, with a 32-bit systick running at 1000Hz (i.e. &lt;code>configTICK_RATE_HZ = 1000&lt;/code>), the maximum duration it can run before a rollover occurs is ~50 days:&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p></description></item><item><title>Print a 64-bit integer using 32-bit integers</title><link>https://smhk.net/note/2023/01/print-64-bit-integer-using-32-bit-integers/</link><pubDate>Thu, 26 Jan 2023 18:00:00 +0700</pubDate><guid>https://smhk.net/note/2023/01/print-64-bit-integer-using-32-bit-integers/</guid><description>&lt;p>To print a 64-bit unsigned integer in a portable way, ideally you would do as follows:&lt;/p></description></item><item><title>VMware Workstation VM gets sluggish then locks up</title><link>https://smhk.net/note/2023/01/vmware-workstation-vm-sluggish-then-locks-up-following-recent-windows-update/</link><pubDate>Mon, 09 Jan 2023 16:13:00 +0700</pubDate><guid>https://smhk.net/note/2023/01/vmware-workstation-vm-sluggish-then-locks-up-following-recent-windows-update/</guid><description>&lt;p>&lt;em>TL;DR: Following Windows update 22H2, Ubuntu (and other VMs) running inside VMware Workstation 16 get progressively slower, over the course of about 30 minutes, before eventually locking up. &lt;a href="#fix-2-config-change">This can be fixed by making a small config change&lt;/a>.&lt;/em>&lt;/p>
&lt;p>&lt;em>Update 2023-12-12: Added &lt;a href="#fix-2-config-change">Fix 2&lt;/a>, and marked it as the preferred solution, since &lt;a href="https://smhk.net/note/2023/01/vmware-workstation-vm-sluggish-then-locks-up-following-recent-windows-update/#fix-1-disable-windows-hypervisor-platform">Fix 1&lt;/a> prevents Docker from working.&lt;/em>&lt;/p></description></item><item><title>Hugo anchors next to headers</title><link>https://smhk.net/note/2022/09/hugo-anchors-next-to-headers/</link><pubDate>Sat, 17 Sep 2022 12:35:00 +0700</pubDate><guid>https://smhk.net/note/2022/09/hugo-anchors-next-to-headers/</guid><description>&lt;p>This note documents how to add anchors to your headings in hugo, which show up only upon hover.&lt;/p></description></item><item><title>Collecting Netlify Analytics data with Python</title><link>https://smhk.net/note/2022/05/collecting-netlify-analytics-data-with-python/</link><pubDate>Mon, 16 May 2022 18:00:00 +0700</pubDate><guid>https://smhk.net/note/2022/05/collecting-netlify-analytics-data-with-python/</guid><description>&lt;p>As you can see here, Netlify Analytics only spans the past 30 days:&lt;/p>



 




&lt;figure class="single">
 &lt;div class="single-inset">
 










 
 
 
 &lt;a href="https://smhk.net/images/netlify/netlify_analytics_pageviews_2022-05-16.png">
 
 &lt;img src="https://smhk.net/images/netlify/netlify_analytics_pageviews_2022-05-16_hu1662994392826439646.png" alt="Screenshot of Netlify Analytics showing last 30 days of pageviews for this website">
 
 &lt;/a>
 


 &lt;figcaption style="max-width: 380px; margin: auto;">
 
 It&amp;rsquo;s pretty quiet around here&amp;hellip;
 
 &lt;/figcaption>
 &lt;/div>
&lt;/figure>

&lt;p>This could easily be solved if we could download the data and retain it ourselves. However, there is no way in the Netlify Analytics UI to download the data.&lt;/p>
&lt;p>But what if we take a look under the hood?&lt;/p></description></item><item><title>Decimal error when upgrading from Django 3.1 to Django 3.2</title><link>https://smhk.net/note/2022/05/decimal-error-when-upgrading-django/</link><pubDate>Thu, 05 May 2022 18:00:00 +0700</pubDate><guid>https://smhk.net/note/2022/05/decimal-error-when-upgrading-django/</guid><description>&lt;p>Attempting to upgrade from Django 3.1 to Django 3.2 caused one of our &lt;code>Decimal&lt;/code> handling unit tests to fail.&lt;/p>
&lt;p>We run the unit tests against two backends: MySQL and SQLite. Interestingly, the failure only occurred against the MySQL backend.&lt;/p></description></item><item><title>Setting up CMocka with CMake for Linux and Windows</title><link>https://smhk.net/note/2022/04/setting-up-cmocka/</link><pubDate>Tue, 26 Apr 2022 18:30:00 +0700</pubDate><guid>https://smhk.net/note/2022/04/setting-up-cmocka/</guid><description>&lt;p>&lt;a href="https://cmocka.org/">CMocka is a unit testing framework for C&lt;/a>. These notes cover how to configure CMake for an existing C project to use CMocka, for both Linux and Windows, in the following steps:&lt;/p>
&lt;ul>
&lt;li>Install CMocka.&lt;/li>
&lt;li>Update your CMake modules to find CMocka.&lt;/li>
&lt;li>Update your &lt;code>CMakeLists.txt&lt;/code> to link against CMocka.&lt;/li>
&lt;li>Update your project structure to add a &lt;code>tests&lt;/code> directory.&lt;/li>
&lt;li>Add a basic CMocka test to your project.&lt;/li>
&lt;li>Run the test.&lt;/li>
&lt;li>Add a more advanced test which hooks into your project.&lt;/li>
&lt;/ul>
&lt;p>In these notes I&amp;rsquo;m using Windows 10 and Debian 9.3 &amp;ldquo;stretch&amp;rdquo;. For a terminal, on Windows I&amp;rsquo;m using Git Bash.&lt;/p></description></item><item><title>Porting a simple SDL2 game to Emscripten</title><link>https://smhk.net/note/2022/04/porting-a-simple-sdl2-game-to-emscripten/</link><pubDate>Thu, 21 Apr 2022 18:00:00 +0700</pubDate><guid>https://smhk.net/note/2022/04/porting-a-simple-sdl2-game-to-emscripten/</guid><description>&lt;p>In these notes, we take a simple SDL2 game written in C, which builds on both Linux and Windows 10, and port it to Emscripten so that the game works in the browser. This assumes we have already set up the Emscripten build, which was covered earlier for &lt;a href="https://smhk.net/note/2022/04/setting-up-emscripten-on-windows-10/">Windows&lt;/a> and &lt;a href="https://smhk.net/note/2022/04/setting-up-emscripten-on-linux/">Linux&lt;/a>. This requires a few changes to the game&amp;rsquo;s source code, which are:&lt;/p>
&lt;ul>
&lt;li>Fixing the compiler errors and warnings.&lt;/li>
&lt;li>Adding some conditional &lt;code>#ifdef&lt;/code> for Emscripten-specific code.&lt;/li>
&lt;li>Add data (such as images) to the build, so that images display in the browser.&lt;/li>
&lt;li>Update the main event loop to use the Emscripten &lt;code>emscripten_set_main_loop&lt;/code> function, so that we do not cause the browser to freeze.&lt;/li>
&lt;li>Increase the memory size to permit the game to run.&lt;/li>
&lt;/ul>
&lt;p>We&amp;rsquo;ll cover these points in more detail below.&lt;/p>
&lt;p>Our first goal is simply to get a successful build, and our second goal is to ensure that build plays nicely in the browser.&lt;/p></description></item><item><title>Setting up Emscripten with CMake on Linux</title><link>https://smhk.net/note/2022/04/setting-up-emscripten-on-linux/</link><pubDate>Thu, 21 Apr 2022 09:00:00 +0700</pubDate><guid>https://smhk.net/note/2022/04/setting-up-emscripten-on-linux/</guid><description>&lt;p>Setting up Emscripten with CMake on Linux is largely the same as the earlier instructions for &lt;a href="https://smhk.net/note/2022/04/setting-up-emscripten-on-windows-10/">Setting up Emscripten with CMake in Git Bash on Windows 10&lt;/a>. The steps about creating Python aliases can be skipped, since they only apply to Git Bash. The rest is then the same, since using Git Bash means we can use the Linux commands.&lt;/p>
&lt;p>As a quick refresher, assuming you have a git repository with emsdk as a submodule, the steps for getting started on Linux are as follows:&lt;/p></description></item><item><title>Setting up Emscripten with CMake in Git Bash on Windows 10</title><link>https://smhk.net/note/2022/04/setting-up-emscripten-on-windows-10/</link><pubDate>Wed, 20 Apr 2022 09:00:00 +0700</pubDate><guid>https://smhk.net/note/2022/04/setting-up-emscripten-on-windows-10/</guid><description>&lt;p>These notes cover in detail how to set up Emscripten with CMake in Git Bash on Windows 10.&lt;/p></description></item><item><title>Network adapter disappears after reboot</title><link>https://smhk.net/note/2022/04/network-adapter-disappears-after-reboot/</link><pubDate>Wed, 20 Apr 2022 07:50:00 +0700</pubDate><guid>https://smhk.net/note/2022/04/network-adapter-disappears-after-reboot/</guid><description>&lt;p>At some point recently my Windows 10 computer developed an issue where the Ethernet network adapter would seemingly disappear after waking up from sleep. I would then have no network connection until restarting the machine.&lt;/p></description></item><item><title>A portable timegm alternative</title><link>https://smhk.net/note/2022/04/portable-timegm-alternative/</link><pubDate>Tue, 05 Apr 2022 09:28:11 -0700</pubDate><guid>https://smhk.net/note/2022/04/portable-timegm-alternative/</guid><description>&lt;p>Today I ran into an issue on an embedded software platform. The &lt;code>timegm&lt;/code> function is nonstandard. My host unit tests pass, because &lt;code>timegm&lt;/code> exists on the host, but it fails to build for the target platform because the function does not exist on that platform.&lt;/p>
&lt;p>Fortunately, there is an easy solution. However, it was well hidden in my searches.&lt;/p>
&lt;p>In the notes section of &lt;a href="https://linux.die.net/man/3/timegm">the &lt;code>timegm&lt;/code> man page&lt;/a>, there is a portable implementation:&lt;/p></description></item><item><title>Fixing apt-key deprecation on Debian 9 ("stretch")</title><link>https://smhk.net/note/2022/03/fixing-apt-key-deprecation/</link><pubDate>Wed, 30 Mar 2022 14:13:00 +0700</pubDate><guid>https://smhk.net/note/2022/03/fixing-apt-key-deprecation/</guid><description>&lt;p>Recently &lt;code>apt-key&lt;/code> has been deprecated, which has meant that running &lt;code>apt update&lt;/code> on my Debian 9 (&amp;ldquo;stretch&amp;rdquo;) machine started giving out warnings such as &lt;code>Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in apt-key(8)&lt;/code>. In these notes are documented how I fixed these warnings for:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="#dropbox">Dropbox&lt;/a>&lt;/li>
&lt;li>&lt;a href="#sublime-text">Sublime Text&lt;/a>&lt;/li>
&lt;li>&lt;a href="#enpass">Enpass&lt;/a>&lt;/li>
&lt;li>&lt;a href="#nodejs-node-source">NodeJS (Node Source)&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>The general process is, for each application:&lt;/p>
&lt;ul>
&lt;li>Find the key (usually on the application&amp;rsquo;s website somewhere).&lt;/li>
&lt;li>Install the key to &lt;code>/usr/share/keyrings/&amp;lt;application&amp;gt;-archive-keyring.gpg&lt;/code>.
* Depending upon whether the key is ASCII, non-ASCII or a keyserver affects how we install the key.&lt;/li>
&lt;li>Update &lt;code>/etc/apt/sources.list.d/&amp;lt;application&amp;gt;.list&lt;/code> to add &lt;code>[signed-by=/usr/share/keyrings/&amp;lt;application&amp;gt;-archive-keyring.gpg]&lt;/code>.&lt;/li>
&lt;li>Run &lt;code>apt-key del&lt;/code> to delete the key from &lt;code>/etc/apt/trusted.gpg&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>The process for each application is detailed below, since there are some special steps.&lt;/p></description></item><item><title>The Important Files (part 7): Switching to TrueNAS after 2 years powered off</title><link>https://smhk.net/note/2021/08/the-important-files-part-7/</link><pubDate>Fri, 13 Aug 2021 21:58:00 +0800</pubDate><guid>https://smhk.net/note/2021/08/the-important-files-part-7/</guid><description>&lt;p>Three years ago, I first set up my NAS using FreeNAS (see &lt;a href="https://smhk.net/note/2018/06/the-important-files-part-1/">part 1&lt;/a>). A few months later it was powered off, placed in a shipping container along with the rest of my household contents, as I moved across the world from the UK to Seattle. Now that I&amp;rsquo;ve been in Seattle for almost two years, I&amp;rsquo;ve finally got around to setting up my NAS again(!). These notes cover the process of getting it up and running, recovering from a dying USB stick, and bringing everything up-to-date.&lt;/p>
&lt;p>An overview of this post is:&lt;/p>
&lt;ul>
&lt;li>Powered on NAS&lt;/li>
&lt;li>Unable to boot due to error message&lt;/li>
&lt;li>Determined error was due to USB stick used for FreeNAS OS dying&lt;/li>
&lt;li>Discovered that FreeNAS has become TrueNAS&lt;/li>
&lt;li>Used ddrescue to clone dying USB stick&lt;/li>
&lt;li>Resurrected NAS using cloned USB stick&lt;/li>
&lt;li>Decided to use SSD instead of USB stick to avoid same issue happening in future&lt;/li>
&lt;li>Ordered parts to fit extra SSD in NAS enclosure&lt;/li>
&lt;li>Installed FreeNAS on SSD&lt;/li>
&lt;li>Exported NAS config from USB stick&lt;/li>
&lt;li>Imported NAS config onto SSD&lt;/li>
&lt;li>Upgraded OS on SSD from FreeNAS to TrueNAS&lt;/li>
&lt;li>Verified backups still work&lt;/li>
&lt;/ul></description></item><item><title>Notes on history of LTE and 4G</title><link>https://smhk.net/note/2021/01/notes-on-history-of-lte-and-4g/</link><pubDate>Thu, 07 Jan 2021 12:30:00 +0000</pubDate><guid>https://smhk.net/note/2021/01/notes-on-history-of-lte-and-4g/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;ul>
&lt;li>LTE was designed by 3GPP (Third Generation Partnership Project)&lt;/li>
&lt;li>LTE is known in full as 3GPP Long-Term Evolution&lt;/li>
&lt;li>LTE evolved from UMTS (Universal Mobile Telecommunications System), which in turn evolved into GSM (Global System for Mobile Communications)&lt;/li>
&lt;/ul></description></item><item><title>Permission denied in Docker container upon COPY and RUN of a file from git</title><link>https://smhk.net/note/2020/10/permission-denied-in-docker-container-upon-copy-and-run-of-a-file-from-git/</link><pubDate>Fri, 16 Oct 2020 12:17:42 -0800</pubDate><guid>https://smhk.net/note/2020/10/permission-denied-in-docker-container-upon-copy-and-run-of-a-file-from-git/</guid><description>&lt;p>&lt;em>TL;DR: Using &lt;code>COPY&lt;/code> to copy files into a Docker container retains the permissions from the host system. If host obtains the files from &lt;code>git&lt;/code>, make sure that file attributes are set correctly in &lt;code>git&lt;/code> to avoid getting a &amp;ldquo;Permission denied&amp;rdquo; error when using &lt;code>RUN&lt;/code> to execute these files in the Docker container.&lt;/em>&lt;/p></description></item><item><title>Docker build invalid reference format</title><link>https://smhk.net/note/2020/10/docker-build-invalid-reference-format/</link><pubDate>Thu, 08 Oct 2020 17:17:42 -0800</pubDate><guid>https://smhk.net/note/2020/10/docker-build-invalid-reference-format/</guid><description>&lt;p>&lt;em>TL;DR: If &amp;ldquo;Container Registry&amp;rdquo; is not enabled for your project, &lt;code>docker build&lt;/code> will give a cryptic error such as: &lt;code>invalid argument &amp;quot;:master&amp;quot; for &amp;quot;-t, --tag&amp;quot; flag: invalid reference format&lt;/code>&lt;/em>&lt;/p></description></item><item><title>nmcli tips</title><link>https://smhk.net/note/2020/08/nmcli-tips/</link><pubDate>Wed, 12 Aug 2020 10:41:42 -0700</pubDate><guid>https://smhk.net/note/2020/08/nmcli-tips/</guid><description>&lt;p>&lt;code>nmcli&lt;/code> is used to control NetworkManager from the command line. This can be very useful to troubleshoot and configure headless machines.&lt;/p></description></item><item><title>Workaround for installing Dropbox on Debian with libpango transitional package</title><link>https://smhk.net/note/2020/05/workaround-for-installing-dropbox-on-debian-with-libpango-transitional-package/</link><pubDate>Sun, 31 May 2020 12:24:00 +0700</pubDate><guid>https://smhk.net/note/2020/05/workaround-for-installing-dropbox-on-debian-with-libpango-transitional-package/</guid><description>&lt;p>tl;dr:&lt;/p>
&lt;p>On Linux, &lt;a href="https://www.dropboxforum.com/t5/Dropbox-installs-integrations/Unable-to-install-Dropbox-on-Ubuntu-20-04/td-p/405158">Dropbox depends upon &lt;code>libpango1.0-0&lt;/code>&lt;/a>, which was &lt;a href="https://askubuntu.com/questions/389158/why-two-libpango-exist">renamed to &lt;code>libpango-1.0-0&lt;/code> over six years ago&lt;/a>. In the meantime, &lt;code>libpango1.0-0&lt;/code> became a deprecated, &amp;ldquo;transitional package&amp;rdquo;, which depends upon &lt;code>libpango-1.0-0&lt;/code> as a means of easing the renaming transition. A month ago, &lt;a href="https://salsa.debian.org/gnome-team/pango/-/blob/debian/master/debian/changelog">Debian removed the transitional package&lt;/a>. Until Dropbox update their package to depend upon &lt;code>libpango-1.0-0&lt;/code>, it is no longer possible to do &lt;code>apt install dropbox&lt;/code>.&lt;/p>
&lt;p>The best workaround for now is to &lt;a href="https://www.dropboxforum.com/t5/Dropbox-installs-integrations/Unable-to-install-Dropbox-on-Ubuntu-20-04/td-p/405158/page/2">create your own transitional package&lt;/a>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">apt install equivs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir /tmp/pkg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cd /tmp/pkg
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cat &amp;lt;&amp;lt;EOF &amp;gt;&amp;gt;libpango1.0-0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Package: libpango1.0-0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Version: 1.44.7-4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Depends: libpango-1.0-0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">EOF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">equivs-build libpango1.0-0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt install ./libpango1.0-0_1.44.7-4_all.deb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-mark auto libpango1.0-0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Removing a third-party Django app and its tables</title><link>https://smhk.net/note/2020/05/removing-a-third-party-django-app-and-its-tables/</link><pubDate>Thu, 21 May 2020 15:30:00 +0700</pubDate><guid>https://smhk.net/note/2020/05/removing-a-third-party-django-app-and-its-tables/</guid><description>&lt;p>&lt;a href="https://tldlife.com/safely-removing-a-django-app-17bdb2803200">Removing an app from Django is fairly straight-forward&lt;/a>, if its an &lt;em>internal&lt;/em> app, i.e. one for which you control the source code. In a nutshell, you delete your models, then run &lt;code>makemigrations&lt;/code> to generate a migration that deletes the tables from the database, and then you can delete the app.&lt;/p>
&lt;p>But if it is an &lt;em>external&lt;/em> (third-party) app, i.e. one for which you &lt;em>don&amp;rsquo;t&lt;/em> control the source code, the same process isn&amp;rsquo;t possible. Since the migrations for deleting the tables cannot be added to the app that you don&amp;rsquo;t have control over.&lt;/p>
&lt;p>I had assumed it would be a common use-case to want to &lt;em>remove&lt;/em> an app from Django, and to also remove all its associated tables. However, either my search-fu has failed me, or it&amp;rsquo;s not as common as I had assumed.&lt;/p></description></item><item><title>Running inkscape_merge against Inkscape v1.0</title><link>https://smhk.net/note/2020/05/running-inkscape-merge-against-inkscape-v1-0/</link><pubDate>Sun, 17 May 2020 19:00:00 +0700</pubDate><guid>https://smhk.net/note/2020/05/running-inkscape-merge-against-inkscape-v1-0/</guid><description>&lt;p>Just upgraded Inkscape from v0.92 to v1.0. This caused my &lt;code>inkscape_merge&lt;/code> tool to break, with the error:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="console">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> inkscape_merge
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="go">bash: /usr/local/bin/inkscape_merge: /usr/bin/ruby2.5: bad interpreter: No such file or directory
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Sonic Pi on Debian</title><link>https://smhk.net/note/2020/05/sonic-pi-on-debian/</link><pubDate>Sat, 16 May 2020 18:30:00 +0700</pubDate><guid>https://smhk.net/note/2020/05/sonic-pi-on-debian/</guid><description>&lt;p>&lt;a href="https://sonic-pi.net/">Sonic Pi&lt;/a> allows you to compose music with code, and edit it on the fly.&lt;/p>
&lt;p>I heard about it through &lt;a href="https://news.ycombinator.com/item?id=23066922">Hacker News&lt;/a>.&lt;/p></description></item><item><title>mypy tips</title><link>https://smhk.net/note/2020/04/mypy-tips/</link><pubDate>Fri, 17 Apr 2020 19:30:00 +0700</pubDate><guid>https://smhk.net/note/2020/04/mypy-tips/</guid><description>&lt;p>Following are some tips from my experience of using mypy.&lt;/p></description></item><item><title>SSH tips</title><link>https://smhk.net/note/2020/04/ssh-tips/</link><pubDate>Thu, 09 Apr 2020 19:30:00 +0700</pubDate><guid>https://smhk.net/note/2020/04/ssh-tips/</guid><description>&lt;p>Following are some tips to help remind myself how to perform SSH tasks that I do less frequently.&lt;/p></description></item><item><title>mypy and verbose logging</title><link>https://smhk.net/note/2020/03/mypy-and-verbose-logging/</link><pubDate>Tue, 24 Mar 2020 19:30:00 +0700</pubDate><guid>https://smhk.net/note/2020/03/mypy-and-verbose-logging/</guid><description>&lt;p>&lt;strong>tl;dr:&lt;/strong>&lt;/p>
&lt;p>If you want to add a custom logging level to Python (e.g. &lt;code>verbose&lt;/code>), and you want it to play nicely with mypy (e.g. to not report &lt;code>error: &amp;quot;Logger&amp;quot; has no attribute &amp;quot;verbose&amp;quot;&lt;/code> every time you call &lt;code>logger.verbose()&lt;/code>), there doesn&amp;rsquo;t seem to be a better way than appending &lt;code># type: ignore&lt;/code> to every call to &lt;code>logger.verbose()&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">init_verbose_logging&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">logger&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getLogger&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">verbose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;this is logged at verbose level&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># type: ignore&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>I&amp;rsquo;d love to find a better way to do this, so please reach out if you know a solution. I&amp;rsquo;ve done quite a lot of digging into this, but I&amp;rsquo;m new to mypy, so perhaps I&amp;rsquo;ve missed something. Regardless, read on for the story on how I came to this conclusion, and what the worse alternative solution is.&lt;/p></description></item><item><title>Researching generating playing cards from an SVG template using Inkscape</title><link>https://smhk.net/note/2020/02/researching-generating-playing-cards-from-an-svg-template-using-inkscape/</link><pubDate>Sun, 02 Feb 2020 20:02:02 +0100</pubDate><guid>https://smhk.net/note/2020/02/researching-generating-playing-cards-from-an-svg-template-using-inkscape/</guid><description>&lt;p>I&amp;rsquo;m working on developing a board game, and would like to make some playing cards for it. My thinking is that it should be possible to have one (or a few) SVG templates (made in Inkscape), which are then populated with data from a CSV (or otherwise), somewhat akin to mail-merge. This would then make it easy to iterate on the design by updating the CSV file and then automatically generating a new set of playing cards, which could be printed off.&lt;/p></description></item><item><title>Migrating from Tastypie to Django REST Framework</title><link>https://smhk.net/note/2019/07/migrating-from-tastypie-to-django-rest-framework/</link><pubDate>Tue, 16 Jul 2019 09:00:00 +0100</pubDate><guid>https://smhk.net/note/2019/07/migrating-from-tastypie-to-django-rest-framework/</guid><description>&lt;p>Here are some tips from my experience of migrating an existing Django application from using Tastypie for its REST API to instead using Django REST Framework.&lt;/p>
&lt;p>These notes assume the following versions:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">django==2.2.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">django-tastypie==0.14.2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">djangorestframework==3.9.4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">django-filter==2.1.0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Python abi3 wheels</title><link>https://smhk.net/note/2019/07/python-abi3-wheels/</link><pubDate>Wed, 03 Jul 2019 09:00:00 +0100</pubDate><guid>https://smhk.net/note/2019/07/python-abi3-wheels/</guid><description>&lt;p>When updating the Python cryptography package from v1.x to &lt;a href="https://pypi.org/project/cryptography/2.7/#files">v2.7&lt;/a> I could not find a &lt;code>manylinux1&lt;/code> wheel for Python 3.6. It turned out &lt;a href="https://github.com/pyca/cryptography/issues/4406">I was not the only person to notice this&lt;/a>. However what I learnt is that &lt;a href="https://github.com/pyca/cryptography/issues/4404">past v2.1.4&lt;/a>, Cryptography publish &amp;ldquo;abi3&amp;rdquo; wheels. (See &lt;a href="https://www.python.org/dev/peps/pep-0425/">PEP 425&lt;/a>)&lt;/p></description></item><item><title>Configure Python package to install dependencies only for specific combinations of Python version and platform</title><link>https://smhk.net/note/2019/03/configure-python-package-to-install-dependencies-only-for-specific-combinations-of-python-version-and-platform/</link><pubDate>Thu, 07 Mar 2019 11:30:00 +0000</pubDate><guid>https://smhk.net/note/2019/03/configure-python-package-to-install-dependencies-only-for-specific-combinations-of-python-version-and-platform/</guid><description>&lt;p>&lt;a href="https://www.python.org/dev/peps/pep-0508/#environment-markers">PEP 508&lt;/a> introduced environment markers which let you specify conditions for whether a package should be installed. These can be used inside the &lt;code>install_requires&lt;/code> or &lt;code>setup_requires&lt;/code> argument in &lt;code>setup.py&lt;/code>, or within the &lt;code>requirements.txt&lt;/code>.&lt;/p>
&lt;p>The most simple example is:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">argparse; python_version &amp;lt; &amp;#34;2.7&amp;#34;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>This would only install &lt;code>argparse&lt;/code> if the Python version is lower than &lt;code>2.7&lt;/code>.&lt;/p>
&lt;p>However PEP 508 details many more enivonment markers, and specifies the grammar for combining these. For example:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">baz; python_version &amp;gt;= &amp;#34;3.6&amp;#34; and platform_system == &amp;#34;Linux&amp;#34;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>This would only install &lt;code>baz&lt;/code> if the Python version is greater than or equal to &lt;code>3.6&lt;/code>, and the system is Linux.&lt;/p></description></item><item><title>Python click: allow user to retry input upon validation failure</title><link>https://smhk.net/note/2019/02/python-click-allow-user-to-retry-input-upon-validation-failure/</link><pubDate>Thu, 07 Feb 2019 18:00:00 +0000</pubDate><guid>https://smhk.net/note/2019/02/python-click-allow-user-to-retry-input-upon-validation-failure/</guid><description>&lt;p>It can be a frustrating experience for a user if you naïvely reject their input without giving them another chance to correct their input, or know why it is wrong:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">param&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Aborts if value is too long.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">32&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">UsageError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Too long!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">callback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">echo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Assigning &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Using the above &lt;code>assign&lt;/code> command with a namer longer than 32 characters will cause the command to abort. This frustration compounds if the command takes multiple options, since the user may have already given input to several options, only to have to retry and enter it all over again.&lt;/p>
&lt;p>Instead it is much better to let the user retry straightaway if their input fails:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">param&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Lets user retry, with explanation, if value is too long.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">32&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Actually tell the user why their input was rejected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;must be 32 characters or fewer&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># `get_error_hint` will return `&amp;#34;--name-&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">hint&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">param&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_error_hint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Use styling to make the error message prominent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">secho&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Error: Invalid value for &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hint&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;red&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bold&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Prompt the user to try again&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">param&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@click.option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">callback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">assign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">echo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Assigning &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>The above implementation is better because:&lt;/p>
&lt;ul>
&lt;li>The user can try again any number of times (they can &lt;code>CTRL-C&lt;/code> to abort)&lt;/li>
&lt;li>The user is told why their input was rejected&lt;/li>
&lt;li>(As a bonus) the user can easily distinguish at a glance that it is an error because of the styling&lt;/li>
&lt;/ul></description></item><item><title>Call Python script from pylint init-hook</title><link>https://smhk.net/note/2019/01/call-python-script-from-pylint-init-hook/</link><pubDate>Fri, 25 Jan 2019 12:51:00 +0000</pubDate><guid>https://smhk.net/note/2019/01/call-python-script-from-pylint-init-hook/</guid><description>&lt;p class="warning">This note is deprecated as of December 2024. I recommend using &lt;code>source-roots&lt;/code> instead, which is available from Pylint v2.17.7. See &lt;a href="https://smhk.net/note/2024/12/pylint-source-roots/">this note&lt;/a>.&lt;/p>
&lt;p>The &lt;code>.pylintrc&lt;/code> has a &lt;code>init-hook&lt;/code> field, which is a one-liner of Python code that gets executed when pylint initialises. While you can use semicolons to write multiple lines within the &lt;code>init-hook&lt;/code>, this does not scale well, and does not diff well. There seems to be no way to do multi-line Python code within the &lt;code>init-hook&lt;/code>, so the best way I&amp;rsquo;ve found is to use the &lt;code>init-hook&lt;/code> to import a separate file, in which you can write multi-line Python code.&lt;/p>
&lt;p>First, create an &lt;code>import_hook.py&lt;/code> in the same directory as your &lt;code>.pylintrc&lt;/code>. Put in here whatever Python code you would like. Typically this might be adding stuff to the &lt;code>sys.path&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">&lt;figcaption class="mono" style="height: 1.5em;" >import_hook.py&lt;/figcaption>
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma has-caption">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;foo&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;bar&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># etc...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Then update the &lt;code>.pylintc&lt;/code> to import the &lt;code>import_hook.py&lt;/code>. We&amp;rsquo;re being lazy here and making use of import side-effects (i.e. there is no &lt;code>if __name__ == &amp;quot;__main__&amp;quot;&lt;/code> in the &lt;code>import_hook.py&lt;/code>, all code just executes upon import). That could easily be avoided but it would make the &lt;code>init-hook&lt;/code> a little longer:&lt;/p>
&lt;figure class="code-fig ">&lt;figcaption class="mono" style="height: 1.5em;" >.pylintrc&lt;/figcaption>
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma has-caption">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">init-hook=&amp;#34;import imp, os; from pylint.config import find_pylintrc; imp.load_source(&amp;#39;import_hook&amp;#39;, os.path.join(os.path.dirname(find_pylintrc()), &amp;#39;import_hook.py&amp;#39;))&amp;#34;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Python DEPRECATION warning and pip --no-cache-dir breakage</title><link>https://smhk.net/note/2019/01/python-deprecation-warning-and-pip-no-cache-dir-breakage/</link><pubDate>Wed, 23 Jan 2019 10:48:00 +0000</pubDate><guid>https://smhk.net/note/2019/01/python-deprecation-warning-and-pip-no-cache-dir-breakage/</guid><description>&lt;p>Today, Python 2.7&amp;rsquo;s pip started echoing the following:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won&amp;#39;t be maintained after that date. A future version of pip will drop support for Python 2.7.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Simultaneously (and perhaps related, though perhaps coincidentally) Python 2.7&amp;rsquo;s pip started giving the following error:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> File in &amp;#34;...\lib\site-packages\pip\_internal\cli\base_command.py&amp;#34;, line 176, in main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status = self.run(options, args)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File in &amp;#34;...\lib\site-packages\pip\_internal\commands\install.py&amp;#34;, line 346, in run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> session=session, autobuilding=True
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File in &amp;#34;...\lib\site-packages\pip\_internal\wheel.py&amp;#34;, line 848, in build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> assert building_is_possible
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AssertionError&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>libusb usb_open error -4</title><link>https://smhk.net/note/2019/01/libusb-usb-open-error-4/</link><pubDate>Thu, 03 Jan 2019 13:36:00 +0000</pubDate><guid>https://smhk.net/note/2019/01/libusb-usb-open-error-4/</guid><description>&lt;p>If you get the following error with &lt;code>pylibftdi&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;/opt/myvenv/lib/python2.7/site-packages/pylibftdi/device.py&amp;#34;, line 156, in open
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> raise FtdiError(msg)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">FtdiError: usb_open() failed (-4)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Make sure to first have these &lt;code>udev&lt;/code> rules in place:&lt;/p>
&lt;figure class="code-fig ">&lt;figcaption class="mono" style="height: 1.5em;" >/etc/udev/rules.d/11-ftdi.rules&lt;/figcaption>
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma has-caption">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># The MODE=&amp;#34;0666&amp;#34; is necessary, else pylibftdi will get error -4 when it calls libusb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SUBSYSTEM==&amp;#34;usb&amp;#34;, ATTR{idVendor}==&amp;#34;1234&amp;#34;, ATTR{idProduct}==&amp;#34;5678&amp;#34;, GROUP=&amp;#34;plugdev&amp;#34;, MODE=&amp;#34;0666&amp;#34;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>These are all the possible &lt;code>libusb&lt;/code> &lt;a href="http://libusb.sourceforge.net/api-1.0/group__misc.html">error codes&lt;/a>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">LIBUSB_SUCCESS = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_IO = -1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_INVALID_PARAM = -2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_ACCESS = -3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_NO_DEVICE = -4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_NOT_FOUND = -5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_BUSY = -6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_TIMEOUT = -7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_OVERFLOW = -8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_PIPE = -9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_INTERRUPTED = -10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_NO_MEM = -11
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_NOT_SUPPORTED = -12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LIBUSB_ERROR_OTHER = -99&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Troubleshooting wifi driver crashing (part 4)</title><link>https://smhk.net/note/2018/11/troubleshooting-wifi-driver-crashing-part-4/</link><pubDate>Thu, 29 Nov 2018 20:16:00 +0000</pubDate><guid>https://smhk.net/note/2018/11/troubleshooting-wifi-driver-crashing-part-4/</guid><description>&lt;p>The &lt;a href="https://www.thinkpenguin.com/gnu-linux/penguin-wireless-n-usb-adapter-gnu-linux-tpe-n150usb">USB Wi-Fi adapter&lt;/a> I ordered a month ago arrived today! It took so longer because the first order got lost in the post. Once I reached out to the &lt;a href="https://www.thinkpenguin.com/">Think Penguin&lt;/a> support, they were very quick to help and sent my order again. It arrived 3 days later.&lt;/p>
&lt;p>Now let&amp;rsquo;s try and get this USB Wi-Fi adapter working. (Skip to the end to see how to actually do it. There is a lot of meandering first&amp;hellip;)&lt;/p></description></item><item><title>Making Visio 2016 Usable</title><link>https://smhk.net/note/2018/11/making-visio-2016-usable/</link><pubDate>Wed, 14 Nov 2018 10:44:00 +0000</pubDate><guid>https://smhk.net/note/2018/11/making-visio-2016-usable/</guid><description>&lt;p>The bane of my experience with Visio usually comes down to connectors; being unable to attach them where I want, being unable to adjust the lines to where I want, and then once I&amp;rsquo;ve got them in place, they start jumping around by themselves when something else is moved. Fortunately I found a few tricks to help ease the pain.&lt;/p>
&lt;h2 id="use-the-connection-point-command-to-adjust-where-connectors-connect-to-an-object">Use the &amp;ldquo;Connection Point&amp;rdquo; command to adjust where connectors connect to an object&lt;/h2>
&lt;p>If you&amp;rsquo;re like me, &lt;a href="https://superuser.com/a/727933">you&amp;rsquo;ll have never even noticed this button existed&lt;/a>. Under the Home tab next to the Connector button, there is a blue &amp;ldquo;X&amp;rdquo; symbol called &amp;ldquo;Connection Point&amp;rdquo;. Select this, then select an object. You can move existing connection points by clicking on them and dragging. You can delete existing connection points by selecting them and pressing delete. You can create new connection points by holding down Ctrl and clicking.&lt;/p></description></item><item><title>Troubleshooting wifi driver crashing (part 3)</title><link>https://smhk.net/note/2018/10/troubleshooting-wifi-driver-crashing-part-3/</link><pubDate>Wed, 24 Oct 2018 20:30:00 +0100</pubDate><guid>https://smhk.net/note/2018/10/troubleshooting-wifi-driver-crashing-part-3/</guid><description>&lt;p>We&amp;rsquo;re going to try and downgrade &lt;code>iwlwifi&lt;/code> from version &lt;code>-22&lt;/code> to &lt;code>-17&lt;/code>, &lt;a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=849330">roughly following advice from here&lt;/a>.&lt;/p>
&lt;p>&lt;a href="https://ubuntuforums.org/showthread.php?t=2350328">This person&lt;/a> also has trouble with the &lt;code>-22&lt;/code> version:&lt;/p>
&lt;p>&lt;a href="https://github.com/OpenELEC/iwlwifi-firmware/issues/6">Other people&lt;/a> reported problems with the &lt;code>-21&lt;/code> version:&lt;/p>
&lt;p>Okay, first stop apt from trying to upgrade this package:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt-mark hold firmware-iwlwifi&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Then remove the &lt;code>-22&lt;/code> and &lt;code>-21&lt;/code> version. This should force the kernel to load the previous version which is &lt;code>-17&lt;/code> from what I can see in &lt;code>/lib/firmware/&lt;/code>.&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo mv /lib/firmware/iwlwifi-7265D-22.ucode .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo mv /lib/firmware/iwlwifi-7265D-21.ucode .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Time to reboot&amp;hellip;&lt;/p></description></item><item><title>Troubleshooting wifi driver crashing (part 2)</title><link>https://smhk.net/note/2018/10/troubleshooting-wifi-driver-crashing-part-2/</link><pubDate>Thu, 18 Oct 2018 20:06:00 +0100</pubDate><guid>https://smhk.net/note/2018/10/troubleshooting-wifi-driver-crashing-part-2/</guid><description>&lt;p>Going to attempt upgrading to a more recent iwlwifi firmware version to see if that fixes my wifi issues. Using the Debian testing distribution instead of stable allows me to upgrade from iwlwifi &lt;code>-22&lt;/code> to &lt;code>-29&lt;/code>:&lt;/p></description></item><item><title>Install Hugo testing distribution on Debian</title><link>https://smhk.net/note/2018/10/install-hugo-testing-distribution-on-debian/</link><pubDate>Sun, 14 Oct 2018 21:01:00 +0100</pubDate><guid>https://smhk.net/note/2018/10/install-hugo-testing-distribution-on-debian/</guid><description>&lt;p>On a default installation of Debian, trying to install hugo with &lt;code>apt install hugo&lt;/code> will fetch the latest &lt;code>stable&lt;/code> version of Hugo, which at time of writing is v0.18.1, and &lt;a href="https://gohugo.io/news/0.18-relnotes/">was released in December 2016&lt;/a>! I would like to install the more recent version 0.47.1 which came out &lt;a href="https://discourse.gohugo.io/t/hugo-0-47-1-released/13707">last month in August 2018&lt;/a>. However it is on the &lt;code>testing&lt;/code> distribution so by default cannot be installed. To enable installing the &lt;code>testing&lt;/code> version of Hugo, follow these steps:&lt;/p></description></item><item><title>Debian center window keyboard shortcut</title><link>https://smhk.net/note/2018/10/debian-center-window-keyboard-shortcut/</link><pubDate>Sun, 14 Oct 2018 18:24:00 +0100</pubDate><guid>https://smhk.net/note/2018/10/debian-center-window-keyboard-shortcut/</guid><description>&lt;p>On Debian Stretch (9.5), wanting to add a keyboard shortcut which un-maximises the current window and centers it, making it slightly smaller than the screen resolution, so that some of the background can be seen.&lt;/p>
&lt;p>Finds the Mozilla Firefox window and stops it from being maximised:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wmctrl -r &lt;span class="s2">&amp;#34;Mozilla Firefox&amp;#34;&lt;/span> -b remove,maximized_vert,maximized_horz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Finds the Mozilla Firefox window and sets it to a specific size. Note that if the window is maximised, this will appear to do nothing:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wmctrl -r &lt;span class="s2">&amp;#34;Mozilla Firefox&amp;#34;&lt;/span> -e 0,210,80,1500,900&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Uses &lt;code>xdotool&lt;/code> to get the active window rather than hard-coding &amp;ldquo;Mozilla Firefox&amp;rdquo;. Combines the above two commands into a one-liner:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">wmctrl -ri &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>xdotool getactivewindow&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -b remove,maximized_vert,maximized_horz &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> wmctrl -ri &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="k">$(&lt;/span>xdotool getactivewindow&lt;span class="k">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> -e 0,210,80,1500,900&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>This one-liner can then be bound to a keyboard command.&lt;/p>
&lt;p>In Debian, go to &lt;strong>Settings → Keyboard&lt;/strong>, then scroll to the bottom and click the plus sign to add a new keyboard shortcut. I pasted the above command, named it &amp;ldquo;Center window&amp;rdquo; and bound it to Ctrl+Super+Down.&lt;/p></description></item><item><title>Troubleshooting wifi driver crashing (part 1)</title><link>https://smhk.net/note/2018/10/troubleshooting-wifi-driver-crashing/</link><pubDate>Sat, 13 Oct 2018 12:20:00 +0100</pubDate><guid>https://smhk.net/note/2018/10/troubleshooting-wifi-driver-crashing/</guid><description>&lt;p>Perhaps a couple of months ago (unfortunately I&amp;rsquo;m not sure exactly when) I started experiencing the following wifi problem on my laptop: the wifi connection would die, and nothing except rebooting the machine would fix the problem. Sometimes it would happen after the laptop had been on for hours, and sometimes it would happen within a minute of booting, but it was inevitable that at some point the wifi would die.&lt;/p>
&lt;p>I had been hoping this issue would go away in a future update, but it persisted and became ever more irritating, since each time it struck I had to close everything and reboot. So at last I began to dig into this issue with hopes to find a resolution.&lt;/p></description></item><item><title>Bootstrapping pip</title><link>https://smhk.net/note/2018/08/bootstrapping-pip/</link><pubDate>Fri, 31 Aug 2018 15:38:00 +0100</pubDate><guid>https://smhk.net/note/2018/08/bootstrapping-pip/</guid><description>&lt;p>What doesn&amp;rsquo;t seem to be documented in the &lt;a href="https://pip.pypa.io/en/stable/installing/">pip installation guide&lt;/a> is that pip can bootstrap itself. You can &lt;a href="https://stackoverflow.com/a/49524140">install pip using pip without pip being already installed&lt;/a>. No internet access is required, just an installation of Python and the pip wheel:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">python pip-18.0-py2.py3-none-any.whl/pip install pip-18.0-py2.py3-none-any.whl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Invalid handle error from Coverage inside Tox</title><link>https://smhk.net/note/2018/08/invalid-handle-error-from-coverage-inside-tox/</link><pubDate>Fri, 31 Aug 2018 14:06:00 +0100</pubDate><guid>https://smhk.net/note/2018/08/invalid-handle-error-from-coverage-inside-tox/</guid><description>&lt;p>My tox py36 task was failing with a &lt;code>The handle is invalid&lt;/code> error, and the traceback showed it was somewhere inside &lt;a href="https://coverage.readthedocs.io/">Coverage v4.2.2&lt;/a>:&lt;/p></description></item><item><title>Run Django makemigrations as tox task</title><link>https://smhk.net/note/2018/08/run-django-makemigrations-as-tox-task/</link><pubDate>Thu, 30 Aug 2018 14:26:00 +0100</pubDate><guid>https://smhk.net/note/2018/08/run-django-makemigrations-as-tox-task/</guid><description>&lt;p>The following is an example Windows batch script which runs makemigrations inside a tox virtual environment, then copies the migrations out of the virtual environment into your src directory. This is very useful for making changes to your Django model and generating the migrations on your Windows development machine. It would be straight-forward to convert this to run on Linux too.&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="batch">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-batch" data-lang="batch">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">REM Ensure the py36 virtualenv present&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">tox -e py36 --notest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">REM Activate the py36 environment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">call&lt;/span> .tox\py36\Scripts\activate.bat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">REM Execute the makemigrations command&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">REM (Lots of quote escaping going on!)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python -c &lt;span class="s2">&amp;#34;from django.core.management import execute_from_command_line; execute_from_command_line([\&amp;#34;&lt;/span>\&lt;span class="s2">&amp;#34;, \&amp;#34;&lt;/span>makemigrations\&lt;span class="s2">&amp;#34;, \&amp;#34;&lt;/span>--settings=my_django_app.settings\&lt;span class="s2">&amp;#34;])&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">REM Copy the migrations from the py36 virtualenv into the src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">copy&lt;/span> /Y .tox\py36\Lib\site-packages\my_django_app\migrations\* src\my_django_app\migrations\&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>New dependency font(:lang-en) required by fontconfig-2.10.95-11</title><link>https://smhk.net/note/2018/08/new-dependency-fontconfig/</link><pubDate>Tue, 28 Aug 2018 10:57:00 +0100</pubDate><guid>https://smhk.net/note/2018/08/new-dependency-fontconfig/</guid><description>&lt;p>I upgraded fontconfig on a machine that does not have internet access, from fontconfig-2.10.95-10 to fontconfig-2.10.95-11, and got the following error:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">error: Failed dependencies:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> font(:lang=en) is needed by fontconfig-2.10.95-11.el7.x86_64&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>This dependency upon &lt;code>font(lang=en)&lt;/code> was added in &lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1403957">#1403957&lt;/a> (which I do not have access to) and a related bug was raised in &lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1484094">#1484094&lt;/a> (which I do have access to!).&lt;/p></description></item><item><title>Installing rpms offline using a local yum repository</title><link>https://smhk.net/note/2018/08/installing-rpms-offline-using-a-local-yum-repository/</link><pubDate>Fri, 24 Aug 2018 18:00:00 +0100</pubDate><guid>https://smhk.net/note/2018/08/installing-rpms-offline-using-a-local-yum-repository/</guid><description>&lt;p>If you have a CentOS 7 machine that does not have internet access, resolving dependencies when installing rpms can be tricky. Rather manually trying to resolve all dependencies (e.g. &lt;code>rpm -Uvh blah.rpm blah-dependency-1.rpm ...etc&lt;/code>), it is much simpler and more maintainable to create a local yum repository. Then, with only a couple of extra command line options, you can use yum to install dependencies from your local repository and let it resolve all the dependencies for you (e.g. &lt;code>yum --disablerepo=\* --enablerepo=offline-myrepo install blah&lt;/code>). This page documents how to achieve that.&lt;/p></description></item><item><title>The Important Files (part 6): Linux Samba share</title><link>https://smhk.net/note/2018/08/the-important-files-part-6/</link><pubDate>Tue, 07 Aug 2018 21:15:00 +0100</pubDate><guid>https://smhk.net/note/2018/08/the-important-files-part-6/</guid><description>&lt;p>Trying to fix &lt;code>operation not permitted&lt;/code> when trying to write to NFS share.&lt;/p>
&lt;p>Using SMB and NFS together is perhaps not safe:&lt;/p>
&lt;p>&lt;a href="https://forums.freenas.org/index.php?threads/cifs-and-nfs-together.10918/">https://forums.freenas.org/index.php?threads/cifs-and-nfs-together.10918/&lt;/a>&lt;/p>
&lt;p>Let&amp;rsquo;s get rid of the NFS share and just use SMB.&lt;/p></description></item><item><title>The Important Files (part 5): Setting up borg backup to sync.net</title><link>https://smhk.net/note/2018/07/the-important-files-part-5/</link><pubDate>Tue, 31 Jul 2018 22:28:00 +0100</pubDate><guid>https://smhk.net/note/2018/07/the-important-files-part-5/</guid><description>&lt;p>In these notes I configure borg on my FreeNAS jail (named &lt;code>fnbbu&lt;/code>) to automatically backup up every night to both a local repository, and a remote repository at rsync.net.&lt;/p>
&lt;h2 id="rsyncnet-vs-wasabi">rsync.net vs Wasabi&lt;/h2>
&lt;p>First, a quick comparison of rsync.net and Wasabi on price alone. Note that there are many aspects we could use to compare them (uptime, data center distribution, technical support, etc.) so this is not a well balanced comparison.&lt;/p></description></item><item><title>The Important Files (part 4): Setting up borg in a jail</title><link>https://smhk.net/note/2018/07/the-important-files-part-4/</link><pubDate>Sun, 29 Jul 2018 23:00:00 +0100</pubDate><guid>https://smhk.net/note/2018/07/the-important-files-part-4/</guid><description>&lt;p>In these notes I create a FreeNAS jail (using iocage rather than warden), install borg inside the jail, and use borg to communicate with a remote borg server hosted by &lt;a href="https://www.rsync.net/">rsync.net&lt;/a>. Initially I try using &lt;a href="https://wasabi.com/">Wasabi&lt;/a> rather than rsync.net, but decide against Wasabi.&lt;/p>
&lt;p>As usual, the notes were gathered as I ran through the process, and tidied up a little afterwards, so this is not a slick guide. I go back on myself and try alternative routes.&lt;/p></description></item><item><title>How to reload Ethernet udev rules without reboot</title><link>https://smhk.net/note/2018/07/how-to-reload-ethernet-udev-rules-without-reboot/</link><pubDate>Thu, 19 Jul 2018 15:55:00 +0000</pubDate><guid>https://smhk.net/note/2018/07/how-to-reload-ethernet-udev-rules-without-reboot/</guid><description>&lt;p>This note detail how to update udev rules for an Ethernet connection and have them take effect without rebooting the machine. The critical part is using &lt;code>rmmod&lt;/code> and &lt;code>modprobe&lt;/code> to remove and re-load the networking module for the Ethernet connection, which effectively &amp;ldquo;unplugs&amp;rdquo; and &amp;ldquo;replugs&amp;rdquo; the connection, causing the udev rules to take effect. Note that this will cause brief downtime for the specific network interface, but avoids rebooting the entire machine.&lt;/p>
&lt;p>First lets run &lt;code>ip addr&lt;/code> to show the interfaces we are starting with:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inet 127.0.0.1/8 scope host lo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> valid_lft forever preferred_lft forever
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inet6 ::1/128 scope host
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> valid_lft forever preferred_lft forever
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2: ens33u1: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc pfifo_fast state UP qlen 1000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inet 192.168.0.12/24 brd 192.168.0.255 scope global dynamic ens33u1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> valid_lft 2460sec preferred_lft 2460sec
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> inet6 fe80::20c:29ff:fe1a:4605/64 scope link
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> valid_lft forever preferred_lft forever&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Next we add our udev rule, which should give the Ethernet interface with the MAC address &lt;code>aa:bb:cc:dd:ee:ff&lt;/code> the interface name &lt;code>ethfoo&lt;/code>.&lt;/p></description></item><item><title>Modify bash history</title><link>https://smhk.net/note/2018/07/modify-bash-history/</link><pubDate>Tue, 10 Jul 2018 16:21:00 +0000</pubDate><guid>https://smhk.net/note/2018/07/modify-bash-history/</guid><description>&lt;p>Using bash &lt;code>history&lt;/code> to try and find a previous command, I found it had vanished and there was a mysterious &lt;code>*&lt;/code> present against the line, which I had never seen before:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> 56 vi /etc/resolv.conf
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 57 sudo systemctl restart dnsmasq
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 58*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 59 curl foo.bar&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Some searching found out this indicates &lt;a href="https://superuser.com/questions/789955/how-to-modify-history-lines-in-linux">the line has been modified&lt;/a>.&lt;/p>
&lt;p>It turns out modifying bash history is rather simple: press up until you find the line, modify it, then press down. I must have modified the history by accident.&lt;/p></description></item><item><title>Freeing up disk space on Arch Linux</title><link>https://smhk.net/note/2018/07/freeing-up-disk-space-on-arch-linux/</link><pubDate>Fri, 06 Jul 2018 20:55:00 +0100</pubDate><guid>https://smhk.net/note/2018/07/freeing-up-disk-space-on-arch-linux/</guid><description>&lt;h1 id="visualising-what-is-using-up-disk-space">Visualising what is using up disk space&lt;/h1>
&lt;p>To visualise what is taking up disk space I use &lt;code>qdirstat&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="sh">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">$ sudo pacman -S qdirstat
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ qdirstat&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Launching it opens a GUI which can be used to see what is hogging disk space.&lt;/p>
&lt;p>The pacman cache is taking up a significant chunk.&lt;/p></description></item><item><title>The Important Files (part 3): Create a dataset and shares</title><link>https://smhk.net/note/2018/06/the-important-files-part-3/</link><pubDate>Sat, 23 Jun 2018 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2018/06/the-important-files-part-3/</guid><description>&lt;p>In these notes I create a dataset in FreeNAS, then set up the Windows (SMB) and Linux (NFS) shares.&lt;/p>
&lt;p>To create the user and Windows shares, I was helped by &lt;a href="https://www.ceos3c.com/freenas/create-windows-share-freenas/">this guide&lt;/a>.&lt;/p></description></item><item><title>The Important Files (part 2): Install FreeNAS</title><link>https://smhk.net/note/2018/06/the-important-files-part-2/</link><pubDate>Tue, 12 Jun 2018 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2018/06/the-important-files-part-2/</guid><description>&lt;p>In these notes I write the FreeNAS ISO to USB and use it to install FreeNAS onto an HP Proliant Microserver; the server has four 3.5&amp;quot; SATA HDDs which will be configured as RAID, and one USB stick which will house the FreeNAS operating system. (FreeNAS cannot be installed on its own storage volumes).&lt;/p></description></item><item><title>Calling iPerf3 from Python</title><link>https://smhk.net/note/2018/06/calling-iperf3-from-python/</link><pubDate>Wed, 06 Jun 2018 16:12:00 +0100</pubDate><guid>https://smhk.net/note/2018/06/calling-iperf3-from-python/</guid><description>&lt;h2 id="creating-the-client-with-iperf3-python">Creating the Client with iperf3-python&lt;/h2>
&lt;p>If when calling this:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="nn">iperf3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">iperf3&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Client&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>You get this error:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Exception AttributeError: &amp;#34;&amp;#39;Client&amp;#39; object has no attribute &amp;#39;_stdout_fd&amp;#39;&amp;#34; in &amp;lt;bound method Client.__del__ of &amp;lt;iperf3.iperf3.Client object at 0x2505f90&amp;gt;&amp;gt; ignored
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Traceback (most recent call last):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;/opt/myapp/virtualenv/lib/python2.7/site-packages/iperf3/iperf3.py&amp;#34;, line 414, in __init__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> super(Client, self).__init__(role=&amp;#39;c&amp;#39;, *args, **kwargs)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;/opt/myapp/virtualenv/lib/python2.7/site-packages/iperf3/iperf3.py&amp;#34;, line 110, in __init__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> lib_name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OSError: Couldn&amp;#39;t find shared library libiperf.so.0, is iperf3 installed?&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Try explicitly setting &lt;code>lib_name&lt;/code> to the location of your &lt;code>libiperf.so.0&lt;/code>. For example:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="nn">iperf3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">iperf3&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Client&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lib_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;/usr/local/lib/libiperf.so.0&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>The Important Files (part 1): The goal</title><link>https://smhk.net/note/2018/06/the-important-files-part-1/</link><pubDate>Sat, 02 Jun 2018 10:00:00 +0100</pubDate><guid>https://smhk.net/note/2018/06/the-important-files-part-1/</guid><description>&lt;p>Our irreplacable family photos are stored scattered throughout the following diverse locations:&lt;/p>
&lt;ul>
&lt;li>My phone&lt;/li>
&lt;li>My old phone&lt;/li>
&lt;li>My wife&amp;rsquo;s phone&lt;/li>
&lt;li>My wife&amp;rsquo;s laptop&lt;/li>
&lt;li>My DropBox&lt;/li>
&lt;li>My old desktop PC&lt;/li>
&lt;li>My old Mac&lt;/li>
&lt;li>The SD card in our camera&lt;/li>
&lt;li>The SD card in a box in the loft&lt;/li>
&lt;li>The DVD of our wedding given to us by my Uncle&lt;/li>
&lt;/ul>
&lt;p>My old desktop PC also contains the source code for all video games I&amp;rsquo;ve developed from when I was a teenager (read: did not use version control).&lt;/p>
&lt;p>I refer to all of the above as &amp;ldquo;The Important Files&amp;rdquo;. They are all files that we would like to hold on to, are irreplacable, and would feel saddened if it was lost.&lt;/p>
&lt;p>In this note I begin the plan to design and assemble a solution.&lt;/p></description></item><item><title>Debugging a Python process not terminating due to active threads</title><link>https://smhk.net/note/2018/05/debugging-python-process-not-terminating-due-to-active-threads/</link><pubDate>Wed, 16 May 2018 11:41:00 +0100</pubDate><guid>https://smhk.net/note/2018/05/debugging-python-process-not-terminating-due-to-active-threads/</guid><description>&lt;h2 id="situation">Situation&lt;/h2>
&lt;p>A multi-threaded Python process is not terminating because a non-daemon thread is still alive. Given a running Python process, we want to identify the name of this thread.&lt;/p></description></item><item><title>Lint Bash on Windows</title><link>https://smhk.net/note/2018/05/lint-bash-on-windows/</link><pubDate>Tue, 15 May 2018 09:25:00 +0100</pubDate><guid>https://smhk.net/note/2018/05/lint-bash-on-windows/</guid><description>&lt;ol>
&lt;li>&lt;a href="https://github.com/lukesampson/scoop">Install Scoop&lt;/a>, a command line installer for Windows.&lt;/li>
&lt;li>Using Scoop, &lt;a href="https://github.com/koalaman/shellcheck">install ShellCheck&lt;/a>, a Bash linter.&lt;/li>
&lt;li>Within Sublime, install &lt;a href="https://github.com/SublimeLinter/SublimeLinter">SublimeLinter&lt;/a> and &lt;a href="https://github.com/SublimeLinter/SublimeLinter-shellcheck">SublimeLinter-shellcheck&lt;/a>.&lt;/li>
&lt;/ol></description></item><item><title>Convert Emacs org files to markdown</title><link>https://smhk.net/note/2018/04/convert-emacs-org-files-to-markdown/</link><pubDate>Thu, 26 Apr 2018 22:35:15 +0100</pubDate><guid>https://smhk.net/note/2018/04/convert-emacs-org-files-to-markdown/</guid><description>&lt;h2 id="what-is-orgmk">What is Orgmk?&lt;/h2>
&lt;blockquote>
&lt;p>&lt;a href="https://github.com/fniessen/orgmk">Orgmk&lt;/a> is a suite of bash Bash scripts for automating the conversion of Org mode documents to different formats (such as HTML or PDF)&lt;/p>
&lt;/blockquote>
&lt;p>Or in my case, to markdown!&lt;/p>
&lt;p>The documentation for Orgmk is thorough and clear, so you could do little better than to read the official documentation. While it hasn&amp;rsquo;t been updated in 2 years, that is perhaps because it largely does what it needs to do. However towards the bottom of the readme it becomes apparent that the author intended to do more.&lt;/p></description></item><item><title>Emacs package managers</title><link>https://smhk.net/note/2018/04/emacs-package-managers/</link><pubDate>Thu, 26 Apr 2018 20:45:33 +0100</pubDate><guid>https://smhk.net/note/2018/04/emacs-package-managers/</guid><description>&lt;p>Starting with Emacs 24, &lt;code>package.el&lt;/code> is included with Emacs which provides a built-in package manager.&lt;/p>
&lt;p>You can list packages:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">M-x package-list&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>And install packages:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">M-x package-install [RET] org&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Make temporary network changes permanent under CentOS 7</title><link>https://smhk.net/note/2018/04/make-temporary-network-changes-permanent-under-centos-7/</link><pubDate>Wed, 25 Apr 2018 10:47:00 +0100</pubDate><guid>https://smhk.net/note/2018/04/make-temporary-network-changes-permanent-under-centos-7/</guid><description>&lt;p>Say you make some temporary changes to your network configuration in CentOS 7. For example, assigning an IP address to an interface:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ip addr add 10.12.23.42/24 dev ens192u1&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>In order for the above change to become permanent it needs to be written to file at &lt;code>/etc/sysconfig/network-scripts/ifcfg-ens192u1&lt;/code>.&lt;/p>
&lt;p>Rather than creating this file manually, you can automatically generate it from the current network configuration (including your temporary changes).&lt;/p>
&lt;p>Run &lt;code>nmtui&lt;/code> (as root), select &amp;ldquo;Edit a connection&amp;rdquo;, select your connection, then without making any changes select &amp;ldquo;OK&amp;rdquo; and then quit. It will have generated the &lt;code>ifcfg-ens192u1&lt;/code> file for you if it did not exist already.&lt;/p></description></item><item><title>Lightweight Python SIP library</title><link>https://smhk.net/note/2018/04/lightweight-python-sip-library/</link><pubDate>Fri, 20 Apr 2018 12:07:00 +0000</pubDate><guid>https://smhk.net/note/2018/04/lightweight-python-sip-library/</guid><description>&lt;h2 id="situation">Situation&lt;/h2>
&lt;p>Need to create &lt;a href="https://en.wikipedia.org/wiki/Session_Initiation_Protocol">SIP&lt;/a> requests and parse SIP responses as defined in &lt;a href="https://tools.ietf.org/html/rfc3261">RFC 3261&lt;/a>. Do not need a &amp;ldquo;heavyweight&amp;rdquo; solution that runs a SIP server/proxy. Needs to support Python 2.7 and 3.6.&lt;/p></description></item><item><title>Removing unused COM ports from Windows</title><link>https://smhk.net/note/2018/04/removing-unused-com-ports-from-windows/</link><pubDate>Thu, 19 Apr 2018 14:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/04/removing-unused-com-ports-from-windows/</guid><description>&lt;p>Through Device Manager in Windows you can view which COM port has been assigned to which device. Each new device gets assigned the next COM port number, starting from COM1. If over the years you have connected many different devices which are assigned COM ports, this is not forgotten:&lt;/p>



 




&lt;figure class="single">
 &lt;div class="single-inset">
 










 
 
 
 &lt;a href="https://smhk.net/images/ports_hidden.png">
 
 &lt;img src="https://smhk.net/images/ports_hidden_hu8180607308872382095.png" alt="Screenshot of Windows 7 Device Manager with hidden devices hidden">
 
 &lt;/a>
 


 &lt;figcaption style="max-width: 352px; margin: auto;">
 
 Hidden devices hidden
 
 &lt;/figcaption>
 &lt;/div>
&lt;/figure>

&lt;p>It may be desirable to unassigned COM ports from devices you no longer use. This way, when you connect your new device it might get assigned COM2, for instant, rather than COM46.&lt;/p></description></item><item><title>Attach shell to running Python process</title><link>https://smhk.net/note/2018/04/attach-shell-to-running-python-process/</link><pubDate>Mon, 16 Apr 2018 09:47:01 +0000</pubDate><guid>https://smhk.net/note/2018/04/attach-shell-to-running-python-process/</guid><description>&lt;p>&lt;code>pyrasite-shell&lt;/code> (part of &lt;a href="http://pyrasite.readthedocs.io/en/latest/">&lt;code>pyrasite&lt;/code>&lt;/a>) can be used to open a shell in an already running Python process.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;p>It can be installed on CentOS 7 with the following:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="console">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> yum install gdb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gp">$&lt;/span> pip install &lt;span class="nv">pyrasite&lt;/span>&lt;span class="o">==&lt;/span>2.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Hugo 0.37 does not support h6 markdown heading</title><link>https://smhk.net/note/2018/03/hugo-h6-markdown-heading/</link><pubDate>Mon, 19 Mar 2018 20:55:00 +0000</pubDate><guid>https://smhk.net/note/2018/03/hugo-h6-markdown-heading/</guid><description>&lt;p>Posts for this website are written in markdown, and the HTML you read is generated by hugo. Recently, when trying to use &lt;code>###### blah&lt;/code> for a markdown heading, which should generate a &lt;code>&amp;lt;h6&amp;gt;&lt;/code> HTML tag, I found that hugo started generating a table of contents for the post. Remove the heading, or reduce it to &lt;code>##### blah&lt;/code>, and the table of contents goes away.&lt;/p>
&lt;p>Normally a table of contents would only be shown for a post if the &lt;code>{{ .TableOfContents }}&lt;/code> tag is used. However if an h6 markdown heading is used, then a table of contents is generated and there is no way to stop that happening.&lt;/p></description></item><item><title>Bind hugo to localhost for development</title><link>https://smhk.net/note/2018/03/bind-hugo-to-localhost-for-development/</link><pubDate>Sun, 18 Mar 2018 20:30:00 +0000</pubDate><guid>https://smhk.net/note/2018/03/bind-hugo-to-localhost-for-development/</guid><description>&lt;p>Situation: Want to run hugo on my laptop and access the website from my phone over wifi so that I can debug an issue that only occurs on my phone (issue does not occur in responsive mode using developer tools).&lt;/p>
&lt;p>By default hugo binds to &lt;code>localhost&lt;/code> which is only accessible internally (e.g. from on the same computer). Typically binding to &lt;code>0.0.0.0&lt;/code> will allow a server to be accessed externally (e.g. from another computer on the same network). However, just doing:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="console">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-console" data-lang="console">&lt;span class="line">&lt;span class="cl">&lt;span class="go">hugo --bind=0.0.0.0
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Does not completely work. Hugo will still use &lt;code>localhost&lt;/code> to try and load resources (e.g. images, stylesheets).&lt;/p></description></item><item><title>Calling MPSSE over FTDI from Python on CentOS 7</title><link>https://smhk.net/note/2018/03/calling-mpsse-over-ftdi-from-python-on-centos-7/</link><pubDate>Mon, 12 Mar 2018 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/03/calling-mpsse-over-ftdi-from-python-on-centos-7/</guid><description>&lt;p>If wanting to use MPSSE commands with an FTDI device, you cannot simply use Python &lt;a href="https://pypi.python.org/pypi/pyserial">&lt;code>pyserial&lt;/code>&amp;rsquo;s&lt;/a> &lt;code>serial.Serial()&lt;/code>. Instead you must use the FTDI driver in order to switch the device into MPSSE mode.&lt;/p></description></item><item><title>Hugo tag and category pages</title><link>https://smhk.net/note/2018/03/hugo-tag-and-category-pages/</link><pubDate>Sun, 11 Mar 2018 15:38:00 +0000</pubDate><guid>https://smhk.net/note/2018/03/hugo-tag-and-category-pages/</guid><description>&lt;p>Taxonomies are ways of grouping content within Hugo. &lt;a href="https://gohugo.io/content-management/taxonomies/#default-taxonomies">By default&lt;/a>, Hugo defines two taxonomies: &amp;ldquo;tags&amp;rdquo; and &amp;ldquo;categories&amp;rdquo;. You can then, for example, write a post and define &lt;code>tags = [&amp;quot;test&amp;quot;]&lt;/code> in the front matter. Hugo will then generate a taxonomy list page at &lt;code>https://mywebsite.com/tags/&lt;/code> and you can view all posts with the &lt;code>&amp;quot;test&amp;quot;&lt;/code> tag at &lt;code>https://mywebsite.com/tags/test&lt;/code>.&lt;/p>
&lt;p>As far as Hugo is concerned, there is no difference between &amp;ldquo;tags&amp;rdquo; and &amp;ldquo;categories&amp;rdquo;. Each post can have as many tags and/or categories as you would like!&lt;/p>
&lt;p>Conceptually though, I consider &amp;ldquo;categories&amp;rdquo; to be the broad topic, and &amp;ldquo;tags&amp;rdquo; to be more specific aspects. For example, for this post I might set:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="toml">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">categories&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;software&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;web-dev&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">tags&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;Hugo&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;taxonomies&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;h2 id="plural-vs-singular-taxonomies">Plural vs singular taxonomies&lt;/h2>
&lt;p>So far we have been using &amp;ldquo;tags&amp;rdquo; &lt;em>plural&lt;/em>. For this website, I instead wanted the taxonomy list page to be &lt;code>https://mywebsite.com/tag&lt;/code> &lt;em>singular&lt;/em>. It turns out this is rather easy!&lt;/p></description></item><item><title>Calling C from Python</title><link>https://smhk.net/note/2018/03/calling-c-from-python/</link><pubDate>Fri, 09 Mar 2018 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/03/calling-c-from-python/</guid><description>&lt;p>&lt;a href="https://docs.python.org/3.6/library/ctypes.html">&lt;code>ctypes&lt;/code> is Python library&lt;/a> for calling C code from Python. Following is a very simple example of how to build a shared object (&lt;code>*.so&lt;/code> file) from C code which can be called from Python using &lt;code>ctypes&lt;/code>.&lt;/p></description></item><item><title>Building against `libusb`</title><link>https://smhk.net/note/2018/03/building-against-libusb/</link><pubDate>Thu, 08 Mar 2018 15:40:00 +0000</pubDate><guid>https://smhk.net/note/2018/03/building-against-libusb/</guid><description>&lt;p>Situation: given an existing small C program which was developed on Ubuntu 12.04 32-bit, build it on CentOS 7 64-bit with minimal, if any, modifications to the source. The C program depends upon &lt;code>libftdi&lt;/code> and &lt;code>libusb&lt;/code>, of which there are four &amp;ldquo;versions&amp;rdquo;.&lt;/p></description></item><item><title>Get errno from Python requests `ConnectionError`</title><link>https://smhk.net/note/2018/02/get-errno-from-python-requests-connectionerror/</link><pubDate>Tue, 20 Feb 2018 11:05:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/get-errno-from-python-requests-connectionerror/</guid><description>&lt;p>Using the Python requests library, I wanted to make a &lt;code>PUT&lt;/code> request, and ignore any &lt;code>ConnectionError&lt;/code> if it was an error &lt;code>104&lt;/code> &amp;ldquo;Connection reset by peer&amp;rdquo;. (&lt;code>104&lt;/code> is &lt;code>ECONNRESET&lt;/code> defined in &lt;a href="https://en.wikipedia.org/wiki/Errno.h">&lt;code>errno.h&lt;/code>&lt;/a>). If this error was raised, the traceback I received was as follows:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;/usr/bob/venv/lib/python2.7/site-packages/requests/sessions.py&amp;#34;, line 518, in request
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> resp = self.send(prep, **send_kwargs)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;/usr/bob/venv/lib/python2.7/site-packages/requests/sessions.py&amp;#34;, line 639, in send
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> r = adapter.send(request, **kwargs)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> File &amp;#34;/usr/bob/venv/lib/python2.7/site-packages/requests/adapters.py&amp;#34;, line 488, in send
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> raise ConnectionError(err, request=request)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ConnectionError: (&amp;#39;Connection aborted.&amp;#39;, error(104, &amp;#39;Connection reset by peer&amp;#39;))&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Python logging: %s vs format</title><link>https://smhk.net/note/2018/02/python-logging/</link><pubDate>Fri, 16 Feb 2018 11:05:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/python-logging/</guid><description>&lt;p>Using &lt;code>format&lt;/code> rather than &lt;code>%s&lt;/code> syntax for formatting strings in Python has many advantages.&lt;/p>
&lt;ul>
&lt;li>It &lt;a href="https://stackoverflow.com/a/5082482">handles tuples more intuitively&lt;/a>.&lt;/li>
&lt;li>It allows using &lt;code>kwargs&lt;/code> to more clearly name arguments.&lt;/li>
&lt;li>It allows using &lt;code>kwargs&lt;/code> to reuse arguments in the format string.&lt;/li>
&lt;/ul>
&lt;p>However when it comes to logging, often the reverse is the case.&lt;/p></description></item><item><title>Escaping Hugo shortcodes within Hugo markdown</title><link>https://smhk.net/note/2018/02/escape-hugo-sortcodes-within-markdown/</link><pubDate>Sun, 11 Feb 2018 16:45:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/escape-hugo-sortcodes-within-markdown/</guid><description>&lt;p>If you naïvely try and write a post in Hugo about shortcodes using markdown, including code examples of the shortcodes, Hugo will still try to parse and render the shortcode. For example, if your markdown is as follows&amp;hellip;:&lt;/p></description></item><item><title>Automatic image thumbnails in Hugo from static directory</title><link>https://smhk.net/note/2018/02/automatic-image-thumbnails-in-hugo-from-static-directory/</link><pubDate>Sun, 11 Feb 2018 16:30:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/automatic-image-thumbnails-in-hugo-from-static-directory/</guid><description>&lt;p>Hugo v0.32 &lt;a href="https://gohugo.io/about/new-in-032/#image-processing">introduced image processing&lt;/a>, which can be used to resize, fit or fill images. This can be useful to, for example, automatically create a low resolution thumbnail from a high resolution image. Hugo v0.34 (latest at time of writing) &lt;a href="https://gohugo.io/news/0.34-relnotes/">modified the API for image processing&lt;/a>.&lt;/p>
&lt;p>Using the image processing in Hugo v0.34, my goal is this:&lt;/p>
&lt;ol>
&lt;li>Store my full resolution &amp;ldquo;original&amp;rdquo; images in the static directory (&lt;code>/static/img/&lt;/code>)&lt;/li>
&lt;li>Use Hugo to automatically generate low resolution &amp;ldquo;thumbnail&amp;rdquo; images&lt;/li>
&lt;li>Use a &lt;a href="https://gohugo.io/content-management/shortcodes/">shortcode&lt;/a> to include the thumbnail image in markdown as a link to the original image&lt;/li>
&lt;/ol></description></item><item><title>Captured `SystemExit` with pytest and tox</title><link>https://smhk.net/note/2018/02/captured-systemexit-with-pytest-and-tox/</link><pubDate>Fri, 09 Feb 2018 15:58:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/captured-systemexit-with-pytest-and-tox/</guid><description>&lt;p>I wanted to unit test some Python code that uses &lt;code>sys.exit(exit_code)&lt;/code> to verify whether the &lt;code>exit_code&lt;/code> was as expected. The &lt;a href="https://stackoverflow.com/a/30256520">first answer&lt;/a> I came across suggests using the built in &lt;code>capsys&lt;/code> fixture which is enabled with setting &lt;code>--capture=sys&lt;/code> when running &lt;code>pytest&lt;/code>. However for me this clashed with my &lt;code>tox.ini&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[testenv]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">commands =
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pytest -vv {posargs}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>I take advantage of the &lt;code>{posargs}&lt;/code> feature by sometimes running &lt;code>tox&lt;/code> with arguments for &lt;code>pytest&lt;/code>, for example &lt;code>tox -e py27 -- --capture=no&lt;/code> in order to stop &lt;code>pytest&lt;/code> from capturing &lt;code>stdout&lt;/code>. Using the &lt;code>--capture=sys&lt;/code> method in my &lt;code>tox.ini&lt;/code> as mentioned above did not seem to play well with optionally using &lt;code>--capture=no&lt;/code> when invoking &lt;code>pytest&lt;/code> through &lt;code>tox&lt;/code>.&lt;/p></description></item><item><title>Tastypie "TypeError of type 'NoneType' is not iterable"</title><link>https://smhk.net/note/2018/02/tastypie-typeerror-of-type-nonetype-is-not-iterable/</link><pubDate>Fri, 09 Feb 2018 10:20:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/tastypie-typeerror-of-type-nonetype-is-not-iterable/</guid><description>&lt;p>A unit test which performed a &lt;code>GET&lt;/code> to a Tastypie endpoint always raised the error:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="python">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_filtering&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filter_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;exact&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filter_bits&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Given a field name, a optional filter type and an optional list of
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> additional relations, determine if a field can be filtered on.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> If a filter does not meet the needed conditions, it should raise an
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ``InvalidFilterError``.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> If the filter meets the conditions, a list of attribute names (not
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> field names) will be returned.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">filter_bits&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">filter_bits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">field_name&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_meta&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filtering&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">E&lt;/span> &lt;span class="ne">TypeError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">argument&lt;/span> &lt;span class="n">of&lt;/span> &lt;span class="nb">type&lt;/span> &lt;span class="s1">&amp;#39;NoneType&amp;#39;&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">iterable&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>The fix was to define &lt;code>filtering&lt;/code> to at least be an empty dictionary in the Tastypie resource. For example:&lt;/p></description></item><item><title>Using a non-pk as a foreign key in a Tastypie endpoint</title><link>https://smhk.net/note/2018/02/using-non-pk-as-foreign-key-in-tastypie-endpoint/</link><pubDate>Thu, 08 Feb 2018 16:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/using-non-pk-as-foreign-key-in-tastypie-endpoint/</guid><description>&lt;p>Situation: using a &lt;a href="http://django-tastypie.readthedocs.io/en/latest/cookbook.html#using-non-pk-data-for-your-urls">non-pk for the detail URL in tastypie&lt;/a>. Then performing a &lt;code>PATCH&lt;/code> to update an item which is a foreign key to a non-pk detail URL gives the following error:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="json">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;my_endpoint&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;my_field_to_a_detail_url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Select a valid choice. That choice is not one of the available choices.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>pytest and functools decorator</title><link>https://smhk.net/note/2018/02/pytest-and-functools-decorator/</link><pubDate>Tue, 06 Feb 2018 10:20:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/pytest-and-functools-decorator/</guid><description>&lt;p>If using a decorator created using &lt;code>functools.wraps&lt;/code> with pytest, it will give an error similar to the following if you attempt to use fixtures:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="text">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">TypeError: test_func() takes exactly 1 argument (0 given).&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>&lt;a href="https://stackoverflow.com/a/19614807">The solution&lt;/a> is to use the &lt;a href="https://pypi.python.org/pypi/decorator">decorator&lt;/a> package instead of functools to create the decorator.&lt;/p>
&lt;p>A comment on the above stackoverflow answer (which is over 4 years old) states that pytest depends upon the decorator package. However as far as I can tell from looking at the &lt;a href="https://github.com/pytest-dev/pytest/blob/e289c60c3a6c97cc51cce8eced65c86a3d7ae750/setup.py">current setup.py for pytest&lt;/a>, it does not depend upon decorator. However using decorator still fixed the issue.&lt;/p></description></item><item><title>normalize-scss with hugo</title><link>https://smhk.net/note/2018/02/normalize-scss-with-hugo/</link><pubDate>Fri, 02 Feb 2018 17:30:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/normalize-scss-with-hugo/</guid><description>&lt;p>Before beginning to develop the CSS for this website, I wanted to ensure I had a consistent base style across all browsers. Originally I was going to use &lt;a href="https://www.npmjs.com/package/reset-css">reset.css&lt;/a> but upon further research decided to use &lt;a href="https://necolas.github.io/normalize.css/">normalize.css&lt;/a> because it normalizes everything to sensible defaults rather than zeroing everything out, &lt;a href="https://stackoverflow.com/a/8357635">among other benefits&lt;/a>.&lt;/p></description></item><item><title>Automating hugo development with npm scripts</title><link>https://smhk.net/note/2018/02/automating-hugo-development-with-npm-scripts/</link><pubDate>Fri, 02 Feb 2018 17:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/02/automating-hugo-development-with-npm-scripts/</guid><description>&lt;p>I wanted one command I could run that would automatically watch my SCSS files and HTML files, and build them upon change, and serve up the resulting website. In order to avoid complicating development by introducing a task runner as an additional dependency (such as &lt;a href="https://gruntjs.com/">grunt&lt;/a> or &lt;a href="https://gulpjs.com/">gulp&lt;/a>) I decided to try using npm scripts. It turned out to be very simple and easy!&lt;/p></description></item><item><title>RabbitMQ handshake timeout caused by hostname change</title><link>https://smhk.net/note/2018/01/rabbitmq-handshake-timeout-caused-by-hostname-change/</link><pubDate>Mon, 29 Jan 2018 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/rabbitmq-handshake-timeout-caused-by-hostname-change/</guid><description>&lt;p>Changing the hostname of our VM running &lt;code>rabbitmq-server&lt;/code> from &lt;code>old-hostname&lt;/code> to &lt;code>new-hostname&lt;/code> caused RabbitMQ to timeout all handshakes. See this excerpt from &lt;code>/var/log/messages&lt;/code> where a worker attempts to connect but the handshake times out exactly 10 seconds later.&lt;/p></description></item><item><title>Run Django `manage.py makemigrations` without a database</title><link>https://smhk.net/note/2018/01/run-django-makemigrations-without-a-database/</link><pubDate>Mon, 29 Jan 2018 10:00:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/run-django-makemigrations-without-a-database/</guid><description>&lt;p>The &lt;code>makemigrations&lt;/code> command does not require a database to be present. As such, it is actually &lt;a href="https://stackoverflow.com/a/46347877">quite straightforward&lt;/a> to run &lt;code>makemigrations&lt;/code> without even having a database. This can be very useful for the development cycle of: modifying model, run tests, update tests; without having to have a full deployment.&lt;/p></description></item><item><title>Avoid a process terminating when logging off a remote SSH session</title><link>https://smhk.net/note/2018/01/avoid-process-terminating-when-logging-off-remote-ssh-session/</link><pubDate>Thu, 25 Jan 2018 09:41:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/avoid-process-terminating-when-logging-off-remote-ssh-session/</guid><description>&lt;p>For any arbitrary command:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">nohup &amp;lt;cmd&amp;gt; &amp;gt; cmd_log.txt &lt;span class="p">&amp;amp;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>The &lt;code>&amp;amp;&lt;/code> forks &lt;code>&amp;lt;cmd&amp;gt;&lt;/code> into a separate process, and &lt;code>nohup&lt;/code> (&lt;a href="https://en.wikipedia.org/wiki/Nohup">&amp;ldquo;no hang up&amp;rdquo;&lt;/a>) ensures the process does not terminate once you log out. This can be useful to, for example, SSH into a remote machine and launch a long running command, without forcing you to keep the session open.&lt;/p></description></item><item><title>Celery pinging</title><link>https://smhk.net/note/2018/01/celery-pinging/</link><pubDate>Thu, 25 Jan 2018 09:41:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/celery-pinging/</guid><description>&lt;p>In Celery, you can ping all workers with either &lt;code>app.control.ping()&lt;/code> or &lt;code>app.control.inspect().ping()&lt;/code>. The former has an &lt;a href="http://docs.celeryproject.org/en/latest/_modules/celery/app/control.html#Control.ping">optional &lt;code>timeout&lt;/code> keyword argument&lt;/a>, while the latter &lt;a href="http://docs.celeryproject.org/en/latest/_modules/celery/app/control.html#Inspect.ping">does not&lt;/a>. Under the hood, the latter simply calls the former, but returns a slightly different data structure.&lt;/p></description></item><item><title>Kombu "timed out" bug: connection hangs forever in Kombu 4.1.0</title><link>https://smhk.net/note/2018/01/kombu-timed-out-bug-connection-hangs-forever/</link><pubDate>Thu, 25 Jan 2018 09:41:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/kombu-timed-out-bug-connection-hangs-forever/</guid><description>&lt;p>Kombu connects to RabbitMQ. However in Kombu 4.1.0 there is a bug where &lt;a href="https://github.com/celery/celery/issues/4296">it waits forever if the broker is down rather than timing out&lt;/a>. This &lt;a href="https://github.com/celery/kombu/issues/795">affects Celery&lt;/a> too. There &lt;a href="https://github.com/celery/kombu/pull/769">is a fix&lt;/a> in trunk but at time of writing there is no new release of Kombu which contains this fix. A workaround is to downgrade to Kombu 4.0.2.&lt;/p></description></item><item><title>Manually invoking Celery app control commands from a Python package</title><link>https://smhk.net/note/2018/01/manually-invoking-celery-app-control-commands-from-python-package/</link><pubDate>Wed, 24 Jan 2018 09:46:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/manually-invoking-celery-app-control-commands-from-python-package/</guid><description>&lt;p>Given a Python package named &lt;code>my_package&lt;/code> installed in the virtual environment &lt;code>/opt/venv/&lt;/code>, and which contains a top level file &lt;code>celery_app.py&lt;/code> which defines the Celery &lt;code>app&lt;/code> object, this is how you can manually invoke &lt;a href="http://docs.celeryproject.org/en/latest/userguide/workers.html#additional-commands">Celery commands&lt;/a> (e.g. to perform a &lt;code>ping&lt;/code>):&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="shell">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ &lt;span class="nb">source&lt;/span> /opt/venv/bin/activate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">(&lt;/span>venv&lt;span class="o">)&lt;/span> $ python
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt;&amp;gt; from my_package.celery_app import app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt;&amp;gt; app.control.ping&lt;span class="o">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Tastypie and pytest</title><link>https://smhk.net/note/2018/01/tastypie-and-pytest/</link><pubDate>Wed, 24 Jan 2018 09:46:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/tastypie-and-pytest/</guid><description>&lt;p>Tastypie offers a &lt;a href="https://django-tastypie.readthedocs.io/en/latest/testing.html#resourcetestcasemixin-api-reference">&lt;code>ResourceTestCaseMixin&lt;/code>&lt;/a> which can be inherited in conjunction with the Django &lt;a href="https://docs.djangoproject.com/en/2.0/topics/testing/tools/#django.test.TestCase">&lt;code>TestCase&lt;/code>&lt;/a> to allow unit testing Tastypie endpoints in a &lt;a href="https://docs.python.org/3.5/library/unittest.html">unittest&lt;/a> style manner. However if wanting to use &lt;a href="https://docs.pytest.org/en/latest/">pytest&lt;/a> instead of unittest in order to take advantages of its features such as fixtures, I found a different approach had to be taken.&lt;/p></description></item><item><title>Debugging RabbitMQ</title><link>https://smhk.net/note/2018/01/debugging-rabbitmq/</link><pubDate>Mon, 22 Jan 2018 09:58:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/debugging-rabbitmq/</guid><description>&lt;p>Monitor &lt;code>rabbitmq-server&lt;/code>&amp;rsquo;s status with:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="shell">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ rabbitmqctl status&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure>
&lt;p>Note that it must be run with root else you will get the rabbitmq help printed out with the appended message &lt;code>Only root or rabbitmq should run rabbitmqctl&lt;/code>.&lt;/p></description></item><item><title>SSH tunnel via gateway</title><link>https://smhk.net/note/2018/01/ssh-tunnel-via-gateway/</link><pubDate>Fri, 19 Jan 2018 11:24:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/ssh-tunnel-via-gateway/</guid><description>&lt;p>To set up an SSH tunnel from &lt;code>here&lt;/code> to &lt;code>remote&lt;/code> via &lt;code>gateway&lt;/code>, as illustrated below:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="ascii-art">&lt;/div>
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-ascii-art" data-lang="ascii-art">&amp;#43;------&amp;#43; &amp;#43;---------&amp;#43; &amp;#43;--------&amp;#43;
| here | | gateway | | remote |
| 22:&amp;#43;-----&amp;#43;:22 | | |
|50000:----------------&amp;#43;-----&amp;#43;:22 |
| &amp;#43;-----&amp;#43; | | |
| | | | | |
&amp;#43;------&amp;#43; &amp;#43;---------&amp;#43; &amp;#43;--------&amp;#43;&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Show whitespace in vi</title><link>https://smhk.net/note/2018/01/show-whitespace-in-vi/</link><pubDate>Thu, 18 Jan 2018 10:35:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/show-whitespace-in-vi/</guid><description>&lt;p>Opened a configuration file with &lt;code>vi&lt;/code>:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="bash">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">vi /etc/iproute2/rt_table&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>Using a different port with Fabric</title><link>https://smhk.net/note/2018/01/using-a-different-port-with-fabric/</link><pubDate>Tue, 16 Jan 2018 10:39:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/using-a-different-port-with-fabric/</guid><description>&lt;p>By default Fabric uses port &lt;code>22&lt;/code> because it is over SSH. However you can easily specify a different port e.g. &lt;code>123&lt;/code> in the typical format:&lt;/p>
&lt;figure class="code-fig ">
 &lt;div class="code-type" data-descr="shell">&lt;/div>
 
 &lt;pre tabindex="0" class="chroma ">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">$ fab -H 192.168.202.100:123 my_command&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/figure></description></item><item><title>CentOS 7 firewalld</title><link>https://smhk.net/note/2018/01/centos-7-firewalld/</link><pubDate>Mon, 15 Jan 2018 14:20:00 +0000</pubDate><guid>https://smhk.net/note/2018/01/centos-7-firewalld/</guid><description>&lt;h2 id="general">General&lt;/h2>
&lt;p>The full manual for firewalld on CentOS 7 can be found in &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-configuring_firewalld">section 5 of the RHEL 7 security guide&lt;/a>.&lt;/p></description></item></channel></rss>