feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
# The instance's host at home
|
|
|
|
|
|
|
|
|
|
|
|
halkyon.li runs at home on balthasar (ADR 0039) and is published through ramiel
|
|
|
|
|
|
(`../forward/README.md`). This folder holds what the host needs beyond Docker: SSH for
|
|
|
|
|
|
melchior, the root disk unlocked over SSH at boot, Docker's data on the HDD, and home's
|
|
|
|
|
|
end of the tunnel.
|
|
|
|
|
|
|
|
|
|
|
|
| File | On the host | What for |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `sshd-halkyon.conf` | `/etc/ssh/sshd_config.d/10-halkyon.conf` | keys alone, two users (D17) |
|
|
|
|
|
|
| `dropbear.conf` | `/etc/dropbear/initramfs/dropbear.conf` | the passphrase over SSH at boot (D20) |
|
|
|
|
|
|
| `initramfs-modules` | appended to `/etc/initramfs-tools/modules` | the USB network adapter at boot (D20) |
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
| `initramfs-ip` | `/etc/initramfs-tools/conf.d/halkyon-ip` | its address by DHCP at boot (D20) |
|
|
|
|
|
|
| `grub-halkyon.cfg` | `/etc/default/grub.d/halkyon.cfg` | GRUB's menu for 5 s, the boot in text (D20) |
|
|
|
|
|
|
| `grub-dracut-fallback` | `/etc/grub.d/41_dracut_fallback` | the way back to dracut's image (D20) |
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
| `fstab-docker` | appended to `/etc/fstab` | Docker's and containerd's data on the HDD (D12) |
|
|
|
|
|
|
| `docker-after-data.conf` | `docker.service.d/` and `containerd.service.d/` | both wait for it (D12) |
|
|
|
|
|
|
| `wg-halkyon.conf` | `/etc/wireguard/wg-halkyon.conf` | home's end of the tunnel (D8) |
|
|
|
|
|
|
| `wg-halkyon.nft` | `/etc/wireguard/wg-halkyon.nft` | what the tunnel may carry (D9, D17) |
|
2026-11-08 20:45:46 +01:00
|
|
|
|
| `wg-quick-halkyon.conf` | `wg-quick@wg-halkyon.service.d/halkyon.conf` | the tunnel before Docker, its rules around it (D9, D12) |
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
|
|
|
|
|
|
`test/deploy/halkyon_li_forward_test.dart` holds these files to ADR 0039's rules.
|
|
|
|
|
|
|
|
|
|
|
|
## Steps
|
|
|
|
|
|
|
|
|
|
|
|
As the developer at the iMac (`sudo`), from this checkout (`~/project-halkyon/halkyon`),
|
|
|
|
|
|
in this order: each step leaves the machine working, and the early ones keep a way in
|
|
|
|
|
|
for the later.
|
|
|
|
|
|
|
|
|
|
|
|
1. **sshd for melchior** (D17). On balthasar, then from melchior, then on balthasar:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo apt install openssh-server
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
ssh-copy-id raisondetredev@192.168.178.53 # on melchior, with balthasar's password
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo install -m 644 deploy/host/sshd-halkyon.conf /etc/ssh/sshd_config.d/10-halkyon.conf
|
|
|
|
|
|
sudo sshd -t && sudo systemctl reload ssh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Then, before closing anything, from melchior: `ssh raisondetredev@192.168.178.53`
|
|
|
|
|
|
must still get in, and `ssh -o PubkeyAuthentication=no raisondetredev@192.168.178.53`
|
|
|
|
|
|
must be refused. In the FRITZ!Box, give balthasar always the same address
|
|
|
|
|
|
(*Heimnetz › Netzwerk › balthasar › Diesem Netzwerkgerät immer die gleiche
|
|
|
|
|
|
IPv4-Adresse zuweisen*), so that 192.168.178.53 stays its address.
|
|
|
|
|
|
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
2. **The passphrase over SSH at boot** (D20). Ubuntu 26.04 builds its boot images with
|
|
|
|
|
|
dracut; `dropbear-initramfs` needs initramfs-tools, which replaces it. So first the
|
|
|
|
|
|
way back: today's image, dracut's, kept for the kernel running now, as an entry in
|
|
|
|
|
|
GRUB's menu, which shows for 5 seconds from now on:
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
sudo cp /boot/initrd.img-$(uname -r) /boot/initrd.img-$(uname -r).dracut
|
|
|
|
|
|
sudo install -m 644 deploy/host/grub-halkyon.cfg /etc/default/grub.d/halkyon.cfg
|
|
|
|
|
|
sudo install -m 755 deploy/host/grub-dracut-fallback /etc/grub.d/41_dracut_fallback
|
|
|
|
|
|
sudo update-grub # "Found the way back: … with dracut's image"
|
2026-11-08 21:11:24 +01:00
|
|
|
|
sudo grep -c 'the way back' /boot/grub/grub.cfg # 1 (root alone reads it)
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Then initramfs-tools, with what opens the root disk (`cryptsetup-initramfs`) and
|
|
|
|
|
|
dropbear. apt lists `dracut` and `dracut-core` to be removed, and nothing else:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo apt install initramfs-tools cryptsetup-initramfs dropbear-initramfs
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
sudo install -m 644 deploy/host/dropbear.conf /etc/dropbear/initramfs/dropbear.conf
|
|
|
|
|
|
# melchior's public key alone (from ~/.ssh/authorized_keys, step 1), restricted:
|
|
|
|
|
|
printf 'no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="/usr/bin/cryptroot-unlock" %s\n' \
|
|
|
|
|
|
"$(cat ~/.ssh/authorized_keys)" | sudo tee /etc/dropbear/initramfs/authorized_keys
|
|
|
|
|
|
grep -v '^#' deploy/host/initramfs-modules | sudo tee -a /etc/initramfs-tools/modules
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
sudo install -m 644 deploy/host/initramfs-ip /etc/initramfs-tools/conf.d/halkyon-ip
|
|
|
|
|
|
sudo update-initramfs -u -k all
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Before any reboot, each new image must hold what opens the disk, dropbear, its key
|
|
|
|
|
|
and the adapter's driver:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
for image in /boot/initrd.img-*-generic; do
|
|
|
|
|
|
echo "== $image"
|
|
|
|
|
|
sudo lsinitramfs "$image" | grep -E 'cryptroot/crypttab$|sbin/cryptsetup$|sbin/dropbear$|/authorized_keys$|cdc_ncm\.ko|sbin/lvm$'
|
|
|
|
|
|
done # for each: crypttab, cryptsetup, dropbear, authorized_keys, cdc_ncm, lvm
|
|
|
|
|
|
sudo sh -c 'unmkinitramfs /boot/initrd.img-$(uname -r) /tmp/initrd &&
|
|
|
|
|
|
find /tmp/initrd -path "*cryptroot/crypttab" -exec cat {} +; rm -rf /tmp/initrd'
|
|
|
|
|
|
# dm_crypt-0 UUID=… none luks…
|
|
|
|
|
|
sudo dropbearkey -y -f /etc/dropbear/initramfs/dropbear_ed25519_host_key | grep -i fingerprint
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
The last line is dropbear's own host key, which melchior sees on port 2222 at the
|
2026-11-08 21:51:04 +01:00
|
|
|
|
next boot. melchior's key isn't at one of ssh's default paths, so it is named for the
|
|
|
|
|
|
unlock in melchior's `~/.ssh/config`, its host key apart from sshd's:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Host balthasar-unlock
|
|
|
|
|
|
HostName 192.168.178.53
|
|
|
|
|
|
Port 2222
|
|
|
|
|
|
User root
|
|
|
|
|
|
IdentityFile ~/.ssh/raisondetredev@ramiel
|
|
|
|
|
|
IdentitiesOnly yes
|
|
|
|
|
|
HostKeyAlias balthasar-unlock
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
At the next boot, at home: `ssh balthasar-unlock` from melchior asks for the root
|
|
|
|
|
|
disk's passphrase, and the boot goes on. Both the boot image and the system ask the
|
|
|
|
|
|
FRITZ!Box for their address, differently (the system with a client ID), so the
|
|
|
|
|
|
FRITZ!Box keeps 192.168.178.53 for balthasar's MAC address, `00:50:B6:BF:FC:FF`
|
|
|
|
|
|
(step 1); without it, the system got another address than the boot image. The keyboard at the iMac still
|
fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)
Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 21:10:22 +01:00
|
|
|
|
works as before. Should the new image not open the disk, choose *the way back* in
|
|
|
|
|
|
GRUB's menu at the iMac: it boots as before this step. Once the new images have
|
|
|
|
|
|
booted: `sudo rm /etc/grub.d/41_dracut_fallback /boot/initrd.img-*.dracut && sudo update-grub`.
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
|
|
|
|
|
|
3. **No desktop** (D12): it stops starting at boot, and Docker Desktop, which runs a VM of
|
2026-11-08 20:54:31 +01:00
|
|
|
|
its own beside Docker, goes. Multipass is switched off while no VM is needed: its
|
|
|
|
|
|
daemon alone held 505 MB with none (8 Nov 2026). `snap disable` keeps it installed,
|
|
|
|
|
|
and `sudo snap enable multipass` brings it back; a VM then gets a fixed size
|
|
|
|
|
|
(`--memory`), as it shares the instance's memory:
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo systemctl set-default multi-user.target # back: graphical.target
|
|
|
|
|
|
sudo apt remove docker-desktop
|
|
|
|
|
|
sudo snap disable multipass
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
4. **Docker's data on the HDD** (D12). Everything Docker holds now (the home Jellyfin's
|
2026-11-08 19:15:25 +01:00
|
|
|
|
images and containers) moves with it. ADR 0032's home Jellyfin stops here for good;
|
|
|
|
|
|
its configuration and library stay where they are and move into the instance later
|
|
|
|
|
|
(`../sidecars/README.md`, *Jellyfin on halkyon.li*):
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-11-08 21:01:08 +01:00
|
|
|
|
sudo docker compose -p halkyon-home-jellyfin down
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
sudo systemctl stop docker docker.socket containerd
|
|
|
|
|
|
sudo mkdir -p /data/docker /data/containerd
|
|
|
|
|
|
sudo rsync -aHAX --numeric-ids /var/lib/docker/ /data/docker/
|
|
|
|
|
|
sudo rsync -aHAX --numeric-ids /var/lib/containerd/ /data/containerd/
|
|
|
|
|
|
sudo mv /var/lib/docker /var/lib/docker.old
|
|
|
|
|
|
sudo mv /var/lib/containerd /var/lib/containerd.old
|
|
|
|
|
|
sudo mkdir /var/lib/docker /var/lib/containerd
|
|
|
|
|
|
grep -v '^#' deploy/host/fstab-docker | sudo tee -a /etc/fstab
|
|
|
|
|
|
for unit in docker containerd; do
|
|
|
|
|
|
sudo install -D -m 644 deploy/host/docker-after-data.conf \
|
|
|
|
|
|
/etc/systemd/system/$unit.service.d/after-data.conf
|
|
|
|
|
|
done
|
|
|
|
|
|
sudo systemctl daemon-reload
|
|
|
|
|
|
sudo mount /var/lib/docker && sudo mount /var/lib/containerd
|
|
|
|
|
|
sudo systemctl start containerd docker
|
|
|
|
|
|
sudo docker images # as before
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Once Docker runs as before: `sudo rm -rf /var/lib/docker.old /var/lib/containerd.old`.
|
|
|
|
|
|
|
|
|
|
|
|
5. **Home's end of the tunnel** (D8, D9, D12). Home's key, made here and never leaving
|
|
|
|
|
|
but into Proton Pass (vault *Infra*, an item *balthasar wg-halkyon*):
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo apt install wireguard-tools
|
|
|
|
|
|
sudo install -d -m 700 /etc/wireguard
|
|
|
|
|
|
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/wg-halkyon.key'
|
|
|
|
|
|
sudo cat /etc/wireguard/wg-halkyon.key # into Proton Pass
|
2026-11-08 20:30:10 +01:00
|
|
|
|
sudo sh -c 'wg pubkey < /etc/wireguard/wg-halkyon.key' # home's public key: ramiel's config
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Then the files, with ramiel's public key (`../forward/README.md`, step 3) in place of
|
|
|
|
|
|
`<ramiel's public key>`:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo install -m 600 deploy/host/wg-halkyon.conf deploy/host/wg-halkyon.nft /etc/wireguard/
|
|
|
|
|
|
sudoedit /etc/wireguard/wg-halkyon.conf
|
|
|
|
|
|
sudo nft -c -f /etc/wireguard/wg-halkyon.nft # checks the rules, loads nothing
|
2026-11-08 20:45:46 +01:00
|
|
|
|
sudo install -D -m 644 deploy/host/wg-quick-halkyon.conf \
|
|
|
|
|
|
/etc/systemd/system/wg-quick@wg-halkyon.service.d/halkyon.conf
|
feat(deploy): halkyon.li's forward through ramiel and its host at home (ADR 0039)
ramiel's HAProxy passes halkyon.li's TLS connections, unopened, through a
WireGuard tunnel to the instance at home, with the visitor's address in a
PROXY header, and everything else to ramiel's own Caddy, moved to the
loopback. Each end's nftables table lets the tunnel carry HAProxy's 443 and
the SSH jump's 22 alone, and nothing from home to ramiel.
The host at home: sshd with keys alone, the root disk unlocked over SSH at
boot (dropbear-initramfs, D20), Docker's and containerd's data bind-mounted
from the HDD, and home's end of the tunnel before Docker. An instance behind
the forward (compose.forwarded.yaml) publishes 443 on the tunnel's address
alone, trusts the PROXY header from ramiel alone, and gets its certificates
through TLS-ALPN, without HTTP/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:05:47 +01:00
|
|
|
|
sudo systemctl daemon-reload
|
|
|
|
|
|
sudo systemctl enable --now wg-quick@wg-halkyon
|
|
|
|
|
|
sudo wg show wg-halkyon # a handshake once ramiel is up too
|
|
|
|
|
|
ping -c 1 10.61.97.1 # refused by home's own rules: nothing new goes to ramiel
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
From melchior, away from home: `ssh -J ramiel raisondetredev@10.61.97.2`.
|
|
|
|
|
|
|
|
|
|
|
|
6. **Reboot**, at home, to see each step hold: the passphrase over SSH (step 2), no
|
|
|
|
|
|
desktop (step 3), Docker on the HDD (step 4), the tunnel up before Docker (step 5).
|
|
|
|
|
|
|
feat(deploy): halkyon.li's first start, its .env made on the host (ADR 0039, D21)
first-start.md takes the instance from a bare host to running, once: the
sidecars' users, deploy with a read-only key to the forge and the
developer's keys (GPG for redeploy.sh's check, SSH pinned to it), .env,
SimpleLogin's app, the sidecars' folders and Jellyfin moved in, a role and
database for each app's server, the first start, the first operator by
ADR 0021's SQL, and the backups.
halkyon-li-env.sh writes .env with every secret made on the host and never
over an existing file, as the peppers must not change once people sign in:
SimpleLogin alone with approval, everything running behind the forward,
Train's map and routes over southern Bavaria and its search over Germany
until the Instance console cuts regions, Germany as where data and backups
are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 22:00:51 +01:00
|
|
|
|
The instance itself follows, once: `first-start.md` (its users, checkout, `.env`, the
|
|
|
|
|
|
databases, the first start, the first operator, the backups). Then:
|
build(ci,deploy)!: the runner at home in a nightly window, deploys by hand (ADR 0039, D6, D13, D16)
The CI runner moves to halkyon.li's host at home and runs from 02:00 to
05:00 alone: a timer starts it, its unit's RuntimeMaxSec ends it, and
night.sh rests the instance's heavy sidecars around it as deploy, while
Caddy, PostgreSQL and the apps' servers keep serving. Jobs pushed during the
day wait in Forgejo's queue.
Its sandbox fits the new host: its folder on the HDD, the rest of /data
hidden, containerd's store out of reach, no private IPv4 network and no
IPv6 at all (the home network's IPv6 addresses are public ones under a
changing prefix, on the internet's own interface), 5 GB of memory with swap
beyond. The Sandbox step checks the tunnel, the home router and IPv6.
BREAKING CHANGE: CI deploys nothing any more: the deploy job goes with
learn.halkyon.eu's server, and halkyon.li deploys by hand with redeploy.sh
once a commit's night's run passed. Jobs run on the runner labelled
balthasar; sahaquiel's runner picks up none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:20:11 +01:00
|
|
|
|
|
|
|
|
|
|
7. **The CI runner in its nightly window** (D6, D16), once `deploy` and its checkout
|
|
|
|
|
|
exist. Its user, its folder on the HDD, Forgejo's runner (v13.1.0, the version
|
|
|
|
|
|
`../runner/config.yml` is written for) and the units:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo useradd --system --uid 61000 --user-group --no-create-home \
|
|
|
|
|
|
--home-dir /var/lib/forgejo-runner --shell /usr/sbin/nologin runner
|
|
|
|
|
|
sudo install -d -o runner -g runner -m 700 /data/forgejo-runner /var/lib/forgejo-runner
|
|
|
|
|
|
sudo install -m 755 forgejo-runner /usr/local/bin/forgejo-runner # v13.1.0, its checksum checked
|
|
|
|
|
|
sudo install -d -m 700 /etc/forgejo-runner
|
|
|
|
|
|
sudo install -m 644 deploy/runner/config.yml /etc/forgejo-runner/config.yml
|
|
|
|
|
|
sudo install -m 644 deploy/runner/forgejo-runner.service deploy/runner/forgejo-runner.timer \
|
|
|
|
|
|
deploy/runner/halkyon-night.service deploy/runner/halkyon-night.timer \
|
|
|
|
|
|
deploy/runner/halkyon-morning.service deploy/runner/halkyon-morning.timer \
|
|
|
|
|
|
/etc/systemd/system/
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
In Forgejo (`code.halkyon.app`, the repository's *Settings › Actions › Runners*): a new
|
|
|
|
|
|
runner, `balthasar`, its UUID into `/etc/forgejo-runner/config.yml` (`RUNNER_UUID`) and
|
|
|
|
|
|
its token into `/etc/forgejo-runner/token` (root, 0600); sahaquiel's runner removed
|
|
|
|
|
|
there once it is gone. Then:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo systemctl daemon-reload
|
|
|
|
|
|
sudo systemctl enable --now forgejo-runner.timer halkyon-night.timer halkyon-morning.timer
|
|
|
|
|
|
systemctl list-timers 'forgejo-runner*' 'halkyon-*' # 01:55, 02:00, 05:00
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
To try it by day: `sudo systemctl start forgejo-runner` (it stops itself three hours
|
|
|
|
|
|
later) and push a branch. The job's first step, *Sandbox*, fails if the runner
|
|
|
|
|
|
reaches the instance, the home network, the tunnel or any IPv6 address, or sees `/data`.
|