halkyon/deploy/host/README.md
Tizian 「ティツィアーン」 4d8ea9c3a5
docs(deploy): the tunnel's public key read as root, not through the shell's redirect
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 22:07:19 +01:00

164 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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) |
| `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) |
| `wg-quick-before-docker.conf` | `wg-quick@wg-halkyon.service.d/` | the tunnel before Docker (D12) |
`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.
2. **The passphrase over SSH at boot** (D20):
```sh
sudo apt install dropbear-initramfs
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
echo 'IP=::::balthasar:enx0050b6bffcff:dhcp' | sudo tee -a /etc/initramfs-tools/initramfs.conf
sudo update-initramfs -u
```
At the next boot, at home: `ssh -p 2222 root@192.168.178.53` from melchior asks for the
root disk's passphrase, and the boot goes on. The keyboard at the iMac still works as
before, so a mistake here costs no more than the walk to it.
3. **No desktop** (D12): it stops starting at boot, and Docker Desktop, which runs a VM of
its own beside Docker, goes. Multipass only if it isn't used:
```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
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*):
```sh
sudo docker compose -p home-jellyfin down
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
sudo sh -c 'wg pubkey < /etc/wireguard/wg-halkyon.key' # home's public key: ramiel's config
```
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
sudo install -D -m 644 deploy/host/wg-quick-before-docker.conf \
/etc/systemd/system/wg-quick@wg-halkyon.service.d/before-docker.conf
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).
The instance itself follows (its users, checkout, `.env` and first deploy). Then:
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`.