<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sysadmin on George Honeywood's blog</title><link>https://george.honeywood.org.uk/tags/sysadmin/</link><description>Recent content in Sysadmin on George Honeywood's blog</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>content: &lt;a href='https://creativecommons.org/licenses/by-nc-sa/4.0/'&gt;CC BY-NC-SA 4.0&lt;/a&gt; &amp;mdash; theme: modified &lt;a href='https://github.com/jakewies/hugo-theme-codex'&gt;codex&lt;/a&gt;</copyright><lastBuildDate>Sun, 01 Nov 2026 20:41:29 +0000</lastBuildDate><atom:link href="https://george.honeywood.org.uk/tags/sysadmin/index.xml" rel="self" type="application/rss+xml"/><item><title>It's all optics</title><link>https://george.honeywood.org.uk/blog/optics/</link><pubDate>Sun, 01 Nov 2026 20:41:29 +0000</pubDate><guid>https://george.honeywood.org.uk/blog/optics/</guid><description>&lt;p&gt;In computer networking, if you need to send data at a rate greater than 1 gigabit/second, you&amp;rsquo;ll have to look a bit further than the ubiquitous 1000BASE-T.&lt;/p&gt;
&lt;p&gt;10GBASE‑T &amp;mdash; a 10 gigabit/second iteration of the 1000BASE-T standard &amp;mdash; does exist, but it isn&amp;rsquo;t too popular. 10GBASE‑T uses the same twisted pair cables as 1000BASE-T (CAT 6/CAT 5e), but it is more power hungry (computationally intensive), higher latency, and is more expensive than other options available.&lt;/p&gt;
&lt;p&gt;The easy option if you&amp;rsquo;re only going small distances &amp;mdash; less than 5 meters, i.e. within a single datacenter rack &amp;mdash; is to use DAC cables (direct attach copper, colloquially known as twinax). DAC cables can handle high speeds, and are usually cheaper than using optical networking.&lt;/p&gt;
&lt;p&gt;However, if you need to send data further than within a single rack, at 10 gigabit/second or faster, you&amp;rsquo;ll probably want to use optical networking. In a simple form you&amp;rsquo;ll have some setup like this:&lt;/p&gt;
&lt;figure&gt;
&lt;a href='https://george.honeywood.org.uk/blog/optics/images/optics.drawio.svg'&gt;
&lt;picture&gt;
&lt;img
style=""
src='https://george.honeywood.org.uk/blog/optics/images/optics.drawio.svg'
loading="lazy"
alt='Simple network setup with fibre optics'
/&gt;
&lt;/picture&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;So in total, to connect two servers together via a switch, you&amp;rsquo;ll need four optics, and two fibre optic cables.
This is obviously a bit more complicated than using DAC cables, where you&amp;rsquo;d just need the two cables! Such is the price of extra range and flexibility. This is all Ethernet, despite the very different physical forms.&lt;/p&gt;
&lt;p&gt;In an enterprise setup, where you want redundancy, it gets rather more complicated. One method is to configure two switches in an MLAG (multi-chassis link aggregation group), then patch each server up to both of the MLAG switches. Once you&amp;rsquo;re patched up, configure link aggregation using LACP on the servers and switches:&lt;/p&gt;
&lt;figure&gt;
&lt;a href='https://george.honeywood.org.uk/blog/optics/images/optics-mlag.drawio.svg'&gt;
&lt;picture&gt;
&lt;img
style=""
src='https://george.honeywood.org.uk/blog/optics/images/optics-mlag.drawio.svg'
loading="lazy"
alt='Enterprise network setup with MLAG and LACP'
/&gt;
&lt;/picture&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;The idea behind this arrangement is that you can tolerate either a switch failure, or the loss of one of the paths up to the switches &amp;mdash; in both scenarios the server will stay connected to the network. An adjacent benefit here is that you can use the bandwidth of both links in aggregate, so long as you&amp;rsquo;re using layer3+4 packet hashing and there are multiple TCP connections.&lt;/p&gt;
&lt;h2 id="nics"&gt;NICs&lt;/h2&gt;
&lt;p&gt;Servers generally do not come with networking integrated onto the motherboard like desktop PCs.
Instead, external NICs are used, either in traditional PCIe slots, or via hotswappable OCP cards (these also use PCIe transport).&lt;/p&gt;
&lt;p&gt;These allow you to configure your server based on the uplink bandwidth and amount of ports required.&lt;/p&gt;
&lt;h2 id="optics"&gt;Optics&lt;/h2&gt;
&lt;p&gt;Optics are what take the electrical signals from the NIC and serialize them into the light that will be sent over the fibre optic cable.
On the other end of the connection, the remote optic will deserialize the light back into electrical signals to be handled by the NIC.&lt;/p&gt;
&lt;figure&gt;
&lt;a href='https://george.honeywood.org.uk/blog/optics/images/1000BASE-LX-optic_hu_5d5a77406422a944.webp'&gt;
&lt;picture&gt;
&lt;source
type="image/webp"
srcset="https://george.honeywood.org.uk/blog/optics/images/1000BASE-LX-optic_hu_26ab41bd57c430e3.webp 320w, https://george.honeywood.org.uk/blog/optics/images/1000BASE-LX-optic_hu_909fb408055959ad.webp 640w, https://george.honeywood.org.uk/blog/optics/images/1000BASE-LX-optic_hu_8b486af2f448f412.webp 960w, https://george.honeywood.org.uk/blog/optics/images/1000BASE-LX-optic_hu_9455ca6a499c0082.webp 1280w"
sizes="(max-width: 600px) 100vw, 600px"
/&gt;
&lt;img
style=""
src='https://george.honeywood.org.uk/blog/optics/images/1000BASE-LX-optic_hu_309f8082cd1a9897.jpg'
width="1280"
height="960"
loading="lazy"
alt='Duplex LC connector on an SFP optic'
/&gt;
&lt;/picture&gt;
&lt;/a&gt;
&lt;figcaption&gt;
Duplex LC connector on an SFP optic
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Optics vary wildly in price depending on the vendor selling them, the rated bandwidth, and the type of fibre they work with.
Wikipedia has nice lists of the different standards for optics, for &lt;a href="https://en.wikipedia.org/wiki/100_Gigabit_Ethernet"
target="_blank" rel="noreferrer noopener"
&gt;40G/100G&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/Terabit_Ethernet"
target="_blank" rel="noreferrer noopener"
&gt;200G+&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="sfps-and-form-factors"&gt;SFPs and form factors&lt;/h2&gt;
&lt;p&gt;NIC ports and optics come in various physical form factors. These are standardized as different kinds of SFPs (small factor pluggables):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SFP: SFP is the smallest of the SFP form-factors. There are different specifications under this size, including SFP, SFP+, and SFP28. Usually these ports handle between 1 and 25 gigabit/s speeds.&lt;/li&gt;
&lt;li&gt;QSFP (quad-SFP): Within this size, common specifications are QSFP, QSFP+, QSFP28, QSFP56. These ports are generally used for 40+ gigabit/s speeds. Internally, a single QSFP port is made up of four SFP ports known as lanes. This means you can do funky things, like use a &amp;ldquo;breakout&amp;rdquo; cable to connect a single 100G QSFP28 port on a switch to 4x servers, each with a SFP28 port (each server uplinked at 25G).&lt;/li&gt;
&lt;li&gt;OSFP (octal-SFP): the largest physical size. This is 8x SFP lanes, used for very high bandwidth connections.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="connectors"&gt;Connectors&lt;/h2&gt;
&lt;p&gt;There are three kinds of connectors I&amp;rsquo;ve encountered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LC: These are probably the most common connector seen in datacenters, generally in duplex (two fibre strands), where one strand is used for sending traffic (TX) and the other for receiving (RX).&lt;/li&gt;
&lt;li&gt;MPO: These contain many fibre strands (typically 8-24) in a single connector.&lt;br&gt;
One usecase of these high strand counts is trunk cabling between racks. Say you need to connect a rack with 12 servers to a different rack which has your switch in it. In this scenario, you&amp;rsquo;ve got some options:
&lt;ul&gt;
&lt;li&gt;Run 12x duplex LC terminated cables between the racks. This can get fiddly with so many cables.&lt;/li&gt;
&lt;li&gt;Run a single MPO-24 cable (24 strands) between the racks. This MPO-24 cable can then terminate into a cassette on both ends, which will change the presentation into 12x duplex LC ports. From the cassettes you can then run short patch cables to the optics in the switchports and server NICs.&lt;br&gt;
High strand counts are also handy for breakout applications. For example, if you have a MPO-8 connector on the 1x QSFP side of the breakout, you can split this out into 4x duplex LC connectors on the SFP side.&lt;/li&gt;
&lt;li&gt;You can also buy cables which bundle multiple fibres into a single physical cable, but are split out at the ends. You&amp;rsquo;d probably want to use some kind of patch panel to make the cable management easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SC: I&amp;rsquo;ve not seen these in the datacenter, instead commonly used for fibre-to-the-premises installs (GPON), where a single fibre strand is used (simplex).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These connectors come with different, incompatible end faces on the fibre strands. The common ones are UPC (blue connectors), and APC (green connectors).&lt;br&gt;
APC connectors have favourable light loss and light return characteristics compared to UPC, which can become more important with longer fibre runs. You need to make sure if you&amp;rsquo;ve got UPC optics, you have UPC cables!&lt;/p&gt;
&lt;p&gt;Another concern is polarity. With duplex LC connectors, you need to make sure the RX side on one optic is connected to the TX side on the other optic (and vice versa). If RX is connected to RX, you won&amp;rsquo;t get a link up! Generally you can swap the fibres round in the duplex LC connector, so it&amp;rsquo;s possible to fix at least.&lt;/p&gt;
&lt;p&gt;This polarity problem gets worse with MPO cables and their high strand counts. Cables come in different types, A, B, and C, which are used in different cabling polarity &amp;ldquo;methods&amp;rdquo;. With an MPO-8 cable, you need to make sure the 4x TX strands hit the 4x RX strands on the other optic, in the right order.&lt;/p&gt;
&lt;p&gt;Another thing to be a bit careful with MPO cables is that the different fibre strands don&amp;rsquo;t always line up when you mix MPO standards.
You can mix and match MPO-8 and MPO-12 (the inner 4 strands of the MPO-12 will be unused). However, if you try to mix MPO-12 and MPO-24, none of the strands will line up!&lt;/p&gt;
&lt;h2 id="fibre-optic-cables"&gt;Fibre optic cables&lt;/h2&gt;
&lt;p&gt;Fibre optic cables come in two main types: multimode and singlemode. If you&amp;rsquo;ve got multimode fibre, you&amp;rsquo;ll need to use multimode optics, and vice versa for singlemode.
Light in multimode fibre experiences modal dispersion, so reach is limited compared to singlemode fibre.&lt;/p&gt;
&lt;figure&gt;
&lt;a href='https://george.honeywood.org.uk/blog/optics/images/OS2-duplex-fibre-LC-connector_hu_747284bfab398a1b.webp'&gt;
&lt;picture&gt;
&lt;source
type="image/webp"
srcset="https://george.honeywood.org.uk/blog/optics/images/OS2-duplex-fibre-LC-connector_hu_66054e501c574f2.webp 320w, https://george.honeywood.org.uk/blog/optics/images/OS2-duplex-fibre-LC-connector_hu_b38e21c684225c24.webp 640w, https://george.honeywood.org.uk/blog/optics/images/OS2-duplex-fibre-LC-connector_hu_ea38766f2b26dbfd.webp 960w, https://george.honeywood.org.uk/blog/optics/images/OS2-duplex-fibre-LC-connector_hu_b9ba8546782cd39.webp 1280w"
sizes="(max-width: 600px) 100vw, 600px"
/&gt;
&lt;img
style=""
src='https://george.honeywood.org.uk/blog/optics/images/OS2-duplex-fibre-LC-connector_hu_f19a4e4c66716805.jpg'
width="1280"
height="960"
loading="lazy"
alt='OS2 (yellow) duplex singlemode fibre with UPC (blue) LC connector'
/&gt;
&lt;/picture&gt;
&lt;/a&gt;
&lt;figcaption&gt;
OS2 (yellow) duplex singlemode fibre with UPC (blue) LC connector
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;OS2 is a singlemode fibre standard, colour-coded yellow. OS2 fibre is capable of reaching 40 kilometres when used with -ER optics &amp;ndash; much further than multimode!&lt;/p&gt;
&lt;p&gt;Probably the most common type of multimode fibre is OM4, which is usually colour-coded aqua. OM4 supports distances of around 100 to 150 meters.
OM5 is a revision of this standard, which can extend the reach of OM4 by around 50 meters.&lt;/p&gt;
&lt;p&gt;The economics here can be tricky. Multimode cables tend to be more expensive than singlemode cables, but singlemode optics are often more expensive than multimode optics.&lt;br&gt;
Historically multimode fibre would be used for shorter runs, e.g. within a DC cage, and singlemode used when longer distances are required. However, costs for singlemode optics have gone down, so multimode has become a bit rarer.&lt;/p&gt;
&lt;h2 id="multiplexing"&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;In their simplest form, optics send only a single wavelength of light down a fibre strand, but it is possible to send multiple wavelengths down a single strand &amp;mdash; this is known as multiplexing. This is more common with singlemode fibre than multimode.
There are a couple of different usecases here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bidirectional optics. BiDi optics both send and receive light on a single strand of fibre (simplex). Simplex fibre is handy for the residential usecase of hiding a fibre along a wall, or under doors. G.657B3 bend insensitive fibre can be less than a millimeter wide, with a minimum bend radius of circa 5mm.&lt;/li&gt;
&lt;li&gt;High bandwidth. Multiplexing is useful if you want to increase your throughput over a single strand.
Higher speed links are usually made up of multiple combined lanes of traffic, and the simplest way to transport this is to use multiple strands of fibre. For example, 100GBASE-SR4 optics use 8 strands of multimode fibre, 4 for each direction, each strand running at 25G.&lt;br&gt;
However, say you&amp;rsquo;re reusing an existing run of OS2 duplex fibre, you can use 100GBASE-LR4 optics instead, which multiplex 4x 25G wavelengths onto each directional strand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Optics which use multiplexing tend to be more expensive than non-multiplexing ones &amp;mdash; this is why multimode optics tend to be cheaper than singlemode optics!&lt;/p&gt;
&lt;h2 id="footnote"&gt;Footnote&lt;/h2&gt;
&lt;p&gt;There are lots of things I&amp;rsquo;ve missed out or glossed over here. FEC (forward error correction) in particular is very interesting. I&amp;rsquo;d also like to cover link auto-negotiation, but I&amp;rsquo;d need to learn how that works first!
Bear in mind I&amp;rsquo;m writing from the blinkered perspective of a Linux sysadmin, rather than a network engineer or fibre tech &amp;mdash; I&amp;rsquo;ve never spliced a fibre!&lt;/p&gt;</description></item><item><title>Bodging a brouter</title><link>https://george.honeywood.org.uk/blog/bodging-a-brouter/</link><pubDate>Fri, 17 Oct 2025 19:20:35 +0100</pubDate><guid>https://george.honeywood.org.uk/blog/bodging-a-brouter/</guid><description>&lt;p&gt;I wanted to do something a bit weird the other day.&lt;br&gt;
I&amp;rsquo;ve got two networks. &lt;code&gt;192.168.0.1/24&lt;/code&gt; and &lt;code&gt;192.168.1.1/24&lt;/code&gt;, which are at different sites &amp;mdash; &lt;code&gt;.1.&lt;/code&gt; is at my parent&amp;rsquo;s house, &lt;code&gt;.0.&lt;/code&gt; is at my flat.&lt;br&gt;
A Jellyfin media server lives on the &lt;code&gt;.1.&lt;/code&gt; network, and I want to be able to reach that from my the &lt;code&gt;.0.&lt;/code&gt; network, where I have a Roku with the Jellyfin app installed.&lt;/p&gt;
&lt;p&gt;The really easy option here is to add a port forward on the &lt;code&gt;.1.&lt;/code&gt; side router, such that you can reach the Jellyfin server from the public internet &amp;mdash; this is exactly what I used to do for Plex. However, exposing things to the internet isn&amp;rsquo;t usually a great idea from a security perspective, especially when you aren&amp;rsquo;t great at keeping things up to date!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got Tailscale (which is a fancy Wireguard VPN solution) set up on the &lt;code&gt;.1.&lt;/code&gt; side, so I can reach that network from specific devices that have the Tailscale app installed, but unfortunately there isn&amp;rsquo;t an app for the Roku.
What makes things a bit more complicated is that the router for the &lt;code&gt;.0.&lt;/code&gt; network is a rubbish ISP provided one. I can&amp;rsquo;t easily avoid using it as the flat has 4G/LTE internet, don&amp;rsquo;t ask!
As such I can&amp;rsquo;t configure any extra routes or install the Tailscale VPN on the router itself.&lt;/p&gt;
&lt;p&gt;My first pass at this was very lazy. On my desktop on the &lt;code&gt;.0.&lt;/code&gt; side I&amp;rsquo;ve got Tailscale running, so it can reach the Jellyfin server over on &lt;code&gt;.1.&lt;/code&gt; fine. All that&amp;rsquo;s required is a little TCP proxy &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;desktop$ socat -dd TCP4-LISTEN:8096,fork,reuseaddr TCP4:192.168.1.30:8096
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then on the Roku I can configure the Jellyfin client to hit the desktop LAN &lt;code&gt;.0.&lt;/code&gt; IP and the connection is forwarded on to the remote Jellyfin server at &lt;code&gt;192.168.1.30&lt;/code&gt;. This worked perfectly fine, but having to keep my desktop on whenever I wanted to use Jellyfin was a bit annoying.&lt;/p&gt;
&lt;p&gt;My next idea was to just put this on a Raspberry Pi which I can leave on permenantly without using too much expensive electricity. The downside with this &lt;code&gt;socat&lt;/code&gt; trick is that it&amp;rsquo;s a bit static, if you wanted to talk to multiple endpoints you&amp;rsquo;d need to listen on multiple ports and remember all the mappings.&lt;/p&gt;
&lt;p&gt;What I really wanted was a proper routed setup, where any &lt;code&gt;.0.&lt;/code&gt; device can route to anything on the &lt;code&gt;.1.&lt;/code&gt; network. This is where I had a devious idea &amp;mdash; to use the Raspberry Pi as a bridge for all traffic on the way to my router. Bridging is where a device with two NICs, links them, such that traffic coming in on one, is sent straight back out the other.&lt;br&gt;
For my purpose the Pi can&amp;rsquo;t act purely as a bridge though, it also needs to do some routing of the &lt;code&gt;.1.&lt;/code&gt; network bound packets, such that they pass through the tailscale tunnel.&lt;/p&gt;
&lt;p&gt;As far as I can tell, this is a bit of an unusual thing to want to do &amp;mdash; it&amp;rsquo;s called bridge routing (or brouting). It doesn&amp;rsquo;t seem to easy to do this with a usual Linux bridge device &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, but thankfully it&amp;rsquo;s pretty simple to configure something that behaves the same.&lt;/p&gt;
&lt;p&gt;These are the interfaces on my brouter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@raspberry:~# networkctl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IDX LINK TYPE OPERATIONAL SETUP
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; lo loopback carrier unmanaged
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; eth0 ether routable configured
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; eth1 ether routable configured
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; tailscale0 none routable unmanaged
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m using &lt;code&gt;eth0&lt;/code&gt; as the upstream side (plugged into the ISP router) and &lt;code&gt;eth1&lt;/code&gt; as downstream (plugged into a switch and WAP).&lt;br&gt;
You&amp;rsquo;ll need some kernel config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@raspberry:~# cat /etc/sysctl.d/99-proxyarp.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.ip_forward&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.conf.eth0.proxy_arp&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;net.ipv4.conf.eth1.proxy_arp&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ip_forward&lt;/code&gt; makes the kernel to retransmit packets that weren&amp;rsquo;t destined for a local IP address.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_arp&lt;/code&gt; causes ARP traffic coming in on one interface to be sent back out of the other, replacing the MAC addresses such that layer 2 works as expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you&amp;rsquo;ve got your kernel parameters set, you need a bit of &lt;code&gt;iptables&lt;/code&gt; magic to handle the routing part of the equation. We want to mark the &lt;code&gt;.1.&lt;/code&gt; bound packets, so that they are forced to route via our &lt;code&gt;tailscale0&lt;/code&gt; interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iptables -t mangle -A PREROUTING -d 192.168.1.0/24 -j MARK --set-mark &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip rule add fwmark &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; table &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip route add default dev tailscale0 table &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This isn&amp;rsquo;t quite enough to make things work unfortunately. Checking the traffic, I can see packets headed out over the Tailscale interface, but no replies coming back:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@raspberry:~# tcpdump -i tailscale0 &lt;span style="color:#e6db74"&gt;&amp;#39;icmp&amp;#39;&lt;/span&gt; -n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:53:28.804692 IP 192.168.0.3 &amp;gt; 192.168.1.30: ICMP echo request, id 253, seq 1, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:53:29.805010 IP 192.168.0.3 &amp;gt; 192.168.1.30: ICMP echo request, id 253, seq 2, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:53:30.829186 IP 192.168.0.3 &amp;gt; 192.168.1.30: ICMP echo request, id 253, seq 3, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is because devices on the &lt;code&gt;.1.&lt;/code&gt; net can&amp;rsquo;t route directly back to &lt;code&gt;.0.&lt;/code&gt;. To make this work we have to SNAT (source NAT) the traffic such that it appears to come from the local side of the Tailscale tunnel:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iptables -t nat -A POSTROUTING -o tailscale0 -j MASQUERADE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we see replies coming back too! &lt;code&gt;100.120.7.128&lt;/code&gt; is the local IP of the &lt;code&gt;tailscale0&lt;/code&gt; interface.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@raspberry:~# tcpdump -i tailscale0 &lt;span style="color:#e6db74"&gt;&amp;#39;icmp&amp;#39;&lt;/span&gt; -n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:56:50.428553 IP 100.120.7.128 &amp;gt; 192.168.1.30: ICMP echo request, id 254, seq 1, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:56:50.499516 IP 192.168.1.30 &amp;gt; 100.120.7.128: ICMP echo reply, id 254, seq 1, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:56:51.429393 IP 100.120.7.128 &amp;gt; 192.168.1.30: ICMP echo request, id 254, seq 2, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:56:51.517161 IP 192.168.1.30 &amp;gt; 100.120.7.128: ICMP echo reply, id 254, seq 2, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:56:52.431187 IP 100.120.7.128 &amp;gt; 192.168.1.30: ICMP echo request, id 254, seq 3, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18:56:52.509740 IP 192.168.1.30 &amp;gt; 100.120.7.128: ICMP echo reply, id 254, seq 3, length &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I figure I better include a diagram here:&lt;/p&gt;
&lt;figure&gt;
&lt;a href='https://george.honeywood.org.uk/blog/bodging-a-brouter/images/network.svg'&gt;
&lt;picture&gt;
&lt;img
style=""
src='https://george.honeywood.org.uk/blog/bodging-a-brouter/images/network.svg'
loading="lazy"
alt='Network diagram of brouting setup'
/&gt;
&lt;/picture&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;The final piece of the puzzle is that this setup breaks DHCP (dynamic IP configuration) &amp;mdash; as it uses broadcast traffic, and the kernel won&amp;rsquo;t forward broadcast traffic. This is pretty easy to work around, you can install a little daemon to proxy traffic recieved downstream on &lt;code&gt;eth1&lt;/code&gt; up to the ISP router on &lt;code&gt;eth0&lt;/code&gt;. I used &lt;code&gt;isc-dhcp-relay&lt;/code&gt; and the config is trivial:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@raspberry:~# cat /etc/default/isc-dhcp-relay
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# What servers should the DHCP relay forward requests to?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SERVERS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;192.168.0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# On what interfaces should the DHCP relay (dhrelay) serve DHCP requests?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INTERFACES&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;eth1 eth0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The network flow for DHCP now looks like this, where &lt;code&gt;192.168.0.125&lt;/code&gt; is &lt;code&gt;eth0&lt;/code&gt;&amp;rsquo;s local IP and &lt;code&gt;192.168.0.126&lt;/code&gt; is &lt;code&gt;eth1&lt;/code&gt;&amp;rsquo;s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@raspberry:~# tcpdump -i any &lt;span style="color:#e6db74"&gt;&amp;#39;port bootps&amp;#39;&lt;/span&gt; -n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5112 eth1 B IP 0.0.0.0.68 &amp;gt; 255.255.255.255.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length &lt;span style="color:#ae81ff"&gt;312&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5114 eth0 Out IP 192.168.0.125.67 &amp;gt; 192.168.0.1.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length &lt;span style="color:#ae81ff"&gt;312&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5345 eth0 In IP 192.168.0.1.67 &amp;gt; 192.168.0.126.67: BOOTP/DHCP, Reply, length &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5346 eth1 Out IP 192.168.0.126.67 &amp;gt; 0.0.0.0.68: BOOTP/DHCP, Reply, length &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5418 eth1 B IP 0.0.0.0.68 &amp;gt; 255.255.255.255.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length &lt;span style="color:#ae81ff"&gt;308&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5419 eth0 Out IP 192.168.0.125.67 &amp;gt; 192.168.0.1.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length &lt;span style="color:#ae81ff"&gt;308&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5684 eth0 In IP 192.168.0.1.67 &amp;gt; 192.168.0.126.67: BOOTP/DHCP, Reply, length &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5685 eth1 Out IP 192.168.0.126.67 &amp;gt; 192.168.0.53.68: BOOTP/DHCP, Reply, length &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5760 eth1 B IP 0.0.0.0.68 &amp;gt; 255.255.255.255.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length &lt;span style="color:#ae81ff"&gt;318&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5761 eth0 Out IP 192.168.0.125.67 &amp;gt; 192.168.0.1.67: BOOTP/DHCP, Request from aa:bb:cc:dd:ee:ff, length &lt;span style="color:#ae81ff"&gt;318&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5814 eth0 In IP 192.168.0.1.67 &amp;gt; 192.168.0.126.67: BOOTP/DHCP, Reply, length &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19:12:11.5816 eth1 Out IP 192.168.0.126.67 &amp;gt; 192.168.0.53.68: BOOTP/DHCP, Reply, length &lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The advantage of this setup is that it&amp;rsquo;s transparent to downstream devices, they think they are just talking to the ISP router normally. That it just happens to sit on the path is useful if something goes wrong with the Pi &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, I can just physically unplug it and I won&amp;rsquo;t lose anything other than my extra route.&lt;/p&gt;
&lt;p&gt;It probably would have been easier if I had set up my Pi with OpenWRT or similar, and had it act as a normal router, downstream of the ISP provided router. Well, at least this way I learnt some stuff!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;You could also use a HTTP reverse proxy for this, but these tools usually need a bit more config than &lt;code&gt;socat&lt;/code&gt; does.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;It might be possible via the &lt;code&gt;br_netfilter&lt;/code&gt; kernel module and &lt;code&gt;net.bridge.bridge-nf-call-iptables=1&lt;/code&gt; parameter, but I couldn&amp;rsquo;t figure out how to make it work!&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;SD card failure being what I am afraid of here!&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>ZFS snapshots and how to sync them</title><link>https://george.honeywood.org.uk/blog/zfs/</link><pubDate>Sat, 07 Dec 2024 20:50:25 +0000</pubDate><guid>https://george.honeywood.org.uk/blog/zfs/</guid><description>&lt;p&gt;I am quite a big fan of ZFS. One of its useful features is snapshotting, allowing you to record the state of your filesystem at a specific point in time. This means if you later change or delete files, you will be able to roll-back to this previous snapshot, or restore individual files. So far, so normal &amp;mdash; even NTFS has a kind of snapshot/restore capability.&lt;/p&gt;
&lt;p&gt;What makes ZFS&amp;rsquo;s snapshots even more useful is what they enable in terms of filesystem synchronization. My use case is that I have 10 TB of data at a remote site that I want to back up to my local desktop. I am constrained by a slow internet connection, so I need a solution that doesn&amp;rsquo;t use much bandwidth.&lt;/p&gt;
&lt;p&gt;What I can do is to have a matching snapshot pair on both sides, then take another snapshot on the sending end. ZFS can then find which blocks have changed on the sending side, and send and replay the differences to the receiving side. Unfortunately you still have to do a single initial replication on the whole dataset &amp;mdash; you can&amp;rsquo;t get around that!&lt;/p&gt;
&lt;p&gt;In my case the initial replication was going to take more than a week over the slow link; I ended up &lt;a href="https://en.wikipedia.org/wiki/Sneakernet"
target="_blank" rel="noreferrer noopener"
&gt;sneakernetting&lt;/a&gt; the data between sites to speed up the process. While you can achieve incremental results with a tool like &lt;code&gt;rsync&lt;/code&gt;, the process isn&amp;rsquo;t as efficient, as it has to check to see if each file has been updated.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jimsalterjrs/sanoid#syncoid"
target="_blank" rel="noreferrer noopener"
&gt;&lt;code&gt;syncoid&lt;/code&gt;&lt;/a&gt; is the tool I use to automate the &lt;code&gt;zfs snapshot&lt;/code&gt;, &lt;code&gt;zfs send | receive&lt;/code&gt; process.
I run a command like this to pull all the changes since the previous run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@desktop:~# syncoid --recursive --skip-parent root@192.168.1.10:tank tank-offsite
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here &lt;code&gt;tank&lt;/code&gt; is a pool on the remote machine at &lt;code&gt;192.168.1.10&lt;/code&gt;, and &lt;code&gt;tank-offsite&lt;/code&gt; is mounted on the local desktop.
I don&amp;rsquo;t use the desktop machine that runs this backup consistently, it suspended most of the time, so I needed a way to schedule this job regularly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt; to the rescue! I created a service to run the &lt;code&gt;syncoid&lt;/code&gt; command, and a timer to run the service on a schedule.
The magic here is the &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html#WakeSystem="
target="_blank" rel="noreferrer noopener"
&gt;&lt;code&gt;WakeSystem=&lt;/code&gt;&lt;/a&gt; timer option &amp;mdash; which resumes my desktop from suspend when the timer triggers.
Here is my config &amp;mdash; it is pleasantly simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@desktop:~# systemctl cat tank-offsite.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;service,timer&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/systemd/system/tank-offsite.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;Unit&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Description&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Backup tank to tank-offsite
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requires&lt;span style="color:#f92672"&gt;=&lt;/span&gt;network-online.target
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;After&lt;span style="color:#f92672"&gt;=&lt;/span&gt;network-online.target
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;Service&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;oneshot
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# wait for the network to be usable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExecStartPre&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sh -c &lt;span style="color:#e6db74"&gt;&amp;#39;until ping -c 1 192.168.1.10; do sleep 1; done&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# do the sync&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExecStart&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/local/sbin/syncoid --recursive --skip-parent root@192.168.1.10:tank tank-offsite
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# suspend the machine again&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExecStopPost&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/bin/systemctl suspend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /etc/systemd/system/tank-offsite.timer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;Unit&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Description&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Backup tank to tank-offsite on a schedule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;Timer&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;OnCalendar&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Mon-Sun 03:00
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WakeSystem&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;Install&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WantedBy&lt;span style="color:#f92672"&gt;=&lt;/span&gt;timers.target
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The only little hack here is the &lt;code&gt;ExecStartPre=&lt;/code&gt; line; my first pass at this didn&amp;rsquo;t include it. I was hoping that &lt;code&gt;After=network-online.target&lt;/code&gt; would be enough to make sure the network stack was up before the &lt;code&gt;syncoid&lt;/code&gt; command would run. Evidently not:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ journalctl -u tank-offsite
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:28 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Starting tank-offsite.service - Backup tank to tank-offsite...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:29 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;162504&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: ssh: connect to host 192.168.1.10 port 22: Network is unreachable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:29 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;162505&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: ssh: connect to host 192.168.1.10 port 22: Network is unreachable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:29 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;162423&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: CRITICAL ERROR: ssh connection echo test failed &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; root@192.168.1.10 with exit code &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; at /usr/local/sbin/syncoid line 1714.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:29 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: tank-offsite.service: Control process exited, code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;exited, status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;1/FAILURE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:29 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: tank-offsite.service: Failed with result &lt;span style="color:#e6db74"&gt;&amp;#39;exit-code&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;22&lt;/span&gt; 03:00:29 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Failed to start tank-offsite.service - Backup tank to tank-offsite.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt;&amp;rsquo;s docs have &lt;a href="https://systemd.io/NETWORK_ONLINE/#modyfing-the-meaning-of-network-onlinetarget"
target="_blank" rel="noreferrer noopener"
&gt;some discussion of this scenario&lt;/a&gt;. They suggest adding a separate service that runs the same ping check with &lt;code&gt;Before=network-online.target&lt;/code&gt; specified. This means that any services depending on &lt;code&gt;network-online.target&lt;/code&gt; will only start once the ping has been successful. As I only have a single service that cares about this, keeping it all together in the same unit with &lt;code&gt;ExecStartPre=&lt;/code&gt; made sense to me.&lt;/p&gt;
&lt;p&gt;You may have noticed a slight issue with this timer setup: my machine will always suspend after the backup job finishes. This is the desired outcome when the timer has caused the desktop to wake up, but it will probably be quite annoying if I were using the machine at the time. Luckily I&amp;rsquo;m usually fast asleep before 03:00! I couldn&amp;rsquo;t find a neat way of avoiding this.&lt;/p&gt;
&lt;p&gt;One thing that I&amp;rsquo;d still like to get working is ZFS&amp;rsquo;s &lt;a href="https://openzfs.org/wiki/Features#nop-write"
target="_blank" rel="noreferrer noopener"
&gt;&lt;code&gt;nop-write&lt;/code&gt;&lt;/a&gt; support.
My use for this is that I have a different daily job that tars up data from my VPS, overwriting a file on disk.
This means that every day I have to sync the full tar file, as from ZFS&amp;rsquo;s perspective it is completely new data, even though there will have been very few changes.
The data in question is only about 1.3 GB &amp;mdash; so it isn&amp;rsquo;t too slow, but if I can avoid having to copy it at all then it would be preferable.
Alternatively, I could just swap to using &lt;code&gt;rsync&lt;/code&gt; for this job, and avoid re-writing the data at all. ZFS&amp;rsquo;s compression should replicate the benefits I was getting from compressing the tar file.&lt;/p&gt;
&lt;h2 id="a-few-days-later"&gt;a few days later&lt;/h2&gt;
&lt;p&gt;Getting &lt;a href="https://openzfs.org/wiki/Features#nop-write"
target="_blank" rel="noreferrer noopener"
&gt;&lt;code&gt;nop-write&lt;/code&gt;&lt;/a&gt; working was indeed really just as simple as swapping the dataset checksum setting to &lt;code&gt;sha256&lt;/code&gt; (or you can pick one of &lt;a href="https://openzfs.github.io/openzfs-docs/Basic%20Concepts/Checksums.html#checksum-algorithms"
target="_blank" rel="noreferrer noopener"
&gt;the other options&lt;/a&gt;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ zfs set checksum&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sha256 pool_name/dataset_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now even though the job rewrites this large tar file daily, there is nothing for &lt;code&gt;syncoid&lt;/code&gt; to copy over! (well, only 84 KB compared to 1.3 GB)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ journalctl -qu tank-offsite --grep &lt;span style="color:#e6db74"&gt;&amp;#39;tank/backup@&amp;#39;&lt;/span&gt; | tail -n &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# without `nop-write`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;28&lt;/span&gt; 03:00:43 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;337636&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup@syncoid_desktop_2024-12-27:23:38:04-GMT00:00 ... syncoid_desktop_2024-12-28:03:00:43-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ 1.3 GB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# with `nop-write`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Dec &lt;span style="color:#ae81ff"&gt;28&lt;/span&gt; 13:28:38 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;26300&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup@syncoid_desktop_2024-12-28:13:12:15-GMT00:00 ... syncoid_desktop_2024-12-28:13:28:38-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ &lt;span style="color:#ae81ff"&gt;84&lt;/span&gt; KB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I am now questioning why I didn&amp;rsquo;t use &lt;code&gt;rsync&lt;/code&gt; in the first place, however.
&lt;code&gt;nop-write&lt;/code&gt; saves me from replicating the unchanged overwritten file, by preventing the writes from actually hitting the disk.
It doesn&amp;rsquo;t avoid the whole daily download from the VPS though!
ZFS couldn&amp;rsquo;t possibly know what writes would be no-ops without having the new data to checksum against what is already on disk.&lt;/p&gt;
&lt;p&gt;The only slight gotcha with &lt;code&gt;nop-write&lt;/code&gt; is that you need to make sure that you don&amp;rsquo;t truncate the file you rewrite.
For example something like this won&amp;rsquo;t work:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh $VPS_HOSTNAME &lt;span style="color:#e6db74"&gt;&amp;#39;tar -cf - /var/www/&amp;#39;&lt;/span&gt; &amp;gt; var-www.tar
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you do this, then the shell first truncates the file before writing the new data.
There is probably a clever bash way of avoiding the truncation, but using &lt;code&gt;dd&lt;/code&gt; with the &lt;code&gt;conv=notrunc&lt;/code&gt; option is a bit more self documenting:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh $VPS_HOSTNAME &lt;span style="color:#e6db74"&gt;&amp;#39;tar -cf - /var/www/&amp;#39;&lt;/span&gt; | dd of&lt;span style="color:#f92672"&gt;=&lt;/span&gt;var-www.tar conv&lt;span style="color:#f92672"&gt;=&lt;/span&gt;notrunc bs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;1M
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Although &lt;code&gt;nop-write&lt;/code&gt; sounds very compelling at first &amp;ndash; it is actually fairly niche. Most of the time it is possible (and more efficient) to avoid rewriting unchanged data!&lt;/p&gt;
&lt;h2 id="a-few-weeks-later"&gt;a few weeks later&lt;/h2&gt;
&lt;p&gt;At some point I decided to enable some power management settings on my desktop.
Usually, my desktop is suspended. However, sometimes one of my meddling cats will step on the keyboard, waking it up, so it is handy if the machine automatically suspends after being idle for some amount of time.
Auto-suspend however created a bit of headache for this backup job:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:44 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Starting tank-offsite.service - Backup tank to tank-offsite...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:55 desktop sh&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296224&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: PING 192.168.1.10 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;192.168.1.10&lt;span style="color:#f92672"&gt;)&lt;/span&gt; 56&lt;span style="color:#f92672"&gt;(&lt;/span&gt;84&lt;span style="color:#f92672"&gt;)&lt;/span&gt; bytes of data.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:55 desktop sh&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296224&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt; bytes from 192.168.1.10: icmp_seq&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; ttl&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;62&lt;/span&gt; time&lt;span style="color:#f92672"&gt;=&lt;/span&gt;12.8 ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:55 desktop sh&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296224&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: --- 192.168.1.10 ping statistics ---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:55 desktop sh&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296224&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; packets transmitted, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; received, 0% packet loss, time 0ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:55 desktop sh&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296224&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: rtt min/avg/max/mdev &lt;span style="color:#f92672"&gt;=&lt;/span&gt; 12.842/12.842/12.842/0.000 ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:00:56 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup@syncoid_desktop_2025-01-09:03:00:51-GMT00:00 ... syncoid_desktop_2025-01-10:03:00:56-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ 3.2 MB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:01:11 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup/email@syncoid_desktop_2025-01-09:03:01:11-GMT00:00 ... syncoid_desktop_2025-01-10:03:01:11-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ 1.5 MB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:01:13 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup/lxc@syncoid_desktop_2025-01-09:03:01:13-GMT00:00 ... syncoid_desktop_2025-01-10:03:01:13-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; KB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:01:14 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup/pbs@syncoid_desktop_2025-01-09:03:01:13-GMT00:00 ... syncoid_desktop_2025-01-10:03:01:14-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ 1.0 GB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 08:52:35 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296484&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: cannot receive incremental stream: dataset is busy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:29:53 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296488&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: lzop: Broken pipe: &amp;lt;stdout&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:29:53 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: CRITICAL ERROR: ssh -S /tmp/syncoid-root@192.168.1.10-1736478055-2255 root@192.168.1.10 &lt;span style="color:#e6db74"&gt;&amp;#39; zfs send -I &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;tank/backup/pbs&amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;@&amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;syncoid_desktop_2025-01-09:03:01:13-G&amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:29:55 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/backup/veeam@syncoid_desktop_2025-01-09:03:15:13-GMT00:00 ... syncoid_desktop_2025-01-10:19:29:55-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; KB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:29:59 desktop syncoid&lt;span style="color:#f92672"&gt;[&lt;/span&gt;296226&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Sending incremental tank/data@syncoid_desktop_2025-01-09:05:52:20-GMT00:00 ... syncoid_desktop_2025-01-10:19:29:58-GMT00:00 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;~ 35.8 MB&lt;span style="color:#f92672"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:30:39 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: tank-offsite.service: Main process exited, code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;exited, status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;2/INVALIDARGUMENT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:30:39 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: tank-offsite.service: Failed with result &lt;span style="color:#e6db74"&gt;&amp;#39;exit-code&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:30:39 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Failed to start tank-offsite.service - Backup tank to tank-offsite.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 19:30:39 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: tank-offsite.service: Consumed 31.336s CPU time, 43.7M memory peak.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It wasn&amp;rsquo;t immediately clear what was going wrong to me, as I&amp;rsquo;d completely forgotten that auto-suspend was enabled.
What made it more obvious was this other journal entry:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:15:40 desktop systemd-logind&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1367&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: The system will suspend now!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:15:40 desktop ModemManager&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1645&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &amp;lt;msg&amp;gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;sleep-monitor-systemd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; system is about to suspend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:15:40 desktop NetworkManager&lt;span style="color:#f92672"&gt;[&lt;/span&gt;2071&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &amp;lt;info&amp;gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1736478940.0158&lt;span style="color:#f92672"&gt;]&lt;/span&gt; manager: sleep: sleep requested &lt;span style="color:#f92672"&gt;(&lt;/span&gt;sleeping: no enabled: yes&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:15:40 desktop NetworkManager&lt;span style="color:#f92672"&gt;[&lt;/span&gt;2071&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: &amp;lt;info&amp;gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;1736478940.0163&lt;span style="color:#f92672"&gt;]&lt;/span&gt; manager: NetworkManager state is now ASLEEP
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:15:40 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Reached target sleep.target - Sleep.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 03:15:40 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Starting nvidia-suspend.service - NVIDIA system suspend actions...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Conveniently, it is quite easy to add &amp;ldquo;inhibitions&amp;rdquo; that prevent the system from sleeping.
I added a &lt;code&gt;systemd-inhibit&lt;/code&gt; call on the &lt;code&gt;ExecStart=&lt;/code&gt; line of the unit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExecStart&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/bin/systemd-inhibit /usr/local/sbin/syncoid &lt;span style="color:#f92672"&gt;[&lt;/span&gt;-snip-&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This wasn&amp;rsquo;t quite enough, however. There is &lt;a href="https://github.com/systemd/systemd/issues/14045"
target="_blank" rel="noreferrer noopener"
&gt;a race condition&lt;/a&gt; if you call &lt;code&gt;systemd-inhibit&lt;/code&gt; while the machine is still waking from sleep:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; 03:00:51 desktop systemd-inhibit&lt;span style="color:#f92672"&gt;[&lt;/span&gt;398074&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: Failed to inhibit: The operation inhibition has been requested &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; is already running
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Jan &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; 03:00:51 desktop systemd&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: tank-offsite.service: Main process exited, code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;exited, status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;1/FAILUR
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ve hacked around this by adding a 30s sleep in another &lt;code&gt;ExecStartPre=&lt;/code&gt; line.
I&amp;rsquo;m somewhat surprised there isn&amp;rsquo;t a built-in option to inhibit sleep while a unit is running, but I suppose this is a niche thing to do.&lt;/p&gt;</description></item></channel></rss>