<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Holth</title>
    <description>The latest articles on DEV Community by Daniel Holth (@dholth).</description>
    <link>https://dev.to/dholth</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F141667%2F02456a95-f390-4497-ad88-da26f34d93c2.jpeg</url>
      <title>DEV Community: Daniel Holth</title>
      <link>https://dev.to/dholth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dholth"/>
    <language>en</language>
    <item>
      <title>Configuring Tvheadend for ATSC in North America</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Fri, 31 Jan 2025 23:22:04 +0000</pubDate>
      <link>https://dev.to/dholth/configuring-tvheadend-for-atsc-in-north-america-2e9a</link>
      <guid>https://dev.to/dholth/configuring-tvheadend-for-atsc-in-north-america-2e9a</guid>
      <description>&lt;p&gt;Here's how I configured &lt;a href="https://tvheadend.org/" rel="noopener noreferrer"&gt;Tvheadend&lt;/a&gt; for ATSC (North America)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Tvheadend. I would up using the Fedora packages and not the container, after spending too much time debugging containerization issues.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dmesg&lt;/code&gt; showed error messages related to the firmware. The USB stick's firmware was available in Ubuntu but not in Fedora. The same model card can have different tuners, but my &lt;a href="https://www.linuxtv.org/wiki/index.php/Hauppauge_WinTV-HVR-950" rel="noopener noreferrer"&gt;Hauppauge WinTV HVR-850&lt;/a&gt; required &lt;code&gt;sudo wget -O /lib/firmware/dvb-fe-xc5000-1.6.114.fw https://linuxtv.org/downloads/firmware/dvb-fe-xc5000-1.6.114.fw&lt;/code&gt; per the &lt;a href="https://linuxtv.org" rel="noopener noreferrer"&gt;linuxtv.org website&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Enable TCP 9981, 9982 through the firewall.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In tvheadend's configuration interface,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;em&gt;TV Adapters&lt;/em&gt;, enable the ATSC-T side of your adapter. Always press &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;em&gt;Networks&lt;/em&gt;, add and enable an ATSC-T network perhaps named after your city. Use &lt;code&gt;United States: us-ATSC-center-frequencies-8VSB-062009&lt;/code&gt;. The ones with a suffix omit no-longer-used TV frequencies, don't worry about &lt;code&gt;w_scan&lt;/code&gt;, we only have 35 to worry about.&lt;/li&gt;
&lt;li&gt;Go back to &lt;em&gt;TV Adapters&lt;/em&gt;. Add your new network to the enabled ATSC-T adapter's &lt;em&gt;Networks&lt;/em&gt; dropdown.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Force Scan&lt;/em&gt; on the &lt;em&gt;Networks&lt;/em&gt; tab. The system will check each ATSC channel for signal, and populate found channels on &lt;em&gt;Services&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;On &lt;em&gt;Services&lt;/em&gt;, choose &lt;em&gt;Map All Services&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tvheadend will gradually populate the program guide for channels that provide one. It's possible to watch the channels that don't appear in the guide by switching to them.&lt;/p&gt;

&lt;p&gt;Hopefully now you have Tvheadend showing signs of life, and can configure it to work with a more pleasant frontend interface like Kodi.&lt;/p&gt;

</description>
      <category>atsc</category>
      <category>dvb</category>
      <category>linux</category>
      <category>tv</category>
    </item>
    <item>
      <title>Self-updating Containers on Linux with Quadlet aka podman-system-generator</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Wed, 30 Oct 2024 18:20:26 +0000</pubDate>
      <link>https://dev.to/dholth/self-updating-containers-on-linux-with-quadlet-aka-podman-system-generator-41l5</link>
      <guid>https://dev.to/dholth/self-updating-containers-on-linux-with-quadlet-aka-podman-system-generator-41l5</guid>
      <description>&lt;p&gt;If you are using a modern Linux system like Fedora 40, you may want to configure &lt;code&gt;systemd&lt;/code&gt; which manages services, and &lt;code&gt;podman&lt;/code&gt; to run containers as an alternative to Docker. This setup makes it possible to run &lt;a href="https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md" rel="noopener noreferrer"&gt;rootless containers&lt;/a&gt; as a normal user without worrying about giving the container global permissions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt; uses &lt;code&gt;.service&lt;/code&gt; files to control what to run. For example, &lt;code&gt;samba&lt;/code&gt;, a file server, has &lt;code&gt;/usr/lib/systemd/system/samba.service&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Samba AD Daemon
Documentation=man:samba(8) man:samba(7) man:smb.conf(5)
Wants=network-online.target
After=network.target network-online.target

[Service]
Type=notify
PIDFile=/run/samba.pid
LimitNOFILE=16384
EnvironmentFile=-/etc/sysconfig/samba
ExecStart=/usr/sbin/samba --foreground --no-process-group $SAMBAOPTIONS
ExecReload=/bin/kill -HUP $MAINPID
Environment=KRB5CCNAME=FILE:/run/samba/krb5cc_samba

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want our container to start automatically on boot and restart when needed, so we need a &lt;code&gt;systemd&lt;/code&gt; &lt;code&gt;.service&lt;/code&gt; file. In the old days we would create a container using &lt;code&gt;podman&lt;/code&gt; and then manually put a &lt;code&gt;.service&lt;/code&gt; file for that container &lt;em&gt;somewhere&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;This works for a while but our service references an exact container. When we want to update the container, we have to re-write the service file to reference the update. On my system the service file referenced the new container in one place and the old one in the other, I forgot to update both, and systemd kept restarting my container.&lt;/p&gt;

&lt;p&gt;Six months later, I discovered &lt;a href="https://www.redhat.com/en/blog/quadlet-podman" rel="noopener noreferrer"&gt;Quadlet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quadlet is a &lt;code&gt;generator&lt;/code&gt; to alleviate the burden of manually writing &lt;code&gt;.service&lt;/code&gt; files, one of many found in &lt;code&gt;/usr/lib/systemd/system-generators&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ls -1 /usr/lib/systemd/system-generators&lt;/span&gt;
kdump-dep-generator.sh
nfs-server-generator
podman-system-generator
rpc-pipefs-generator
selinux-autorelabel-generator.sh
systemd-bless-boot-generator
systemd-cryptsetup-generator
systemd-debug-generator
systemd-fstab-generator
systemd-getty-generator
systemd-gpt-auto-generator
systemd-hibernate-resume-generator
systemd-integritysetup-generator
systemd-rc-local-generator
systemd-run-generator
systemd-system-update-generator
systemd-sysv-generator
systemd-veritysetup-generator
zram-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generators convert higer-level configuration to systemd early on during startup, when we call &lt;code&gt;systemctl daemon-reload&lt;/code&gt;, or when we call &lt;code&gt;systemctl --user daemon-reload&lt;/code&gt; for rootless services.&lt;/p&gt;

&lt;h1&gt;
  
  
  Examples
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://mosquitto.org/" rel="noopener noreferrer"&gt;Mosquitto&lt;/a&gt; is a simple message broker I've chosen to run as root. After placing the &lt;code&gt;.container&lt;/code&gt; file in the right place and running &lt;code&gt;systemctl daemon-reload&lt;/code&gt; (or rebooting), we can run &lt;code&gt;systemctl start mosquitto&lt;/code&gt;. All the &lt;code&gt;podman&lt;/code&gt; commands to pull, run &lt;code&gt;docker.io/library/eclipse-mosquitto:latest&lt;/code&gt; are taken care of for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# cat /etc/containers/systemd/mosquitto.container&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Container]
&lt;span class="nv"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mosquitto
&lt;span class="nv"&gt;HostName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hass
&lt;span class="nv"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker.io/library/eclipse-mosquitto:latest
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/mosquitto:/mosquitto/config
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mosquitto/data
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mosquitto/log
&lt;span class="c"&gt;# mqtt&lt;/span&gt;
&lt;span class="nv"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1883:1883
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service winds up in &lt;code&gt;/etc/containers/systemd/mosquitto.container&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Automatically generated by /usr/lib/systemd/system-generators/podman-system-generator&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# unifi.container&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;X-Container]
&lt;span class="nv"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mosquitto
&lt;span class="nv"&gt;HostName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;hass
&lt;span class="nv"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker.io/library/eclipse-mosquitto:latest
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/mosquitto:/mosquitto/config
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mosquitto/data
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mosquitto/log
&lt;span class="c"&gt;# mqtt&lt;/span&gt;
&lt;span class="nv"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1883:1883

&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target
&lt;span class="nv"&gt;SourcePath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/containers/systemd/mosquitto.container
&lt;span class="nv"&gt;RequiresMountsFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/containers
&lt;span class="nv"&gt;RequiresMountsFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/mosquitto

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;PODMAN_SYSTEMD_UNIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%n
&lt;span class="nv"&gt;KillMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mixed
&lt;span class="nv"&gt;ExecStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/podman &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--cidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/%N.cid
&lt;span class="nv"&gt;ExecStopPost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-/usr/bin/podman &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--cidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/%N.cid
&lt;span class="nv"&gt;Delegate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes
&lt;/span&gt;&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;notify
&lt;span class="nv"&gt;NotifyAccess&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;all
&lt;span class="nv"&gt;SyslogIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%N
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/podman run &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mosquitto &lt;span class="nt"&gt;--cidfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%t/%N.cid &lt;span class="nt"&gt;--replace&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--cgroups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;split&lt;/span&gt; &lt;span class="nt"&gt;--sdnotify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;conmon &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; /etc/mosquitto:/mosquitto/config &lt;span class="nt"&gt;-v&lt;/span&gt; /mosquitto/data &lt;span class="nt"&gt;-v&lt;/span&gt; /mosquitto/log &lt;span class="nt"&gt;--publish&lt;/span&gt; 1883:1883 &lt;span class="nt"&gt;--hostname&lt;/span&gt; hass docker.io/library/eclipse-mosquitto:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Rootless Containers
&lt;/h1&gt;

&lt;p&gt;The rootless &lt;code&gt;.container&lt;/code&gt; files go into &lt;code&gt;~/.config/containers/systemd&lt;/code&gt;. I'm using one to run &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;find .&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="se"&gt;\*&lt;/span&gt;.container
.config/containers/systemd/homeassistant.container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My container file looks like this. I also gave it permission to &lt;code&gt;/dev/ttyACM0&lt;/code&gt; for a ZigBee radio, and &lt;code&gt;CAP_NET_RAW,CAP_NET_BIND_SERVICE&lt;/code&gt; to attempt to allow it to bind to any port, and monitor the network for new devices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;homeassistant.container
&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Homeassistant

&lt;span class="o"&gt;[&lt;/span&gt;Container]
&lt;span class="nv"&gt;AddCapability&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CAP_NET_RAW,CAP_NET_BIND_SERVICE
&lt;span class="nv"&gt;AddDevice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/ttyACM0
&lt;span class="nv"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;homeassistant
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;TZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;America/New_York
&lt;span class="nv"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghcr.io/home-assistant/home-assistant:latest
&lt;span class="nv"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host
&lt;span class="nv"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/me/hass/config:/config
&lt;span class="nv"&gt;PodmanArgs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;--group-add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;keep-groups
&lt;span class="c"&gt;# GroupAdd=keep-groups # future release? https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated service file winds up in &lt;code&gt;/run/&amp;lt;uid&amp;gt;/systemd&lt;/code&gt;. Now an up-to-date Home Assistant container runs as my user when I start my Fedora server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;/run/user/1000/systemd$&lt;/span&gt; &lt;span class="err"&gt;cat&lt;/span&gt; &lt;span class="err"&gt;generator/*&lt;/span&gt;
&lt;span class="c"&gt;# Automatically generated by /usr/lib/systemd/user-generators/podman-user-generator
#
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Wants&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Homeassistant&lt;/span&gt;
&lt;span class="py"&gt;SourcePath&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/me/.config/containers/systemd/homeassistant.container&lt;/span&gt;
&lt;span class="py"&gt;RequiresMountsFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%t/containers&lt;/span&gt;
&lt;span class="py"&gt;RequiresMountsFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/me/hass/config&lt;/span&gt;

&lt;span class="nn"&gt;[X-Container]&lt;/span&gt;
&lt;span class="py"&gt;AddCapability&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;CAP_NET_RAW,CAP_NET_BIND_SERVICE&lt;/span&gt;
&lt;span class="py"&gt;AddDevice&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/dev/ttyACM0&lt;/span&gt;
&lt;span class="py"&gt;ContainerName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;homeassistant&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;TZ=America/New_York&lt;/span&gt;
&lt;span class="py"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ghcr.io/home-assistant/home-assistant:latest&lt;/span&gt;
&lt;span class="py"&gt;Network&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;
&lt;span class="py"&gt;Volume&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/me/hass/config:/config&lt;/span&gt;
&lt;span class="py"&gt;PodmanArgs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;--privileged --group-add=keep-groups&lt;/span&gt;

&lt;span class="c"&gt;# GroupAdd=keep-groups # future release? https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
&lt;/span&gt;
&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PODMAN_SYSTEMD_UNIT=%n&lt;/span&gt;
&lt;span class="py"&gt;KillMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mixed&lt;/span&gt;
&lt;span class="py"&gt;ExecStop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid&lt;/span&gt;
&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid&lt;/span&gt;
&lt;span class="py"&gt;Delegate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;notify&lt;/span&gt;
&lt;span class="py"&gt;NotifyAccess&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;
&lt;span class="py"&gt;SyslogIdentifier&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%N&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/podman run --name=homeassistant --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=host --sdnotify=conmon -d --device=/dev/ttyACM0 --cap-add=cap_net_raw,cap_net_bind_service -v /home/me/hass/config:/config --env TZ=America/New_York --privileged --group-add=keep-groups ghcr.io/home-assistant/home-assistant:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt; has many features including user-mode process management, powerful &lt;a href="https://opensource.com/article/20/7/systemd-timers" rel="noopener noreferrer"&gt;&lt;code&gt;cron&lt;/code&gt;-like service timers&lt;/a&gt;, and now container management features that long-term Linux users may not be familiar with. After struggling through the sometimes-spotty documentation of this newish feature, we've managed to create a maintainable set of containerized services on our home Linux server.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using nginx as a SSL offloading proxy to MQTT</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Sat, 12 Oct 2024 20:06:38 +0000</pubDate>
      <link>https://dev.to/dholth/using-nginx-as-a-ssl-offloading-proxy-to-mqtt-185</link>
      <guid>https://dev.to/dholth/using-nginx-as-a-ssl-offloading-proxy-to-mqtt-185</guid>
      <description>&lt;p&gt;nginx's &lt;a href="http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html" rel="noopener noreferrer"&gt;stream_proxy&lt;/a&gt; and &lt;a href="http://nginx.org/en/docs/stream/ngx_stream_ssl_module.html" rel="noopener noreferrer"&gt;stream_ssl&lt;/a&gt; modules can be used to add tls/ssl support to &lt;a href="https://mosquitto.org/" rel="noopener noreferrer"&gt;mosquitto&lt;/a&gt; or any tcp server.&lt;/p&gt;

&lt;p&gt;This can be useful because &lt;code&gt;mosquitto&lt;/code&gt; only supports certain certificate types. The mbedTLS stack I was using on a microcontroller board wasn't able to understand my certificate loaded into mosquitto; but it was able to make &lt;code&gt;https://&lt;/code&gt; requests to nginx on the same server with the same certificate. Our nginx server supports, and has permission to read, these certificates.&lt;/p&gt;

&lt;p&gt;For MQTT we will proxy tcp directly and we will not use http. Edit &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; to add &lt;code&gt;include /etc/nginx/conf.d/*.stream;&lt;/code&gt; at the top level, as &lt;code&gt;/etc/nginx/conf.d/*.conf&lt;/code&gt; is loaded into the &lt;code&gt;http&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;On Fedora 40, &lt;code&gt;dnf install nginx-mod-stream&lt;/code&gt; to get the module.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/nginx/conf.d/01-mqtt.stream&lt;/code&gt;::&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stream {
        upstream backend {
                hash $remote_addr consistent;
                server localhost:1883;
        }
        server {
                listen       8883 ssl;
                listen       [::]:8883 ssl;
                server_name  hass;

                ssl_certificate "/etc/letsencrypt/live/hass/fullchain.pem";
                ssl_certificate_key "/etc/letsencrypt/live/hass/privkey.pem";
                ssl_session_cache shared:SSLB:1m;
                ssl_session_timeout  10m;
                ssl_ciphers PROFILE=SYSTEM;
                ssl_prefer_server_ciphers on;

                proxy_connect_timeout 1s;
                proxy_timeout 10m; # is default
                proxy_pass backend;
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a restart, nginx listens on the mqtt-tls port 8883, handles encryption, and forwards plain text to &lt;code&gt;localhost:1883&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For mosquitto, the only configuration needed is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;password_file /mosquitto/config/pwfile.conf
listener 1883
# or listener 1883 0.0.0.0 to listen on all interfaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>mqtt</category>
      <category>mosquitto</category>
      <category>nginx</category>
      <category>tls</category>
    </item>
    <item>
      <title>How we reduced conda's index fetch bandwidth by 99%</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Sun, 23 Apr 2023 19:20:39 +0000</pubDate>
      <link>https://dev.to/dholth/how-we-reduced-condas-index-fetch-bandwidth-by-99-43pk</link>
      <guid>https://dev.to/dholth/how-we-reduced-condas-index-fetch-bandwidth-by-99-43pk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The new conda 23.3.1 release from March, 2023 includes an &lt;code&gt;--experimental=jlap&lt;/code&gt; flag or &lt;code&gt;experimental: ["jlap"]&lt;/code&gt; &lt;code&gt;.condarc&lt;/code&gt; setting that can reduce repdata.json fetch bandwidth by orders of magnitude. This is how we developed conda's new incremental repodata feature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Conda is a cross-platform, language-agnostic binary package manager that includes a constraint solver to choose compatible sets of packages. Before conda can install a package, it downloads information about all available packages.  This allows the solver to make global decisions about which packages to install.  The time and bandwidth spent downloading this metadata can be significant, but we have improved this in conda 23.3.1. By enabling the &lt;code&gt;experimental: ["jlap"]&lt;/code&gt; feature in &lt;code&gt;.condarc&lt;/code&gt;, conda users can see more than a 99% reduction in index fetch bandwith.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;p&gt;Traditionally, conda tries to fetch each channel's entire &lt;code&gt;repodata.json&lt;/code&gt; or smaller &lt;code&gt;current_repodata.json&lt;/code&gt; every time the cache expires, typically between 30 seconds to 20 minutes from the last remote request. If the channel has not changed this is a quick &lt;code&gt;304 Not Modified&lt;/code&gt;, but very active channels will change several times an hour. Any change, usually only a few added packages, requires the user to re-download the entire index; most conda users will be familiar with this process. My manager said one of &lt;a href="https://www.anaconda.com/"&gt;Anaconda&lt;/a&gt;'s customers wanted a better way to track changes in our repository and I became interested in solving the problem.&lt;/p&gt;

&lt;p&gt;I began to pursue a solution based on computing patches between successive versions of &lt;code&gt;repodata.json&lt;/code&gt; to let users download only the changes, once they had an initial, complete copy of the index in their cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Prototypes
&lt;/h2&gt;

&lt;p&gt;I chose &lt;a href="https://datatracker.ietf.org/doc/html/rfc6902"&gt;RFC 6902 JSON Patch&lt;/a&gt;, a generic patch format for &lt;code&gt;.json&lt;/code&gt;. &lt;code&gt;JSON Patch&lt;/code&gt; shows the logical difference between two &lt;code&gt;.json&lt;/code&gt; files instead of comparing the files textually, saving space by discarding formatting.&lt;/p&gt;

&lt;p&gt;I wrote a &lt;a href="https://github.com/dholth/jdiff"&gt;Rust implementation of a &lt;code&gt;.json&lt;/code&gt; patchset format&lt;/a&gt; based on a single &lt;code&gt;.json&lt;/code&gt; file with an array of patches.&lt;/p&gt;

&lt;p&gt;The Rust implementation helped to simplify the format and show that it could be language independent. In Python, we might have included optional or multiple-typed "string or null" fields without thinking about it. In Rust, "mandatory, always a single type" fields are easiest to specify. I was surprised to find that this change simplified the Python code as well.&lt;/p&gt;

&lt;p&gt;Experimentation showed that &lt;a href="https://www.pypy.org/"&gt;PyPy&lt;/a&gt; was faster than Rust for comparing two large &lt;code&gt;repodata.json&lt;/code&gt;; CPython's &lt;code&gt;json.loads&lt;/code&gt; and &lt;code&gt;json.dumps&lt;/code&gt; also perform shockingly well. Stopped development on the Rust implementation.&lt;/p&gt;

&lt;p&gt;I began writing a formal specification based on array-of-patches style.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specification
&lt;/h2&gt;

&lt;p&gt;I wrote a Conda Enhancmenent Proposal (CEP) for a new &lt;a href="https://github.com/conda-incubator/ceps/pull/20/files"&gt;json lines based &lt;code&gt;.jlap&lt;/code&gt; format&lt;/a&gt;. The earlier array-of-patches format has the same problem as &lt;code&gt;repodata.json&lt;/code&gt; but in miniature, since the client has to download every patch each time. In contrast, &lt;a href="https://github.com/conda-incubator/ceps/pull/20/files"&gt;the new &lt;code&gt;.jlap&lt;/code&gt; system&lt;/a&gt; appends patches to the end of a file. It is designed to fetch new patches from a growing file using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range"&gt;HTTP Range requests&lt;/a&gt;. With this system, update bandwidth is proportional to the amount of change that has happened since last time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.jlap&lt;/code&gt; format allows clients to fetch the newest patches and only the newest patches with a single HTTP Range request. It consists of a leading checksum, any number of lines of patches, one per line in the &lt;a href="https://jsonlines.readthedocs.io/en/latest/"&gt;JSON Lines&lt;/a&gt; format, and a trailing checksum.&lt;/p&gt;

&lt;p&gt;The checksums are constructed in such a way that the trailing checksum can be re-verified without re-reading (or retaining) the beginning of the file, if the client remembers an intermediate checksum. The trailing checksum is used to make sure the rest of the remote file was not changed.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;repodata.json&lt;/code&gt; changes, the server wil truncate the &lt;code&gt;metadata&lt;/code&gt; line, appending new patches, a new metadata line and a new trailing checksum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We needed patch data to test this system. &lt;a href="https://github.com/dholth/repodata-fly"&gt;I wrote a web service&lt;/a&gt; that would create that data.  The service checked &lt;code&gt;repodata.json&lt;/code&gt; every five minutes, compared the current and previous versions, updated a patch file and hosted it separately from the main repository.&lt;/p&gt;

&lt;p&gt;The initial demonstration used a &lt;a href="https://github.com/dholth/repodata-fly/blob/main/app/repodata_proxy.py"&gt;&lt;code&gt;repodata.json&lt;/code&gt; proxy,&lt;/a&gt; fetching patches from &lt;code&gt;repodata.fly.dev&lt;/code&gt; while forwarding package requests to the upstream server. The user points conda at the proxy server instead of the default channel. Another prototype adds a similar proxy into conda's &lt;code&gt;requests&lt;/code&gt;-based HTTP backend, but duplicates an extra local cache on top of the existing cache.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.jlap&lt;/code&gt; format is generic over &lt;code&gt;JSON&lt;/code&gt;. The underlying checksum/range request system is generic for any growing line-based file. Consider adapting it if you have a similar problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Server Side Improvements
&lt;/h2&gt;

&lt;p&gt;I became the &lt;a href="https://github.com/conda/conda-index"&gt;&lt;code&gt;conda-index&lt;/code&gt;&lt;/a&gt; maintainer, rewriting it from &lt;code&gt;conda-build index&lt;/code&gt; to a new standalone package. We solved speed problems updating large repositories like &lt;code&gt;conda-forge&lt;/code&gt; and &lt;code&gt;defaults&lt;/code&gt;.  This involvement made it easy to control the server-side data so that we could improve the client and server together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Team Move
&lt;/h2&gt;

&lt;p&gt;I became a full-time member of the conda team, having transferred from Anaconda's packaging team. I slowly began understanding conda's internals enough to be able to produce a solution that could integrate with conda's existing cache, instead of a local caching proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of zstandard-compressed repodata.json, parallel downloads
&lt;/h2&gt;

&lt;p&gt;On November 6, &lt;a href="https://github.com/conda/infrastructure/issues/648"&gt;a community member&lt;/a&gt; noticed that fetching &lt;code&gt;repodata.json&lt;/code&gt; with on-the-fly server compression was slower than fetching it uncompressed, if your connection was faster than the remote server's gzip compressor. We merged &lt;a href="https://github.com/conda/conda-index/pull/65"&gt;server-side &lt;code&gt;repodata.json.zst&lt;/code&gt; support&lt;/a&gt; into conda-index on November 14.&lt;/p&gt;

&lt;p&gt;November's conda release included parallel package downloads and extraction, a speed improvement that makes a difference proportional to your latency to the package server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship &lt;code&gt;repodata.json.zst&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We shipped zstd-compressed repodata to &lt;code&gt;conda-forge&lt;/code&gt; and &lt;code&gt;defaults&lt;/code&gt; on December 15.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactor cache
&lt;/h2&gt;

&lt;p&gt;January's 23.1.0 conda release included a refactor of its cache that was important for incremental &lt;code&gt;repodata.json&lt;/code&gt; support. Instead of inlining cache metadata into a modified &lt;code&gt;repodata.json&lt;/code&gt;, conda stores unmodified &lt;code&gt;repodata.json&lt;/code&gt; in its cache, and stores cache metadata in a separate file. We avoid having to reserialize &lt;code&gt;repodata.json&lt;/code&gt; in many cases and can preserve its orginal content and formatting.&lt;/p&gt;

&lt;p&gt;Wolf Vollprecht submitted a draft CEP to standardize the cache format between conda and mamba. We continue to converge on a shared format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship &lt;code&gt;repodata.jlap&lt;/code&gt; incremental repodata
&lt;/h2&gt;

&lt;p&gt;March's 23.3.1 conda release shipped support for &lt;code&gt;repodata.jlap&lt;/code&gt; under the &lt;code&gt;--experimental=jlap&lt;/code&gt; flag. This feature also includes support for &lt;code&gt;repodata.json.zst&lt;/code&gt; with a fallback to &lt;code&gt;repodata.json&lt;/code&gt; if unavailable.&lt;/p&gt;

&lt;p&gt;When the cache is empty, conda will try to download &lt;code&gt;repodata.json.zst&lt;/code&gt;. This file is much faster to download and decompress compared to &lt;code&gt;Content-Encoding: gzip&lt;/code&gt; and is slightly smaller.&lt;/p&gt;

&lt;p&gt;When the cache is primed, conda will look for &lt;code&gt;repodata.jlap&lt;/code&gt;. It will download the entire file, apply any relevant patches (comparing to a content hash of &lt;code&gt;repodata.json&lt;/code&gt;) and remember the length of the patch file.&lt;/p&gt;

&lt;p&gt;On subsequent fetches, conda will use a HTTP Range request to download new bytes, if any, added to &lt;code&gt;repodata.jlap&lt;/code&gt;, and apply new patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/574800"&gt;This screen capture of a conda-forge/noarch search&lt;/a&gt; shows that were able to download a single update to the channel in 1464 bytes for what would have otherwise have been a 10358799-byte download of the complete index. The patch size is proportional to the amount of change that has happened since the last time you ran conda.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;--experimental=jlap&lt;/code&gt; is enabled, frequent conda users will see much faster index updates, especially when bandwidth is limited.&lt;/p&gt;

</description>
      <category>conda</category>
      <category>python</category>
    </item>
    <item>
      <title>certbot hooks for gandi.net v5 DNS API</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Sun, 13 Nov 2022 01:53:34 +0000</pubDate>
      <link>https://dev.to/dholth/certbot-hooks-for-gandinet-v5-dns-api-27mm</link>
      <guid>https://dev.to/dholth/certbot-hooks-for-gandinet-v5-dns-api-27mm</guid>
      <description>&lt;p&gt;I wanted to use &lt;a href="https://api.gandi.net/docs/livedns/"&gt;gandi.net's API&lt;/a&gt; to update DNS for certbot/letsencrypt. I wasn't able to get TSIG (a standard DNS update method) to work. There is a plugin for gandi's API, but I wanted to make my own.&lt;/p&gt;

&lt;p&gt;First, I went to &lt;a href="https://account.gandi.net/en/users/USERNAME/security"&gt;https://account.gandi.net/en/users/USERNAME/security&lt;/a&gt; to generate an API key.&lt;/p&gt;

&lt;p&gt;I saved the key as HTTP headers in &lt;code&gt;/etc/letsencrypt/gandi-api-key.txt&lt;/code&gt; and &lt;code&gt;chmod 600 gandi-api-key.txt&lt;/code&gt; to keep it secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Apikey SECRET-API-KEY
Content-Type: application/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and created two hooks:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/letsencrypt/gandi_hook.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CERTBOT_DOMAIN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# "domain.example.com"
&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;zone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tld&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"curl -H @gandi-api-key.txt https://api.gandi.net/v5/livedns/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records/_acme-challenge.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/TXT -X PUT -d"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"rrset_ttl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rrset_values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CERTBOT_VALIDATION"&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/etc/letsencrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# https://www.gandi.net/en-US/domain/dns '250 ms'
&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the cleanup script, &lt;code&gt;/etc/letsencrypt/gandi_cleanup.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CERTBOT_DOMAIN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# "fedora.monotreme.club"
&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;zone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tld&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"curl -H @gandi-api-key.txt https://api.gandi.net/v5/livedns/domains/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/records/_acme-challenge.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/TXT -X DELETE"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/etc/letsencrypt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chmod +x gandi_hook.py gandi_cleanup.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To run certbot, &lt;code&gt;certbot certonly --manual --preferred-challenge=dns --manual-auth-hook /etc/letsencrypt/gandi_hook.py --manual-cleanup-hook /etc/letsencrypt/gandi_cleanup.py -d test.monotreme.club --test-cert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Modern Linux includes a systemd timer to automatically renew the certificates. Enable it with &lt;code&gt;systemctl enable certbot-renew.timer&lt;/code&gt; and &lt;code&gt;systemctl start certbot-renew.timer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unlike cron, the systemd timer has a random start time to avoid DDOS'ing letsencrypt's servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Timer]
OnCalendar=*-*-* 00/12:00:00
RandomizedDelaySec=12hours
Persistent=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>certbot</category>
      <category>letsencrypt</category>
      <category>gandi</category>
      <category>dns</category>
    </item>
    <item>
      <title>Using sql.js-httpvfs with browser &lt;script type=module&gt;</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Tue, 22 Feb 2022 23:32:49 +0000</pubDate>
      <link>https://dev.to/dholth/using-sqljs-httpvfs-with-browser-1il8</link>
      <guid>https://dev.to/dholth/using-sqljs-httpvfs-with-browser-1il8</guid>
      <description>&lt;p&gt;&lt;a href="https://www.npmjs.com/package/sql.js-httpvfs"&gt;https://www.npmjs.com/package/sql.js-httpvfs&lt;/a&gt; is an amazing package that lets us perform SQL queries against a remote database hosted anywhere &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests"&gt;range requests&lt;/a&gt; are supported. A special .wasm SQLite runs in the browser; a typical query might only need to fetch half a dozen 4kb pages from a 1GB database file.&lt;/p&gt;

&lt;p&gt;It is normally used with webpack. What if we want to distribute it as a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules"&gt;JavaScript module&lt;/a&gt; so we can just import it from our browser-native &lt;code&gt;&amp;lt;script type=module&amp;gt;&lt;/code&gt; and develop a simple project in pure JavaScript?&lt;/p&gt;

&lt;p&gt;I edited the example's &lt;code&gt;webpack.config.js&lt;/code&gt; (&lt;a href="https://github.com/phiresky/sql.js-httpvfs/tree/master/example"&gt;https://github.com/phiresky/sql.js-httpvfs/tree/master/example&lt;/a&gt;) to output a module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;tsx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.tsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sql-httpvfs.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// output a JavaScript module&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// truly&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;experiments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;outputModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;// yes, we really want one&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;optimization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;minimize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;index.ts&lt;/code&gt; is changed to export a useful function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createDbWorker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sql.js-httpvfs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sql.js-httpvfs/dist/sqlite.worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasmUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sql.js-httpvfs/dist/sql-wasm.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createDbWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;serverMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;requestChunkSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;workerUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;wasmUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// only `load` is visible to the importer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run webpack. In this example it will write 3 files to &lt;code&gt;./dist/&lt;/code&gt;. We can copy those files to wherever we want to use our new module.&lt;/p&gt;

&lt;p&gt;Now we can import that module directly in &lt;code&gt;index.html&lt;/code&gt;, and play around with loading database URLs in the browser console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./dist/sql-httpvfs.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadDB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modules are automatically &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer"&gt;deferred&lt;/a&gt;, and won't run until the document has been parsed. Our module code can start manipulating the page right away without having to e.g. register a &lt;code&gt;load&lt;/code&gt; or &lt;code&gt;$(document).ready&lt;/code&gt; handler.&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>webpack</category>
    </item>
    <item>
      <title>uWSGI is still pretty great, actually</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Tue, 22 Feb 2022 23:09:19 +0000</pubDate>
      <link>https://dev.to/dholth/uwsgi-is-still-pretty-great-actually-59el</link>
      <guid>https://dev.to/dholth/uwsgi-is-still-pretty-great-actually-59el</guid>
      <description>&lt;p&gt;I've used uWSGI for a long time but newer Python web servers have come along. Let's compare uWSGI to uvicorn with the all-important "hello, world" style program. I yield the body in pieces instead of sending back the whole body at once to include some context switching overhead.&lt;/p&gt;

&lt;p&gt;I've also been working on an experimental &lt;a href="https://github.com/dholth/uwsgi"&gt;PyPy + ASGI plugin&lt;/a&gt; for uWSGI. Let's see if the performance is in the right ballpark.&lt;/p&gt;

&lt;p&gt;I'm using &lt;code&gt;hey -z 8s http://localhost&lt;/code&gt; to send some requests to the app.&lt;/p&gt;

&lt;p&gt;WSGI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import sys
import pprint


def application(env, start_response):
    start_response("200 OK", [("Content-Type", "text/html")])
    results = [
        b"&amp;lt;pre&amp;gt;",
        b"Hello World\n",
        sys.executable.encode("utf-8"),
        b" ",
        pprint.pformat(env),
        b"&amp;lt;/pre&amp;gt;",
    ]

    for result in results:
        if isinstance(result, str):
            result = result.encode("utf-8")
        yield result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ASGI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import sys
import pprint


def results(scope):
    results = [
        b"&amp;lt;pre&amp;gt;",
        b"Hello World\n",
        sys.executable.encode("utf-8"),
        b" ",
        pprint.pformat(scope),
        b"&amp;lt;/pre&amp;gt;",
    ]

    for result in results:
        if isinstance(result, str):
            result = result.encode("utf-8")
        yield result


async def application(scope, receive, send):
    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [[b"content-type", b"text/html"],],
        }
    )
    for result in results(scope):
        await send({"type": "http.response.body", "body": result, "more_body": True})

    await send({"type": "http.response.body", "body": result, "more_body": False})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WSGI, Python 3.8 and standard uWSGI Python plugin: &lt;code&gt;uwsgi --plugin python --wsgi minimal --http [::]:80 --enable-threads --disable-logging --listen 4096&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary:
  Total:    8.0239 secs
  Slowest:  0.0306 secs
  Fastest:  0.0057 secs
  Average:  0.0231 secs
  Requests/sec: 2157.3084


Response time histogram:
  0.006 [1] |
  0.008 [1] |
  0.011 [1] |
  0.013 [2] |
  0.016 [8] |
  0.018 [10]    |
  0.021 [12]    |
  0.023 [10728] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.026 [6388]  |■■■■■■■■■■■■■■■■■■■■■■■■
  0.028 [95]    |
  0.031 [64]    |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WSGI, Python 3.8, &lt;code&gt;uvicorn --host 0.0.0.0 --port 80 --interface wsgi minimal:application --workers 1 --backlog 4096 --no-access-log&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary:
  Total:    8.0296 secs
  Slowest:  0.0939 secs
  Fastest:  0.0092 secs
  Average:  0.0461 secs
  Requests/sec: 1080.8754


Response time histogram:
  0.009 [1] |
  0.018 [197]   |■■■■■
  0.026 [732]   |■■■■■■■■■■■■■■■■■■■
  0.035 [1490]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.043 [1454]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.052 [1408]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.060 [1542]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.068 [1129]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.077 [591]   |■■■■■■■■■■■■■■■
  0.085 [96]    |■■
  0.094 [39]    |■
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ASGI, Python 3.8, &lt;code&gt;uvicorn --host 0.0.0.0 --port 80 minimalasgi:application --workers 1 --backlog 4096 --no-access-log&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary:
  Total:    8.0146 secs
  Slowest:  0.0450 secs
  Fastest:  0.0051 secs
  Average:  0.0221 secs
  Requests/sec: 2258.2405


Response time histogram:
  0.005 [1] |
  0.009 [11]    |
  0.013 [349]   |■
  0.017 [58]    |
  0.021 [1049]  |■■■
  0.025 [16560] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.029 [43]    |
  0.033 [8] |
  0.037 [8] |
  0.041 [10]    |
  0.045 [2] |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ASGI version, PyPy 3.6.9 and experimental plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary:
  Total:    8.0430 secs
  Slowest:  0.0757 secs
  Fastest:  0.0077 secs
  Average:  0.0460 secs
  Requests/sec: 1081.9301


Response time histogram:
  0.008 [1] |
  0.014 [5] |
  0.021 [6] |
  0.028 [7] |
  0.035 [8] |
  0.042 [14]    |
  0.048 [7824]  |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.055 [164]   |■
  0.062 [69]    |
  0.069 [147]   |■
  0.076 [457]   |■■
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WSGI version, PyPy 3.6.9 and experimental plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary:
  Total:    8.0179 secs
  Slowest:  0.0396 secs
  Fastest:  0.0022 secs
  Average:  0.0187 secs
  Requests/sec: 2660.7978


Response time histogram:
  0.002 [1] |
  0.006 [3] |
  0.010 [13]    |
  0.013 [17]    |
  0.017 [108]   |
  0.021 [20001] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.025 [352]   |■
  0.028 [105]   |
  0.032 [146]   |
  0.036 [318]   |■
  0.040 [270]   |■
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thought it was pretty interesting that uWSGI and uvicorn's WSGI / ASGI performance is flipped: in this test uWSGI's WSGI speed is about the same as uvicorn's ASGI speed. The WSGI mode in uvicorn also shows a greater spread of response times than the others.&lt;/p&gt;

&lt;p&gt;The experimental plugin is written almost entirely in Python. It hasn't been optimized yet but it performs well in WSGI mode. A lot of things might be done to improve its ASGI mode.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Working local ipv6 DNS on dd-wrt</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Fri, 14 May 2021 02:37:41 +0000</pubDate>
      <link>https://dev.to/dholth/working-local-ipv6-dns-on-dd-wrt-4no1</link>
      <guid>https://dev.to/dholth/working-local-ipv6-dns-on-dd-wrt-4no1</guid>
      <description>&lt;p&gt;If you'd like to be able to &lt;code&gt;ping6 name&lt;/code&gt; on your local network based on the names requested by your hosts,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable radvd; dnsmasq will do it.&lt;/li&gt;
&lt;li&gt;Disable dhcp6s; dnsmasq will do it.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Remove static leases&lt;/em&gt;. These are ipv4-only; we will define ipv6-compatible leases in configuration.&lt;/li&gt;
&lt;li&gt;Set 'Additional Dnsmasq Options:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;no-negcache
expand-hosts
domain-needed
enable-ra
no-resolv
server=8.8.8.8
server=8.8.4.4
dhcp-range=::1,::400,constructor:br0,ra-names
dhcp-host=00:24:8C:26:8C:67,cardamom,192.168.1.100,[::1]
dhcp-host=38:f9:d3:cf:ee:86,laptop,192.168.1.104,[::18f]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;no-negcache&lt;/code&gt;: Don't cache negative lookups.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expand-hosts&lt;/code&gt;: Add our network name to lookups.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;domain-needed&lt;/code&gt;: Don't pass hostname-only lookups to upstream DNS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enable-ra&lt;/code&gt;: Dnsmasq provides radvd&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no-resolv&lt;/code&gt;: Stay far away from the ISP domain name servers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server&lt;/code&gt;: Use Google's&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dhcp-range=::1,::400,constructor:br0,ra-names&lt;/code&gt;: Our LAN is on br0; dhcpv6 assigns addresses based on the delegated prefix, this range, and the device id. ra-names assigns DNS names to dual-stack hosts that advertise a name.&lt;/li&gt;
&lt;li&gt;Our hosts. The MAC address, the desired hostname, the ipv4 address, and a portion of the ipv6 address appended to the delegated prefix from our ISP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ipv4 static leases were the sticking point. These need to be configured only in the dnsmasq configuration; the ipv4 dd-wrt configuration will write a hosts file and prevent dnsmasq from doing what we want.&lt;/p&gt;

&lt;p&gt;SLAAC + ra-names doesn't seem to work for me when &lt;code&gt;dhcp-host&lt;/code&gt; specifies a hostname and an ipv4 address only. Instead, I specify the mac address, the desired hostname, the ipv4 address, and the suffix of the ipv6 address. The suffix can be either constructed-within-range style &lt;code&gt;[::18f]&lt;/code&gt; or whatever you want &lt;code&gt;[::1]&lt;/code&gt;, &lt;code&gt;[::2]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The host will continue to use its autoconfigured address for outgoing connections and will gain an additional DHCP-assigned address that can be used for incoming connections. My Android phone ignores the assigned suffix, but gets a working assigned ipv6 name anyway.&lt;/p&gt;

&lt;p&gt;I am using DHCPv6 with prefix delegation on the WAN side, and my Dhcp6c config is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface vlan2 {
 send ia-pd 0;
 send rapid-commit;
# request domain-name-servers;
 script "/sbin/dhcp6c-state";
};
id-assoc pd 0 {
 prefix ::/56 infinity;
 prefix-interface br0 {
  sla-id 0;
  sla-len 8;
 };
};
id-assoc na 0 { };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;dnsmasq is enabled and is providing local dns.&lt;/p&gt;

&lt;p&gt;The dd-wrt "Used Domain" is set to "LAN &amp;amp; WLAN", adding a line &lt;code&gt;domain=&amp;lt;what you typed in the LAN Domain field&amp;gt;&lt;/code&gt; to &lt;code&gt;dnsmasq.conf&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>ipv6</category>
    </item>
    <item>
      <title>How to use Vagrant with Docker instead of VirtualBox on OSX, without changing the world</title>
      <dc:creator>Daniel Holth</dc:creator>
      <pubDate>Wed, 24 Apr 2019 02:16:28 +0000</pubDate>
      <link>https://dev.to/dholth/how-to-use-docker-instead-of-virtualbox-on-osx-without-changing-the-world-4ack</link>
      <guid>https://dev.to/dholth/how-to-use-docker-instead-of-virtualbox-on-osx-without-changing-the-world-4ack</guid>
      <description>&lt;p&gt;My team has been using vagrant for some time to manage our development environment. Vagrant automatically provisions a Linux virtual machine that contains a miniature version of our entire system, plus development tools. We even re-use these scripts when we are deploying the real system. It has been working well, but I recently became frustrated with the resource usage of the underlying VirtualBox VM. Continuous laptop fan.&lt;/p&gt;

&lt;p&gt;The obvious solution was to try replacing VirtualBox with a more efficient hypervisor, something that wasn't spending all its time pretending to be an entire PC. Recent versions of OSX contain a hypervisor framework that lets unprivileged, app-store compliant programs manage virtual machines. There are just a few programs that take advantage of this framework, and one of them is Docker.&lt;/p&gt;

&lt;p&gt;Docker Desktop for Mac runs an efficient Linux virtual machine inside the hypervisor framework and then runs your containers inside that virtual machine. Vagrant supports docker as a provider as well as virtualbox, vmware, and others. If we can figure out how to make a Docker container that behaves like our old VirtualBox VM, then we should be able to get our environment running in the hypervisor instead.&lt;/p&gt;

&lt;h4&gt;
  
  
  Are you supposed to do that with Docker?
&lt;/h4&gt;

&lt;p&gt;At this point anyone who's read the docker documentation will ask whether you're supposed to use docker this way. They say &lt;em&gt;change the world&lt;/em&gt; by running exactly one application in its own container, perhaps with an orchestration framework. And that would be very cool, but there's no technical reason why you can't run an entire Linux distribution in a container instead, that's what I need, and docker is really easy to install.&lt;/p&gt;

&lt;h4&gt;
  
  
  What does vagrant require from a container?
&lt;/h4&gt;

&lt;p&gt;Once you've decided to use vagrant with docker, the main obstacle is that you have to provide your own &lt;a href="https://www.vagrantup.com/docs/boxes/base.html"&gt;base box&lt;/a&gt;. This is a box that is running a ssh server, with a vagrant user authenticated with vagrant's key pair, and passwordless sudo. Vagrant uses a Dockerfile to build this. In a Dockerfile adjacent my Vagrantfile,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;useradd vagrant &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"vagrant"&lt;/span&gt; | passwd &lt;span class="nt"&gt;--stdin&lt;/span&gt; vagrant &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; usermod &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; wheel vagrant

&lt;span class="c"&gt;# allow vagrant to login&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~vagrant &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; .ssh &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .ssh/authorized_keys &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; vagrant:vagrant .ssh &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;0700 .ssh &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;0600 .ssh/authorized_keys &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"vagrant ALL=(ALL) NOPASSWD: ALL"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/sudoers.d/vagrant_user

&lt;span class="c"&gt;# install sudo, sshd, scp&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yum &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install sudo &lt;/span&gt;openssh-server openssh-clients&lt;span class="p"&gt;;&lt;/span&gt; systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;sshd.service

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 22&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/usr/sbin/init"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other tricky bit is that in order for systemd, the init implementation used in the CentOS Linux distribution, to run inside our container, we have to expose a few filesystems by passing them to the &lt;code&gt;docker run&lt;/code&gt; command. In Vagrantfile,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'--tmpfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/tmp:exec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'--tmpfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/run'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/sys/fs/cgroup:/sys/fs/cgroup:ro'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vagrant will use these arguments when invoking &lt;code&gt;docker run --tmpfs /tmp:exec ...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This tells Docker to give systemd a read-only view of the cgroup filesystem, and in-memory /tmp and /run directories. (Since &lt;code&gt;/sys&lt;/code&gt; is not one of the OSX folders in Docker Desktop's file sharing preferences, it exposes the folder from the Linux box that's really running docker under the hood instead). If you don't expose these directories, systemd will not be able to start any services, including the ssh server.&lt;/p&gt;

&lt;p&gt;Once this is all in place, &lt;code&gt;vagrant up&lt;/code&gt; should be able to automatically build the Dockerfile, and then run the existing Vagrant provisioning steps by connecting to the container over ssh. From this point on the vagrant workflow is the same as if it was using a VirtualBox provider. Then you can go to town by moving more provisioning steps into the Dockerfile so that rebuilding the vagrant box is more efficient.&lt;/p&gt;

&lt;p&gt;I hope this will help someone else to use Docker with Vagrant. A working configuration with this setup and more is at &lt;a href="https://github.com/dholth/vagrant-docker/"&gt;https://github.com/dholth/vagrant-docker/&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>vagrant</category>
      <category>virtualbox</category>
      <category>osx</category>
    </item>
  </channel>
</rss>
