<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Terin Stock</title><link>https://terinstock.com/</link><description>Recent content on Terin Stock</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>terin@terinstock.com (Terin Stock)</managingEditor><webMaster>terin@terinstock.com (Terin Stock)</webMaster><copyright>© 2014-2026 Terin Stock</copyright><lastBuildDate>Wed, 26 Feb 2025 03:28:00 +0000</lastBuildDate><atom:link href="https://terinstock.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Netboot Windows 11 with iSCSI and iPXE</title><link>https://terinstock.com/post/2025/02/Netboot-Windows-11-with-iSCSI-and-iPXE/</link><pubDate>Wed, 26 Feb 2025 03:28:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2025/02/Netboot-Windows-11-with-iSCSI-and-iPXE/</guid><content type="html">&lt;figure>&lt;a href="https://terinstock.com/media/f9/fb032e2f72ad1df49f59f6bee29b35b7eabf8068eb9245bb0c950b9aac4ab8.png">&lt;img src="https://terinstock.com/media/f9/fb032e2f72ad1df49f59f6bee29b35b7eabf8068eb9245bb0c950b9aac4ab8.png"
alt="A fictious screenshot of a permanent ban from a game, in the Windows 95 installer style, with a 90s-era PC and a joystick in the left banner. The text is titled &amp;#34;Permanent Suspension&amp;#34; and reads &amp;#34;Your account has been permanently suspended due to the use of unauthorized Operating Systems or unauthorized virtual machines. This type of behavior causes damage to our community and the game&amp;#39;s competitive integrity. This action will not be reversed.&amp;#34;">&lt;/a>&lt;figcaption>
&lt;h4>Purposefully ambiguous and fictious permanent ban.&lt;/h4>&lt;p>(created with &lt;a href="https://digipres.club/@foone">@foone&lt;/a>&amp;rsquo;s &lt;a href="https://deathgenerator.com/">The Death Generator&lt;/a>)&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>My primary operating system is Linux: I have it installed on my laptop and desktop. Thanks to the amazing work of the &lt;a href="https://www.winehq.org/">WINE&lt;/a>, &lt;a href="https://www.codeweavers.com/">CodeWeavers&lt;/a>, and &lt;a href="https://www.valvesoftware.com/">Valve&lt;/a> developers, it&amp;rsquo;s also where I do PC gaming. I can spin up Windows in a virtual machine for the rare times I need to use it, and even pass through a GPU if I want to do gaming.&lt;/p>
&lt;p>There is one pretty big exception: playing the AAA game ████████████████ with friends. Unfortunately, the developer only allows Windows. If you attempt to run the game on Linux or they detect you&amp;rsquo;re running in a virtual machine, your device and account are permanently banned. I would prefer not to be permanently banned.&lt;/p>
&lt;p>For the past several years my desktop has also had a disk dedicated to maintaining a Windows install. I&amp;rsquo;d prefer to use the space in my PC case&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> for disks for Linux. Since I already run a home NAS, and my Windows usage is infrequent, I wondered if I could offload the Windows install to my NAS instead. This lead me down the course of netbooting Windows 11 and writing up these notes on how to do a simplified &amp;ldquo;modern&amp;rdquo; version.&lt;/p>
&lt;h1 id="ipxe-and-iscsi">iPXE and iSCSI&lt;/h1>
&lt;p>My first task was determining how to get a computer to boot from a NAS. My experience with network block devices is with Ceph RBD, where a device is mounted into an already running operating system. For booting over an Ethernet IP network the standard is iSCSI. A great way to boot from an iSCSI disk is with &lt;a href="https://ipxe.org">iPXE&lt;/a>. To avoid any mistakes during this process, I removed all local drives from the system.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;p>I didn&amp;rsquo;t want to run a TFTP server on my home network, or reconfigure DHCP to provide TFTP configuration. Even if I did, the firmware for my motherboard is designed for &amp;ldquo;&lt;span class="rgb">gamers&lt;/span>&amp;rdquo;, there&amp;rsquo;s no PXE ROM. I can enable UEFI networking and a network boot option appears in the boot menu, but no DHCP requests are made&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. Fortunately, iPXE is available as &lt;a href="https://boot.ipxe.org/ipxe.usb">bootable USB image&lt;/a>, which loaded and started trying to fetch configuration from the network.&lt;/p>
&lt;p>Hitting &lt;code>ctrl-b&lt;/code> as directed on screen to drop to the iPXE shell, I could verify basic functionality was working.&lt;/p>
&lt;pre tabindex="0">&lt;code>iPXE 1.21.1+ (e7585fe) -- Open Source Network Boot Firmware -- https://ipxe.org
Features: DNS FTP HTTP HTTPS iSCSI NFS TFTP VLAN SRP AoE EFI Menu
iPXE&amp;gt; dhcp
Configuring (net0 04:20:69:91:C8:DD)...... ok
iPXE&amp;gt; show ${net0/ip}
192.0.2.3
&lt;/code>&lt;/pre>&lt;p>I decided to use &lt;a href="https://github.com/fujita/tgt">tgt&lt;/a> as the iSCSI target daemon on my NAS&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup> as the configuration seemed the least complicated. In &lt;code>/etc/tgt/targets.conf&lt;/code> I configured it with two targets: one as the block device I wanted to install Windows onto and the other being the installation ISO.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-sgml" data-lang="sgml">&amp;lt;target iqn.2025-02.com.example:win-gaming&amp;gt;
backing-store /dev/zvol/zroot/sans/win-gaming
params thin-provisioning=1
&amp;lt;/target&amp;gt;
&amp;lt;target iqn.2025-02.com.example:win11.iso&amp;gt;
backing-store /opt/isos/Win11_24H2_English_x64.iso
device-type cd
readonly 1
&amp;lt;/target&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Back on the PC, I could tell iPXE to use these iSCSI disks, then boot onto the DVD. As multiple network drives are being added, each must be given a different drive ID starting from &lt;code>0x80&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code>iPXE&amp;gt; sanhook --drive 0x80 iscsi:nas.example.com:::1:iqn.2025-02.com.example:win-gaming
Registered SAN device 0x80
iPXE&amp;gt; sanhook --drive 0x81 iscsi:nas.example.com:::1:iqn.2025-02.com.example:win11.iso
Registered SAN device 0x81
iPXE&amp;gt; sanboot --drive 0x81
Booting from SAN device 0x81
&lt;/code>&lt;/pre>&lt;p>After a minute of the Windows 11 logo and a spinner, the Windows 11 setup appears. In an ideal situation, I could immediately start installing. Unfortunately, the Windows 11 DVD does not ship drivers for my network card, and the iSCSI connection information passed to the booted system from iPXE couldn&amp;rsquo;t be used. I&amp;rsquo;m a bit impressed the GUI loaded at all, instead of just crashing.&lt;/p>
&lt;h1 id="chainloading-winpe">Chainloading WinPE&lt;/h1>
&lt;p>To rectify this, I would need to build a &lt;a href="https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/winpe-intro">Windows PE&lt;/a> image that included my networking drivers. WinPE is the minimal environment used when installing Windows. Fortunately, Microsoft has made this pretty easy nowadays. I downloaded and installed the &lt;a href="https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install">Windows Assessment and Deployment Kit&lt;/a> and the Windows PE add-on. After running &amp;ldquo;Deployment and Imaging Tools Environment&amp;rdquo; as an administrator, I could make a folder containing a base WinPE image.&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; mkdir C:\winpe
&amp;gt; copype amd64 C:\winpe\amd64
&lt;/code>&lt;/pre>&lt;p>After mounting the image, I was able to slipstream the &lt;a href="https://www.intel.com/content/www/us/en/download/727998/intel-network-adapter-driver-for-microsoft-windows-11.html">Intel drivers&lt;/a>. I searched through the inf files to find the folder that supported my network card.&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; imagex /mountrw C:\winpe\amd64\media\sources\boot.wim C:\winpe\amd64\mount
&amp;gt; dism /image:C:\winpe\amd64\mount /add-driver /driver:C:\temp\intel\PRO1000\Winx64\W11\
&amp;gt; imagex /unmount /commit C:\winpe\amd64\mount
&lt;/code>&lt;/pre>&lt;p>This new image is what we need to boot into to install Windows. As my NAS is also running an HTTP server, I copied over the files relevant to netbooting: from &amp;ldquo;C:‍\winpe\amd64\media&amp;rdquo; I copied &amp;ldquo;boot/BCD&amp;rdquo;, &amp;ldquo;boot/boot.sdi&amp;rdquo;, and &amp;ldquo;sources/boot.wim&amp;rdquo;, preserving the folders. I also downloaded &lt;a href="https://github.com/ipxe/wimboot/releases">wimboot&lt;/a> to the same directory.&lt;/p>
&lt;p>You can use iPXE to execute a script fetched with HTTP, which I took advantage of to reduce the amount of typing I&amp;rsquo;ll need to do at the shell. I saved the following script as &amp;ldquo;install.ipxe&amp;rdquo; in the same HTTP directory.&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-ipxe" data-lang="ipxe">#!ipxe
sanhook --drive 0x80 iscsi:nas.example.com:::1:iqn.2025-02.com.example:win-gaming
sanhook --drive 0x81 iscsi:nas.example.com:::1:iqn.2025-02.com.example:win11.iso
kernel wimboot
initrd boot/BCD BCD
initrd boot/boot.sdi boot.sdi
initrd sources/boot.wim boot.wim
boot
&lt;/code>&lt;/pre>&lt;p>Rebooting back to the iPXE prompt I could then boot using this script.&lt;/p>
&lt;pre tabindex="0">&lt;code>iPXE&amp;gt; dhcp
iPXE&amp;gt; chain http://nas.example.com/ipxe/install.ipxe
&lt;/code>&lt;/pre>&lt;p>After a few seconds I was booted into WinPE with a Command Prompt. The command &amp;ldquo;wpeinit&amp;rdquo; ran automatically, configuring the network card and mounting the iSCSI disks. I found the DVD had been mounted as drive &amp;ldquo;D&amp;rdquo;, and could start the Windows Setup with &amp;ldquo;D:‍\setup.exe&amp;rdquo;.&lt;/p>
&lt;p>However, after reaching the &amp;ldquo;Searching for Disks&amp;rdquo; screen the installer closed itself without any error. This seems to be a bug with the new version of setup, as restarting it and selecting the &amp;ldquo;Previous Version of Setup&amp;rdquo; on an earlier page used a version of the installer that worked.&lt;/p>
&lt;p>The installation was spread across several restarts. Fortunately, once the installation files are copied over, nothing but the main disk image is required, reducing what I needed to type in the iPXE shell. The HTTP server could also be cleaned up at this point.&lt;/p>
&lt;pre tabindex="0">&lt;code>iPXE&amp;gt; dhcp
iPXE&amp;gt; sanboot iscsi:nas.example.com:::1:iqn.2025-02.com.example:win-gaming
&lt;/code>&lt;/pre>&lt;p>After several more minutes, and a forced installation of a Windows zero-day patch, I was greeted by a Windows 11 desktop, booted over iSCSI. Task Manager even reports the C drive as being &amp;ldquo;SDD (iSCSI)&amp;rdquo;.&lt;/p>
&lt;h1 id="automating-boots">Automating Boots&lt;/h1>
&lt;p>Booting from a USB stick and typing into an iPXE prompt every time I want to boot into Windows isn&amp;rsquo;t a great user experience. Fortunately, iPXE is also available as an &lt;a href="https://boot.ipxe.org/ipxe.efi">EFI application&lt;/a> which can be installed to the local EFI System Partition. I also discovered that iPXE will execute commands provided on the command line.&lt;/p>
&lt;p>I reinstalled the disks used for Linux, copied over ipxe.efi to the EFI System Partition, and added a new entry to systemd-boot by creating &amp;ldquo;$ESP/loader/entries/win11.conf&amp;rdquo;&lt;/p>
&lt;pre tabindex="0">&lt;code>title Windows 11 (iPXE)
efi /ipxe/ipxe.efi
options prompt &amp;amp;&amp;amp; dhcp &amp;amp;&amp;amp; sanboot iscsi:nas.example.com:::1:iqn.2025-02.com.example:win-gaming
&lt;/code>&lt;/pre>&lt;p>There seems to be a bug where the first word in the options field is ignored.&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup> I used a valid iPXE command &lt;code>prompt&lt;/code>, which also provides a clear signal should it ever start being interpreted in the future version.&lt;/p>
&lt;h1 id="lets-game">Let&amp;rsquo;s Game&lt;/h1>
&lt;p>After a little bit of extra setup (installing Firefox and switching to dark mode), I was able to install Steam and the game. The game took a little bit longer to install due the slower disk speed over my network (time to upgrade to 10GbE?), but there was no noticeable delay during normal gameplay. I didn&amp;rsquo;t see any network saturation or high disk latencies in Task Manager during loading.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>My computer is built into the &lt;a href="https://www.coolermaster.com/en-eu/products/masterbox-nr200p/">Cooler Master NR200P&lt;/a> mini-ITX case, which only has space for two 2.5&amp;quot; SATA drives.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>You probably want to remove the disks anyways so Windows doesn&amp;rsquo;t install its boot manager to an EFI System Partition on a local disk. To boot with iPXE later, you&amp;rsquo;ll want Windows to create an EFI System Partition on the iSCSI disk.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>The UEFI configuration doesn&amp;rsquo;t have any options for manually specifying networking parameters, either.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>Long time iSCSI on Linux users may find the choice of STGT peculiar, it having been replaced with &lt;a href="https://lwn.net/Articles/424004/">the kernel-space target LIO&lt;/a> as the iSCSI target daemon back in 2010. My limited usage at home isn&amp;rsquo;t going to run into performance issues with tgt, and I prefer being able to start and stop a user-space daemon.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>I&amp;rsquo;m not sure if this is a bug in systemd-boot or in iPXE.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Assembling a Game Boy Game with Meson</title><link>https://terinstock.com/post/2024/10/Assembling-a-Game-Boy-Game-with-Meson/</link><pubDate>Mon, 28 Oct 2024 02:31:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2024/10/Assembling-a-Game-Boy-Game-with-Meson/</guid><content type="html">&lt;p>I&amp;rsquo;ve been working on a Game Boy game off-and-on for the last few months (hopefully more details about this soon!). Up until recently I was building the game with GNU Make, but I was frustrated with configuring Make&amp;rsquo;s prerequisites for accurate dependency tracking. Often I&amp;rsquo;d change a file included by my target, but Make wasn&amp;rsquo;t rebuilding appropriately.&lt;/p>
&lt;p>I started looking at other build systems to solve this problem. I really liked how approachable &lt;a href="https://mesonbuild.com/">Meson&lt;/a>&amp;rsquo;s build rules where, and also that it supported projects composed of multiple languages. This is useful to me as parts of my toolchain for handling assets, and converting them to a format usable on the Game Boy, are written in higher level languages. If I change the code of part of that toolchain, the build system knows what assets need to processed and what parts of the game need to be relinked, automatically.&lt;/p>
&lt;p>There&amp;rsquo;s just one problem: Meson doesn&amp;rsquo;t support the Game Boy. However, that&amp;rsquo;s never stopped me before!&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/52/1726b132b398a4215c6b7a75f6d5e1226da4a3b7817414768800533764ada6.png">&lt;img src="https://terinstock.com/media/52/1726b132b398a4215c6b7a75f6d5e1226da4a3b7817414768800533764ada6.png">&lt;/a>&lt;figcaption>
&lt;h4>Technology is incredible!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I&amp;rsquo;ve created &lt;a href="https://github.com/terinjokes/meson/commit/722c207c31148c5ff7e6b382548c972d1ff1a3f4">a fork&lt;/a> of Meson that adds support for using &lt;a href="https://rgbds.gbdev.io/">RGBDS&lt;/a> to assemble and link Game Boy games. This was surprisingly easy to do, which is a testament for both the Meson and RGBDS projects. In Meson I added a new &amp;ldquo;language&amp;rdquo; &lt;code>rgbds&lt;/code> which sets up &lt;code>rgbasm&lt;/code> as the compiler/assembler and &lt;code>rgblink&lt;/code> as the linker. A ROM can be built just by calling the &lt;code>executable&lt;/code> function.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-meson" data-lang="meson">&lt;span style="display:flex;">&lt;span>project(&lt;span style="color:#87ceeb">&amp;#39;rgbds-test&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;rgbds&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>executable(&lt;span style="color:#87ceeb">&amp;#39;rgbdstest.gb&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;src/main.asm&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> include_directories: [&lt;span style="color:#87ceeb">&amp;#39;include&amp;#39;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> link_language: &lt;span style="color:#87ceeb">&amp;#39;rgbds&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then Meson can be initialized to assemble to the Game Boy&amp;rsquo;s CPU and the game built.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> You can notice that Meson configures &lt;code>rgbasm&lt;/code> to output dependency tracking metadata, which &lt;code>ninja&lt;/code> uses for fast rebuilds.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-console" data-lang="console">&lt;span style="display:flex;">&lt;span>$ ../../../meson.py setup --cross-file crossfile.ini --wipe build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>The Meson build system
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Version: 1.6.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Source dir: /home/terin/Development/github.com/mesonbuild/meson/test cases/rgbds/1 basic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Build dir: /home/terin/Development/github.com/mesonbuild/meson/test cases/rgbds/1 basic/build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Build type: cross build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Project name: rgbds-test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Project version: undefined
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Rgbds compiler for the host machine: rgbasm (rgbds 0.8.0)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Rgbds linker for the host machine: rgblink rgblink 0.8.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Compiler for language rgbds for the build machine not found.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Build machine cpu family: x86_64
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Build machine cpu: x86_64
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Host machine cpu family: sm83
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Host machine cpu: sm8320
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Target machine cpu family: sm83
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Target machine cpu: sm8320
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Build targets in project: 1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rgbds-test undefined
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User defined options
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Cross files: crossfile.ini
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Found ninja-1.12.1 at /usr/bin/ninja
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ ninja -C build -v
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ninja: Entering directory `build&amp;#39;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[1/2] rgbasm -I../include -I.. -I. -Irgbdstest.gb.p -M rgbdstest.gb.p/src_main.asm.o.d -MQ rgbdstest.gb.p/src_main.asm.o -o rgbdstest.gb.p/src_main.asm.o ../src/main.asm
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[2/2] rgblink -o rgbdstest.gb rgbdstest.gb.p/src_main.asm.o
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ ninja -C build -t deps
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rgbdstest.gb.p/src_main.asm.o: #deps 2, deps mtime 1730079958938870943 (VALID)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ../src/main.asm
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ../include/hardware.inc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Game Boy ROM files have a header that needs to be set correctly so real hardware will play the cartridge, and emulators know what cartridge type to emulate. Since this header contains two checksums, usually it&amp;rsquo;s configured using the &lt;code>rgbfix&lt;/code> tool rather than being hardcoded in assembly. Unfortunately, I haven&amp;rsquo;t figured out a good way for &lt;code>executable&lt;/code> to automatically call this tool with the correct parameters after the rom is linked, but the tool can be invoked as a &lt;code>custom_target&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-meson" data-lang="meson">&lt;span style="display:flex;">&lt;span>project(&lt;span style="color:#87ceeb">&amp;#39;rgbds-test&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;rgbds&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rgbfix = find_program(&lt;span style="color:#87ceeb">&amp;#39;rgbfix&amp;#39;&lt;/span>, required: &lt;span style="color:#f00">true&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rom = executable(&lt;span style="color:#87ceeb">&amp;#39;rgbdstest&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;src/main.asm&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> include_directories: [&lt;span style="color:#87ceeb">&amp;#39;include&amp;#39;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> link_language: &lt;span style="color:#87ceeb">&amp;#39;rgbds&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>custom_target(output: &lt;span style="color:#87ceeb">&amp;#39;rgbdstest.gb&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input: rom,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rgbfix,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;--title&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;EXAMPLE&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;--old-licensee&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;0x33&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;--mbc-type&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;ROM&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;--rom-version&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;--non-japanese&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;--validate&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;-&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> build_by_default: &lt;span style="color:#f00">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> capture: &lt;span style="color:#f00">true&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> feed: &lt;span style="color:#f00">true&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This example also showcases using &lt;code>custom_target&lt;/code> to interact with tools that need to be &amp;ldquo;feed&amp;rdquo; via stdin and where the results need to be &amp;ldquo;captured&amp;rdquo; on stdout, which is convenient as otherwise &lt;code>rgbfix&lt;/code> edits the file in-place, which could cause issues later.&lt;/p>
&lt;p>Since I&amp;rsquo;m not ready to release my game just yet, I&amp;rsquo;ve &lt;a href="https://github.com/terinjokes/pokered/commit/3a2da2b7a9c738f3dfca144f2d12c1a9bcd1abba">modified pokered&lt;/a> to use my fork of Meson. This builds the two image converters written in C, converts all the images to a format suitable for the Game Boy, and assembles and links with RGBDS. The resulting game matches the expected checksum.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-console" data-lang="console">&lt;span style="display:flex;">&lt;span>$ ninja -C build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ninja: Entering directory `build&amp;#39;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[1374/1374] Generating pokered.gbc with a custom command (wrapped by meson to capture output, to feed input)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ shasum -c --ignore-missing ../roms.sha1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pokered.gbc: OK
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ jollygood -c sameboy build/pokered.gbc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>i: Core: sameboy (SameBoy 0.16.5)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>i: Video: OpenGL OpenGL ES 3.2 Mesa 24.1.7
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>i: Audio: 48000Hz Stereo, Speex 3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>i: Emulated Input 1: gameboy1, Game Boy, 0 axes, 10 buttons
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;figure>&lt;a href="https://terinstock.com/media/6e/e4eccf81b849f74b73eaf137948fa65269363a98bcb51d41ee1828c45b6ea45.png">&lt;img src="https://terinstock.com/media/6e/e4eccf81b849f74b73eaf137948fa65269363a98bcb51d41ee1828c45b6ea45.png">&lt;/a>&lt;figcaption>
&lt;h4>Pokémon Red&amp;#39;s start screen&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I&amp;rsquo;ll be submitting these changes to the upstream Meson project, in the off chance they&amp;rsquo;re fans of the Game Boy.&lt;/p>
&lt;hr>
&lt;p>&lt;em>Edit 29 Oct:&lt;/em> I&amp;rsquo;ve &lt;a href="https://github.com/mesonbuild/meson/pull/13831">opened a PR&lt;/a> to submit my changes to upstream Meson.&lt;/p>
&lt;p>Meson has a concept of module, which are in-tree extensions to the core language to help handle common build tasks with large libraries, such as &lt;a href="https://mesonbuild.com/Qt6-module.html#compile_moc">compiling moc files&lt;/a> in Qt projects. I&amp;rsquo;ve created a &amp;ldquo;rgbds&amp;rdquo; module which has a function run &lt;code>rgbfix&lt;/code> to patch the ROM header, instead of needing to implement the above &lt;code>custom_target&lt;/code> yourself.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-meson" data-lang="meson">&lt;span style="display:flex;">&lt;span>rgbds = import(&lt;span style="color:#87ceeb">&amp;#39;rgbds&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rgbds.fix(&lt;span style="color:#87ceeb">&amp;#39;rgbdstest.gb&amp;#39;&lt;/span>, rom,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title: &lt;span style="color:#87ceeb">&amp;#39;EXAMPLE&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mbc_type: &lt;span style="color:#87ceeb">&amp;#39;ROM&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fix_spec: &lt;span style="color:#87ceeb">&amp;#39;lhg&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This module also adds a function with a barebones implementation of &lt;code>rgbgfx&lt;/code>, the graphics converter from the RGBDS project. This was previously implemented as a &lt;code>custom_target&lt;/code> in the pokered fork above.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-meson" data-lang="meson">&lt;span style="display:flex;">&lt;span>pngs = [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;bug&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;plant&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;snake&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;quadruped&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">foreach&lt;/span> f : pngs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gen = custom_target(output: &lt;span style="color:#87ceeb">&amp;#39;@0@_conv.png&amp;#39;&lt;/span>.format(f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input: &lt;span style="color:#87ceeb">&amp;#39;@0@.png&amp;#39;&lt;/span>.format(f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command: [rgbgfx, &lt;span style="color:#87ceeb">&amp;#39;-o&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;@OUTPUT@&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;@INPUT@&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gfx += custom_target(output: &lt;span style="color:#87ceeb">&amp;#39;@0@.2bpp&amp;#39;&lt;/span>.format(f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input: gen,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command: [tools_gfx, &lt;span style="color:#87ceeb">&amp;#39;-o&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;@OUTPUT@&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;@INPUT@&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">endforeach&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The inner for loop can now use the rgbds module.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-meson" data-lang="meson">&lt;span style="display:flex;">&lt;span>pngs = [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;bug&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;plant&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;snake&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#39;quadruped&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">foreach&lt;/span> f : pngs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gen = rgbds.gfx(&lt;span style="color:#87ceeb">&amp;#39;@0@_conv.2bpp&amp;#39;&lt;/span>.format(f), &lt;span style="color:#87ceeb">&amp;#39;@0@.png&amp;#39;&lt;/span>.format(f))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gfx += custom_target(output: &lt;span style="color:#87ceeb">&amp;#39;@0@.2bpp&amp;#39;&lt;/span>.format(f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> input: gen,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> command: [tools_gfx, &lt;span style="color:#87ceeb">&amp;#39;-o&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;@OUTPUT@&amp;#39;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#39;@INPUT@&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">endforeach&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;a href="https://github.com/terinjokes/pokered/tree/meson">meson branch&lt;/a> of my pokered fork has been updated with these changes.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>While this is the technically correct thing to do, it seems a bit much here as the only system RGBDS can compile to is the Sharp SM8320.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Adding support for compiling Pokémon Blue, as well as the debug and Virtual Console patches, have been left as an exercise for the reader.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Portage + ORAS: Using a Docker Registry for Gentoo Packages</title><link>https://terinstock.com/post/2024/09/Portage--ORAS-Using-a-Docker-Registry-for-Gentoo-Packages/</link><pubDate>Wed, 04 Sep 2024 22:19:27 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2024/09/Portage--ORAS-Using-a-Docker-Registry-for-Gentoo-Packages/</guid><content type="html">&lt;figure>&lt;a href="https://terinstock.com/media/4e/88d3656decbef9ec7c11b5ecc26f10fa7484f2311ac50c168fe53488778320.jpg">&lt;img src="https://terinstock.com/media/4e/88d3656decbef9ec7c11b5ecc26f10fa7484f2311ac50c168fe53488778320:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>You didn&amp;#39;t think I had a Type R or &amp;#39;86 did you?&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;a href="https://wiki.gentoo.org/wiki/Project:Portage">Portage&lt;/a> is Gentoo&amp;rsquo;s source-based package manager, commonly invoked with the &lt;code>emerge&lt;/code> tool. The &lt;a href="https://wiki.gentoo.org/wiki/Project:Prefix">Prefix&lt;/a> project allows it to be used by users of other distributions and Unix-like operating systems in a &amp;ldquo;virtualenv&amp;rdquo;-type fashion. In Portage, packages are described in ebuilds, which are composed of shell functions understandable by many Linux users. This makes it a good fit for sharing packages across distributions and users working on a project.&lt;/p>
&lt;p>To compile packages from source, Portage requires being able to fetch archives of the software, and requires those archives to have stable hashes. This works fine for public source code: projects often provide archives for releases, and the Gentoo project can mirror archives generated automatically by public forges. This requirement poses problems for private packages, however, as the archives generated by forges are not guaranteed to be public.&lt;/p>
&lt;p>After being stuck manually copying archives between machines for a year, I finally decided to tackle this problem this past weekend. I could mirror the archives to a web server, like the Gentoo project does. However, there was no web server already available, and I was not interested in setting up infrastructure just for Portage, sorting out the permissions to allow multiple people to upload archives, or managing backups of the archives.&lt;/p>
&lt;p>There was one bit of infrastructure that was already setup with these attributes: a Docker (or OCI) registry. While we tend to think of these registries as a place to distribute container images and their layers, and they are, beneath the surface they are content-addressable stores that can be used to distribute other types artifacts too. The &lt;a href="https://notaryproject.dev/">Notary&lt;/a> and &lt;a href="https://github.com/sigstore/cosign">cosign&lt;/a> projects distribute signatures of containers by storing them in the registry, and &lt;a href="https://helm.sh/docs/topics/registries/">Helm charts&lt;/a> can be distributed via registries as well.&lt;/p>
&lt;p>Could I use a registry as a source archive mirror? Yes! The &lt;a href="https://oras.land/">ORAS&lt;/a>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> project provides a CLI, and libraries, for uploading and downloading artifacts to these registries. For example to upload and download the file &amp;ldquo;hello-world.txt&amp;rdquo;, we can can use the &lt;code>oras&lt;/code> command and &amp;ldquo;push&amp;rdquo; and &amp;ldquo;pull&amp;rdquo; with an image tag, very similar to using &lt;code>docker&lt;/code> or &lt;code>podman&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>oras push example.com/hello:v1 hello-world.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>oras pull example.com/hello:v1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Using the &lt;a href="https://git.terinstock.com/ebuilds.git/tree/app-shells/direnv/direnv-2.34.0.ebuild?id=ddc3c14404b016a93946204f3e54e1ad98ad7c97">ebuild for direnv v2.34.0&lt;/a> for example, there are two source archives: one is an archive of the source code, and the other is an archive of the Go module dependencies, following the current &lt;a href="https://wiki.gentoo.org/wiki/Writing_go_Ebuilds">recommendations&lt;/a> from the Gentoo Wiki. After we have download and generated these two archives, we can upload them to a registry with a variation on the above &amp;ldquo;push&amp;rdquo; command.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>oras push --artifact-type application/vnd.example.archives.v1 &lt;span style="color:#87ceeb">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#87ceeb">&lt;/span> example.com/distfiles/direnv:2.34.0 &lt;span style="color:#87ceeb">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#87ceeb">&lt;/span> direnv-2.34.0.tar.gz:application/x-compressed-tar &lt;span style="color:#87ceeb">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#87ceeb">&lt;/span> direnv-2.34.0-vendor.tar.xz:application/x-compressed-tar
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This creates one tag, &amp;ldquo;example.com/distfiles/direnv:2.34.0&amp;rdquo;, with two layers for the two archives we uploaded. We can later pull these archives by pulling the tag.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>oras pull example.com/distfiles/direnv:2.34.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At this point you might be thinking to yourself how this is helpful for ebuilds? As each tarball is now in the content-addressable store, we can use this location in the ebuild. To get those locations we can fetch the manifest at the tag.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-console" data-lang="console">&lt;span style="display:flex;">&lt;span>$ oras manifest fetch example.com/distfiles/direnv:2.34.0 --pretty
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;schemaVersion&amp;#34;: &lt;span style="color:#f60">2&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;mediaType&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;application/vnd.oci.image.manifest.v1+json&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;artifactType&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;application/vnd.example.archives.v1&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;config&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;mediaType&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;application/vnd.oci.empty.v1+json&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;digest&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;size&amp;#34;: &lt;span style="color:#f60">2&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;data&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;e30=&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;layers&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;mediaType&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;application/x-compressed-tar&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;digest&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;sha256:3d7067e71500e95d69eac86a271a6b6fc3f2f2817ba0e9a589524bf3e73e007c&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;size&amp;#34;: &lt;span style="color:#f60">94449&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;annotations&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;org.opencontainers.image.title&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;direnv-2.34.0.tar.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;mediaType&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;application/x-compressed-tar&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;digest&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;sha256:b86942d442f7e2a92a86dcad9b36d9316948e990592f65314137235df6c43293&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;size&amp;#34;: &lt;span style="color:#f60">327916&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;annotations&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;org.opencontainers.image.title&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;direnv-2.34.0-vendor.tar.xz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;annotations&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;org.opencontainers.image.created&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;2024-09-04T21:08:22Z&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Each source artifact was uploaded as a layer, and can be identified with the &amp;ldquo;org.opencontainers.image.title&amp;rdquo; annotation automatically added by ORAS during our push. We can then use the digest as part of the URL in the ebuild.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ebuild" data-lang="ebuild">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eedd82">SRC_URI&lt;/span>=&lt;span style="color:#87ceeb">&amp;#34;https://example.com/v2/distfiles/direnv/blobs/sha256:3d7067e71500e95d69eac86a271a6b6fc3f2f2817ba0e9a589524bf3e73e007c -&amp;gt; &lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">P&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">.tar.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eedd82">SRC_URI&lt;/span>+=&lt;span style="color:#87ceeb">&amp;#34; https://example.com/v2/distfiles/direnv/blobs/sha256:b86942d442f7e2a92a86dcad9b36d9316948e990592f65314137235df6c43293 -&amp;gt; &lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">P&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">-vendor.tar.xz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>While this would work, it&amp;rsquo;s not very convenient to update for new versions. We can do better by leveraging Portage&amp;rsquo;s support for customizing fetch commands with &lt;a href="https://wiki.gentoo.org/wiki/FETCHCOMMAND">FETCHCOMMAND&lt;/a>. Portage ships with commands for http(s), FTP, SSH, SFTP, and rsync.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-console" data-lang="console">&lt;span style="display:flex;">&lt;span>$ portageq envvar FETCHCOMMAND_RSYNC
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rsync -LtvP &amp;#34;${URI}&amp;#34; &amp;#34;${DISTDIR}/${FILE}&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By following the format of &lt;code>FETCHCOMMAND_${protocol}&lt;/code>, we can add support for our own protocols by setting a command in Portage&amp;rsquo;s configuration.&lt;/p>
&lt;p>I&amp;rsquo;ve a new fetch command &lt;code>FETCHCOMMAND_ORAS&lt;/code> defining a custom &amp;ldquo;oras&amp;rdquo; protocol. This uses the ORAS CLI to fetch the manifest referenced by a tag, selects the digest of the requested file, and downloads it to the prescribed location.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eedd82">FETCHCOMMAND_ORAS&lt;/span>=&lt;span style="color:#87ceeb">&amp;#34;bash -c \&amp;#34;x=\\\${0#oras://}; image=\\\${x%/*}; blob=\\\${x##*/}; oras blob fetch \\\$image@\\\$(oras manifest fetch \\\$image | jq -r --arg blob \\\$blob &amp;#39;.layers|map(select(.annotations[\\\&amp;#34;org.opencontainers.image.title\\\&amp;#34;]==\\\$blob))[0].digest&amp;#39;) -o \\\$1 \&amp;#34; \${URI} \${DISTDIR}/\${FILE}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we can reference this new protocol in our ebuild, reducing the amount of toil needed to update or add a new package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eedd82">SRC_URI&lt;/span>=&lt;span style="color:#87ceeb">&amp;#34;oras://example.com/distfiles/&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">PN&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">:&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">PV&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">P&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">.tar.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eedd82">SRC_URI&lt;/span>+=&lt;span style="color:#87ceeb">&amp;#34; oras://example.com/distfiles/&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">PN&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">:&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">PV&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>&lt;span style="color:#eedd82">P&lt;/span>&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">-vendor.tar.xz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As an end user, once configured fetching artifacts with ORAS is then transparently used by Portage.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-console" data-lang="console">&lt;span style="display:flex;">&lt;span>$ emerge -1v apps-shells/direnv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt;&amp;gt;&amp;gt; Emerging (&lt;span style="color:#f60">1&lt;/span> of 1) app-shells/direnv-2.34.0::terinjokes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt;&amp;gt;&amp;gt; Downloading &lt;span style="color:#87ceeb">&amp;#39;oras://example.com/distfiles/direnv:2.34.0/direnv-2.34.0.tar.gz&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>✓ Downloaded application/octet-stream 92.2/92.2 kB 100.00% 0s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ sha256:3d7067e71500e95d69eac86a271a6b6fc3f2f2817ba0e9a589524bf3e73e007c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> * direnv-2.34.0.tar.gz BLAKE2B SHA512 size ;-) ... [ ok ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt;&amp;gt;&amp;gt; Downloading &lt;span style="color:#87ceeb">&amp;#39;oras://example.com/distfiles/direnv:2.34.0/direnv-2.34.0-vendor.tar.xz&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>✓ Downloaded application/octet-stream 320/320 kB 100.00% 0s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └─ sha256:b86942d442f7e2a92a86dcad9b36d9316948e990592f65314137235df6c43293
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> * direnv-2.34.0-vendor.tar.xz BLAKE2B SHA512 size ;-) ... [ ok ]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>OCI Registry As Storage&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Hopefully in the future &lt;code>oras pull&lt;/code> will support selecting specific file names, avoiding the subshell to fetch the manifest and parse with jq.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>When Serial Isn't RS-232, and Geocaching with the Garmin GPS 95</title><link>https://terinstock.com/post/2024/08/When-Serial-Isnt-RS-232-and-Geocaching-with-the-Garmin-GPS-95/</link><pubDate>Sun, 11 Aug 2024 18:35:11 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2024/08/When-Serial-Isnt-RS-232-and-Geocaching-with-the-Garmin-GPS-95/</guid><content type="html">&lt;figure>&lt;a href="https://terinstock.com/media/76/bb71a42372de077a408a1f4ba6972a6870afe9fca186e9a748e730b1da560f.jpg">&lt;img src="https://terinstock.com/media/76/bb71a42372de077a408a1f4ba6972a6870afe9fca186e9a748e730b1da560f:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>Found the first Dutch Geocache, Amsterdam Urban 1.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Recently I picked up a box of early 1990s Garmin GPS receivers along with an
array of accessories. I cleaned up one receiver, a GPS 95, installed 4 new AA
batteries, and positioned it with a clear view of the sky. After an agonizing
long time searching for satellites it eventually received a full almanac and
made a GPS lock. It was pretty cool that it still works!&lt;/p>
&lt;p>However there were two problems discovered while using the receiver. First,
while the longitude and latitude were accurate, the altitude was completely
wrong. Second, the receiver still thought it was in the 1990s. While the first
problem is inherent to GPS, we can fix the second issue!&lt;/p>
&lt;p>The receiver has no control for changing the date or time: it relies almost
entirely on the GPS signal. Surely, the date being sent by GPS wasn&amp;rsquo;t wrong. The
&lt;a href="https://gpsjam.org/?lat=48.11278&amp;amp;lon=4.65724&amp;amp;z=3.8&amp;amp;date=2024-08-08">GPSJAM&lt;/a> map did not show any interference anywhere near the Netherlands.&lt;/p>
&lt;figure>&lt;a href="https://gpsjam.org/?lat=48.11278&amp;amp;lon=4.65724&amp;amp;z=3.8&amp;amp;date=2024-08-08">&lt;img src="https://terinstock.com/media/b0/e6c1bd68a2caf3c05b0b90d2bf16217afb647275606757b9c8a929cbd10da6.png">&lt;/a>&lt;figcaption>
&lt;h4>No GPS jamming detected in the Netherlands.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>The &amp;ldquo;legacy&amp;rdquo; LNAV signal transmitted by GPS doesn&amp;rsquo;t actually contain the date.
That is instead computed by the receiver based on two other signals transmitted:
a 10-bit week counter that is incremented once a week, and a 19-bit time of week
signal incremented every 1.5 seconds. This week counter rolls over every 1024
weeks (or approximately 19.6 years).&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> So a GPS receiver requires some
other way to know which week counter epoch to use.&lt;/p>
&lt;p>The first GPS rollover occurred at midnight between 21-22 August 1999. This was
at the tail end of support for the GPS 95 receivers. Fortunately, Garmin
released a tool at the time &amp;ldquo;GPS EOW&amp;rdquo;&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup> that existed entirely to adjust the
time of the reciver&amp;rsquo;s clock. This would allow the built-in logic to track the
date afterwards. The GPS 95 has now joined us in the 21st century.&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/8e/5399a7f72c37eb9678edca573b1fd67c4640c023f4fb513d524c9ea1f3cd15.png">&lt;img src="https://terinstock.com/media/8e/5399a7f72c37eb9678edca573b1fd67c4640c023f4fb513d524c9ea1f3cd15:800.png">&lt;/a>&lt;figcaption>
&lt;h4>GPS EOW, a very minimalist application for Windows 98.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I started my Windows 98SE virtual machine&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup> and attached &lt;code>/dev/ttyUSB0&lt;/code> as &lt;code>COM1&lt;/code>
and started &amp;ldquo;GPS EOW&amp;rdquo;. Except it complained that it couldn&amp;rsquo;t communicate with
the receiver.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/eb/8a5bd61d49f383e6418d71a2f3e88034fbfdc9cc59e52d1c973bab6c25ade7.png">&lt;img src="https://terinstock.com/media/eb/8a5bd61d49f383e6418d71a2f3e88034fbfdc9cc59e52d1c973bab6c25ade7:800.png">&lt;/a>&lt;figcaption>
&lt;h4>GPS EOW was unable to connect to the GPS 95.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>After some fiddling, I switched to a different USB adapter and that worked
successfully.&lt;/p>
&lt;p>Later, I wanted to try out &lt;a href="https://github.com/ezod/flipperzero-gps">GPS for Flipper Zero&lt;/a>, an application for the
Flipper Zero that decodes NMEA 0183 messages, with the GPS 95. I connected the
serial connection from the GPS receiver to the Flipper Zero as described, but
the application never saw any data.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/8e/a0b24c745855fa742127c21475a47a1712724a63b79c91794be33691ddc960.png">&lt;figcaption>
&lt;h4>GPS for Flipper Zero reporting receiving no fix.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Since I knew NMEA 0183 messages are ASCII, I switched to the &lt;a href="https://github.com/cool4uma/UART_Terminal">UART Terminal&lt;/a>
application. Instead of the expected NMEA, I saw complete gibberish.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/54/90d65f44728ae9331f37243dbee565d7b27c5ab4804b931c503b3f43161a70.png">&lt;figcaption>
&lt;h4>The UART Terminal application displaying garbage.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Those that have been around serial for a while are undoubtedly rushing to a
comment section to complain about the bone-headed move I just described. I may
have just destroyed the pins on my Flipper Zero. As we&amp;rsquo;ll see in a bit, I lucked
out and did not.&lt;/p>
&lt;p>The serial port on the PC platform uses RS-232, a loose standard going back to
the 1960s describing the electrical and timing of the signals, but not the
encoding of data. This is a bipolar signal ±25V where the data signal are
&amp;ldquo;inverted&amp;rdquo;, that is a signal ≤-3V is a logical &amp;ldquo;1&amp;rdquo; and a signal ≥3V is a logical
&amp;ldquo;0&amp;rdquo;. The range between -3V and 3V is undefined. The pins of a Flipper Zero are
at 3.3V, but is tolerant of signals up to 5V. If the GPS 95 was indeed sending
RS-232 levels that could have gone very wrong.&lt;/p>
&lt;p>To better understand what was going on, I used the oscilloscope at the &lt;a href="https://techinc.nl/">Technologia Incognita&lt;/a> hackerspace. Fortunately, the GPS 95&amp;rsquo;s serial was only going up to 5V, but also not using negative voltages at all. This reminded me of &amp;ldquo;TTL serial&amp;rdquo;, the type of serial we use with microcontrollers and what the original USB serial adapter and the Flipper Zero expect. However, like the UART Terminal app, the UART decoder on the oscilloscope was also having difficulties decoding something useful from what should be NMEA.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/6a/9d1f7bc0926653b4f1216ac01179165af246b704827860c6e44b2e99b4bc53.jpg">&lt;img src="https://terinstock.com/media/6a/9d1f7bc0926653b4f1216ac01179165af246b704827860c6e44b2e99b4bc53:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>The Garmin GPS 95&amp;#39;s serial data displayed on the Hantek DSO2D15 oscilloscope. Notice how it starts to decode randomly in the middle of the data; it has interpreted something else as the start bit.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Besides the voltages used, there is another major difference between RS-232 and
&amp;ldquo;TTL serial&amp;rdquo;. The latter signal is not inverted. Instead a zero level is a
logical &amp;ldquo;0&amp;rdquo;, which a high level is a logical &amp;ldquo;1&amp;rdquo;.&lt;/p>
&lt;p>If you search online these are the two types of serial you&amp;rsquo;ll see described
again and again. You might find the occasional reference to a third type that
was popular in the early 1990s. This serial type operates between 0-5V like &amp;ldquo;TTL
serial&amp;rdquo;, but with inverted data lines like RS-232, allowing it to be connected
directly to a PC&amp;rsquo;s serial port.&lt;sup id="fnref:6">&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref">6&lt;/a>&lt;/sup>&lt;/p>
&lt;p>While I&amp;rsquo;m not aware of a name used at the time for this type of serial, this is
the type of serial used by the GPS 95. It is my understanding that portable
device manufacturers did not want to add an extra voltage rail just for the
serial port, but RS-232 drivers with integrated charge pumps weren&amp;rsquo;t yet cheap
enough.&lt;/p>
&lt;p>To be able to receive the NMEA data on my Flipper Zero I&amp;rsquo;d need to convert this
to &amp;ldquo;TTL serial&amp;rdquo;; that is invert the signal again so that a zero voltage is a
logical &amp;ldquo;0&amp;rdquo; and a positive voltage is a logical &amp;ldquo;1&amp;rdquo;. I choose to use an
&lt;a href="https://www.ti.com/product/SN74HC04">SN74HC04N&lt;/a> inverter, a &amp;ldquo;jelly bean&amp;rdquo; part I already had on my bench from another
project.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/8b/65caffbd262775a10597b016c60605c397a3bea785a024c94814bdfe7daa3b.jpg">&lt;img src="https://terinstock.com/media/8b/65caffbd262775a10597b016c60605c397a3bea785a024c94814bdfe7daa3b:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>Flipper Zero connected to the Garmin GPS 95 through the inverter chip.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>With the circuit put together on a prototyping board, both the UART Terminal and
GPS for Flipper Zero applications worked correctly! Now I can connect the GPS 95
to any device expecting &amp;ldquo;TTL serial&amp;rdquo; without problems, which opens up some cool
ideas for future projects.&lt;/p>
&lt;p>&lt;figure>&lt;img src="https://terinstock.com/media/73/4570748a532dc30d6d46e39b2540b99ee55029aa99b2296d7b791686e1953c.png">&lt;figcaption>
&lt;h4>The UART Terminal application with NMEA sentences.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/88/7d75fe32538ed79ada5432974b4d45f53fc82c6a6df938f5f6e857f1c6cb4c.png">&lt;figcaption>
&lt;h4>GPS for Flipper Zero is able to decode the data too!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;hr>
&lt;p>Today I set out to find the oldest hidden Geocache in the Netherlands,
&lt;a href="https://coord.info/GC198">Amsterdam Urban 1&lt;/a>. While the Garmin GPS 95 is primarily for aviation,
complete with a database of airports, navigation waypoints, and frequencies last
updated in 1999, adding custom waypoints is well supported.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/cf/3a5340eec5ac9c617ba4d69dafffa05ee90098bb04355e30317fbce0c543c9.jpg">&lt;img src="https://terinstock.com/media/cf/3a5340eec5ac9c617ba4d69dafffa05ee90098bb04355e30317fbce0c543c9:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>The GPS 95 navigating me across Vondelpark to the cache. Just about a quarter of a kilometer to go!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>As the image at the top of this post shows, I was able to find the cache. Since
user waypoints can be synchronized with a PC, it would be interesting in the
future to write a program that can push a list of geocaches to the Garmin
GPS 95. I&amp;rsquo;ll be typing them in manually in the meantime!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>The week counter was expanded to 13-bits when using the newer CNAV signal, rolling over every 157 years.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/GPS_signals#Time">GPS signals#Time&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>The most common version seems to be v1.20 (&lt;a href="https://share.terinstock.com/gpseow.zip">local archive&lt;/a>) (&lt;a href="https://archive.org/details/GPSEOW">Internet Archive&lt;/a>)&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>I&amp;rsquo;ve noticed the altitude is more accurate after the date was corrected, but I&amp;rsquo;m not sure why.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>The application should also work under WINE.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:6">
&lt;p>While undefined in the RS-232 standard, most recievers on the PC platform considered zero voltage to be a logical &amp;ldquo;1&amp;rdquo;, allowing this to work.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>A modern, universal, Dreamcast power supply</title><link>https://terinstock.com/post/2024/06/A-modern-universal-Dreamcast-power-supply/</link><pubDate>Sun, 09 Jun 2024 16:46:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2024/06/A-modern-universal-Dreamcast-power-supply/</guid><content type="html">&lt;p>I have fond memories of playing the Dreamcast growing up, of many hours trying
to figure out &lt;em>Ecco the Dolphin&lt;/em> in the days before ubiquitous home internet. A
few years ago I picked up a Dreamcast and installed an optical disc emulator
(ODE), and had a good time revisiting old games from my childhood. As a huge fan
of &lt;em>Jet Set Radio Future&lt;/em> on the original Xbox, it was fun to play &lt;em>Jet Grind
Radio&lt;/em> for the first time.&lt;/p>
&lt;p>When I moved to the Netherlands I shipped over my consoles, including the
Dreamcast. Unlike most of my other consoles, the Dreamcast used an internal
power supply that was region-specific. As I had a North American console, it was
expecting 120V while the power system in the Netherlands is 240V.&lt;/p>
&lt;p>For convenience I didn’t want the bulk of a step-down converter whenever and
wherever I wanted to play Dreamcast games. So I set out to replace the power
supply instead.&lt;/p>
&lt;h1 id="replacement-power-supplies">Replacement Power Supplies&lt;/h1>
&lt;p>At first I looked at replacing the power supply with a SEGA OEM power supply for
the European region. Unfortunately, I was unable to find a power supply on its
own on eBay or the Dutch auction website Marktplaats, nor a “for parts”
Dreamcast. Most of my options were system bundles, which made this approach
uneconomical since I’d still need to replace the capacitors on the power supply.&lt;/p>
&lt;p>I also looked at the &lt;a href="https://github.com/chriz2600/PicoDreamcast">PicoDreamcast&lt;/a>, which uses the PicoPSU and an external 12V
DC power brick. However, I wanted to avoid the extra bulk of an external power
brick. This also ruled out the similar &lt;a href="https://handheldlegend.com/en-nl/products/dreampsu-power-supply-for-sega-dreamcast">DreamPSU&lt;/a>.&lt;/p>
&lt;h1 id="mean-well-rpt-6003">Mean Well RPT-6003&lt;/h1>
&lt;p>Discouraged by my options, I happened across 3DprintRC’s &lt;a href="https://old.reddit.com/r/dreamcast/comments/fq0kax/mean_well_internal_dreamcast_universal_power/">/r/Dreamcast
post&lt;/a> discussing the near perfect fit of Mean Well’s RPT-6003 power
supply in the Dreamcast. This is a modern universal power supply designed by a
company well-known for making high quality power supplies, and it provides all
the voltages needed by the Dreamcast.&lt;/p>
&lt;p>3DprintRC later posted a &lt;a href="https://old.reddit.com/r/dreamcast/comments/jcku13/clean_and_powerful_psu_for_the_dreamcast_by/">follow-up&lt;/a> showing the RPT-6003 being
used by cutting up the original power supply. Unfortunately, my Dreamcast had a
different revision of the power supply which did not allow for such a clean cut.&lt;/p>
&lt;h1 id="dreamcast-rpt-6003">Dreamcast RPT-6003&lt;/h1>
&lt;figure>&lt;a href="https://terinstock.com/media/0d/7fd0804dd5d48764ac66e9a598c5601680b396d9ed50cbdfb4bbb37da4de47.jpg">&lt;img src="https://terinstock.com/media/0d/7fd0804dd5d48764ac66e9a598c5601680b396d9ed50cbdfb4bbb37da4de47:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>A populated Dreamcast RPT board.&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;a href="https://terinstock.com/media/80/b96f087b73c15b78d58aee4b8428a0fb65ac2ab5dd236bfcc5175db4e60836.jpg">&lt;img src="https://terinstock.com/media/80/b96f087b73c15b78d58aee4b8428a0fb65ac2ab5dd236bfcc5175db4e60836:800.jpg">&lt;/a>&lt;figcaption>
&lt;h4>Dreamcast RPT installed alongside the RPT-6003 in a Dreamcast&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I designed the &lt;a href="https://github.com/terinjokes/dreamcast-rpt">Dreamcast RPT&lt;/a>, a small board that interfaces the RPT-6003 with
the Dreamcast’s AC connector and power button. In my design I focused on being
able to reuse many components already found on the OEM power supply, as well as
documenting widely available modern alternatives.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;p>I had boards manufactured here in Europe with &lt;a href="https://aisler.net">AISLER&lt;/a> and they arrived a few
days later, looking great! It slotted into the Dreamcast case with no issue and
worked the first time. 🥳&lt;/p>
&lt;p>I’ve released the board as open hardware under the CERN-OHL-W 2.0 license. I
designed it in &lt;a href="(https://horizon-eda.org/)">Horizon EDA&lt;/a>, an open source PCB design suite. I’d love to see
others take and adapt the project. If you do, let me know what you build!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I’m still hoping to find a drop-in replacement for the AC connector. The
connector interlocks with the case, which rules out most without a case
modification.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Creating Self-Hosted Tile Maps from OpenStreetMap Data</title><link>https://terinstock.com/post/2023/06/Creating-Self-Hosted-Tile-Maps-from-OpenStreetMap-Data/</link><pubDate>Mon, 19 Jun 2023 21:30:55 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2023/06/Creating-Self-Hosted-Tile-Maps-from-OpenStreetMap-Data/</guid><content type="html">&lt;figure>&lt;img src="https://terinstock.com/media/46/37cf2cce580db1a9863996e0037ab83ef0bce4ab3b1f6bf51acd3ef7a0e37d.png"
alt="The work-in-progess homepage of NLAlerts, showing the most recent alerts.">&lt;figcaption>
&lt;p>The work-in-progess homepage of NLAlerts, showing the most recent alerts.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>I&amp;rsquo;ve been working on &lt;a href="https://nlalerts.terin.nl">NLAlerts&lt;/a>, a project to archive and display the mobile emergency alerts sent by the Netherlands government. The data provided by the government includes the text of the alert, along with targetted geographical area. With JavaScript libraries such as &lt;a href="https://leafletjs.com/">Leaflet&lt;/a> or &lt;a href="https://openlayers.org/">OpenLayers&lt;/a> it is easy to turn this geographical area onto an interactive map.&lt;/p>
&lt;p>These libraries can be used with external tile servers, such as the one provided by OpenStreetMap or one of the many commercial offerings. However, I wanted self-host all the data required for the website, which includes the map tiles. Rendered tiles can be stored in a SQLite database following the &lt;a href="https://github.com/mapbox/mbtiles-spec">MBTiles Specification&lt;/a>. Since the NLAlerts site already uses SQLite by way of being powered by data exploration framework &lt;a href="https://datasette.io">Datasette&lt;/a>, using MBTiles would be fairly complimentary. In fact, there&amp;rsquo;s already a plugin &lt;a href="https://datasette.io/plugins/datasette-tiles">datasette-tiles&lt;/a> to have Datasette act as a tileserver.&lt;/p>
&lt;p>This just left one remaining problems: &lt;em>How do I get OpenStreetMap data into MBTiles?&lt;/em> I found many guides online, but they seemed to fall into one of two camps: they were written many years ago and they suffer from linkrot or bitrot, or they ripped tiles from other servers. The former problem makes it hard to follow these guides in 2023, the latter is often against the terms of service (or rude to the remaining operators of non-profit tileservers).&lt;/p>
&lt;p>This past weekend I was able to cobble together a working pipeline to &amp;ldquo;pre-render&amp;rdquo; tiles from OpenStreetMap extracts to MBTiles.&lt;/p>
&lt;p>I first started by downloading the &amp;ldquo;osm.pbf&amp;rdquo; file for the Netherlands from &lt;a href="https://download.geofabrik.de/europe/netherlands.html">Geofabrik&lt;/a>. This file is the OpenStreetMap data in Protobuf format.&lt;/p>
&lt;p>I then used the &lt;a href="https://github.com/Overv/openstreetmap-tile-server">openstreetmap-tile-server&lt;/a> container to import the &amp;ldquo;osm.pbf&amp;rdquo; file into PostGIS. I created volumes to store the database (&lt;code>osm-data&lt;/code>) as well as where the tiles would later be rendered to (&lt;code>osm-tiles&lt;/code>).&lt;/p>
&lt;pre tabindex="0">&lt;code>podman volume create osm-data
podman volume create osm-tiles
podman run -v $PWD/netherlands-latest.osm.pbf \
-v osm-data:/data/database \
-v osm-tiles:/data/tiles \
overv/openstreetmap-tile-server:2.3.0 \
import
&lt;/code>&lt;/pre>&lt;p>After several minutes, the container should exit successfully and all the data will be imported. The same image could be used to run the renderer. I had to increase the shared memory configured in the container to avoid rendering errors later.&lt;/p>
&lt;pre tabindex="0">&lt;code>podman run -v osm-data:/data/database \
-v osm-tiles:/data/tiles \
-shm-size=1G \
overv/openstreetmap-tile-server:2.3.0 \
run
&lt;/code>&lt;/pre>&lt;p>In another terminal we could exec into this container to start the pre-rendering.&lt;/p>
&lt;pre tabindex="0">&lt;code>podman exec -it --latest bash
&lt;/code>&lt;/pre>&lt;p>Within the container, we can fetch the &lt;code>render_list_geo.pl&lt;/code> script, which wraps the &lt;code>render_list&lt;/code> command to render within a bounding box at different zoom levels. I then ran the script with the bounding box determined with the &lt;a href="https://boundingbox.klokantech.com/">Bounding Box Tool&lt;/a> for zoom levels 8 through 13.&lt;/p>
&lt;pre tabindex="0">&lt;code>wget https://raw.githubusercontent.com/alx77/render_list_geo.pl/master/render_list_geo.pl
perl ./render_list_geo.pl -z 8 -Z 13 \
-x 3.3316001 -X 7.2275102 \
-y 50.7503838 -Y 53.6316
&lt;/code>&lt;/pre>&lt;p>If all went well, we can exit and stop the container, now having completed the hardest part. We have the map rendered in the &lt;code>osm-tiles&lt;/code>, unfortunately not in a format we can directly use, but instead in &lt;a href="https://wiki.openstreetmap.org/wiki/Meta_tiles">meta tile&lt;/a>, which can be efficiently used by &lt;code>mod_tile&lt;/code>.&lt;/p>
&lt;p>Fortunately, Geofabrik has a tool to help us out, &lt;a href="https://github.com/geofabrik/meta2tile">meta2tile&lt;/a>, which can be used to generate MBTiles databases. After cloning the repository, I built the tool in the most barebones configuration, only enabling MBTiles support.&lt;/p>
&lt;pre tabindex="0">&lt;code>gcc -DWITH_MBTILES meta2tile.c -o meta2tile -lsqlite3 -lcrypto -lm -O3
&lt;/code>&lt;/pre>&lt;p>It&amp;rsquo;s worth noting that &lt;code>meta2tile&lt;/code> uses the deprecated &lt;code>MD5&lt;/code> function from OpenSSL, which may stop being provided in a future version of OpenSSL. Fortunately, it&amp;rsquo;s also not the most difficult function to replace.&lt;/p>
&lt;p>On my system Podman runs in rootless mode, with volumes being plain directories on disks. Thus I was able to run &lt;code>meta2tile&lt;/code> directly against this directory. You may need to mount the volume or copy the data out first.&lt;/p>
&lt;pre tabindex="0">&lt;code>meta2tile --mbtiles \
--meta name=&amp;#34;Netherlands&amp;#34; \
--meta type=raster \
--meta format=png \
--meta version=1.0 \
--meta bounds=&amp;#34;3.3316001,50.7503838,7.2275102,53.6316&amp;#34; \
--meta description=&amp;#34;OpenStreetMap tiles for Netherlands&amp;#34;
/home/terin/.local/share/containers/storage/volumes/osm-tiles/_data/default/ \
nl.db
&lt;/code>&lt;/pre>&lt;p>I was then able to start Datasette with the datasette-tiles plugin installed and browse to &lt;code>http://localhost:8001/-/tiles/nl/&lt;/code> and browse my tiles.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/91/b5d02f42066b1b588292231c3386688099374679ad8d6a3fe81731ff322f6a.png"
alt="The alert page showing a test alert sent, rendering the targetted area with OpenLayers.">&lt;figcaption>
&lt;p>The alert page showing a test alert sent, rendering the targetted area with OpenLayers.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>When creating my template for the custom alert pages, I was able to reference &lt;code>&amp;quot;/-/tiles/nl/{z}/{x}/{y}.png&amp;quot;&lt;/code> as the location of the tileserver. The result can be seen on this &lt;a href="https://nlalerts.terin.nl/alerts/c2006b50-7179-498d-bddd-933899f354bd">test alert&lt;/a>.&lt;/p></content></item><item><title>Controlling the XM PCR receiver from Linux</title><link>https://terinstock.com/post/2023/05/Controlling-the-XM-PCR-receiver-from-Linux/</link><pubDate>Mon, 08 May 2023 20:04:12 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2023/05/Controlling-the-XM-PCR-receiver-from-Linux/</guid><content type="html">&lt;figure>&lt;img src="https://terinstock.com/media/df/68e3d31d315e0a6cdc75d048d8ab99ba727105ce483c473781d0572758ab3e:800.jpg"
alt="The XM PCR in question, complete with mysterious screw holes.">&lt;figcaption>
&lt;p>The XM PCR in question, complete with mysterious screw holes.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>The XM PCR was a satellite radio receiver sold to consumers in 2003 that was controllable by a PC running Windows over USB. Other than satellite, audio and USB connectivity, the receiver does not have any form of user interactivity, everything is done over USB. XM Radio discontinued it in 2004 due to software to easily rip songs, which was discussed on &lt;a href="https://archive.org/details/The_Tech_Guy_Audio_71/20040904-1.mp3">The Tech Guy #71&lt;/a>.&lt;/p>
&lt;p>It&amp;rsquo;s been nearly 20 years since the device was released, and the SiriusXM satellites are still broadcasting audio in a format this receiver can understand. I was shocked when the activation signal went through and the radio sprang to life. With the radio working, audio compression artifacts and all, I sought to control it from Linux.&lt;/p>
&lt;p>The receiver uses an FTDI component to expose a serial port over USB. This is supported by modern Linux, and after attaching the receiver to the computer, a serial device was exposed under &lt;code>/dev&lt;/code>. This was off to a promising start.&lt;/p>
&lt;p>After a little bit of investigation I found that the protocol had already been reverse engineered and Michael Minn had released a GUI program &lt;a href="https://michaelminn.com/linux/mmxmpcr/">MMXMPCR&lt;/a> in 2003. It was last updated in 2005 which was a long time ago in the realm of Linux desktop software. I wasn&amp;rsquo;t even sure if modern distributions still shipped the required libraries, and if they did, if the program would even compile. Fortunately, Michael still hosts the source tarballs on their website; I downloaded the latest release to find out.&lt;/p>
&lt;p>For the moment, Linux distributions still ship the necessary &lt;a href="https://en.wikipedia.org/wiki/Motif_(software)">MOTIF&lt;/a> and &lt;a href="https://en.wikipedia.org/wiki/X_Toolkit_Intrinsics">X Toolkit Intrinsics&lt;/a> libraries. As desktop Linux continues the migration to Wayland, I wonder how much longer this will hold true. I modified the makefile to specify the correct header location and the linker flag for the Intrinsics library. I ran make and was greeted with a &lt;code>mmxmpcr&lt;/code> binary, and a wall of compiler warnings.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/4f/e9aef17a793fdae00505a470623f105f43dc9de072959e0772c9247d657abb.png"
alt="Screenshot of mmxmpcr, showing the first 10 channels in the application window.">&lt;figcaption>
&lt;p>Screenshot of mmxmpcr, showing the first 10 channels in the application window.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Upon running &lt;code>mmxmpcr&lt;/code>, a window appeared which began populating with channel information from the radio, and I could control the receiver without issue. To remain cool, I tuned into TikTok Radio and attempted to dance.&lt;/p>
&lt;p>In order to preserve the project into the future, I&amp;rsquo;ve used the available release tarballs to create the &lt;a href="https://github.com/terinjokes/mmxmpcr">mmxmpcr repository&lt;/a> on GitHub. It would be interesting to refactor the project to use &lt;a href="https://libusb.info/">libusb&lt;/a> to discover an attached radio, rather than hardcoding a path into the binary.&lt;/p>
&lt;p>However, the original Windows software distributed by XM Radio for this receiver seems missing from the Internet Archive. If you have it, please upload it and let me know!&lt;/p></content></item><item><title>HPSS-Disassembly Progress Report (May 2022)</title><link>https://terinstock.com/post/2022/05/HPSS-Disassembly-Progress-Report-May-2022/</link><pubDate>Tue, 31 May 2022 16:16:16 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2022/05/HPSS-Disassembly-Progress-Report-May-2022/</guid><content type="html">&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;figure>&lt;img src="https://terinstock.com/media/ce/00bb21b49f9f4f766b4c3444d6f679f230a38970fd6b136474e5679264cc4c.png"
alt="Title screen of Harry Potter and the Sorcerer&amp;rsquo;s Stone for the Game Boy Color">&lt;figcaption>
&lt;p>Title screen of &lt;em>Harry Potter and the Sorcerer&amp;rsquo;s Stone&lt;/em> for the Game Boy Color&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>A little over 20 years ago the first movie in the &lt;em>Harry Potter&lt;/em> series, &lt;em>Harry
Potter and the Sorcerer&amp;rsquo;s Stone&lt;/em> in the United States, was released by Warner
Bros. I had been caught up in the Harry Potter mania and received the movie tie-in
Game Boy Color game as a Christmas gift. I recall playing it for a while, before
my attention returned to the other hype machine of 2001: the &lt;em>Pokémon&lt;/em> series.&lt;/p>
&lt;figure>&lt;a href="https://www.fdossena.com/?p=hp1/i.md" target="_blank">&lt;img src="https://terinstock.com/media/73/0524f0dccdee9e9fca1e241c9d76f1ef7837d40ed029b4841c64a6675d9de5:800.jpg"
alt="Harry Potter, Ron Weasley, and Hermione Granger running through a Hogwarts hallway in the PC version.">&lt;/a>&lt;figcaption>
&lt;p>&lt;em>Harry Potter and the Sorcerer&amp;rsquo;s Stone&lt;/em> on PC. Image credit Federico Dossena.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>The PlayStation and PC versions of the tie-in game are 3D action-puzzle games
where you play as the titular Harry Potter as he navigates around Hogwarts, the
grounds, the Forbidden Forest, and a side quest to Diagon Alley. The PlayStation
version is where we get the low-poly meme Hagrid from.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/ff/34b0288d7ba9a34ad693fbef6d358d5e1b573546c2f9dbec7952e10902c495.png"
alt="Harry Potter fighting bats in a Final Fantasy-inspired battle system.">&lt;figcaption>
&lt;p>Harry Potter fighting bats in a Final Fantasy-inspired battle system.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>The Game Boy Color version was completely different: a top-down Role Playing
Game in the style of classic &lt;em>Final Fantasy&lt;/em>. While you still play as Harry
Potter, it follows the plot of the book much more faithfully than the 3D
counterparts. There are some changes to adapt into a game: spells are learned
for combat, items can be equipped for buffs, and wizard cards can be collected
and used for combat effects.&lt;/p>
&lt;p>I had entirely forgotten about this game until YouTube channel
&lt;a href="https://www.youtube.com/channel/UC9i9MfllgUd2Z6gSEGK3Vaw">Flandrew&lt;/a> put out a
comparison of every version of &lt;em>Harry Potter and the Philosopher&amp;rsquo;s Stone&lt;/em>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>,
jogging back a flood of forgotten memories.&lt;/p>
&lt;p>Picking it up again, I was excited by how colorful, expansive, and smooth the
game felt, especially compared to Pokémon titles of the time. I decided I would
learn more about how the game ticked.&lt;/p>
&lt;h2 id="disassembly-beginnings">Disassembly Beginnings&lt;/h2>
&lt;p>Disassembling Game Boy games isn&amp;rsquo;t new; &lt;em>Pokémon Red and Blue&lt;/em> has been
&lt;a href="https://github.com/pret/pokered">completely reverse engineered&lt;/a> (this has
prompted projects disassembling the rest of the Pokémon series). Other classics,
such as &lt;a href="https://github.com/zladx/LADX-Disassembly">&lt;em>Zelda: Link&amp;rsquo;s Awakening
DX&lt;/em>&lt;/a>, have also been in progress for
years.&lt;/p>
&lt;p>I&amp;rsquo;ve &lt;a href="https://github.com/terinjokes/HPSS-Disassembly">started a project&lt;/a> to
disassemble &lt;em>Harry Potter and the Sorcerer&amp;rsquo;s Stone&lt;/em> for the Game Boy Color. My
goal is to document the techniques used to develop Game Boy games during the
final years of the hardware lifecycle. I&amp;rsquo;m also planning on taking techniques
learned from this project forward towards disassembling the sequel, &lt;em>Harry
Potter and the Chamber of Secrets&lt;/em>, which also holds the distinction of being
the last Game Boy Color game released in North America.&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;p>As I&amp;rsquo;ve never worked on disassembling a game before, I had to start from
scratch. Fortunately, the Game Boy homebrew development scene is quite active,
so modern tools are available to help get started.&lt;/p>
&lt;h3 id="mgbdis-the-disassembler">mgbdis, the disassembler&lt;/h3>
&lt;p>To begin understanding what&amp;rsquo;s happening &amp;ldquo;behind the curtain&amp;rdquo; we need to turn the
binary code the Game Boy Color CPU executes back into assembly code.&lt;/p>
&lt;p>Unfortunately, the labels and symbols are not preserved in the binary code, so
we&amp;rsquo;ll never be able to reproduce the exact structure the original game
developers had for the game. However, we can use computers to make best guesses
as a starting point, and use logic and reason (and our own guesses) to craft a
structure back on top.&lt;/p>
&lt;p>I used Matt Currie&amp;rsquo;s &lt;a href="https://github.com/mattcurrie/mgbdis">mgbdis&lt;/a> disassembler
to begin this task. &lt;em>Sorcerer&amp;rsquo;s Stone&lt;/em> is a 4 MiB game, so disassembling took a
while, but it ended up creating 256 files each representing 16 KiB of bankable
memory. I also attempted to use Matt&amp;rsquo;s emulator, &lt;a href="https://mattcurrie.com/bdm/">Beaten Dying
Moon&lt;/a>, to automate generating a symbol file to aid
in separating executing code from data like images, but it did not seem very
successful.&lt;/p>
&lt;h3 id="rgbds-the-assembler-and-linker">RGBDS, the assembler and linker&lt;/h3>
&lt;p>&lt;img src="https://terinstock.com/media/96/3a82c9766776a2b8fcc77552b1f34d9f8a5befae96de65d1fec70f074510f9.png" alt="RGBDS logo">&lt;/p>
&lt;p>Once we have disassembled the game (even with our imperfect results) in order to
get back to a playable game again we&amp;rsquo;ll need an assembler and linker.
Fortunately we have &lt;a href="https://rgbds.gbdev.io/">RGBDS&lt;/a>, an open source toolchain
dating back to the 1990s.&lt;/p>
&lt;p>RGBDS&amp;rsquo;s assembler, &lt;a href="https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.1">&lt;code>rgbasm&lt;/code>&lt;/a>,
takes the assembly files as inputs and turned them into object files. It&amp;rsquo;s
likely down the road we&amp;rsquo;ll have object files containing code that are logically
related to each other, but for now we assemble each bank into it&amp;rsquo;s own object
file.&lt;/p>
&lt;p>RGBDS&amp;rsquo;s linker, &lt;a href="https://rgbds.gbdev.io/docs/v0.5.2/rgblink.1">&lt;code>rgblink&lt;/code>&lt;/a>,
collects the object files together and decides how to combine them together into
a Game Boy ROM. As the code is &amp;ldquo;fixed&amp;rdquo; in memory, the primary responsibility of
the linker in this project is to resolve symbol references across object files,
so the correct memory locations can be written into the ROM. As we cleanup the
assembly files, the linker will likely become more important.&lt;/p>
&lt;p>RGBDS includes a header fixer,
&lt;a href="https://rgbds.gbdev.io/docs/v0.5.2/rgbfix.1">&lt;code>rgbfix&lt;/code>&lt;/a>, used to generate a
valid Game Boy header. The original hardware uses information in this header as
checksums, to setup compatibility modes, and to implement basic DRM with the
&amp;ldquo;Nintendo&amp;rdquo; logo. When the game was disassembled this header was partially
decoded as instructions, and partially as data. It was removed and replaced by
padding so &lt;code>rgbfix&lt;/code> could be used instead. This allows for greater flexability
in generating debug builds later, as the tool can generate the correct checksums
later.&lt;/p>
&lt;p>One other included tool is RGBDS&amp;rsquo;s image converter,
&lt;a href="https://rgbds.gbdev.io/docs/v0.5.2/rgbgfx.1">&lt;code>rgbgfx&lt;/code>&lt;/a>. This is a tool for
storing graphics as PNGs instead of &lt;code>2bpp&lt;/code>, an encoding more suitable for the
Game Boy&amp;rsquo;s hardware. &lt;code>mgbdis&lt;/code> did not separate the game&amp;rsquo;s images into individual
files, so we won&amp;rsquo;t be using it for now, but it will be indespensible later once
we&amp;rsquo;ve extracted the images.&lt;/p>
&lt;p>The RGBDS project also documents &lt;a href="https://rgbds.gbdev.io/docs/v0.5.2/rgbds.5">the object file
format&lt;/a>, allowing for project
specific tools to be written (if that proves to be necessary). Since &lt;em>Sorcerer&amp;rsquo;s
Stone&lt;/em> is a JRPG, there is a lot of dialog, menus, and world building, resulting
in lots of text. The game also supports 11 different languages, farther
multiplying the amount of text. I suspect we might need tooling to easily handle
all of it.&lt;/p>
&lt;h3 id="gup-the-recursive-build-system">gup, the recursive build system&lt;/h3>
&lt;p>After disassembling a game, mgbdis generates a basic GNU Make-compatible
Makefile. This Makefile calls &lt;code>rgbasm&lt;/code> over a single assembly file &amp;ldquo;game.asm&amp;rdquo;
that simply includes all the banks. For a small game this might work, but for a
large game like &lt;em>Sorcerer&amp;rsquo;s Stone&lt;/em> re-assembling the entire game each rebuild
was actually taking a significant amount of time. It would be far better to only
reassemble the files that changed, and the targets that depend on that file.&lt;/p>
&lt;p>I&amp;rsquo;ve switched the project to using &lt;a href="https://github.com/timbertson/gup">gup&lt;/a>, a
recursive build system inspired by Daniel J. Bernstein&amp;rsquo;s
&lt;a href="https://cr.yp.to/redo.html">redo&lt;/a>. In gup, targets are executable scripts
written in any language, and they can discover and register their own
dependencies.&lt;/p>
&lt;p>In HPSS-Disassembly, gup assembles banks by calling &lt;a href="https://github.com/terinjokes/HPSS-Disassembly/blob/7cf6b905052180b0f86adf24c4c9b4a2a5aca5b4/scripts/as">a
script&lt;/a>
that calls &lt;code>rgbasm&lt;/code> and registers each file it lists as a dependency with gup.
When a file is changed, gup knows it only needs to rebuild banks that included
it the previous build. This makes the testing iteration cycle extremely fast.&lt;/p>
&lt;h2 id="disassembly-progress">Disassembly Progress&lt;/h2>
&lt;p>I&amp;rsquo;ve written a lot of words here about the progress made on project
infrastructure. What have I actually accomplished on the disassembly side? Since
this is my first month working on the project, I&amp;rsquo;m afraid I haven&amp;rsquo;t accomplished
too much.&lt;/p>
&lt;h3 id="ready-lets-start">Ready? Let&amp;rsquo;s Start&lt;/h3>
&lt;figure>&lt;img src="https://terinstock.com/media/54/634c2d974d0a1fa20ac746942cee8e85844405b6032ed7cfb40456370043b9.png"
alt="Error displayed when the game is inserted into a system not compatible with the Game Boy Color.">&lt;figcaption>
&lt;p>Error displayed when the game is inserted into a system not compatible with the Game Boy Color.&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>As &lt;em>Harry Potter and the Sorcerer&amp;rsquo;s Stone&lt;/em> is a Game Boy Color-only game, the
very first thing it does is check to see if it&amp;rsquo;s running on a Game Boy
Color-compatible system. When it jumps to &lt;code>Start&lt;/code> it compares the value left in
the &lt;code>a&lt;/code> register by the system boot ROM to &lt;code>$11&lt;/code>. If the zero flag is set, it
later jumps to code to show an error message.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>Start::
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff0">and&lt;/span> &lt;span style="color:#7fffd4">a&lt;/span> &lt;span style="color:#0f0">; clear flags
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">cp&lt;/span> &lt;span style="color:#7fffd4">BOOTUP_A_CGB&lt;/span> &lt;span style="color:#0f0">; is Game Boy Color?
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff0">ld&lt;/span> &lt;span style="color:#7fffd4">a&lt;/span>, &lt;span style="color:#7fffd4">$00&lt;/span> &lt;span style="color:#0f0">; set a to 0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">jr&lt;/span> &lt;span style="color:#7fffd4">nz&lt;/span>, &lt;span style="color:#7fffd4">.notGBC&lt;/span> &lt;span style="color:#0f0">; if not GBC:
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">inc&lt;/span> &lt;span style="color:#7fffd4">a&lt;/span> &lt;span style="color:#0f0">; increment a (a=1)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.notGBC:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff0">ldh&lt;/span> [&lt;span style="color:#7fffd4">$ef&lt;/span>], &lt;span style="color:#7fffd4">a&lt;/span> &lt;span style="color:#0f0">; save GBC value
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">ld&lt;/span> &lt;span style="color:#7fffd4">sp&lt;/span>, &lt;span style="color:#7fffd4">$cfff&lt;/span> &lt;span style="color:#0f0">; setup stack pointer
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">ldh&lt;/span> &lt;span style="color:#7fffd4">a&lt;/span>, [&lt;span style="color:#7fffd4">$ef&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff0">or&lt;/span> &lt;span style="color:#7fffd4">a&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff0">call&lt;/span> &lt;span style="color:#7fffd4">z&lt;/span>, &lt;span style="color:#7fffd4">Unknown_Non_GBC&lt;/span> &lt;span style="color:#0f0">; call if not GBC
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As this code saves the GBC status into High RAM, instead of directly jumping to
code to display the error message, I wonder if at some point in development the
game was targetting compatibility with the earlier Game Boy systems.&lt;/p>
&lt;h3 id="floop-flatten">&amp;ndash;floop-flatten&lt;/h3>
&lt;p>What do you do if you want quickly copy contiguous memory from multiple parts of
the game, but part of the game needs to copy a different number of bytes, and
you also want to keep the number of CPU cycles to a minimum? One approach taken
by the &lt;em>Sorcerer&amp;rsquo;s Stone&lt;/em>&amp;rsquo;s developers is to flatten the loop. You can call the
&lt;a href="https://github.com/terinjokes/HPSS-Disassembly/blob/dedcb131ea/src/bank_000.asm#L5980-L6168">same 3 instructions 32
times&lt;/a>,
then call into the code at whatever point has the required number of iterations
remaining.&lt;/p>
&lt;p>Fortunately, &lt;code>rgbasm&lt;/code> is a macro assembler, allowing us to refactor this logic
to generate the assembly for us, while also generating more readable names.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-asm" data-lang="asm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff0">FOR&lt;/span> &lt;span style="color:#7fffd4">V&lt;/span>, &lt;span style="color:#f60">32&lt;/span>, &lt;span style="color:#f60">0&lt;/span>, -&lt;span style="color:#f60">1&lt;/span> &lt;span style="color:#0f0">; loop from 32 to 0, decrementing each time
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span>&lt;span style="color:#ff0">CopyHL2DE_&lt;/span>{&lt;span style="color:#7fffd4">d&lt;/span>:&lt;span style="color:#7fffd4">V&lt;/span>}: &lt;span style="color:#0f0">; generate a label we can reference from other code
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">ld&lt;/span> &lt;span style="color:#7fffd4">a&lt;/span>, [&lt;span style="color:#7fffd4">hl&lt;/span>+] &lt;span style="color:#0f0">; load the byte pointed to by hl into a,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#0f0">; and also increment hl
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">ld&lt;/span> [&lt;span style="color:#7fffd4">de&lt;/span>], &lt;span style="color:#7fffd4">a&lt;/span> &lt;span style="color:#0f0">; load a into the byte referenced by de
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span> &lt;span style="color:#ff0">inc&lt;/span> &lt;span style="color:#7fffd4">de&lt;/span> &lt;span style="color:#0f0">; increment de
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">&lt;/span>&lt;span style="color:#ff0">ENDR&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This reduced over 200 lines of code into a much more manageable 6!&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s Next&lt;/h2>
&lt;p>I&amp;rsquo;ve still have a lot of tasks ahead. Some immediate tasks to start working on:&lt;/p>
&lt;ul>
&lt;li>Begin extracting tiles into bitmaps, and convert and assemble on-the-fly
during builds.&lt;/li>
&lt;li>Work on extracting text into forms that can be easier to work with, especially
for translators.&lt;/li>
&lt;li>Label and comment even more code.&lt;/li>
&lt;/ul>
&lt;p>In &lt;a href="https://kemenaran.winosx.com/posts/special-effects-in-zelda-links-awakening">the first blog
post&lt;/a>
for disassembling &lt;em>Zelda: Link&amp;rsquo;s Awakening DX&lt;/em>, Pierre writes:&lt;/p>
&lt;blockquote>
&lt;p>Reverse-engineering assembly code is quite slow, but I&amp;rsquo;ll try to post some
findings on this blog.&lt;/p>&lt;/blockquote>
&lt;p>The statement is just as true here as it was all those years ago. We&amp;rsquo;ll see how
this goes!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Flandrew must be from outside the United States of America.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/List_of_Game_Boy_Color_games">According to
Wikipedia&lt;/a> five
games released after &lt;em>Chamber of Secrets&lt;/em> in other markets, 1 in Germany, 1
in Korea, and 3 in Japan, including the last licensed game &lt;em>Doraemon no
Study Boy: Kanji Yomikaki Master&lt;/em>.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Systemd's clock-epoch for RTC-less systems</title><link>https://terinstock.com/post/2021/12/Systemds-clock-epoch-for-RTC-less-systems/</link><pubDate>Sun, 26 Dec 2021 06:24:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2021/12/Systemds-clock-epoch-for-RTC-less-systems/</guid><content type="html">&lt;p>Earlier today I restarted a Raspberry Pi I use on my home network. When the system came up after reboot, many services failed to resume, which I quickly noticed due to the lack of DNS resolution on my network. Upon checking the system logs, the culprit became clear: CoreDNS was failing to verify the certificates of my DNS-over-TLS provider. According to my Raspberry Pi, we were partying like it was 1999. Without DNS resolution the system time would never automatically synchronize with NTP.&lt;/p>
&lt;p>On a Raspberry Pi running Raspian or Raspberry Pi OS, the system is automatically configured to use &lt;code>fake-hwclock&lt;/code> to save the time to disk from a cron, and restore it at boot. However, this package isn&amp;rsquo;t widely distributed outside Debian and it&amp;rsquo;s derivatives.&lt;/p>
&lt;p>At boot, systemd compares the system time to a builtin epoch, usually the release or build date of systemd. If it finds the system time is before this epoch, it resets the clock to the epoch. Unfortunately, this is still sometimes too old, especially when it comes to TLS certificates.&lt;/p>
&lt;p>We can still utilize this systemd feature however, at least if systemd is version 247 or newer, by utilizing a hook added to aid system administrators and image distributors. If the file &lt;code>/usr/lib/clock-epoch&lt;/code> exists, systemd uses the modified time as the systemd epoch.&lt;/p>
&lt;p>We can even script periodically updating this file. First, create &lt;code>/etc/systemd/system/set-clock-epoch.service&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ini" data-lang="ini">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Description=&lt;span style="color:#87ceeb">Updates the mtime of clock-epoch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">[Service]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ExecStart=&lt;span style="color:#87ceeb">/bin/touch -m /usr/lib/clock-epoch&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, create a timer to start it periodically, at &lt;code>/etc/systemd/system/set-clock-epoch.timer&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ini" data-lang="ini">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">[Unit]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Description=&lt;span style="color:#87ceeb">Timer for updating clock-epoch&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">[Timer]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>OnBootSec=&lt;span style="color:#87ceeb">5min&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>OnUnitInactiveSec=&lt;span style="color:#87ceeb">17min&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">[Install]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>WantedBy=&lt;span style="color:#87ceeb">timers.target&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I&amp;rsquo;ve arbitrarily delayed activation of this timer by setting &lt;code>OnBootSec&lt;/code>, since this is low priority at boot time.&lt;/p>
&lt;p>Then reload the daemon, then enable and start the timer.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>systemctl daemon-reload
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>systemctl enable --now set-clock-epoch.timer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now when the system reboots, systemd will read the modification time of this file during early boot, setting the time to a reasonably close value, allowing certificates to be validated and NTP to take over.&lt;/p>
&lt;hr>
&lt;p>As &lt;a href="https://lobste.rs/s/0jlh6q/systemd_s_clock_epoch_for_rtc_less_systems#c_exj4qa">gioele&lt;/a> on Lobste.rs points out, on systemd version 250 and later, if you&amp;rsquo;re using &lt;code>systemd-timesyncd&lt;/code> you can set &lt;code>SaveIntervalSec=&lt;/code> to automatically write the time out to the filesystem. This follows a similar mechanism, the file &lt;code>/var/lib/systemd/timesync/clock&lt;/code> has it&amp;rsquo;s modification time set, and is used to restore out reboot. The time is set by the epoch mechanism much earlier in the startup process than the timesyncd mechanism (which first requires service activation to begin). It also doesn&amp;rsquo;t help if, like me, you&amp;rsquo;re not using timesyncd.&lt;/p></content></item><item><title>Clearing Custom Search Engines in Chrome</title><link>https://terinstock.com/post/2021/08/Clearing-Custom-Search-Engines-in-Chrome/</link><pubDate>Wed, 18 Aug 2021 09:20:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2021/08/Clearing-Custom-Search-Engines-in-Chrome/</guid><content type="html">&lt;p>In addition to your search engine in Google Chrome, the browser tries to be
helpful by remembering &lt;a href="https://developer.mozilla.org/en-US/docs/Web/OpenSearch#autodiscovery_of_search_plugins">custom search engines&lt;/a> for websites you
visit in the course of navigating the web. As some websites can become
registered simply by navigating to their homepage, this can result in a very
long list of custom search engines.&lt;/p>
&lt;p>Unfortunately, the page to manage custom search engines in Google Chrome
(currently located at &lt;code>chrome://settings/searchEngines&lt;/code>) leaves a lot to be
desired. This page has no mechanism for bulk editing. With over two hundred
engines registered, I did not want to click my mouse to manually manage them.&lt;/p>
&lt;p>Fortunately, we can activate super developer powers!&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">const&lt;/span> nl = document.querySelector(&lt;span style="color:#87ceeb">&amp;#34;settings-ui&amp;#34;&lt;/span>).shadowRoot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .querySelector(&lt;span style="color:#87ceeb">&amp;#34;#main&amp;#34;&lt;/span>).shadowRoot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .querySelector(&lt;span style="color:#87ceeb">&amp;#34;[role=main]&amp;#34;&lt;/span>).shadowRoot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .querySelector(&lt;span style="color:#87ceeb">&amp;#34;settings-search-page&amp;#34;&lt;/span>).shadowRoot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .querySelector(&lt;span style="color:#87ceeb">&amp;#34;settings-search-engines-page&amp;#34;&lt;/span>).shadowRoot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .querySelector(&lt;span style="color:#87ceeb">&amp;#34;#otherEngines&amp;#34;&lt;/span>).shadowRoot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .querySelectorAll(&lt;span style="color:#87ceeb">&amp;#34;settings-search-engine-entry&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ah, right. So, the settings pages uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM">Shadow DOM&lt;/a>, which makes
getting to the actual list of custom search engines a bit of a chore. Query
through a bunch of document fragments to get to the actual list.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">const&lt;/span> ops = Array.from(nl).map((elem) =&amp;gt; elem.shadowRoot)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .filter((elem) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> !elem.querySelector(&lt;span style="color:#87ceeb">&amp;#34;#keyword-column&amp;#34;&lt;/span>).innerText.startsWith(&lt;span style="color:#87ceeb">&amp;#34;!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> })
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .map((elem) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> [elem.querySelector(&lt;span style="color:#87ceeb">&amp;#34;#keyword-column&amp;#34;&lt;/span>).innerText, elem.querySelector(&lt;span style="color:#87ceeb">&amp;#34;#delete&amp;#34;&lt;/span>)];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console.table(ops);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I use the custom search engine feature to implement DuckDuckGo-style &lt;a href="https://duckduckgo.com/bang">!Bang
searches&lt;/a>. So before removing engines, I filter out items starting with
&amp;ldquo;!&amp;rdquo;.&lt;/p>
&lt;p>If the table printed to the console looks good, click the virtual mouse.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>ops.forEach(entry =&amp;gt; entry[&lt;span style="color:#f60">1&lt;/span>].click());
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item><item><title>Setting up a git server on NixOS</title><link>https://terinstock.com/post/2021/01/Setting-up-a-git-server-on-NixOS/</link><pubDate>Wed, 27 Jan 2021 21:23:25 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2021/01/Setting-up-a-git-server-on-NixOS/</guid><content type="html">&lt;p>Over the years I&amp;rsquo;ve tried out many VPS providers, which has resulted in personal
infrastructure spread far and wide. I&amp;rsquo;ve decided to start 2021 by consolidating
this infrastructure, where reasonable. This also gives me an opportunity to
properly document deployments, ideally making them easy to redeploy in the
future. NixOS allows me to solve both goals relatively easily.&lt;/p>
&lt;p>One piece of personal infrastructure is a git server, &lt;a href="https://git.terinstock.com">git.terinstock.com&lt;/a>,
which I use for personal configuration or smaller projects. Since I&amp;rsquo;m the only
user, I don&amp;rsquo;t need the fancy interfaces provided by options like &lt;a href="https://gogs.io/">gogs&lt;/a>,
&lt;a href="https://gitea.io/">gitea&lt;/a>, or &lt;a href="https://about.gitlab.com/features/">GitLab&lt;/a>. I&amp;rsquo;ve chosen to use cgit, which had a few
more features I wanted over &lt;a href="https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb">gitweb&lt;/a>.&lt;/p>
&lt;p>It did feel a little strange to deploy a cgi server in 2021. I think we call
this &amp;ldquo;serverless&amp;rdquo; now?&lt;/p>
&lt;h2 id="cgit">cgit&lt;/h2>
&lt;p>NixOS already has cgit packaged, so we can start with configuring it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">let&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cgit = pkgs.cgit;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cgitConfig = pkgs.writeText &lt;span style="color:#87ceeb">&amp;#34;cgitrc&amp;#34;&lt;/span> (lib.generators.toKeyValue { } {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> css = &lt;span style="color:#87ceeb">&amp;#34;/cgit.css&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> logo = &lt;span style="color:#87ceeb">&amp;#34;/cgit.png&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> favicon = &lt;span style="color:#87ceeb">&amp;#34;/favicon.ico&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> about-filter = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgit&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/lib/cgit/filters/about-formatting.sh&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> source-filter = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgit&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/lib/cgit/filters/syntax-highlighting.py&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> clone-url = (lib.concatStringsSep &lt;span style="color:#87ceeb">&amp;#34; &amp;#34;&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;https://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ssh://git@git.terinstock.com:$CGIT_REPO_URL&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enable-log-filecount = &lt;span style="color:#f60">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enable-log-linecount = &lt;span style="color:#f60">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enable-git-config = &lt;span style="color:#f60">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> root-title = &lt;span style="color:#87ceeb">&amp;#34;git.terinstock.com&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> root-desc = &lt;span style="color:#87ceeb">&amp;#34;Terin&amp;#39;s Git Repositories&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scan-path = &lt;span style="color:#87ceeb">&amp;#34;/srv/git&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This assigns a derivation to &lt;code>cgitConfig&lt;/code> that when evaluated would create the
ini-like configuration file. Most of this comes down to personal preference with
a few exceptions:&lt;/p>
&lt;ul>
&lt;li>&lt;code>about-filter&lt;/code> and &lt;code>source-filter&lt;/code> reference the respective filters wpithin
the cgit package. NixOS will expand these to full paths when creating the
configuration file.&lt;/li>
&lt;li>&lt;code>scan-path&lt;/code> is the location on disk I&amp;rsquo;m using to host the git repositories.&lt;/li>
&lt;/ul>
&lt;h2 id="git-shell">git-shell&lt;/h2>
&lt;p>To allow for authenticated pushes, I use &lt;code>git-shell&lt;/code> provided by the &lt;code>git&lt;/code>
project. This provides a minimal &amp;ldquo;shell&amp;rdquo; that an execute a prescribed list of
git commands. I define a system user &lt;code>git&lt;/code> and assign it this shell.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> users.users.git = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> isSystemUser = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> description = &lt;span style="color:#87ceeb">&amp;#34;git user&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> home = &lt;span style="color:#87ceeb">&amp;#34;/srv/git&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shell = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.git&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/bin/git-shell&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> openssh.authorizedKeys.keys = [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIINNSIl3/j3KqW/x3kFj1ZvZlSxp+MDhk8LAIDlqs/9w&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As I&amp;rsquo;m the only user that will be pushing to these repositories, I don&amp;rsquo;t need to
configure anything extra for authenticating actions.&lt;/p>
&lt;h2 id="web-server-h2o">Web Server, h2o&lt;/h2>
&lt;p>Since cgit is a cgi application, it needs a web server to actually handle the
HTTP connections. I&amp;rsquo;ve chosen to use &lt;a href="https://h2o.examp1e.net/">h2o&lt;/a> as it supports being an
application proxy in addition to serving static files, while still being
configurable with YAML/JSON.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">let&lt;/span> h2oFile = file: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;file.file&amp;#34;&lt;/span> = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>file&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;file.send-compressed&amp;#34;&lt;/span> = &lt;span style="color:#87ceeb">&amp;#34;ON&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> h2oHeaderList = attrs: (lib.mapAttrsToList (k: v: &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>k&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">: &lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>v&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">&amp;#34;&lt;/span>) attrs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> h2oConfig = pkgs.writeText &lt;span style="color:#87ceeb">&amp;#34;h2o.conf&amp;#34;&lt;/span> (lib.generators.toYAML { } {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> hosts.&lt;span style="color:#87ceeb">&amp;#34;git.terinstock.com&amp;#34;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> paths = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;/cgit.css&amp;#34;&lt;/span> = h2oFile &lt;span style="color:#87ceeb">./cgit.css&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;/cgit.png&amp;#34;&lt;/span> = h2oFile &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgit&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/cgit/cgit.png&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;/favicon.ico&amp;#34;&lt;/span> = h2oFile &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgit&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/cgit/favicon.ico&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;/robots.txt&amp;#34;&lt;/span> = h2oFile &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgit&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/cgit/robots.txt&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;/&amp;#34;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;fastcgi.spawn&amp;#34;&lt;/span> = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.h2o&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/share/h2o/fastcgi-cgi&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> setenv = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SCRIPT_FILENAME = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgit&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/cgit/cgit.cgi&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGIT_CONFIG = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>cgitConfig&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> compress = &lt;span style="color:#87ceeb">&amp;#34;ON&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;header.set&amp;#34;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header = (h2oHeaderList {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x-frame-options = &lt;span style="color:#87ceeb">&amp;#34;deny&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x-xss-protection = &lt;span style="color:#87ceeb">&amp;#34;1, mode=block&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> x-content-type-options = &lt;span style="color:#87ceeb">&amp;#34;nosniff&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> referrer-policy = &lt;span style="color:#87ceeb">&amp;#34;no-referrer, strict-origin-when-cross-origin&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cache-control = &lt;span style="color:#87ceeb">&amp;#34;no-transform&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> strict-transport-security = &lt;span style="color:#87ceeb">&amp;#34;max-age=63072000&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content-security-policy = (lib.concatStringsSep &lt;span style="color:#87ceeb">&amp;#34;; &amp;#34;&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;default-src &amp;#39;none&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;style-src &amp;#39;self&amp;#39; &amp;#39;unsafe-inline&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;img-src &amp;#39;self&amp;#39; data: https://img.shields.io&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;script-src-attr &amp;#39;unsafe-inline&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expect-ct = &lt;span style="color:#87ceeb">&amp;#34;enforce, max-age=30&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> listen = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> port = &lt;span style="color:#f60">443&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ssl = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certificate-file = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/acme/git.terinstock.com/fullchain.pem&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key-file = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/acme/git.terinstock.com/key.pem&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min-version = &lt;span style="color:#87ceeb">&amp;#34;TLSv1.2&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cipher-preference = &lt;span style="color:#87ceeb">&amp;#34;server&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cipher-suite = (lib.concatStringsSep &lt;span style="color:#87ceeb">&amp;#34;:&amp;#34;&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;TLS_AES_128_GCM_SHA256&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;TLS_AES_256_GCM_SHA384&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;TLS_CHACHA20_POLY1305_SHA256&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ECDHE-ECDSA-AES128-GCM-SHA256&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ECDHE-RSA-AES128-GCM-SHA256&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ECDHE-ECDSA-AES256-GCM-SHA384&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ECDHE-RSA-AES256-GCM-SHA384&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ECDHE-ECDSA-CHACHA20-POLY1305&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;ECDHE-RSA-CHACHA20-POLY1305&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;DHE-RSA-AES128-GCM-SHA256&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;DHE-RSA-AES256-GCM-SHA384&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Like with cgit&amp;rsquo;s configuration, &lt;code>h2oConfig&lt;/code> is assigned a derivation that when
evaluated will create a configuration file for h2o. In this configuration one
host is defined and paths are defined for static assets, A fastcgi handler is
configured for the root, which will be used for all paths not matched by a
static file path.&lt;/p>
&lt;p>h2o does not support cgi directly, but ships with a script to proxy through a
fastcgi server. This wrapper is configured with the path to cgit&amp;rsquo;s and the cgit
configuration file defined earlier.&lt;/p>
&lt;p>This is more verbose than strictly neccessary, as I wanted to have high marks on
Qualys&amp;rsquo;s &lt;a href="https://www.ssllabs.com/index.html">SSL Labs&lt;/a> report and &lt;a href="https://securityheaders.com/">Security Headers&lt;/a>. I
imagine in the future I&amp;rsquo;ll have refactored this into smaller functions and
options.&lt;/p>
&lt;p>The documented syntax for adding response headers involves setting the
&lt;code>headers.set&lt;/code> key multiple times. This is not representable in Nix, as Nix will
not allow the same key to be set multiple times. An undocumented syntax using a
YAML sequence instead has been available since 2.3.0-rc2.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>header.set:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#87ceeb">&amp;#34;x-frame-options: \&amp;#34;deny\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#87ceeb">&amp;#34;x-xss-protection: \&amp;#34;1, mode=block\&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Finally, I we need to start the h2o web server. At the time of writing, h2o does
not have a NixOS module, but we can use lower-level modules ourselves.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> systemd.services.h2o = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> description = &lt;span style="color:#87ceeb">&amp;#34;H2O web server&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> after = [ &lt;span style="color:#87ceeb">&amp;#34;network-online.target&amp;#34;&lt;/span> &lt;span style="color:#87ceeb">&amp;#34;acme-git.terinstock.com.service&amp;#34;&lt;/span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wantedBy = [ &lt;span style="color:#87ceeb">&amp;#34;multi-user.target&amp;#34;&lt;/span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> path = [ pkgs.perl pkgs.openssl ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> serviceConfig = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecStart = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.h2o&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/bin/h2o --mode master --conf &lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>h2oConfig&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecReload = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.coreutils&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/bin/kill -s HUP $MAINPID&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecStop = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.coreutils&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/bin/kill -s QUIT $MAINPID&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> User = &lt;span style="color:#87ceeb">&amp;#34;h2o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Group = &lt;span style="color:#87ceeb">&amp;#34;h2o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Type = &lt;span style="color:#87ceeb">&amp;#34;simple&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Restart = &lt;span style="color:#87ceeb">&amp;#34;on-failure&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AmbientCapabilities = &lt;span style="color:#87ceeb">&amp;#34;cap_net_bind_service&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CapabilitiesBoundingSet = &lt;span style="color:#87ceeb">&amp;#34;cap_net_bind_service&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NoNewPrivileges = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LimitNPROC = &lt;span style="color:#f60">64&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> LimitNOFILE = &lt;span style="color:#f60">1048576&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PrivateDevices = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PrivetTmp = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProtectHome = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProtectSystem = &lt;span style="color:#87ceeb">&amp;#34;full&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ReadOnlyPaths = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/acme/&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ReadWriteDirectories = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/h2o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This configures a systemd unit that will be enabled at startup. The &lt;code>ExecStart&lt;/code>
will start h2o without daemonizing, and provides the path to the evaluated h2o
configuration. The &lt;code>PATH&lt;/code> environment variable for the service will be updated
to include Perl (for the fastcgi proxy) and OpenSSL (for OCSP stapling). I may
submit a pull request to nixpkgs to fix the h2o derivation to reference these
directly.&lt;/p>
&lt;p>Additional settings are provided to the unit to configure systemd&amp;rsquo;s sandbox for
the service.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> users.users.h2o = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> group = &lt;span style="color:#87ceeb">&amp;#34;h2o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> home = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/h2o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> createHome = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> isSystemUser = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> users.groups.h2o = { };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Another system user is created, this time for h2o, along with a correspondng
group used shortly.&lt;/p>
&lt;h2 id="acme-certificates">ACME Certificates&lt;/h2>
&lt;p>NixOS ships with great support for using ACME to create TLS certificates, such
as from Let&amp;rsquo;s Encrypt or &lt;a href="https://terinstock.com/post/2021/01/Pebble-and-lego-to-test-ACME-with-NixOS/">your own certificate authority&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> security.acme = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0"># Set to true if you agree to your ACME server&amp;#39;s Terms of Service.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0"># For Let&amp;#39;s Encrypt: https://letsencrypt.org/repository/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> acceptTerms = &lt;span style="color:#7fffd4">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> email = &lt;span style="color:#87ceeb">&amp;#34;terinjokes@gmail.com&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certs = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;git.terinstock.com&amp;#34;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dnsProvider = &lt;span style="color:#87ceeb">&amp;#34;cloudflare&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> credentialsFile = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/secrets/cloudflare.secret&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> group = &lt;span style="color:#87ceeb">&amp;#34;h2o&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To avoiding needing to configure h2o to proxy to lego for HTTP challenges, I
prefer to use the DNS ACME challenges with lego&amp;rsquo;s support for &lt;a href="https://go-acme.github.io/lego/dns/cloudflare/">Cloudflare
DNS&lt;/a>. Configuring lego to do DNS challenges is outside the scope of this
post.&lt;/p>
&lt;h2 id="housekeeping">Housekeeping&lt;/h2>
&lt;p>I configure a few more NixOS options for general housekeeping that don&amp;rsquo;t fit in
the above sections.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> environment.systemPackages = [ pkgs.git ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I install git as a system package so it&amp;rsquo;s avialable should I need to log into
the box to handle something.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services.openssh.passwordAuthentication = &lt;span style="color:#7fffd4">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services.sshguard.enable = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I disable OpenSSH&amp;rsquo;s password authentication mechanisms, as I have a strong
preference to using more secure options. I also enable &lt;a href="https://www.sshguard.net/">sshguard&lt;/a> to
block connections that try to log in with a password anyways.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nix.gc = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> automatic = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> options = &lt;span style="color:#87ceeb">&amp;#34;--delete-older-than 30d&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nix.optimise.automatic = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> system.autoUpgrade = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enable = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> allowReboot = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Configures NixOS to perform routine maintaince in the background. This allows
for NixOS to update at a regular interval, rebooting if needed to install kernel
updates, as well as optimizing and garbage collecting the nix store.&lt;/p>
&lt;p>While the goal in 2021 is to have less machines to manage, this ensures I don&amp;rsquo;t
forget to install security patches because I haven&amp;rsquo;t logged in for a while.&lt;/p></content></item><item><title>Pebble and lego to test ACME with NixOS</title><link>https://terinstock.com/post/2021/01/Pebble-and-lego-to-test-ACME-with-NixOS/</link><pubDate>Thu, 14 Jan 2021 22:04:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2021/01/Pebble-and-lego-to-test-ACME-with-NixOS/</guid><content type="html">&lt;p>When configuring a new service I want to run on NixOS, I often use
&lt;a href="https://github.com/Mic92/nixos-shell">nixos-shell&lt;/a> to quickly standup a temporary VM locally with my new
configuration. When this configuration is a service, I often want to ensure I
have TLS configured properly, including the correct permissions on the
certificate and key files managed by &lt;a href="https://go-acme.github.io/lego/">lego&lt;/a>.&lt;/p>
&lt;p>However, I don&amp;rsquo;t neccessarily want to send unacceptable amounts of traffic to
production ACME servers, or deal with proper validation with their staging
services. Fortunately, we can configure the NixOS VM to start a testing ACME
server, &lt;a href="https://github.com/letsencrypt/pebble">Pebble&lt;/a>, and configure Lego to use it.&lt;/p>
&lt;h2 id="pebble">Pebble&lt;/h2>
&lt;p>Pebble is a small, single-binary ACME server intended for testing. Keys and
certificates are randomnized between calls, but this is fine for an emphermial
VM.&lt;/p>
&lt;p>First, we&amp;rsquo;ll want to configure Pebble to start, which we can do with the
&lt;code>systemd.service&lt;/code> NixOS option. I use the &lt;code>toJSON&lt;/code> builtin function to create a
JSON configuration file for Pebble from a Nix attribute set. I also reference
the default certificate and key from the source package, as they are not yet
copied to the output package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{ pkgs }:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">let&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pebbleConfig = pkgs.writeText &lt;span style="color:#87ceeb">&amp;#34;pebble.json&amp;#34;&lt;/span> (builtins.toJSON {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pebble = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> listenAddress = &lt;span style="color:#87ceeb">&amp;#34;0.0.0.0:14000&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> managementListenAddress = &lt;span style="color:#87ceeb">&amp;#34;0.0.0.0:15000&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certificate = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.pebble.src&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/test/certs/localhost/cert.pem&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateKey = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.pebble.src&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/test/certs/localhost/key.pem&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> httpPort = &lt;span style="color:#f60">5002&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tlsPort = &lt;span style="color:#f60">5001&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ocspResponderURL = &lt;span style="color:#87ceeb">&amp;#34;&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> externalAccountBindingRequired = &lt;span style="color:#7fffd4">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">in&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> systemd.services.pebble = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> description = &lt;span style="color:#87ceeb">&amp;#34;Pebble ACME Test Server&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> after = [ &lt;span style="color:#87ceeb">&amp;#34;network-online.target&amp;#34;&lt;/span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> wantedBy = [ &lt;span style="color:#87ceeb">&amp;#34;multi-user.target&amp;#34;&lt;/span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> serviceConfig = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Environment = [ &lt;span style="color:#87ceeb">&amp;#34;PEBBLE_VA_NOSLEEP=1&amp;#34;&lt;/span> &lt;span style="color:#87ceeb">&amp;#34;PEBBLE_VA_ALWAYS_VALID=1&amp;#34;&lt;/span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecStart = &lt;span style="color:#87ceeb">&amp;#34;&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.pebble&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/bin/pebble -config &lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pebbleConfig&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DynamicUser = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can modify some behavior of Pebble through environment variables. I set two
for better behavior in a development VM:&lt;/p>
&lt;ul>
&lt;li>&lt;code>PEBBLE_VA_NOSLEEP&lt;/code> disables any artifical sleeps in the issuance path, as
we&amp;rsquo;re not interested in testing lego&amp;rsquo;s validationg polling.&lt;/li>
&lt;li>&lt;code>PEBBLE_VA_ALWAYS_VALID&lt;/code> disables all validation methods, and assumes domains
have already been successfully validated.&lt;/li>
&lt;/ul>
&lt;p>One warning from the Pebble documentation bares repeating here:&lt;/p>
&lt;blockquote>
&lt;p>Pebble is &lt;strong>NOT INTENDED FOR PRODUCTION USE&lt;/strong>. Pebble is for &lt;strong>testing only&lt;/strong>.&lt;/p>&lt;/blockquote>
&lt;h2 id="lego">lego&lt;/h2>
&lt;p>lego is a widely used ACME client that implements all of the ACME challenges,
bindings for major DNS providers, and support for bundling certificates. It&amp;rsquo;s
used as the implementation to NixOS&amp;rsquo;s &lt;code>security.acme&lt;/code> options. This means most
of the configuration is already done for us.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> security.acme = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server = &lt;span style="color:#87ceeb">&amp;#34;https://localhost:14000/dir&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> acceptTerms = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> email = &lt;span style="color:#87ceeb">&amp;#34;webmaster@example.com&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> certs = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;example.com&amp;#34;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> webroot = &lt;span style="color:#87ceeb">&amp;#34;/var/www/example.com&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> group = &lt;span style="color:#87ceeb">&amp;#34;prosody&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Despite not needing to implement any challenges, as we&amp;rsquo;ve disabled them in
Pebble, we still need to provide &lt;code>webroot&lt;/code> configuration. The &lt;code>group&lt;/code> attribute
is used to set the group permission on the generated certificates and keys, and
should be set to the same user your server is running as.&lt;/p>
&lt;p>There&amp;rsquo;s one farther complication: lego does not trust the certificate authority
used by the ACME server, and thus it won&amp;rsquo;t issue requests out of the box. We can
configure the lego to trust these certificates by setting the environment of the
generated service unit.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> systemd.services.&lt;span style="color:#87ceeb">&amp;#34;acme-example.com&amp;#34;&lt;/span>.serviceConfig.Environment = [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;LEGO_CA_CERTIFICATES=&lt;/span>&lt;span style="color:#87ceeb">${&lt;/span>pkgs.pebble.src&lt;span style="color:#87ceeb">}&lt;/span>&lt;span style="color:#87ceeb">/test/certs/pebble.minica.pem&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;LEGO_CA_SERVER_NAME=localhost&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="usage">Usage&lt;/h2>
&lt;p>The certificates and keys created by &lt;code>security.acme&lt;/code> are stored underneath
&lt;code>/var/lib/acme/&lt;/code>. This can be provided to your server&amp;rsquo;s configuration.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nix" data-lang="nix">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> services.prosody = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enable = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> virtualHosts.&lt;span style="color:#87ceeb">&amp;#34;example.com&amp;#34;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enabled = &lt;span style="color:#7fffd4">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ssl.cert = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/acme/git.terinstock.com/fullchain.pem&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ssl.key = &lt;span style="color:#87ceeb">&amp;#34;/var/lib/acme/git.terinstock.com/key.pem&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item><item><title>K9P: Kubernetes as 9P Files</title><link>https://terinstock.com/talk/2019/11/K9P-Kubernetes-as-9P-Files/</link><pubDate>Wed, 20 Nov 2019 11:55:00 -0800</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/talk/2019/11/K9P-Kubernetes-as-9P-Files/</guid><content type="html">&lt;p>K9P is a virtual filesystem that allows you to interact with Kubernetes clusters via a 9P filesystem, allowing us
to continue to believe that everything is a file. Presented at &lt;a href="https://events19.linuxfoundation.org/events/kubecon-cloudnativecon-north-america-2019/">KubeCon + CloudNativeCon North America 2019&lt;/a>.&lt;/p>
&lt;p>&lt;iframe id="talk_frame_577573" class="speakerdeck-iframe" src="https://speakerdeck.com/player/afbb112c263744a39d2432e9eb91289c" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen">&lt;/iframe>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube-nocookie.com/embed/qykUb8-Nxxw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;/p></content></item><item><title>Building a Hardware MIDI Player</title><link>https://terinstock.com/talk/2019/02/Building-a-Hardware-MIDI-Player/</link><pubDate>Sat, 02 Feb 2019 15:30:00 +0100</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/talk/2019/02/Building-a-Hardware-MIDI-Player/</guid><content type="html">&lt;p>Building a MIDI player using Go and various Linux system interfaces, presented at &lt;a href="https://fosdem.org/2019/schedule/">FOSDEM 2019&lt;/a>.&lt;/p>
&lt;p>&lt;iframe id="talk_frame_491828" class="speakerdeck-iframe" src="https://speakerdeck.com/player/cd4d70fe6fd84990ab11a32730b0126f" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen">&lt;/iframe>
&lt;div
style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"
>
&lt;iframe
src="https://iframe.cloudflarestream.com/f16388f46fc1d28d8143470b03ae8e3b"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
allowfullscreen
frameboarder="0"
title="Cloudflare Stream Video"
>&lt;/iframe>
&lt;/div>
&lt;/p></content></item><item><title>memfd_create: Temporary in-memory files with Go and Linux</title><link>https://terinstock.com/post/2018/10/memfd_create-Temporary-in-memory-files-with-Go-and-Linux/</link><pubDate>Sat, 20 Oct 2018 23:21:27 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2018/10/memfd_create-Temporary-in-memory-files-with-Go-and-Linux/</guid><content type="html">&lt;p>My &lt;a href="https://terinstock.com/post/2018/09/Hardware-MIDI-Player-Part-One/">MIDI Player&lt;/a> project requires interfacing with libraries and system calls
that don&amp;rsquo;t yet have Go equivalents. A few of these C libraries expect to be
invoked with file paths or file descriptors.&lt;/p>
&lt;p>If the data is already available as a file on a mounted disk, this is fine,
I can pass the path or descriptor that already exists. I run into a problem,
however, as soon as I want to work with data that isn&amp;rsquo;t directly backed by
a file, such as after being manipulated by the program, or having been unpacked
from a container format.&lt;/p>
&lt;p>There&amp;rsquo;s a simple way to get a file path: create a temporary file somewhere.
This requires having a writable filesystem mounted, and cleaning up the files
when no longer required. On a device that&amp;rsquo;s intended to be used as an
appliance, I don&amp;rsquo;t have anywhere writable.&lt;/p>
&lt;p>In Linux 3.17 and later, I have another option &lt;a href="https://jlk.fjfi.cvut.cz/arch/manpages/man/core/man-pages/memfd_create.2.en">&lt;code>memfd_create(2)&lt;/code>&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>memfd_create() creates an anonymous file and returns a file descriptor that
refers to it. The file behaves like a regular file, and so can be modified,
truncated, memory-mapped, and so on. However, unlike a regular file, it lives
in RAM and has a volatile backing storage. Once all references to the file are
dropped, it is automatically released.&lt;/p>&lt;/blockquote>
&lt;p>This checks the boxes for most of my requirements: a file descriptor to
something that acts like a normal file, backed by memory, doesn&amp;rsquo;t require any
writable mount points, and automatically cleans up files when closed.&lt;/p>
&lt;p>The system call is available in the Go &lt;a href="https://godoc.org/golang.org/x/sys/unix">&lt;code>x/sys/unix&lt;/code>&lt;/a> package, along with
a few companion calls.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">package&lt;/span> main
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">import&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;golang.org/x/sys/unix&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">// memfile takes a file name used, and the byte slice&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">// containing data the file should contain.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">//&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">// name does not need to be unique, as it&amp;#39;s used only&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">// for debugging purposes.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">//&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#0f0">// It is up to the caller to close the returned descriptor.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">func&lt;/span> &lt;span style="color:#ff0">memfile&lt;/span>(name &lt;span style="color:#ee82ee">string&lt;/span>, b []&lt;span style="color:#ee82ee">byte&lt;/span>) (&lt;span style="color:#ee82ee">int&lt;/span>, &lt;span style="color:#ee82ee">error&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fd, err := unix.&lt;span style="color:#ff0">MemfdCreate&lt;/span>(name, &lt;span style="color:#f60">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> &lt;span style="color:#f60">0&lt;/span>, fmt.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;MemfdCreate: %v&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> err = unix.&lt;span style="color:#ff0">Ftruncate&lt;/span>(fd, int64(len(b)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> &lt;span style="color:#f60">0&lt;/span>, fmt.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;Ftruncate: %v&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data, err := unix.&lt;span style="color:#ff0">Mmap&lt;/span>(fd, &lt;span style="color:#f60">0&lt;/span>, len(b), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> &lt;span style="color:#f60">0&lt;/span>, fmt.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;Mmap: %v&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> copy(data, b)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> err = unix.&lt;span style="color:#ff0">Munmap&lt;/span>(data)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> &lt;span style="color:#f60">0&lt;/span>, fmt.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;Munmap: %v&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">return&lt;/span> fd, &lt;span style="color:#f00">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Some libraries require a file path instead of a file descriptor. Fortunately,
we can turn a file descriptor into a path by way of the proc filesystem using
the symlinks in the /proc/self/fd directory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">package&lt;/span> main
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">import&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;os&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">func&lt;/span> &lt;span style="color:#ff0">main&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fd, err != &lt;span style="color:#ff0">memfile&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;hello&amp;#34;&lt;/span>, []byte(&lt;span style="color:#87ceeb">&amp;#34;hello world!&amp;#34;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> log.&lt;span style="color:#ff0">Fatalf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;memfile: %v&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// filepath to our newly created in-memory file descriptor&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fp := fmt.&lt;span style="color:#ff0">Sprintf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;/proc/self/fd/%d&amp;#34;&lt;/span>, fd)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// create an *os.File, should you need it&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// alternatively, pass fd or fp as input to a library.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f := os.&lt;span style="color:#ff0">NewFile&lt;/span>(uintptr(fd), fp)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">defer&lt;/span> f.&lt;span style="color:#ff0">Close&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Eventually, I&amp;rsquo;ll replace these C libraries with Go packages that operate on
&lt;code>io.Reader&lt;/code>, and this workaround will become unnecessary. Until then, I&amp;rsquo;m glad
the option is available.&lt;/p></content></item><item><title>Hardware MIDI Player (Part One)</title><link>https://terinstock.com/post/2018/09/Hardware-MIDI-Player-Part-One/</link><pubDate>Mon, 17 Sep 2018 00:35:55 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2018/09/Hardware-MIDI-Player-Part-One/</guid><content type="html">&lt;h2 id="overview">Overview&lt;/h2>
&lt;figure>&lt;a href="https://terinstock.com/media/fc/f0760ee810f82952727a4b8afd83dc4d3cf962ef4b993ce71df61163ea0f1a.jpeg">&lt;img src="https://terinstock.com/media/fc/f0760ee810f82952727a4b8afd83dc4d3cf962ef4b993ce71df61163ea0f1a:800.jpeg">&lt;/a>&lt;figcaption>
&lt;h4>Raspberry Pi MIDI Player&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Last December, I watched Techmoan&amp;rsquo;s YouTube video on the Roland
&lt;a href="https://www.youtube.com/watch?v=5ks3ucumilU">MT-80S&lt;/a>, a player built to help teach people how to play songs, that
used HD 3.5&amp;quot; floppy disks as storage medium for MIDI files. Since then AkBKukU
has done a video on the &lt;a href="https://www.youtube.com/watch?v=O-6xUhR9JAY">Yamaha Disk Orchestra DOU-10&lt;/a>, that used DD
disks instead.&lt;/p>
&lt;p>As I enjoy listening to chiptune and DOS-era video game music, I was keen on
having a player in my collection. Cursory searches for players sold online
resulted in typically high eBay prices. Considering my growth in ability with
electronics in the past year, I decided to forgo the market and build one
myself.&lt;/p>
&lt;p>The vision in my mind belongs in the mid-90s. My completed player shouldn&amp;rsquo;t
feel out of place alongside a tape deck and CD changer. The insides, however,
should be built from modern components.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;h2 id="hardware">Hardware&lt;/h2>
&lt;figure>&lt;a href="https://terinstock.com/media/e9/5e117f6004dcf0ad422aa25aa93bd74d2d974769ee78df42b121051302b5fe.jpeg">&lt;img src="https://terinstock.com/media/e9/5e117f6004dcf0ad422aa25aa93bd74d2d974769ee78df42b121051302b5fe:800.jpeg"
alt="Raspberry Pi hidden beneath floppy drive and mess of wires.">&lt;/a>
&lt;/figure>
&lt;p>I began with the internal hardware, which is somewhere underneath all those
wires.&lt;/p>
&lt;p>I&amp;rsquo;ve built the project using Raspberry Pi 3B. This is significantly more
powerful than required for playing back MIDI files.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/3d/fa3360ffc02155f7d82a81408d3d6e00641ff19807be2f3a82a891ce29f627.jpeg">&lt;img src="https://terinstock.com/media/3d/fa3360ffc02155f7d82a81408d3d6e00641ff19807be2f3a82a891ce29f627:800.jpeg"
alt="Two 3.5-inch floppy drives stacked on top of each other, disconnected.">&lt;/a>
&lt;/figure>
&lt;p>In fact, I had originally planned to use an Arduino Uno as a rudimentary
central processor and floppy drive controller. However, I was unable to
reliably control any of the floppy drives shown here—they might not even work
properly—and decided to switch to using a USB floppy drive that I could more
easily verify. I switched to the Raspberry Pi because it is a better USB host
than an Arduino.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/36/4a627e8905d0224e06f929b7e62c70e1dace1afed6ceb9ea62c6fdd68976b9.jpeg">&lt;img src="https://terinstock.com/media/36/4a627e8905d0224e06f929b7e62c70e1dace1afed6ceb9ea62c6fdd68976b9:800.jpeg"
alt="USB floppy drive, on desk.">&lt;/a>&lt;figcaption>
&lt;h4>Maccally USB Floppy Disk Drive for Mac&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>I settled on a rather generic USB floppy drive. Well, not just any USB floppy
drive, but one &amp;ldquo;for Mac&amp;rdquo;. Based on the stripe design, I&amp;rsquo;m guessing it was meant
to pair (or, pear) with the fruit-colored iBook G3 clamshells. Fortunately, the
plastic case comes off simply enough.&lt;/p>
&lt;p>This choice of drive proved to be troublesome as I would soon find myself on
a pretty significant detour as a result.&lt;/p>
&lt;figure>&lt;a href="https://terinstock.com/media/89/cc0b0a19f6427d53fdf3c4576b12c47c927a096c0cd1fe6da52d8b4e75a064.jpeg">&lt;img src="https://terinstock.com/media/89/cc0b0a19f6427d53fdf3c4576b12c47c927a096c0cd1fe6da52d8b4e75a064:800.jpeg"
alt="Comoponents setting on desk, Dreamblaster S2, Protoboard, Raspberry Pi, and quarter-inch jack">&lt;/a>
&lt;/figure>
&lt;p>I used the &lt;a href="https://www.amazon.com/MakerSpot-Raspberry-Protoboard-Breadboard-Prototyping/dp/B01M3SI88S">Protoboard&lt;/a> hat from MakerSpot for the remainder of my
hardware work. The Pi&amp;rsquo;s serial data and power lines are connected through to
a 90s-era &lt;a href="http://members.home.nl/c.kersten/">WaveBlaster-compatible&lt;/a> 2x13&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> header. I&amp;rsquo;m used an
absolutely tiny &lt;a href="https://www.serdashop.com/waveblaster">Dreamblaster S2&lt;/a> as a modern General MIDI synth.
The output from the synthesis chip, a SAM2695, was routed to the Pi&amp;rsquo;s 3.5mm AV
jack. A proper quarter-inch jack has been ordered.&lt;/p>
&lt;h2 id="software">Software&lt;/h2>
&lt;p>As a software guy, I expected the hardware to be the hard part. As Caesar would
have said, &amp;ldquo;The fault, dear Brutus, is not in our stars, but in our software.&amp;rdquo;&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>&lt;/p>
&lt;p>So far I&amp;rsquo;ve only written a rather trivial MIDI player. The path to an SMF or
RIFF encoded MIDI file is provided as the command line argument, the software
decodes and at the appropriate time sends the MIDI event to the S2
daughterboard for synthesis. I also confirmed manual playback of files on
a floppy disk worked.&lt;/p>
&lt;p>Shortly thereafter, I switched from Raspbian to a stripped down Linux
distribution. Much to my horror, I found the floppy drive no longer worked. Any
attempt to mount or otherwise interact with the floppy drive would completely
freeze the running program, and no amount of SIGINT or SIGTERM would regain
control. The floppy drive had to be disconnected from the Pi.&lt;/p>
&lt;p>I spent the next several weeks debugging why the floppy drive stopped working.
I started with debugging the floppy drive itself, then confirmed the power
supply was outputting the correct voltages and was clean. I even recompiled the
Linux kernel with different options!&lt;/p>
&lt;p>Eventually, I discovered the drive worked with the 4.14 Linux kernel, but
stopped working some point thereafter. Facing an 8-month range of changes to
the kernel, I turned to git-bisect and started a week-long hunt for the
breaking change. Since this range was so large, and the underlying files so
different, setting up a compilation cache did not decrease the rebuild
durations.&lt;/p>
&lt;p>After at least 16 bisection points, I was lead to &lt;a href="https://github.com/torvalds/linux/commit/38d2b5fb75c15923fb89c32134516a623515bce4">a single commit&lt;/a>
in the kernel driver for the USB controller used by the Raspberry Pi, the
DesignWare USB2. In this change, a communication delay was added after multiple
successive NAKs in the USB protocol, to allow low-speed Chromebooks (and other
ARM-based devices) to do other work, instead of being tied up in an interrupt
loop.&lt;/p>
&lt;p>The commit requests a 1 millisecond delay, but uses the kernel&amp;rsquo;s low-resolution
timer API. On devices where this timer is coarse, like the Raspberry Pi, this
delay might actually be closer to 10-20 milliseconds. Fortunately, most USB
devices tolerate delays in the communication. Unfortunately, this drive is not
one that does so. This drive only tolerates delays of up to 5 milliseconds.&lt;/p>
&lt;p>With some help from some of the maintainers of the module, I was able to use
the kernel&amp;rsquo;s Ftrace functionality to be able to debug without losing the use
of my serial TTY. I was then able to rapidly iterate with changes to the kernel
module, and load the rebuilt module into the running kernel using rmmod and
insmod.&lt;/p>
&lt;p>I have &lt;a href="https://patchwork.kernel.org/patch/10593569/">submitted my first ever kernel patch&lt;/a>&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>, to convert the delay to the high
resolution API. This API has a finer timer, allowing the delay to operate much
closer to the requested 1 millisecond on retries.&lt;/p>
&lt;h2 id="next-steps">Next Steps&lt;/h2>
&lt;h3 id="complete-player-software">Complete Player Software&lt;/h3>
&lt;p>With my kernel patch in hand, I can now return to work on the MIDI player
software. I want to be able to insert a floppy disk and, with one button, load
all the MIDI files stored there into a playlist and began playback. Then I will
add buttons for the other basic playback controls.&lt;/p>
&lt;p>I&amp;rsquo;ve been happy with using &lt;a href="https://gokrazy.org/">go-krazy&lt;/a> as the Linux distribution. Writing the MIDI
player in Go has been pretty nice, and it makes the whole project feel fast.&lt;/p>
&lt;p>I&amp;rsquo;m now battle testing the MIDI parser to make sure my player can withstand
whatever is thrown at it. I hope to make this available as open source software
by the time I publish the next part of this blog.&lt;/p>
&lt;p>Thinking farther into the future, the following project would add a display for
album and track metadata.&lt;/p>
&lt;h3 id="custom-pcb-and-case">Custom PCB and Case&lt;/h3>
&lt;p>I also aim to design a custom PCB for the synthesizer, buttons, display, and
RTC, as well as integration with a power supply that can signal the Pi to
shutdown before removing power.&lt;/p>
&lt;p>I haven&amp;rsquo;t yet decided if I&amp;rsquo;ll do this PCB as a custom hat, or, since I&amp;rsquo;m not
using the majority of the hardware functions, as a board housing the Raspberry
Pi Compute Module. The decision here affects the case design.&lt;/p>
&lt;p>Like the software, I plan on releasing both the PCB and the case designs as&lt;/p>
&lt;h3 id="midi-albums">MIDI Albums&lt;/h3>
&lt;p>To really sell the illusion that, on an alternate timeline for the 90s, we
bought our pre-recorded music as MIDI on floppies, I&amp;rsquo;d love to commission
musicians to compose some awesome tracks that can be packaged onto a floppy.
Let me know via &lt;a href="mailto:terin@terinstock.com">email&lt;/a> if this appeals to you.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Except, perhaps, the floppy drive itself.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>There are no 12V rails in the current design, as the
WaveBlaster-compatible, which we&amp;rsquo;re able to talk about, doesn&amp;rsquo;t require it.
For compatibility, I&amp;rsquo;m wanting to include them in the final design.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>If not Caesar, then perhaps Peter van Houten.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>At this time, it is still pending review for a future kernel release.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Black-box Serial Testing</title><link>https://terinstock.com/post/2018/07/Black-box-Serial-Testing/</link><pubDate>Fri, 27 Jul 2018 17:43:56 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2018/07/Black-box-Serial-Testing/</guid><content type="html">&lt;p>Occasionally, we all end up working on a program that could, at best, be
described as a ball of mud. We want to refactor it into smaller, more testable
components, but we don’t want to risk breaking it in the process.&lt;/p>
&lt;p>Enter the concept of “black-box testing”. In black-box testing, the
functionality of a program is tested by a tester, without the ability to
directly leverage implementation details of the program.&lt;/p>
&lt;p>For some programs, all we need to do is give it input, and observe the output.
As the software grows in complexity, we may have to compare created files,
setup mock web servers, and test databases. Many the great author and many the
great blog have written on these problems.&lt;/p>
&lt;p>The “ball of mud” side project I’m currently working on communicates over
a serial port. This presents a larger challenge for a tester: how can we
observe behavior of the application with the serial port from our test?
Pointing the program at a file won’t work, as it wants to connect to a remote
device and configure options, such as baud rate and parity bits.&lt;/p>
&lt;p>The long evolutionary history of UNIX provides us with one solution. In the
early days of UNIX, the output from the computer was printed onto serially
connected teleprinter. When technology marched on, terminal emulators replaced
the physical teleprinter, and the functionality needed to create these
pseudo-teleprinters was added to the kernel.&lt;/p>
&lt;p>As these teleprinters were originally serially connected, their modern virtual
forms retain their serial nature.&lt;/p>
&lt;p>In our test harness, we can create a new pseudo-teleprinter then pass the
emulated end to our program as the serial port. This example uses the Go test
harness, but equivalent functionality is available in most programming and
scripting languages, so you can use your favorite.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">package&lt;/span> main
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">import&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;io/ioutil&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;os/exec&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;testing&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;github.com/google/go-cmp/cmp&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;github.com/pkg/term/termios&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">func&lt;/span> &lt;span style="color:#ff0">TestCLI&lt;/span>(t *testing.T) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// create and open the pseudo tty master, and the linked&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// child tty.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pty, tty, err := termios.&lt;span style="color:#ff0">Pty&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;unable to create pty: %s\n&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// execute our program, passing in the child tty name (eg, /dev/pts/1)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// as our serial port paramater.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cmd := exec.&lt;span style="color:#ff0">Command&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;ball-of-mud&amp;#34;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#34;-serialport&amp;#34;&lt;/span>, tty.&lt;span style="color:#ff0">Name&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output, err := cmd.&lt;span style="color:#ff0">Output&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;error running ball-of-mud: %s\n&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// close the child tty. if we forget to do this the&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// master pseudo tty will wait for more data forever.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> tty.&lt;span style="color:#ff0">Close&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// read all the written serial data from the master&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// pseudo tty. ReadAll reads until EOF or an error is&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// returned, but Linux sends EIO when attempting to read&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// from a master with no open children. So let&amp;#39;s just&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// look for the error string.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bytes, err := ioutil.&lt;span style="color:#ff0">ReadAll&lt;/span>(pty)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> err != &lt;span style="color:#f00">nil&lt;/span> &amp;amp;&amp;amp; err.&lt;span style="color:#ff0">Error&lt;/span>() != &lt;span style="color:#87ceeb">&amp;#34;read ptm: input/output error&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;error reading pty: %s\n&amp;#34;&lt;/span>, err)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// confirm we saw the expected text from standard out&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> diff := cmd.&lt;span style="color:#ff0">Diff&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;hello&amp;#34;&lt;/span>, string(output)); diff != &lt;span style="color:#87ceeb">&amp;#34;&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;unexpected output (-want +got):\n%s&amp;#34;&lt;/span>, diff)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#0f0">// confirm we saw the expected bytes written to the serial device&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f00">if&lt;/span> diff := cmd.&lt;span style="color:#ff0">Diff&lt;/span>([]&lt;span style="color:#ee82ee">byte&lt;/span>{&lt;span style="color:#87ceeb">&amp;#34;world&amp;#34;&lt;/span>}, bytes); diff != &lt;span style="color:#87ceeb">&amp;#34;&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t.&lt;span style="color:#ff0">Errorf&lt;/span>(&lt;span style="color:#87ceeb">&amp;#34;unexpected serial (-want +got):\n%s&amp;#34;&lt;/span>, diff)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As the two parts of the pseudo-teleprinter are connected to each other, writes
to one will be readable from the other side. Thus duplex communication is
possible for more complex black-box tests.&lt;/p>
&lt;p>This method isn’t entirely perfect. For example, on Linux, it’s not always
possible to observe the configured baud rate. Even with these limitations, it’s
still a helpful tool in our toolkit.&lt;/p></content></item><item><title>Controller: Extending your K8s cluster</title><link>https://terinstock.com/talk/2018/05/Controller-Extending-your-K8s-cluster/</link><pubDate>Fri, 04 May 2018 14:45:00 +0200</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/talk/2018/05/Controller-Extending-your-K8s-cluster/</guid><content type="html">&lt;p>Building your own Kubernetes Controllers to extend the functionality of your cluster, presented at
&lt;a href="https://events.linuxfoundation.org/events/kubecon-cloudnativecon-europe-2018/">KubeCon + CloudNativeCon Europe 2018&lt;/a>.&lt;/p>
&lt;p>&lt;iframe id="talk_frame_442352" class="speakerdeck-iframe" src="https://speakerdeck.com/player/413d79d1f9d64a5996ea23bf72367ebc" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen">&lt;/iframe>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube-nocookie.com/embed/TM-2GgQ6Q2A?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;/p></content></item><item><title>Go Internalization and Localization</title><link>https://terinstock.com/talk/2018/05/Go-Internalization-and-Localization/</link><pubDate>Wed, 02 May 2018 19:00:00 +0200</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/talk/2018/05/Go-Internalization-and-Localization/</guid><content type="html">&lt;p>An introduction to Go&amp;rsquo;s text package, which allows developers to build localized applications and services.
Presented at &lt;a href="https://www.meetup.com/Go-Cph/">Go Cph&lt;/a> on May 2, 2018.&lt;/p>
&lt;iframe id="talk_frame_442170" class="speakerdeck-iframe" src="https://speakerdeck.com/player/cf7b122a2a504206ab1ad5b45d199c2f" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen">&lt;/iframe></content></item><item><title>Extending Kubernetes</title><link>https://terinstock.com/talk/2018/05/Extending-Kubernetes/</link><pubDate>Tue, 01 May 2018 18:50:00 +0200</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/talk/2018/05/Extending-Kubernetes/</guid><content type="html">&lt;p>Talk on extending Kubernetes Dyanmic Admission control with webhooks, presented at &lt;a href="https://www.meetup.com/GOTO-Nights-CPH/events/249895973/">GOTO Cph&lt;/a>
in Copenhagen. This talk was given as being related to our talk at &lt;a href="https://events.linuxfoundation.org/events/kubecon-cloudnativecon-europe-2018/">KubeCon + CloudNativeCon Europe&lt;/a>.&lt;/p>
&lt;iframe id="talk_frame_442031" class="speakerdeck-iframe" src="https://speakerdeck.com/player/988fcc7d4f3944dbb6eb96b407bd271d" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen">&lt;/iframe></content></item><item><title>Game Boy Collage #1</title><link>https://terinstock.com/post/2017/11/game-boy-collage-1/</link><pubDate>Mon, 13 Nov 2017 05:34:31 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2017/11/game-boy-collage-1/</guid><content type="html">&lt;p>For a few months now, I&amp;rsquo;ve wanted to work on some new old-school artwork: creating a collage from
the internals of a classic Nintendo Game Boy. Similar to the How Things Work series of books, each
component of the Game Boy would have annotations and detailed schematics. Unlike those books, the
Game Boy would continue to work. After all, if it doesn&amp;rsquo;t work, it&amp;rsquo;s not very fun.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/trio.jpg">&lt;figcaption>
&lt;h4>8-bit handhelds&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Although I&amp;rsquo;ll only be taking apart one, I have three 8-bit handhelds.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-front-smurfs.jpg">&lt;figcaption>
&lt;h4>Game Boy (DMG) playing Smurfs&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>On the left is the original gray Game Boy. It sports a super reflective black and green screen,
guzzling 4 AA batteries, and a big boxy shape.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/gbc-front-mariotennis.jpg">&lt;figcaption>
&lt;h4>Game Boy Color (GBC) playing Mario Tennis&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>To the right in the center is a lime green Game Boy Color, capable of up to 32K colors, and backward
compatible with the original Game Boy. I have fond memories playing on my lime green Game Boy Color
growing up. I can&amp;rsquo;t even guess how many hours I put on it.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/hfgbc-front-mariotennis.jpg">&lt;figcaption>
&lt;h4>GB Boy Colour playing Mario Tennis&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Finally, on the rightmost side, is the GB Boy Colour, a 2010s clone of the Color. The screen is the
wrong aspect ratio and the wrong resolution. The custom processor runs the games at the wrong clock
speed. The audio hardware, while loud, is a poor emulation of the original. It does, and this is the
saving grace, have a backlight LCD screen.&lt;/p>
&lt;p>My understanding is that this uses a custom System on Chip design to emulate the Game Boy Color.
I haven&amp;rsquo;t taken it apart, but I think it will be interesting to do so in a future post.&lt;/p>
&lt;p>&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-back.jpg">&lt;figcaption>
&lt;h4>Backside of DMG. Serial Number G27198639&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-back-battery.jpg">&lt;figcaption>
&lt;h4>Backside of DMG with the battery compartment visible&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;p>Moving back to the original, gray, Game Boy unit, before we take it apart, I should take some
pictures of the outside. The serial number and service stickers aren&amp;rsquo;t in pristine condition, but
they&amp;rsquo;re still in pretty decent shape! I&amp;rsquo;d like to repair both labels; I&amp;rsquo;m looking into the best
repair methods.&lt;/p>
&lt;p>&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-side-left.jpg">&lt;figcaption>
&lt;h4>Left side of the DMG&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-side-right.jpg">&lt;figcaption>
&lt;h4>Right side of the DMG&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-side-bottom.jpg">&lt;figcaption>
&lt;h4>Bottom side of the DMG&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;p>The sides look great, too. I&amp;rsquo;m surprised that the condition is this good. It includes a 3.5mm
headphone jack, as electronics should.&lt;/p>
&lt;p>&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-lcd-back.jpg">&lt;figcaption>
&lt;h4>Backside of the LCD board in the case&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-mb-back.jpg">&lt;figcaption>
&lt;h4>Backside of the main board in the case&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;p>After popping it open, I find that it&amp;rsquo;s relatively dust free. The Game Boy separates into two main
pieces, connected by a ribbon cable. The top board contains the LCD screen, input controls, and
contrast knob. The bottom board houses the main CPU, coprocessors, audio amplifiers, volume knob,
and cartridge loader. Two daughterboards are attached to the bottom board, one for power regulation,
the other for the 3.5mm jack.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-mb-full.jpg">&lt;figcaption>
&lt;h4>Front of the main board, along with the power and audio jack boards&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>The manufacturing date of both boards is July 1992, based on the date codes stamped. That means
these capacitors are at least 25 years old. Since most manufacturers rate their capacitors for 15-20
years, I should replace them before this project gets to far along. The boards are otherwise in
great shape.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-front-case-inside.jpg">&lt;figcaption>
&lt;h4>Inside of the front case&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Unfortunately, on closer inspection, this unit has two broken standoffs on the front case. I don&amp;rsquo;t
think it&amp;rsquo;s a problem: you won&amp;rsquo;t be able to see them in the final design.&lt;/p>
&lt;p>&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-battery-contacts.jpg">&lt;figcaption>
&lt;h4>DMG&amp;#39;s battery contacts are covered in acid&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-battery-compartment-acid.jpg">&lt;figcaption>
&lt;h4>Battery acid left behind in DMG&amp;#39;s battery compartment&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;p>The biggest problem is the corrosion in the battery compartment. All the terminals were pretty
corroded. They were pretty easy to pop out, but lots of battery acid stayed behind in the case.&lt;/p>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-lime-juice-solution.jpg">&lt;figcaption>
&lt;h4>A quick aside to the kitchen. For Science!&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;p>Silly me, I had no terminal cleaner available, nor any other acidic household cleaner. So
I improvised a bit: I had an extra lime in the kitchen from a previous meal. Lime juice is a citric
acid with a pH about the same as lemon juice and vinegar, so I figured it would work just as well.
It&amp;rsquo;s also fitting, because, you know, both color handhelds are lime green.&lt;/p>
&lt;p>&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-battery-contacts-clean.jpg">&lt;figcaption>
&lt;h4>No more acid remains&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://terinstock.com/media/dmg/dmg-battery-compartment-clean.jpg">&lt;figcaption>
&lt;h4>No more acid remains&lt;/h4>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;p>After cleaning, it appears the battery acid etched some of the shininess away. But it otherwise
cleaned up nicely. I cleaned up the battery acid in the case using a cotton swab along with the lime
juice.&lt;/p>
&lt;p>That&amp;rsquo;s it for now. In the next installment, I will share the plan for how breaking up the Game Boy
and wiring it together. I also have ideas for future enhancements I think would be cool. I&amp;rsquo;ve also
ordered a set of replacement capacitors; I&amp;rsquo;ll do a post on replacing them once they arrive.&lt;/p></content></item><item><title>Aliasing JavaScript Modules</title><link>https://terinstock.com/post/2017/06/Aliasing-JavaScript-Modules/</link><pubDate>Tue, 20 Jun 2017 17:45:55 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2017/06/Aliasing-JavaScript-Modules/</guid><content type="html">&lt;p>A number of years ago I wrote a &lt;a href="https://terinstock.com/post/2014/02/Replacing-packages-in-a-Browserify-bundle/#the-solution">blog post&lt;/a> detailing how to replace modules in a
Browserify bundle. In the interceding years, the JavaScript ecosystem has had at
least three lifetimes, and we&amp;rsquo;re now due for an update.&lt;/p>
&lt;h2 id="browserify">Browserify&lt;/h2>
&lt;p>As the last post focused entirely on Browserify, I&amp;rsquo;ll first revisit it first. As
noted in the previous post, the &lt;code>browserify-swap&lt;/code> transform is designed to swap
individual files, not modules. We were able to work around it by defining the
configuration as a RegExp, but it&amp;rsquo;s easy to get wrong. You can now use the
&lt;a href="https://github.com/benbria/aliasify">aliasify&lt;/a> transform, which does support replacing modules.&lt;/p>
&lt;p>After installing, enable the transform, and add a new section to your
package.json.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;browserify&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;transform&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;aliasify&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;aliasify&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;aliases&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;underscore&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;lodash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In addition to aliasing modules, aliasify supports replacments that are relative
to the file calling require, as well as using RegExps to define replacement.&lt;/p>
&lt;h2 id="webpack">Webpack&lt;/h2>
&lt;p>Webpack supports aliasing modules &lt;a href="https://webpack.js.org/configuration/resolve/#resolve-alias">right out of the box&lt;/a>, no plugins needed. Just
add an additional section to your Webpack configuration.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;resolve&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;alias&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;underscore&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;lodash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="babel">Babel&lt;/h2>
&lt;p>You can also setup aliases in Babel with the &lt;a href="https://github.com/tleunen/babel-plugin-module-resolver">&lt;code>babel-plugin-module-resolver&lt;/code>&lt;/a> plugin. Like
the above, just add a section to your Babel configuration.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;plugins&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#87ceeb">&amp;#34;module-resolver&amp;#34;&lt;/span>, {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;root&amp;#34;: [&lt;span style="color:#87ceeb">&amp;#34;.&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;alias&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;underscore&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;lodash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="yarn">Yarn&lt;/h2>
&lt;p>Finally, you can alias before dependencies are even written to disk if you use
Yarn&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Since it breaks compatibility with npm, it&amp;rsquo;s best if you only use this
method in private applications, not public applications or in libraries.&lt;/p>
&lt;p>In this method you combine the alias with defining your dependencies in
package.json.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;dependencies&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;underscore&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;npm:lodash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Unlike the other methods in this guide, using the Yarn method also allows you to
install the same dependency at different versions by giving them different
names.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;dependencies&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;lodash3&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;npm:lodash@^3&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;lodash4&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;npm:lodash@^4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Unfortunately, it seems this isn&amp;rsquo;t yet in the documentation. It has, however, been a &amp;ldquo;Yarn tip&amp;rdquo;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>A rose by any other name…</title><link>https://terinstock.com/talk/2017/03/A-rose-by-any-other-name/</link><pubDate>Wed, 15 Mar 2017 06:10:00 -0700</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/talk/2017/03/A-rose-by-any-other-name/</guid><content type="html">&lt;p>I gave the following Browserify talk at the Webpack edition of NodeSource&amp;rsquo;s
monthly meetup. Webpack&amp;rsquo;s Sean Larkin had given his &amp;ldquo;&lt;a href="https://www.youtube.com/watch?v=AZPYL30ozCY">Core Concepts&lt;/a>&amp;rdquo; talk before
I went on, and I made a comparison to Browserify&amp;rsquo;s concepts.&lt;/p>
&lt;iframe id="talk_frame_383901" class="speakerdeck-iframe" src="https://speakerdeck.com/player/1d2a0cb9884b494ea0513561846cb8b7" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen">&lt;/iframe>
&lt;p>While my talk is chiefly satirical, I get serious towards the end: Webpack is
powerful because of how configurable it is. On the other hand, people like
Browserify because it&amp;rsquo;s opinionated, so you can focus on building your project.
I hope to see improvements to Webpack in 2017 that make it more accessible.&lt;/p></content></item><item><title>TLS with Erlang</title><link>https://terinstock.com/post/2014/07/TLS-with-Erlang/</link><pubDate>Wed, 02 Jul 2014 00:00:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2014/07/TLS-with-Erlang/</guid><content type="html">&lt;p>I recently configured an HTTP server written in Erlang for secure communication with Transport Layer Security (TLS), successor to Secure Sockets Layer (SSL). Unfortunately, my attempt resulted in TLS errors from both browsers and command line tools. Determined to find a solution, I dug into the HTTP server and Erlang&amp;rsquo;s SSL library to resolve these TLS connection failures.&lt;/p>
&lt;p>In the process I uncovered issues with intermediate bundles and elliptic curve selections, as well as a configuration optimization.&lt;/p>
&lt;h2 id="bundling-intermediate-certificates">Bundling Intermediate Certificates&lt;/h2>
&lt;p>When you buy a certificate for your site, you&amp;rsquo;re likely to also receive an intermediate bundle. This intermediate bundle is a chain of certificates that bind the certificate issued for your site to a trusted certificate located on the user&amp;rsquo;s computer or browser (a &amp;ldquo;root certificate&amp;rdquo;). If this bundle is excluded, the browser won&amp;rsquo;t trust the connection, since it can&amp;rsquo;t verify each link of the chain.&lt;/p>
&lt;p>The issuer reminds you to add this bundle to your server&amp;rsquo;s configuration—it is important not to forget this step! Erlang&amp;rsquo;s SSL library can be configured to include the intermediate bundle by providing the path to the &lt;code>cacertfile&lt;/code> option.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-erlang" data-lang="erlang">&lt;span style="display:flex;">&lt;span>ssl:&lt;span style="color:#ff0">start&lt;/span>().
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{ok, &lt;span style="color:#eedd82">ListenSocket&lt;/span>} = ssl:&lt;span style="color:#ff0">listen&lt;/span>(&lt;span style="color:#f60">443&lt;/span>, [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {certfile, &lt;span style="color:#87ceeb">&amp;#34;/full/path/to/server_cert.pem&amp;#34;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {keyfile, &lt;span style="color:#87ceeb">&amp;#34;/full/path/to/server_key.pem&amp;#34;&lt;/span>},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {cacertfile, &lt;span style="color:#87ceeb">&amp;#34;/full/path/to/bundle.pem&amp;#34;&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]).
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssl:&lt;span style="color:#ff0">transport_accept&lt;/span>(&lt;span style="color:#eedd82">ListenSocket&lt;/span>).
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Erlang will now send the entire certificate chain to the browser during the connection, and the browser can trust the connection.&lt;/p>
&lt;h2 id="ode-to-debugging-tls">Ode to Debugging TLS&lt;/h2>
&lt;p>At this point, I expected to be done. Unfortunately, while OpenSSL and TLS scanning tools such as &lt;a href="https://github.com/iSECPartners/sslyze">sslyze&lt;/a> would connect fine, my copies of Chrome, Firefox, and curl refused to connect with cryptic SSL errors such as &lt;code>ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED&lt;/code> and &lt;code>sec_error_invalid_key&lt;/code>. Chrome&amp;rsquo;s debugging logs provided no additional information.&lt;/p>
&lt;h3 id="tls-handshakes-a-primer">TLS Handshakes: A Primer&lt;/h3>
&lt;p>To start a connection to a secure site, the client and browser must first configure the secure connection in a process commonly called a &amp;ldquo;TLS Handshake&amp;rdquo;.&lt;/p>
&lt;p>In a full handshake, two roundtrips between the browser and the server are required:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>The browser sends a &lt;code>ClientHello&lt;/code> message to the server.&lt;/p>
&lt;p>This message includes the highest TLS version supported, lists of supported cipher suites and compression algorithms, and other TLS extensions, including a list of known named elliptic curves.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The server responds with &lt;code>ServerHello&lt;/code>, &lt;code>Certificate&lt;/code>, an optional &lt;code>ServerKeyExchange&lt;/code> message, and &lt;code>ServerHelloDone&lt;/code> messages.&lt;/p>
&lt;p>The &lt;code>ServerHello&lt;/code> message details specific options used for the connections including the TLS version, the cipher suite, the compression algorithm, and any additional TLS extensions. The TLS version should be the highest version support by both client and server. The server maintains a priority list for cipher suites and compression algorithms, and it selects the highest priority supported by the browser.&lt;/p>
&lt;p>The server then attaches the entire certificate chain in a &lt;code>Certificate&lt;/code> message, so the browser can verify the authenticity of the connection.&lt;/p>
&lt;p>If the &lt;code>Certificate&lt;/code> message doesn&amp;rsquo;t contain enough information to allow a client to exchange a session key, such as with a Diffie–Hellman key exchange, then a &lt;code>ServerKeyExchange&lt;/code> message is included.&lt;/p>
&lt;p>The server then ends with &lt;code>ServerHelloDone&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The client responds with a &lt;code>ClientKeyExchange&lt;/code>, &lt;code>ChangeCipherSpec&lt;/code> and &lt;code>Finished&lt;/code> messages.&lt;/p>
&lt;p>The &lt;code>ClientKeyExchange&lt;/code> message contains a secret key determined by both sides using public key encryption. The specifics of how the session key is generated is outside the scope of this primer.&lt;/p>
&lt;p>With the &lt;code>ChangeCipherSpec&lt;/code> message, the browser tells the server to switch to encrypted communication for the rest of the communication.&lt;/p>
&lt;p>To verify the integrity of the communications up to this point, a hash of the previous messages is taken and sent as part of the &lt;code>Finished&lt;/code> message. The server will also compute a hash over the same messages and compare.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Finally, the server sends &lt;code>ChangeCipherSpec&lt;/code> and &lt;code>Finished&lt;/code> messages.&lt;/p>
&lt;p>The server verifies the hash sent in the client&amp;rsquo;s &lt;code>Finished&lt;/code> message, then acknowledges the encryption communications and finishes, sending a similar hash to the client.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="the-failed-tls-handshake">The Failed TLS Handshake&lt;/h3>
&lt;p>Interested in understanding the exact exchange between my browser and the server, I logged the TLS traffic with the network protocol analyzer Wireshark. The browser and the server successfully exchanged &lt;code>ClientHello&lt;/code>, &lt;code>ServerHello&lt;/code>, &lt;code>Certificate&lt;/code>, &lt;code>ServerKeyExchange&lt;/code>, and &lt;code>ServerHelloDone&lt;/code> messages before the browser unexpectedly closed the connection.&lt;/p>
&lt;p>Inspecting the &lt;code>ServerHello&lt;/code> message informed me the server agreed on using TLS 1.2 and choose the &lt;code>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA&lt;/code> cipher suite. Both supported by the browser.&lt;/p>
&lt;p>From &lt;a href="http://tools.ietf.org/rfc/rfc4492.txt">RFC4492&lt;/a>, when the &lt;code>ECDHE_ECDSA&lt;/code>, &lt;code>ECDHE_RSA&lt;/code>, or &lt;code>ECDH_anon&lt;/code> ciphers are chosen, a &lt;code>ServerKeyExchange&lt;/code> message is sent containing the ECDHE public key used to derive the shared key.&lt;/p>
&lt;p>The &lt;code>ServerKeyExchange&lt;/code> portion of the TLS handshake from the server to the client is replicated below.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>0000 0c 00 01 49 03 00 16 41 04 b2 33 23 71 c9 da 80
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0010 94 d3 ec eb 05 9f e5 36 91 a7 e2 e5 40 78 aa 03
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0020 38 4f eb 7c 36 1b 92 21 58 cf c3 e5 b7 08 40 5a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0030 6a eb d2 6a 22 90 e0 47 28 ce 70 9b bb 87 17 d3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0040 4a bc 7c 78 14 ef 97 0d 0d 02 01 01 00 91 7e 3c
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0050 ce 9f 06 1d 00 47 4f 53 85 df 2e 04 31 9a 14 a3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0060 25 bd 51 b3 1a 0f dd 3b c3 f4 25 b0 23 d5 34 0a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0070 a3 fc 2a e2 08 34 29 87 00 91 0e 10 6a 40 b3 b5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0080 61 0c 77 9a 8a 0c 50 dc 78 57 ab 2a 51 66 d0 0d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0090 b7 3c 4d c0 28 b9 06 b4 f5 f6 48 f6 5a 02 c2 7e
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>00a0 8f b2 ac 4b 03 3a 40 c0 e2 c6 2f 77 61 58 ea 0d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>00b0 ab 6c 7f 57 be e1 03 0b c6 1e 2a b0 67 ab c2 db
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>00c0 0a 5b c4 ab 51 9a 76 e6 75 2d e6 ca ce 06 4b f5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>00d0 8f dc f0 c1 42 65 14 c0 79 80 51 f2 68 3b 4a 51
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>00e0 0d 50 5a 01 32 e3 5c 8d cd 8c ec c1 c4 fa 84 3a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>00f0 33 37 4c 9d d5 54 f9 6c aa b8 27 27 7b 4a 7c 33
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0100 27 8e 48 48 33 87 73 11 9b 92 0b e3 99 49 23 7b
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0110 c5 ab 53 ef f2 86 df 56 e5 97 6b 2d 93 5f c0 8a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0120 e6 68 4f 6b 3a 1b 55 26 08 aa c0 36 74 21 ed cc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0130 0e c9 22 0b 97 51 c1 01 48 3f 01 d2 74 fe 36 18
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0140 5f 5c 91 47 b3 19 1c 00 69 7f 17 1b c3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>0c&lt;/code> indicates this message is a &lt;code>ServerKeyExchange&lt;/code> message&lt;/li>
&lt;li>&lt;code>00 01 49&lt;/code> is the length of 0x000149 (in decimal, 329) bytes&lt;/li>
&lt;li>&lt;code>03&lt;/code> is the elliptic curve type, in this case &amp;ldquo;named_curve&amp;rdquo;&lt;/li>
&lt;li>&lt;code>00 16&lt;/code> is the named curve, &lt;code>secp256k1&lt;/code>&lt;/li>
&lt;li>The ECDHE public key and signature follows.&lt;/li>
&lt;/ul>
&lt;p>At this point the browser verifies the signature and retrieves the elliptic curve parameters and ECDHE public key from the &lt;code>ServerKeyExchange&lt;/code> message.&lt;/p>
&lt;p>Section 5.4 of RFC4492 ends with the following note:&lt;/p>
&lt;blockquote>
&lt;p>A possible reason for a fatal handshake failure is that the client&amp;rsquo;s capabilities for handling elliptic curves and point formats are exceeded&lt;/p>&lt;/blockquote>
&lt;p>While Erlang supports all 25 elliptic curves named in RFC4492, common browsers only support a smaller subset of two or three.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> In the above snippet, we see that Erlang chose &lt;code>secp256k1&lt;/code>, the elliptic curve used in &lt;a href="https://en.bitcoin.it/wiki/Secp256k1">Bitcoin&lt;/a>, and not one supported by my browsers.&lt;/p>
&lt;p>Erlang&amp;rsquo;s early support of elliptic curves are problematic. When picking an elliptic curve, Erlang does not consider the list of supported curves sent by the browser. This has been resolved with the &lt;a href="http://www.erlang.org/download_release/23">Erlang R16R03-1&lt;/a> release.&lt;/p>
&lt;h2 id="configuration-of-tls-and-ciphers">Configuration of TLS and Ciphers&lt;/h2>
&lt;p>Erlang&amp;rsquo;s SSL library has defaults for the TLS versions, cipher suites and renegotiation behavior. You may want to change these options for client compatibility and for resiliency to TLS attacks.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-erlang" data-lang="erlang">&lt;span style="display:flex;">&lt;span>ssl:&lt;span style="color:#ff0">start&lt;/span>().
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{ok, &lt;span style="color:#eedd82">ListenSocket&lt;/span>} = ssl:&lt;span style="color:#ff0">listen&lt;/span>(&lt;span style="color:#f60">443&lt;/span>, [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {server_renegotiate, true},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {versions: [ &lt;span style="color:#87ceeb">&amp;#34;tlsv1.1&amp;#34;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#34;tlsv1.2&amp;#34;&lt;/span> ]},
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {ciphers: [ &lt;span style="color:#87ceeb">&amp;#34;ECDHE-ECDSA-AES128-SHA256&amp;#34;&lt;/span>, &lt;span style="color:#87ceeb">&amp;#34;ECDHE-ECDSA-AES128-SHA&amp;#34;&lt;/span> ]}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]).
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssl:&lt;span style="color:#ff0">transport_accept&lt;/span>(&lt;span style="color:#eedd82">ListenSocket&lt;/span>).
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://support.cloudflare.com/hc/en-us/articles/200933580">CloudFlare publishes&lt;/a> the cipher suites they use with nginx. You can check the ciphers supported by your Erlang installation by running the following in a &lt;code>erl&lt;/code> session.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-erlang" data-lang="erlang">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff0">rp&lt;/span>(ssl:&lt;span style="color:#ff0">cipher_suites&lt;/span>(openssl)).
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I created &lt;a href="https://git-wip-us.apache.org/repos/asf?p=couchdb.git;a=commit;h=fdb2188">a patch&lt;/a> for CouchDB that adds the configuration options &lt;code>secure_renegotiate&lt;/code>, &lt;code>ciphers&lt;/code>, and &lt;code>tls_versions&lt;/code> to the SSL section:&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-ini" data-lang="ini">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f00">[ssl]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>certfile = &lt;span style="color:#87ceeb">/full/path/to/server_cert.pem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>keyfile = &lt;span style="color:#87ceeb">/full/path/to/server_key.pem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cacertfile = &lt;span style="color:#87ceeb">/full/path/tp/bundle.pem&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>secure_renegotiate = &lt;span style="color:#87ceeb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>tls_versions = &lt;span style="color:#87ceeb">[ &amp;#34;tlsv1.1&amp;#34;, &amp;#34;tlsv1.2&amp;#34; ]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ciphers = &lt;span style="color:#87ceeb">[ &amp;#34;ECDHE-ECDSA-AES128-SHA256&amp;#34;, &amp;#34;ECDHE-ECDSA-AES128-SHA&amp;#34; ]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>While presented through the lens of an HTTP server in Erlang, the same basic steps could be extrapolated to any secure server written in any language. Recapping my debugging process: First, I ensured the server is configured to send the entire certificate chain to the client. Then, I tested the connection with a TLS scanner or a network protocol analyzer. Finally, once the server is properly communicating, I took a look at my server&amp;rsquo;s TLS configuration to ensure it is secure and reflect current best practices.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;strong>Edit:&lt;/strong> An earlier version of this post claimed browsers only supported three elliptic curves: &lt;code>secp192r1&lt;/code>, &lt;code>secp224r1&lt;/code>, and &lt;code>secp256r1&lt;/code>. This information was incorrectly included from an earlier draft.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;strong>Edit:&lt;/strong> My patch did not land in CouchDB 1.6.0. CouchDB has since been reorganized into multiple repos, but my patch continues to be in place for a future stable release.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>Replacing packages in a Browserify bundle</title><link>https://terinstock.com/post/2014/02/Replacing-packages-in-a-Browserify-bundle/</link><pubDate>Thu, 27 Feb 2014 00:00:00 +0000</pubDate><author>terin@terinstock.com (Terin Stock)</author><guid>https://terinstock.com/post/2014/02/Replacing-packages-in-a-Browserify-bundle/</guid><content type="html">&lt;p>As a developer on a large Backbone application built with &lt;a href="https://github.com/substack/node-browserify">Browserify&lt;/a>, there are a number of occasions where I want to replace one dependency with another. In this specific case, I wanted to swap &lt;code>underscore&lt;/code> for &lt;code>lodash&lt;/code>.&lt;/p>
&lt;p>Browserify already supports this with the &amp;ldquo;browser field&amp;rdquo; in &lt;code>package.json&lt;/code>.&lt;/p>
&lt;blockquote>
&lt;p>There is a special &amp;ldquo;browser&amp;rdquo; field you can set in your package.json on a per-module basis to override file resolution for browser-specific versions of files.&lt;/p>&lt;/blockquote>
&lt;p>This only works for resolution within your package, if any of your dependency packages require Underscore they&amp;rsquo;ll get Underscore. This is to help ensure your replacements don&amp;rsquo;t break your dependencies.&lt;/p>
&lt;p>However, it&amp;rsquo;s suboptimal to ship both Lo-Dash and Underscore, as is maintaining a fork simply to replace the dependency. In these edge cases, it&amp;rsquo;s useful to replace files or packages even within dependencies.&lt;/p>
&lt;p>Luckily, the Browserify transform &lt;a href="https://github.com/thlorenz/browserify-swap">browserify-swap&lt;/a> allows you swap dependencies in certain packages, as defined via the &lt;code>@packages&lt;/code> key, while generating the output bundle.&lt;/p>
&lt;p>As I want to replace Underscore in Backbone, Marionnette and related packages, the configuration seemed pretty straight-forward.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>/* package.json */
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;browserify&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;transform&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;browserify-swap&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;browserify-swap&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;@packages&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;backbone&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;marionette&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;backbone.babysitter&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;backbone.wreqr&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;all&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;underscore.js$&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;lodash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I was a bit discouraged to find that Underscore was still present in the output bundle. After triple-checking that my configuration was valid, I broke out the node debugger to find what was wrong.&lt;/p>
&lt;p>I believed &lt;code>browserify-swap&lt;/code> to swap files while resolving the require calls. The transform actually checks if the current file path &lt;a href="https://github.com/thlorenz/browserify-swap/blob/fbb9ca86c8af14e3fa21a75852f6251ea86f45d7/index.js#L38">matches a RegEx&lt;/a> defined in the &lt;code>package.json&lt;/code> file and replaces the contents to require the swapped in file.&lt;/p>
&lt;h2 id="the-solution">The Solution&lt;/h2>
&lt;p>With this information in hand, it became clear that we needed to swap in the &lt;code>underscore&lt;/code> package.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#000;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>/* package.json */
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;browserify&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;transform&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;browserify-swap&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;browserify-swap&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;@packages&amp;#34;: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#87ceeb">&amp;#34;underscore&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;all&amp;#34;: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;#34;underscore.js$&amp;#34;: &lt;span style="color:#87ceeb">&amp;#34;lodash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This causes the swap to happen for each instance of Underscore in the bundle, but only the one instance of Lo-Dash would be included.&lt;/p>
&lt;p>I believe that the transform should also allow swapping packages for other packages, as the folder structure of a package is usually not part of the public API. I&amp;rsquo;ve &lt;a href="https://github.com/thlorenz/browserify-swap/issues/1">opened an issue&lt;/a> against the project.&lt;/p></content></item></channel></rss>