From 09f191d10b9b48b31c4353a350747478ccf24abe Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 15:25:16 +0200 Subject: [PATCH 1/9] feat: add studio.dannydannydanny.me vhost :art: Kyranna's private art-learning archive ("Studio"), served by the same notes service on phantom :8092 (routed by Host header, STUDIO_HOST). Mirrors the map/kf vhosts. Co-Authored-By: Claude Opus 4.8 --- nixos/hosts/vps-relay.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 9f8c3f5..3387aa5 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -160,6 +160,11 @@ "map.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 ''; + # studio — Kyranna's private art-learning archive. Same notes + # service on phantom :8092, routed by Host header (STUDIO_HOST). + "studio.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092 + ''; }; }; From a903d76f654f3d597b75a492f7927365ccbec260 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 19:05:06 +0200 Subject: [PATCH 2/9] nixos/hosts: add README documenting host roles + deploy flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deferred Phase-1-completion task from the de-platform-from-GitHub roadmap (vimwiki/diary/2026-05-03.md). Documents: - per-host role + current services (sunken-ship, phantom-ship, vps-relay, distant-shore, foreign-port, daniel-macbook-air, wsl); - ZT mesh topology + ASCII overview; - the auto-rebuild path (dm-pull-deploy push from sunken-ship → pull/ rebuild on roles.default hosts within ~15 m); - the manual clan-cli flow, including the env -u SSH_AUTH_SOCK and --no-check gotchas we hit in practice; - the vps-relay reverse-proxy pattern for new public apps; - SSH key quick-reference. --- nixos/hosts/README.md | 206 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 nixos/hosts/README.md diff --git a/nixos/hosts/README.md b/nixos/hosts/README.md new file mode 100644 index 0000000..3e668b1 --- /dev/null +++ b/nixos/hosts/README.md @@ -0,0 +1,206 @@ +# Hosts + +Per-host NixOS configs for the homelab and admin Mac. Each `.nix` +declares the host's role and services; the `-hardware.nix` siblings +(where present) describe disks, kernel modules, firmware. Bootstrap + +disko configs live one level up in `../`. + +## Topology + +``` + ┌────────────────────────────────────────────┐ + │ vps-relay (Hetzner, public IP, ZT peer) │ +public traffic ──TLS:443──────│ Caddy + Let's Encrypt → reverse_proxy │ + │ over ZeroTier to a clan backend │ + └──────────────────┬─────────────────────────┘ + │ (ZeroTier mesh) + │ + ┌──────────────────────┬───────────┴───────────┬──────────────────────┐ + │ │ │ │ + sunken-ship phantom-ship distant-shore foreign-port + (LAN, wifi) (LAN, wired) (LAN, wifi) (LAN, wifi) + ZT controller NAT for blank slate blank slate + media + mulbo rusty-anchor (room to grow) (room to grow) + services hub + + ── outside the clan ────────────────────────────────────────────────────────────────────── + Daniel-Macbook-Air rusty-anchor + (admin, runs clan-cli) (downstream of phantom-ship's NAT) +``` + +ZeroTier IPv6 addresses for the four clan machines are declared in +`../../flake-modules/clan.nix` (`sunkenShipZTv6` / `phantomShipZTv6` / +`vpsRelayZTv6` / etc.). They land in every host's `/etc/hosts` as +`.clan` so data-mesher and ad-hoc SSH can resolve over the mesh. + +--- + +## Hosts + +### sunken-ship · media + ZT controller + +- **Hardware:** see `sunken-ship-hardware.nix`. WiFi-only, no LUKS (boots + unattended). +- **Network:** LAN over WiFi, on the ZT mesh as the **controller** (manages + ZT membership for the whole fleet). ZT IPv6 is the clan's "internet" + target for `clan machines update`. +- **Role:** media + the long-running personal services that don't fit on + phantom-ship. +- **Current services:** `navidrome` (subsonic API, `/srv/music`), `uxplay` + (AirPlay receiver), `mulbo-server` (+ `-pull` / `-backfill` / `-enrich` + timers), `fitness-bot` (+ `-pull` / `-shipyard` variants), + `dm-pull-deploy-push` (announces origin/main rev to the mesh every 15 m). + +### phantom-ship · services hub + LAN NAT + +- **Hardware:** see `phantom-ship-hardware.nix`. WiFi for WAN, wired + ethernet (`enp0s31f6`) serves the lab subnet (NAT + dnsmasq for + `rusty-anchor`). +- **Network:** LAN over WiFi, on the ZT mesh. Backends are exposed only on + the ZT interface (`firewall.interfaces."zt+".allowedTCPPorts = [ … ]`) + so vps-relay's Caddy can reach them. WAN side stays closed. +- **Role:** where new self-hosted apps default to going. Hosts a growing + list of mini-app backends + a couple of long-running daemons. +- **Current services:** `forgejo` (`git.dannydannydanny.me`), + `claude-channels` (Telegram bridge for `@HarakatBot`), + `hara-gmail-mcp` + `hara-heartbeat` (timer), + Mini-App backends (`shelfish`, `scuttle`, `bananasimulator`, + `komtolk`, `escape-hormuz`, `bon`), `ollama` (local LLM), `shipyard`, + `dnsmasq` (lab subnet DHCP/DNS). `openclaw-gateway` is disabled — + superseded by `claude-channels` but kept for easy rollback. + +### vps-relay · public reverse proxy + +- **Hardware:** Hetzner Cloud vServer (BIOS-boot, virtio). Disk via + `../disko-cloud.nix`. +- **Network:** public IP `89.167.39.251`. Inbound: SSH/22, HTTP/80, + HTTPS/443 only. fail2ban guards SSH. Outbound to clan backends over ZT. +- **Role:** terminates public TLS, reverse-proxies subdomains over ZT to + whichever clan host runs the backend. **No application data ever lands + here** — this box is a relay. New public app = add a `virtualHosts` + entry + a GoDaddy A record pointing at `89.167.39.251`. +- **Current vhosts:** `navidrome.`, `bbbot.`, `shelfish.`, `scuttle.`, + `bananasimulator.`, `komtolk.`, `git.`, `escapehormuz.`, etc. + +### distant-shore · ThinkPad X13 Gen 2, blank slate + +- **Hardware:** see `distant-shore-hardware.nix`. Intel i5-1145G7, 16 GB. + WiFi-only, headless, no LUKS. Secure-Boot-chained boot (shim + MOK, + see comments in `distant-shore.nix`). +- **Network:** LAN over WiFi, on the ZT mesh. +- **Role:** _to be assigned_. In the clan inventory; auto-rebuilds via + dm-pull-deploy. Drop a service in to give it a purpose. + +### foreign-port · laptop, blank slate (WIP) + +- **Hardware:** see `foreign-port-hardware.nix`. WiFi-only, headless, + no LUKS. Vendor-signed-shim boot chain. +- **Status:** still being wired up — not in the clan inventory yet. +- **Role:** _to be assigned_, same flow as `distant-shore`. + +### daniel-macbook-air · admin + +- **Hardware:** MacBook Air (the daily driver). +- **Role:** outside the clan. Runs `clan machines update` to push to + the servers + holds the SSH keys that authorize root@ on each clan + host. Also a ZT peer. + +### wsl + +- **Role:** WSL development environment (legacy / occasional). + +--- + +## Deployment + +### Automatic (the default) + +`dm-pull-deploy` (clan-community module wired in `clan.nix`): + +1. **Push announcement:** sunken-ship's `dm-pull-deploy-push` timer runs + `dm-send-deploy` every 15 m. It signs and broadcasts the current + `origin/main` rev over the data-mesher gossip protocol. +2. **Pull + rebuild:** each `roles.default` machine (currently + `sunken-ship`, `phantom-ship`) runs a `.path` watcher that fires when + the gossiped rev changes; it `git fetch`es and `nixos-rebuild switch`es. + +So **a push to `origin/main` rolls out within ~15 m** on the two +production hosts. No SSH-from-Mac required. + +`vps-relay` and `distant-shore` are **not** in `roles.default` — they +need a manual deploy (see below) until/unless their role changes. + +### Manual + +From `~/dotfiles` on the Mac: + +``` +nix run 'git+https://git.clan.lol/clan/clan-core#clan-cli' -- \ + machines update +``` + +Caveats encountered in practice: + +- The Mac's `ssh-agent` often has the wrong key loaded for clan deploys. + Prefix with `env -u SSH_AUTH_SOCK` to force `~/.ssh/config` identity + selection. +- A nixpkgs bump may register a new generation but refuse to live-switch + due to "switch inhibitors". Add `--no-check` to force. +- `vps-relay` only accepts `~/.ssh/id_ed25519_sunken_ship` (the Mac's + copy of sunken-ship's authorized key). The agent's other keys won't + open it. + +Putting both together: + +``` +env -u SSH_AUTH_SOCK nix run 'git+https://git.clan.lol/clan/clan-core#clan-cli' \ + -- machines update phantom-ship --no-check +``` + +### From sunken-ship + +`vps-relay` was originally only reachable from sunken-ship's SSH key. +That still works as a fallback — SSH to sunken-ship and run the same +`nix run … -- machines update vps-relay` command from `/etc/dotfiles` +there. The dotfiles checkout at `/etc/dotfiles` is maintained by +dm-pull-deploy. + +--- + +## Public traffic pattern + +``` +user → DNS *.dannydannydanny.me → 89.167.39.251 (vps-relay) + → Caddy (Let's Encrypt, ports 80/443) + → reverse_proxy http://[]: + → service on sunken-ship or phantom-ship +``` + +To add a new public app: + +1. Add a `virtualHosts` entry to `vps-relay.nix` pointing at the + backend's ZT IPv6 and port. +2. Add the GoDaddy A record `.dannydannydanny.me → 89.167.39.251`. +3. Run the backend on the chosen host. Either: + - bind to `127.0.0.1:` (if backend + Caddy are co-resident — not + the case here), **or** + - bind to `0.0.0.0` (or `::`) and add the port to + `networking.firewall.interfaces."zt+".allowedTCPPorts` on the + backend host so only the ZT interface accepts inbound. +4. Push dotfiles. Production hosts auto-rebuild via dm-pull-deploy. + vps-relay needs a manual `clan machines update vps-relay`. + +--- + +## SSH keys (quick reference) + +- **`~/.ssh/id_ed25519_phantom_ship`** (Mac) — authorized as `danny@` and + `root@` on phantom-ship. +- **`~/.ssh/id_ed25519_sunken_ship`** (Mac) — authorized as `danny@` (and + via root mirror) on sunken-ship; also the authorized key on `vps-relay`. +- **sunken-ship `~/.ssh/id_ed25519`** — sunken-ship's own key; used by + cluster-internal ops (mulbo-pull, dm-send-deploy, fallback path for + vps-relay deploys). +- **`~/.ssh/id_ed25519_github`** (Mac) — GitHub auth, not clan. + +Authorized-keys lists live in each host's `users.users.{danny,root}.openssh.authorizedKeys.keys`. From bbe05c971d7a2e094ec9be6952d999b9e31c34d3 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sat, 6 Jun 2026 16:23:09 +0200 Subject: [PATCH 3/9] =?UTF-8?q?feat(distant-shore):=20add=20X13=20Gen=202?= =?UTF-8?q?=20as=20clan=20machine=20w/=20shim+MOK=20secure=20boot=20?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ThinkPad X13 Gen 2, BIOS-locked + Secure Boot enforced. Boots NixOS via Microsoft-signed shim chain-loading MOK-signed systemd-boot + kernel (re-signed each rebuild). WiFi via NetworkManager. Migrated from the standalone install module into clan (zerotier/data-mesher/dm-pull-deploy). --- flake-modules/clan.nix | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 30fe4c9..3b9b45c 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -47,6 +47,7 @@ in { inventory.machines.sunken-ship = { }; inventory.machines.phantom-ship = { }; inventory.machines.vps-relay = { }; + inventory.machines.distant-shore = { }; # ZeroTier mesh VPN. sunken-ship is the controller (manages network # membership); phantom-ship is a peer. The mac joins manually as an @@ -58,6 +59,7 @@ in { roles.peer.machines.phantom-ship = { }; roles.peer.machines.sunken-ship = { }; roles.peer.machines.vps-relay = { }; + roles.peer.machines.distant-shore = { }; }; # data-mesher — signed-file gossip protocol over libp2p (port 7946). @@ -70,6 +72,7 @@ in { module.input = "clan-core"; roles.default.machines.sunken-ship = { }; roles.default.machines.phantom-ship = { }; + roles.default.machines.distant-shore = { }; roles.bootstrap.machines.sunken-ship = { }; }; @@ -87,6 +90,7 @@ in { }; roles.default.machines.sunken-ship.settings.action = "switch"; roles.default.machines.phantom-ship.settings.action = "switch"; + roles.default.machines.distant-shore.settings.action = "switch"; }; # `clan machines update` connection target. Priority 2000 > ZT's 900 @@ -111,6 +115,12 @@ in { host = "89.167.39.251"; user = "danny"; }; + # distant-shore: LAN IP for the first update (not yet on ZT). Swap to + # its generated ZT IPv6 after it joins the mesh, like the others. + roles.default.machines.distant-shore.settings = { + host = "192.168.1.182"; + user = "danny"; + }; }; # Preserve current network / init stack (no systemd-networkd/resolved, @@ -157,6 +167,29 @@ in { ]; }; + # distant-shore — ThinkPad X13 Gen 2, WiFi, Secure Boot via shim+MOK + # (installed standalone, then migrated into clan). targetHost is the LAN + # IP for the first `clan machines update`; switch to its ZT IPv6 once the + # mesh is up. Builds on the box itself (it has nix + internet). + machines.distant-shore = { + imports = [ + { + clan.core.enableRecommendedDefaults = false; + clan.core.networking.targetHost = "danny@192.168.1.182"; + clan.core.networking.buildHost = "danny@192.168.1.182"; + } + clanHostsModule + ../nixos/hosts/distant-shore.nix + config.flake.nixosModules.monitoring-node-exporter + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + machines.phantom-ship = { imports = [ { From df18b1cfaf68bfff265798816196b9b0cfefad85 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 18:38:30 +0200 Subject: [PATCH 4/9] =?UTF-8?q?feat(distant-shore):=20generate=20clan=20va?= =?UTF-8?q?rs=20(zerotier/data-mesher/dm-pull-deploy)=20+=20ZT=20host=20en?= =?UTF-8?q?try=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/clan.nix | 2 ++ sops/machines/distant-shore/key.json | 6 ++++++ sops/secrets/distant-shore-age.key/secret | 14 ++++++++++++++ sops/secrets/distant-shore-age.key/users/danny | 1 + .../identity.cert/machines/distant-shore | 1 + .../identity.cert/secret | 18 ++++++++++++++++++ .../identity.cert/users/danny | 1 + .../identity.key/machines/distant-shore | 1 + .../identity.key/secret | 18 ++++++++++++++++++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 +++ .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/distant-shore | 1 + .../signing.key/secret | 18 ++++++++++++++++++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 +++ .../machines/distant-shore | 1 + .../zerotier/zerotier-identity-secret/secret | 18 ++++++++++++++++++ .../zerotier-identity-secret/users/danny | 1 + .../distant-shore/zerotier/zerotier-ip/value | 1 + 20 files changed, 111 insertions(+) create mode 100755 sops/machines/distant-shore/key.json create mode 100644 sops/secrets/distant-shore-age.key/secret create mode 120000 sops/secrets/distant-shore-age.key/users/danny create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore create mode 100644 vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret create mode 120000 vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny create mode 100644 vars/per-machine/distant-shore/zerotier/zerotier-ip/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 3b9b45c..2e974d5 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -21,6 +21,7 @@ let sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33"; phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; + distantShoreZTv6 = "fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3"; # Shared across both servers: /etc/hosts entries so data-mesher's # libp2p /dns/.clan/... bootstrap multiaddrs resolve over ZT. @@ -29,6 +30,7 @@ let "${sunkenShipZTv6}" = [ "sunken-ship.clan" ]; "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; "${vpsRelayZTv6}" = [ "vps-relay.clan" ]; + "${distantShoreZTv6}" = [ "distant-shore.clan" ]; }; }; in { diff --git a/sops/machines/distant-shore/key.json b/sops/machines/distant-shore/key.json new file mode 100755 index 0000000..f580056 --- /dev/null +++ b/sops/machines/distant-shore/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "type": "age" + } +] \ No newline at end of file diff --git a/sops/secrets/distant-shore-age.key/secret b/sops/secrets/distant-shore-age.key/secret new file mode 100644 index 0000000..27a5780 --- /dev/null +++ b/sops/secrets/distant-shore-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:WTerGWNmve9/q+TLYi8HoGUQI0UgYMZN2zuC/FABX0MC6VuUsz9Doz36X8lsy+MRJzcHNPqdaHmAHopY/hODHLBirfUPLVZjEKI=,iv:ilp+cJivxY2us1jO85dWUHAqLJSsJ7ZKpmYMyi2476I=,tag:H0k2CZDhcH9lYSxz6BAPrg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZS2ZhQlJacmR4R3JMek5l\nZlFRajM4VllmK2R6NFlRMEkwNUJOL05OUUhzCmpWQ0gxQ1BHUkZOVm80QzRUc1BY\nTDNRZDZOL3EyS1FWK1A4UUd6MTFaTjAKLS0tIEUxU3hBSkZqRmc5d0dXZm0rNTYw\nQ0hrZUF5dDJLN0MvM2RQZlVFZkNPc28Kvq8yV+VwqQIuG1SPI/mMYbGwuD7oUOeR\nCzAuZvqGtludjW7+wX5uIwRzHMudU/yP/iME8vsDC3dL6sf75+arHg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:36Z", + "mac": "ENC[AES256_GCM,data:g35f5YmoneVewxmTh3E8ECDGAl0OwUj4v/2bjFs9Dd7MaT3in7PHvu30jJ4WHalYC8pkT5IlpBwsp1nVUnKsgh+2V+jN4JiGizlvTwByaYoalOoGZStIyQa+k8XRQqoUDbV3ESdI5q+dwS5PCWYIOH3MoA0o5b42iQPghrViaeY=,iv:v0UUy4LtQ5SRLB01vbcfNpcm8zgs1Vp3KCK552peXlA=,tag:45b8czXYtNh02q7P42FJmQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/sops/secrets/distant-shore-age.key/users/danny b/sops/secrets/distant-shore-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/sops/secrets/distant-shore-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..58ead86 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:esTlopK7VkLLnWvxsLoZtAGgbYKWKfu0XJde2fzxDuOaf9yUCU6NHpnyRAZnChceEZ3frwS7Lh/LWqX9CTKQ1LHTV8HrJERSERDzrQDHbIXFLtDbeF+qN7M1wYFEwCUa8PVAg4XHMN/ZGy6H71+J8UrktcbxcHUr+8L3pj4DZb5930kT3U02rzSoan8zb4zMhGqA0keq9QJ04uNJEN2Bly1kCBvdgc7kVUBNHwS78s+jfsa3PyOiLy5AI4CEbQ5r/xBjNgY/aSEOzRMoZtVWUFlh5Kxc47gz7MlK2x/2iXyCIAw3qeTIxor30GIL,iv:QbSPukR5aMrhBfYOM6lOb0qSEPm4oEqqQp59WDv0p6Y=,tag:KrMyGleLjIhT1LTlS3S63g==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyWlpRS1hhQThqaFUyNE4y\nYU1YVDFtazZnSHpTOWRFQkZYVThsRk9RQ1RZCnI4ZlFacTRRSHlub2hWVTNSSkhN\ndWExR202RG1nZ2dQTzQ5LzBNNW1kc2sKLS0tIHZlZXNhSm9wdElTZzRXZjQxaDAx\nRXpvcEkwK3dMNHZ2M21OSFluWnhDOFEKv0/yC/Llmhsm3+kV3AUJ2PPF817rOyL5\n6GkqTrb/gB8q8jnQabDr2sHUz7AB4w7zlQaNLRSo3Ba8KFbW7GZNRg==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcEdVdTBsUThiOE1EVWZW\nYXh2NUNCZTVieUZKbjByY2dSZVU2c0Q4L2hBCm1mNzVrcTRTTFpUTkJDZlArYTBZ\nWXhEMERmd1J3VTYxa2dWTlFxOW45N00KLS0tIHNLVzRCdDJGdWk2K0JoY1dJbzIz\nQU9DR2tXU3l2aU9YMGd1RjBGbUJYM0UKYmdAj535wvaGxN6m2VBBVtWRAD5RzQ7K\nbiJjvf8NH4A0aO9RVTFCevqRXUOKBu7jNIpFFfEyUEGHEUCWOuVSlA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:36Z", + "mac": "ENC[AES256_GCM,data:QVkNUsAO6BsVoPAL5GG1/DProapF8ryaUGDr8Y8mYPpD1Y2YXAF2sBRJ4FWkFZkWl4L2sp5DLXfqs+z0tpvi6rpG0jfpgJzy3Du2QKnk5W78WENlK+M74tSzAUfCUPn6RodykLJ8ik+EvxR+yxRmfjStAWsS6eqoTYowa4TGeJ0=,iv:qousMcaNKMtl8hGcfiS1WYBe0ftyb9ohHdBG+gqTio0=,tag:j64zgZpB7cmDfPcq4csjMQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..50edf19 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:kAzaF+nxyux0zwjoqC5QYrx5UyEhMPW0v9hGcYUXExZl6ShMMgCWhKN82al2jY6OnU/CQ7UT9USH6PC+eecimyM6A5YXQ0GvvU3uus0t46GKqXqcGVl4BdgVO6tm8ienIcfjF6ml3LyvMXirjDdIluVkrH/P0vM=,iv:BSQrtg9kgBHRkCV8+nODNyPX3PchkTEjPPTYy5JZrfo=,tag:dPtjxWqDh1Bce9rlW6czyw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMNkR1YTZNU1FyU2VWaUJR\nbjBOc0RSMW1SL1ZkZ1ozVHRmcVdkS01sdkZnCndTbGJlOVFrdDJHVDUxS1JFUmcy\nZS9jWGhRbWFCeGZOMHQwdzYxTFlrSjgKLS0tIEFaZmFzOXdXVjVOeUJuMDdpQ3hK\ndnRkUytmZk1zaXhUTSt1OTljUkNYK2MKpe6f3GHGCfduiidzYh0qaKEBaKyBZY4s\ne/f5QvZVApMiI4HFkOwFmNITOv6JdjGMQOw+OI6po0nqg0mqVnNIVA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpa0p1NCtUeGFLRzJFOFQv\nVXh4MThqOVR4TU9SK3Mrc21Ga1BPdUZrM1c0CmJxQzNyam56aTdUVVB5NVhEenlV\nOTkwb2YyRWdoVXc4K2VEaXhwZXM3TEUKLS0tIEFBajAycEQzelNoR2tCU3l6cVJo\nc3lHbWJZQWFQTkVxd0lBamxlQStZWlkKopG1Z2E0Smt/z/y1+cQeTKUKyJKBXzZr\nCQNkGfi1Dk/7n/WeKwePHWVF/19WqVfOIZW0E3tOKOIqDQZa0Io1Nw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:36Z", + "mac": "ENC[AES256_GCM,data:B6UAFOrK0QIngkf5OA3+BnLAouBvsr0AbW8lKI8RH7ylGQNOyXfnN06fYshi+jQyu5EAZBqovfSZzgcTDm7MDRAjzzmTToT5ekHPZnquleU/F7pF/D7JF78M6rQyw3uG0nwhnJcRqlCAXy+56++kTJhoKEW+B5fUsbvlHTmxwLk=,iv:BXDbLObPBXsL3Uj+TRwIFtNDRzWYJeM0mJyDDluz70s=,tag:eTANaLmNaUUSYBNcIhuIFQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..8f2058b --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAABhcRTNvFEyWkyRBX17KkM5nDuqOvR1xTY5vDqTygvk= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value b/vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..f748b68 --- /dev/null +++ b/vars/per-machine/distant-shore/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooW9pjiKnqmnHSwGRhgyUqKeFydDUE8RvYJDAqHb5PZvzue \ No newline at end of file diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..7c824e7 --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:tLR5iZ7Iro3BuBJlpvkKO7RrA9X2pO2H9Isi6jc8y8krh+a89Eug0PCNb4U/aSASjQgDfZgwg9+SU1y4iIoc3qC4sxw3f4uTdjCWRDEgfAvY3DVWiWI/EbWcfX7bVvl/GCQtHSwBW5z3KwhJV2McLK6Fpblx6fM=,iv:exFXncN3SA9zqSTFxX6o3kstwMGL9y8x0IOqJVNqK+I=,tag:dEkDG3meaWoq74hkRHbplg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3YWliNzFKWnZqdnZYeHRs\nVy8wdk1ZZWpJTlg2WWc4eEpUM2diZEFoamg0CnJnVDZJT3lWaUZlV3NHY1NpN0tW\nMXdRTnNGSjBhSFpLY0xvaER6UDI5RlEKLS0tIDloRHJFV2I1RVN6TXh6dmd5dzV0\nZ1AvZmpOM0VkaVZjNHlFdFBNd0FhTVkKEVFjtN66i+8f7P03ODYgoWZsTUiEcPiL\nYaV4UZKbjnp3SKTAeWk1P/lEj5DkicW3hq0ONQf2xrYriCpAc3/gKw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0VG1wZE52RFQ2ekl1R1ZY\nN2pmYWd1cStGZzU0Tk1LTmNuMnc5c1UwMnlVCmJrMjB6Qzc5ZUE3aXhmUmVuTTN1\neVFWbGhOUUNYUFJJVHF0OGtaR1FvVG8KLS0tIFp4aHgwN1QvWVVnaTNDVG42SXVB\nYVlLRndmQ1ovQjFMcHZYU0dqNS9ML2sKtHjmgLODafDcmrpYQyXRc/ajAR2saGs8\nlh4NVYYYwoXE6sNKSXwzgXXSjGEXGTVLxVvp9OKnSloI5/LsbrxANQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:37Z", + "mac": "ENC[AES256_GCM,data:20RiSc6b3o3xy23NDQRw4pcSf/akdcUMO6ciSFSZMQrhreYPBEa+Tb85qqqZ0dqQHRQFanzE3Usomp+Ux4FhFfSsCxljxdOjkQCAfkQKrg+GQ7/NOUhgdVQtep2+gT7MFrEzo5Jv8kctNuT18kqUjv5CwCOR35QJ98yHeAUULoo=,iv:ocUDNN4vhOX9pCUJKqQiBRhjTHbdRdw96csN6EWFdUg=,tag:Lps0aJy0ctWU5ilCUn9Uww==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..ea47b41 --- /dev/null +++ b/vars/per-machine/distant-shore/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAPVF7m/+s1YroGdvSMxPwKmenJjk4yNrP8tNtZGHEhJI= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore new file mode 120000 index 0000000..2f4e8ad --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/machines/distant-shore @@ -0,0 +1 @@ +../../../../../../sops/machines/distant-shore \ No newline at end of file diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..ed43256 --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:9BN/+IBbsAmgABYuTEZvgB3cJOwiZ1aKu5GqcBEvCBoY3K4T5lDPqHrwdH48msu9/KD435SSz336+Stq8bQB87AXdfDMEhVIUwi8SV/CQg3urXvyqp0+lkbbrP9xyFzcH16L7NDmfD/SlZeFXQoPA3YHLvoYSsWnfjzHqrt0600IhAgq0TK+c+5hCzke9k89pgOrO6ypueHV+6GMx0g4JMcwq17bqT3fOQZ+hHSp9uOWDP1kJrO2TktwR/9AWAN+IG1sjUcaKYg+W34pG4XDkNPnp30NPfXSGMXjrM++MkIxyow1zFeSRI+bP5iLQEFpm1AvFFRdYIGN66hQVCgv0kxaOEJknlrG4QT4TyEJ,iv:MUsdjMEBvuaFkJJ6t3NNDrgECjheLJ0FtdrBsztOKZ8=,tag:lTcmyWAoKYPUhDjkHTd+Iw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLQ0Y3Y1N5aEo5ZDQwZ2g4\neXBDMldtWU42cUFaaTBmS1B6YW5QWktNcVZvCjBMYmNKWjR6cmVIRjhNK2Y2aWg5\nM093ZFhFWW0yZnVrOUxGQ3MzSGY5UkUKLS0tICtTbHFTMUtGQWEycGNDNFlXcTBS\nWmNWbDZSNE5sWUpzQ0dTNTgyemhNdzgKdPZIFY/m3IpEMH1PGsYToyLe9Qzj6LpW\nJhOTJbT9L0dTfE3OzdaG8BkwCkb8XCWxzveLPTLPCOvbP8DmOpjjHA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcXQ4NjE4d00ramdsemhI\nb1dxcldHS040TkVyL2lxUjdxL3J1WUlCdEZNCnExMDRqcmh5MGUxNFpJd3k4MzZT\nMXljSW5ncWxlSGRsYlJBdkoxQjIyZHMKLS0tIEhUSkRpeXhOM3BnTEsrNEpDb1I2\nUlhvZzFjRVNCcng2c3lsYS8vZHVHN00KFMMGm6BJY7/cn5WSP/RgjK6bVo4r7ps2\nkMcPoyMyenPiZrzWdL4iIb5azFB3CI8DAQS84Mt6KPR/wkYNoErxJg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T16:36:38Z", + "mac": "ENC[AES256_GCM,data:Cy3KGFXu58LAWSCUYJGpMeJxBboQxEPS1TzoK8iCFUyTT7Xfak9M9omaBd2r2fEel61iuSDVoDvQbZgNy2RwuiG0HhTXliMXR6G4oOheQIsSQix81tOWoPipu77qoeVkOSUDRhBzHdQVQQmiN7VJvw1kHvCq20u2ZM0057vf91g=,iv:uAmwqd0gpCD7pTFWwgKdkKjjxVadnHeRYUEv+vUgvL8=,tag:iDbx80+08AqhvdZIXJzdgQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/distant-shore/zerotier/zerotier-ip/value b/vars/per-machine/distant-shore/zerotier/zerotier-ip/value new file mode 100644 index 0000000..1de93b8 --- /dev/null +++ b/vars/per-machine/distant-shore/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3 \ No newline at end of file From 0cdb4b8697b814895525fbe8fe2ac3fb08ae086d Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 18:46:31 +0200 Subject: [PATCH 5/9] =?UTF-8?q?fix(distant-shore):=20build=20on=20sunken-s?= =?UTF-8?q?hip=20(avoids=20self-SSH=20on=20closure=20copy)=20=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/clan.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index 2e974d5..cc4b8fe 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -172,13 +172,15 @@ in { # distant-shore — ThinkPad X13 Gen 2, WiFi, Secure Boot via shim+MOK # (installed standalone, then migrated into clan). targetHost is the LAN # IP for the first `clan machines update`; switch to its ZT IPv6 once the - # mesh is up. Builds on the box itself (it has nix + internet). + # mesh is up. buildHost = sunken-ship: it's an x86_64 builder whose key is + # already in distant-shore's authorized_keys, so the closure copy works + # (building on distant-shore itself needs a fragile self-SSH). machines.distant-shore = { imports = [ { clan.core.enableRecommendedDefaults = false; clan.core.networking.targetHost = "danny@192.168.1.182"; - clan.core.networking.buildHost = "danny@192.168.1.182"; + clan.core.networking.buildHost = "danny@sunken-ship"; } clanHostsModule ../nixos/hosts/distant-shore.nix From 610454f0d2488a9627c47845c3be9afa24fb69c3 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 20:27:34 +0200 Subject: [PATCH 6/9] =?UTF-8?q?fix(distant-shore):=20drop=20duplicate=20st?= =?UTF-8?q?andalone=20flake-module=20(clan-managed=20now)=20=F0=9F=A9=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flake-modules/distant-shore.nix | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 flake-modules/distant-shore.nix diff --git a/flake-modules/distant-shore.nix b/flake-modules/distant-shore.nix deleted file mode 100644 index 6c5a023..0000000 --- a/flake-modules/distant-shore.nix +++ /dev/null @@ -1,14 +0,0 @@ -# Standalone nixosSystem registration for distant-shore. -# Temporary: clan integration (zerotier/data-mesher/dm-pull-deploy) needs -# vars generated via sops on the admin machine. Until that runs, this -# keeps the box buildable without clan deps. Delete this file when -# distant-shore moves into flake-modules/clan.nix. -{ inputs, ... }: { - flake.nixosConfigurations.distant-shore = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - inputs.disko.nixosModules.disko - ../nixos/hosts/distant-shore.nix - ]; - }; -} From e2cf93e7d64504d965755730b734b264ffdc378a Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 7 Jun 2026 21:43:28 +0200 Subject: [PATCH 7/9] feat(foreign-port): add WiFi-only laptop as clan machine :sparkles: Mirrors the distant-shore pattern: clan-managed (no standalone flake-module), wired into zerotier/data-mesher/dm-pull-deploy with the generated vars. WiFi via NetworkManager (PSK from /etc/secrets/nm.env); locally-signed boot chain (shim chain-loads sbsign-signed systemd-boot + kernel, refreshed every nixos-rebuild). targetHost is the LAN IP for the first push, switch to ZT IPv6 once on the mesh. buildHost = sunken-ship to avoid self-SSH on the closure copy. --- flake-modules/clan.nix | 36 ++++++ nixos/disko-foreign-port.nix | 36 ++++++ nixos/hosts/foreign-port-hardware.nix | 18 +++ nixos/hosts/foreign-port.nix | 111 ++++++++++++++++++ sops/machines/foreign-port/key.json | 6 + sops/secrets/foreign-port-age.key/secret | 14 +++ sops/secrets/foreign-port-age.key/users/danny | 1 + .../identity.cert/machines/foreign-port | 1 + .../identity.cert/secret | 18 +++ .../identity.cert/users/danny | 1 + .../identity.key/machines/foreign-port | 1 + .../identity.key/secret | 18 +++ .../identity.key/users/danny | 1 + .../identity.pub/value | 3 + .../data-mesher-node-identity/peer.id/value | 1 + .../signing.key/machines/foreign-port | 1 + .../signing.key/secret | 18 +++ .../signing.key/users/danny | 1 + .../signing.pub/value | 3 + .../machines/foreign-port | 1 + .../zerotier/zerotier-identity-secret/secret | 18 +++ .../zerotier-identity-secret/users/danny | 1 + .../foreign-port/zerotier/zerotier-ip/value | 1 + 23 files changed, 310 insertions(+) create mode 100644 nixos/disko-foreign-port.nix create mode 100644 nixos/hosts/foreign-port-hardware.nix create mode 100644 nixos/hosts/foreign-port.nix create mode 100755 sops/machines/foreign-port/key.json create mode 100644 sops/secrets/foreign-port-age.key/secret create mode 120000 sops/secrets/foreign-port-age.key/users/danny create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret create mode 120000 vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value create mode 100644 vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value create mode 120000 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret create mode 120000 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny create mode 100644 vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value create mode 120000 vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port create mode 100644 vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret create mode 120000 vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny create mode 100644 vars/per-machine/foreign-port/zerotier/zerotier-ip/value diff --git a/flake-modules/clan.nix b/flake-modules/clan.nix index cc4b8fe..e4a7944 100644 --- a/flake-modules/clan.nix +++ b/flake-modules/clan.nix @@ -22,6 +22,7 @@ let phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc"; vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3"; distantShoreZTv6 = "fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3"; + foreignPortZTv6 = "fdd5:53a2:de33:d269:6499:9389:9b18:6c52"; # Shared across both servers: /etc/hosts entries so data-mesher's # libp2p /dns/.clan/... bootstrap multiaddrs resolve over ZT. @@ -31,6 +32,7 @@ let "${phantomShipZTv6}" = [ "phantom-ship.clan" ]; "${vpsRelayZTv6}" = [ "vps-relay.clan" ]; "${distantShoreZTv6}" = [ "distant-shore.clan" ]; + "${foreignPortZTv6}" = [ "foreign-port.clan" ]; }; }; in { @@ -50,6 +52,7 @@ in { inventory.machines.phantom-ship = { }; inventory.machines.vps-relay = { }; inventory.machines.distant-shore = { }; + inventory.machines.foreign-port = { }; # ZeroTier mesh VPN. sunken-ship is the controller (manages network # membership); phantom-ship is a peer. The mac joins manually as an @@ -62,6 +65,7 @@ in { roles.peer.machines.sunken-ship = { }; roles.peer.machines.vps-relay = { }; roles.peer.machines.distant-shore = { }; + roles.peer.machines.foreign-port = { }; }; # data-mesher — signed-file gossip protocol over libp2p (port 7946). @@ -75,6 +79,7 @@ in { roles.default.machines.sunken-ship = { }; roles.default.machines.phantom-ship = { }; roles.default.machines.distant-shore = { }; + roles.default.machines.foreign-port = { }; roles.bootstrap.machines.sunken-ship = { }; }; @@ -93,6 +98,7 @@ in { roles.default.machines.sunken-ship.settings.action = "switch"; roles.default.machines.phantom-ship.settings.action = "switch"; roles.default.machines.distant-shore.settings.action = "switch"; + roles.default.machines.foreign-port.settings.action = "switch"; }; # `clan machines update` connection target. Priority 2000 > ZT's 900 @@ -123,6 +129,12 @@ in { host = "192.168.1.182"; user = "danny"; }; + # foreign-port: WiFi-only LAN IP for the first update; swap to its + # generated ZT IPv6 after it joins the mesh. + roles.default.machines.foreign-port.settings = { + host = "192.168.1.223"; + user = "danny"; + }; }; # Preserve current network / init stack (no systemd-networkd/resolved, @@ -194,6 +206,30 @@ in { ]; }; + # foreign-port — WiFi-only laptop server, locally-signed boot chain + # (installed standalone, migrated into clan). targetHost is the LAN IP + # for the first `clan machines update`; switch to its ZT IPv6 once the + # mesh is up. buildHost = sunken-ship for the closure copy (avoids + # self-SSH). + machines.foreign-port = { + imports = [ + { + clan.core.enableRecommendedDefaults = false; + clan.core.networking.targetHost = "danny@192.168.1.223"; + clan.core.networking.buildHost = "danny@sunken-ship"; + } + clanHostsModule + ../nixos/hosts/foreign-port.nix + config.flake.nixosModules.monitoring-node-exporter + inputs.home-manager.nixosModules.home-manager + (hmModule { + user = "danny"; + homeDirectory = "/home/danny"; + stateVersion = "25.11"; + }) + ]; + }; + machines.phantom-ship = { imports = [ { diff --git a/nixos/disko-foreign-port.nix b/nixos/disko-foreign-port.nix new file mode 100644 index 0000000..a928620 --- /dev/null +++ b/nixos/disko-foreign-port.nix @@ -0,0 +1,36 @@ +# Declarative disk layout for distant-shore. UEFI/systemd-boot, no +# encryption: it's a headless, WiFi-only server that must reboot +# unattended (clan dm-pull-deploy), so a LUKS passphrase prompt at boot +# would hang it. Mirrors sunken-ship's plain-ext4 choice. Device is wiped +# + repartitioned at install time by clan/nixos-anywhere. +{ + disko.devices = { + disk.main = { + type = "disk"; + device = "/dev/nvme0n1"; + content = { + type = "gpt"; + partitions = { + ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "fmask=0022" "dmask=0022" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos/hosts/foreign-port-hardware.nix b/nixos/hosts/foreign-port-hardware.nix new file mode 100644 index 0000000..3c52633 --- /dev/null +++ b/nixos/hosts/foreign-port-hardware.nix @@ -0,0 +1,18 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/nixos/hosts/foreign-port.nix b/nixos/hosts/foreign-port.nix new file mode 100644 index 0000000..9705b6e --- /dev/null +++ b/nixos/hosts/foreign-port.nix @@ -0,0 +1,111 @@ +# NixOS laptop server. WiFi-only, headless, unattended auto-rebuild via +# clan dm-pull-deploy. No LUKS (mirrors sunken-ship) so reboots don't +# block on a passphrase. +# +# Blank-slate server for now — no application services. Give it a purpose +# later (just add services here and let dm-pull-deploy roll it out). +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./foreign-port-hardware.nix + ../disko-foreign-port.nix + ]; + + boot.loader.systemd-boot.enable = true; + # Firmware-locked Secure Boot: we can't enrol our own keys into the + # firmware key DB, so a vendor-signed shim is the firmware-booted binary + # and chain-loads a locally-signed systemd-boot + kernel. The NVRAM + # entry points at shim; bootctl is kept away from EFI variables so + # rebuilds don't clobber the entry. + boot.loader.efi.canTouchEfiVariables = false; + boot.loader.efi.efiSysMountPoint = "/boot"; # matches disko ESP mountpoint + + # --- Locally-signed boot chain -------------------------------------------- + # On every bootloader install: re-sign systemd-boot and every kernel + # image, refresh the shim binary on the ESP, and place the helper binary + # beside it. Re-runs on each nixos-rebuild so auto-deployed generations + # stay bootable. Signing material lives in /etc/secrets, never the repo. + boot.loader.systemd-boot.extraInstallCommands = '' + # NixOS's bootloader-install systemd unit runs with a minimal PATH that + # doesn't include coreutils, so use absolute paths for cp/mv. + KEY=/etc/secrets/MOK.key + CRT=/etc/secrets/MOK.crt + sb() { ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$2" "$1"; } + # systemd-boot -> shim's chain-load target + sb /boot/EFI/systemd/systemd-bootx64.efi /boot/EFI/BOOT/grubx64.efi + # shim is the firmware-booted binary; helper binary sits beside it + ${pkgs.coreutils}/bin/cp -f /etc/secrets/shimx64.efi /boot/EFI/BOOT/BOOTX64.EFI + ${pkgs.coreutils}/bin/cp -f /etc/secrets/mmx64.efi /boot/EFI/BOOT/mmx64.efi + # sign each kernel (skip if already signed; leave initrds untouched) + for k in /boot/EFI/nixos/*bzImage.efi; do + [ -e "$k" ] || continue + if ! ${pkgs.sbsigntool}/bin/sbverify --cert "$CRT" "$k" >/dev/null 2>&1; then + ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$k.tmp" "$k" \ + && ${pkgs.coreutils}/bin/mv -f "$k.tmp" "$k" + fi + done + ''; + + networking.hostName = "foreign-port"; + # WiFi via NetworkManager. The wpa_supplicant stack hit two issues on this + # box: (1) it strips CAP_CHOWN so wpa couldn't create its ctrl_interface, + # and (2) dhcpcd didn't grab a lease after the (late) association at boot, + # needing a manual restart — fatal for an unattended headless server. NM + # handles association + DHCP atomically and connected first-try here. + # The PSK stays out of the repo: it's substituted from /etc/secrets/nm.env + # ($PSK_INTENO) into the declared profile at activation. + networking.networkmanager.enable = true; + networking.networkmanager.ensureProfiles.environmentFiles = [ "/etc/secrets/nm.env" ]; + networking.networkmanager.ensureProfiles.profiles."Inteno-89FE-5GHz" = { + connection = { id = "Inteno-89FE-5GHz"; type = "wifi"; autoconnect = true; }; + wifi = { ssid = "Inteno-89FE-5GHz"; mode = "infrastructure"; }; + wifi-security = { key-mgmt = "wpa-psk"; psk = "$PSK_INTENO"; }; + ipv4.method = "auto"; + ipv6.method = "auto"; + }; + hardware.enableRedistributableFirmware = true; # WiFi firmware blobs + time.timeZone = "Europe/Copenhagen"; + + # It's a laptop acting as a server: keep running with the lid shut. + services.logind.settings.Login.HandleLidSwitch = "ignore"; + services.logind.settings.Login.HandleLidSwitchExternalPower = "ignore"; + + # Reduce screen burn-in / power: blank the TTY after a minute. + boot.kernelParams = [ "consoleblank=60" ]; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI) + nix.settings.trusted-users = [ "root" "danny" ]; + system.stateVersion = "25.11"; + + users.users.danny = { + isNormalUser = true; + extraGroups = [ "wheel" "video" "audio" ]; + openssh.authorizedKeys.keys = [ + # Mac admin / fleet key (~/.ssh/id_ed25519_sunken_ship) — the key the + # Mac uses to reach the fleet; clan machines update relies on it. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin" + # TODO: add a per-host key (~/.ssh/id_ed25519_foreign_port) for + # plain `ssh foreign-port`. Generate when convenient. + # sunken-ship (dm-pull-deploy push node) — reach foreign-port over ZT. + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship" + ]; + }; + users.users.root.openssh.authorizedKeys.keys = + config.users.users.danny.openssh.authorizedKeys.keys; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + security.sudo.wheelNeedsPassword = false; + + # mokutil + sbsigntool — manage the shim trust chain and inspect signed + # bootloader/kernel images when debugging. + environment.systemPackages = with pkgs; [ git mokutil sbsigntool ]; +} diff --git a/sops/machines/foreign-port/key.json b/sops/machines/foreign-port/key.json new file mode 100755 index 0000000..6aa3307 --- /dev/null +++ b/sops/machines/foreign-port/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "type": "age" + } +] \ No newline at end of file diff --git a/sops/secrets/foreign-port-age.key/secret b/sops/secrets/foreign-port-age.key/secret new file mode 100644 index 0000000..2ba1f0f --- /dev/null +++ b/sops/secrets/foreign-port-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:MH/ib8WAbzucbm2dhhoo6ESSSLKtKMWmjUwtpAOZhU7KyhOoechpJRSkBBmFV4LzbSP1qeaFbid6USJBnRsxkoz6XvhMzP0kzS0=,iv:9sPwc/JIlo5mzxelNzLCB26k2f+n2C9tB8Y/HEdPvHw=,tag:hJBhzTMsTWd9PDydS4aosg==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6L2JGejlzQlhiNFhES1lT\nNTZsNFFMT1NzZEV0T28rNDA3STJ1UXNRcUZFCkxoenpFYWJicHpGVDhtMUdwNXBo\nS29EazVsRGFST2ZodDJMTkxQN2I1RjAKLS0tIFpib0RoOTJ6bkU0b1F6NnRaV3lF\nVHhvYjNOUUtMbGF5ejdaVk5WdGt2d1kKNU5JR1nIYPQLALUM3wRO945Sk6GLxJpn\nTVmVUEgXcwHcSij10a/cQOyPXNNnsfIC+WJFMJcjHfsjBnwS5W/Bgw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:mVobAhXUhbs49+g0bXfi4TjPG667F7pM8Kk518a7kRZ/HtN2kLYcSyl3XpspTosAs4x3QbFQUbFCgsBgqx+gS6xlw3OAJXM3iG2fNu2qoj9Q7viAEHoWVHwT+ftjA0qVTUf0BDD1r4ow6BNhe6kQy5bQqVu0MhjDfsK9BNTXAu4=,iv:aFHo3bQKgr1XSnwGUajkSFa4UftTWdZbPtXY05N7qOM=,tag:VymYJf4XFLaEGvxQmvF6rA==,type:str]", + "version": "3.12.2" + } +} diff --git a/sops/secrets/foreign-port-age.key/users/danny b/sops/secrets/foreign-port-age.key/users/danny new file mode 120000 index 0000000..215639b --- /dev/null +++ b/sops/secrets/foreign-port-age.key/users/danny @@ -0,0 +1 @@ +../../../users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret new file mode 100644 index 0000000..768ab41 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:wnNPCB0+f3dcxMW1/pcFZFauUVYTC1mfWoWBV2EJmyRzZS3Uux5Un3R/GbYQeDSFZDLzLH+zCZFaxq3mpb3NGTTUzF8vnGMk/OnjlolA8OjAfiODI0mahTiQA7WcWSk1hkkZ15Ri1o+uyumx9hmvJU3dIsKIJe7AizCzwP5bHg1jgRhG2wPKKyIDWKoh4JTlR6SxK6/tOaUPx2gb2ddz2Lk56Xdw7GCbb/9I9D6sRwxdWMCoWFKdTllLsdsD48b8Jfq4ewD+LudYEtiVByk5SpyOjQoAmMLYaGlD+nxFgZz53hePRIXnp0fL0pm4,iv:fA607yxD/yHJatEiGh1SVGDcqKxB+EFeyCUQeF/Z5hA=,tag:glaq+MBCp6ptKqDsw4RM/Q==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0MklDUkpWbEdFcFgxTVJZ\ndEs1OTJtZFhVaEsyb2pobGlUOGhtcTY4RWpVCjFDV3lqRmNGclZMbTR3UXlhcjJv\nVEY1Tjk1YWR4Tmt0SmgvR3laZnNIRUkKLS0tIHB1TURnYmVzZW4xSERMR0ZrRXl5\nbWVJbW1keGkyUkhuQXE0MEFTaXFsS1EKHlsS3FDr9RuMBRU5r4T3bCZWZn38V3k+\nfLUfuZK2IF+xyD7kEiBuATB57wwfd8RzZ1lBwz4fD4jlb+fz0BXoJQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6TnNqc284WkZqdXNLVFBU\nRTJndEFmNjY0Q1YyUnRPLy9jWllpSy9ZaFNFCkFkNmpYenQ2dk1Fb2dRZTNvM0Jl\nemNqUmdjQmpJQUF4M3ZNRmo4UEhXOHcKLS0tIFp4OTZJTGR1algxTEVWemdkQTB5\nME4xTTdlelN6bXJiTGRSM1VSWG5vZUEKOYc71rLx7RTq4DR6ZggrtgllK58sYJ6h\ngw156OTQl3fKWxlrKDd1l4o72M1qmfAIQ1z5YJJ+CfNPk/iMz/R3rQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:AkcOoNTxMNkpF0SrwFlNujBrB8fxL1diu+mGq/kbsiWIj6UqvVD+dimDSvTgVqvnU4HF7/7b9zKriC6SbG42Kz8zScFv7m3idD2tHr+7SE/iR7CowDQs70CRMo1b85wLq8WAxhfQb93NHdum6I2biNVIf0ZXs1+kZ2iNBxtjqfQ=,iv:kWOCWCe953ekq0n0HLe3S2JprIBnBe9QXwIzDFyQMH8=,tag:tLz7VZwj7RrbpJ7QTrBqcg==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.cert/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret new file mode 100644 index 0000000..a79bb25 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:1Hq98rN3U+8DcxIFJpYkvv31gUpSm0WBjfZxivYn7/ZkH6zbJ57fzeU+9PH9SRF6QBuekZKZNIBup3fteI5VqQ/moEyQE9aSvnqGCrkcamDwDQfN5GwKX+rb7W96atESRm/VqhgDWC2KTc3892515gBPpkDG+nc=,iv:tAlghG1jpDPcYgTvEzAlnB2upAetl8mz8IIQercHe4k=,tag:mz3fvVlKolg5JzrjhBNPaw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlaEk2Z1NMZnVtNlJPdXgw\nTWNaTFBCRXo3T2JRUEY2Q2hBY0xpMVV6ckE0ClJOVUpKNDZTcEhGS2RzQm1tSjNp\ndmxQWjl5aHord0RUMHRvTlhyMkVqc1UKLS0tIHlDRXlReUgzZVdLcE9kMFhsTDRq\nOGxpZE9KcUR0VEhyOE9VUkVUVlIyRlEKsnU17famN/qr2M8BdvVpRl5bSWseegrZ\nnB9yljvm+pxsE55xM1WyguNfUwXtHj0YTiVgBl5PIUolj3/J8R76sg==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjTGdDVlB0RGlTcng3M3pE\nNzEvNFpBUzF0aDJDaUJFTFFGWlB3bEVVdHhFClZOZGNDanlMTkxIMk9lbzVGRzAv\nZG93NUFFL3NIM3Z0TlhucFlMTTYwc3MKLS0tIFFQcTIwekNEM0k0MElGZys2QldS\nMDZpRVk5OVNZYVVWSWJDTFZqVFdiRWcKgwuwZgKhKx1PiQwH2CgMoCl0WUQR5Rv9\nx4mpZgkoD5pkEx896117CyAy2BRzrDWo+4SsjEijSMlDynYsbxLReA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:DX9+9MH8ZPtc6sPbYSc+54soAIXJWWEoEWBZdbJ6gT5RhVdzUjMHuEbmb9eMcb+nVu4KSUCoXiJOT9XActSU2dcTNIIiLX1lqpw0aWRS2sAWM+Go4hT4/P98z/0vcsdN/uQOBl3cDlygqKhN9GSoPfJTMT+QTSZsVjxwYxW1pPM=,iv:B9RiMMX+yS1Y+3E1ifTJI30pvLrah5SCPwW6CZKZGNU=,tag:MA007hv+nMIMutOdl5ewkQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value new file mode 100644 index 0000000..b450f2b --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/identity.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAZqy+mwYOfJy3GSHfeC80TFn1c0kYte5zzzbwrP8xww0= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value b/vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value new file mode 100644 index 0000000..f9982a8 --- /dev/null +++ b/vars/per-machine/foreign-port/data-mesher-node-identity/peer.id/value @@ -0,0 +1 @@ +12D3KooWGjAXheQGEfy13JQJP8pSrwcivxoXw5ijRzesfXVDFuyW \ No newline at end of file diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret new file mode 100644 index 0000000..cd27ea5 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:dDO6hu8prxHvoP41Oxky0mGGbrwqcCcrrkg0tbr/Sv8K16gNoQaX2wvaRDExOmt0BZkv5Oe8p5pvKudmm5JN0AS7oaPexW0lE+vFJ+zrRpq01c5BbCYZ0SuuafJ3VmRS/dlYU0/SZ4MyK3eijLzX3rGHPOi3b0g=,iv:hbh49ExGMYyshxcus/5sTIs/ZcOL9pod/3H/oHG1Qs8=,tag:fjHnl2uunGEU0i2FtgZB+g==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZNTlkTWVleld5K3Q5Vklm\nMlphdVduQ0RKY0pEVGdVTm5scHRWR0lNVjAwClV4V3drQnFLUkhpUVk1ZElGcFM1\ncit3UTdURExTRDVjVW1ZdklTZzRINDAKLS0tIHFMYnNycmh1Y0h4OC9UNUtHUmMw\nVXdpVk9QWHlBYmtCS3FOam9SWnRFZG8KDnggBRH/wSh1tfiCGOn1sF/Fdfxkf1us\n7Lzxexrmh+lllns/KY2of9L2HUgDavp+ju/5QVFfT7O3SuSTB6aoow==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5eVpNUmV5QlllaXlPaGgr\ndXZYMURzT3I4UWxWSHBSbnAyZVNsOWNaZ2xJClhkRmZ2ejBYVCtkTVBZZE82YXE5\nWkdZWFJFM0lVQXFFYm5rYnRVZDFEdlkKLS0tIHZ5OUgzcFRLZnFWK3pDUUtWUUJj\nWFF4Zk5IeDl5VFNQWlVsTk1lQWlLQmMKJzaOm0cwOshmwoO+eHovf6i6mGkezjIP\ncXJlDaJyxfPKJxc36XlJ5KT9c4RqTX7WFOifHoKRh4EN58KnvtFj+A==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:18Z", + "mac": "ENC[AES256_GCM,data:UX7265pubBBssugQk4pZsQH5WedsmnqFa77bJQZwu2ixNUTkO9VfR8r9CUiugDOmbDj9Y7TJtoN4JR+v6hBmDOnjHO5w0WO5dONNJebGmO+pGU7r/K6WwSGi5nPANiYjGuHqYZwq7PJe8ZCF/vu/ZI8q7iJijw6xGWuGHaP/Gvw=,iv:Ezo1z5n+pHPdhjh9l+HvmsgElEwJR4eoMPtZKdDhHAI=,tag:57yLRXReSRz098sDxyiQZQ==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.key/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value new file mode 100644 index 0000000..6131304 --- /dev/null +++ b/vars/per-machine/foreign-port/dm-pull-deploy-status-key/signing.pub/value @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA6xYjcIT5B5NDduIARf2EAoE+vsnZK+NWcyiI0fQc0Fg= +-----END PUBLIC KEY----- diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port new file mode 120000 index 0000000..96f5ba3 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/machines/foreign-port @@ -0,0 +1 @@ +../../../../../../sops/machines/foreign-port \ No newline at end of file diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret new file mode 100644 index 0000000..5fd8d79 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:PO0Thn6D7kcIGWr7MwmS8H58+9JYSDDGQZlx28B7T6noXTA6tWqMJlqY4aMn1dXJ1CKAqV4q5VZpd/kP9KQvSL4DRnRrFteRe0C+k/mlLfwsWVqLGFY7eqoG1QTZwc4w8cw3FB7R0YUfxRlHq3mIyrbf+8POX2Rq2r5L5GNWVkGTKZOPRtNawPxTrUgfVM4B9ksc1vtTZeWn1GymSwevnt4KPX/8efFAgIclTUHh+Eh+F9xSU9efnkT+Phsh3QLf+3+UHiXQXlpMgwuKrvBJdHWLxJz/3aTpU2+nByqv0IANhGhR8ut0EbFXr8Zr1pIYrt4mWCAyYJvnwxR6iljQ1zyhI0GXUNAHJPQ7wRYq,iv:yDOBYu2+HK/KfS/hbR5QgOi2QHp9RzGPiKxojQX2s8c=,tag:q6s6LemFyoFBEq+ojd4D6A==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1bEkwSUhxR0JQR2psZ3Uy\nUlFpWi93NTBhZ0s0TlpkQ2VkVUdDWHIyNmhFClBhVFBnb1h0c2o2cm9OODZpZWMy\nQTB2YmxnWmN5Ylo4M1JHMVVVdklWeWMKLS0tIDBSY2NQdmRTZnA1QUtnaHloUFJJ\nb0VvZGlwSko0UitTa2t6TDZ4bnhsSWMKt5awUoFdny/Qg5krgUAzHeqIoIhprPmF\nBNleiSJdAvSsK53a7CT2rGInnl3dcrtpkEWluK7WJlFTJBdekMwQuA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6TFZjRjk4Wm8vcEcxN0ZP\na05yd282UmR3NllXM2MyeUpSb0VuWURvTHp3CmJzL0cwcU5WWGJuME1KcmtxSFVw\nL1lFdzg3Z2t4TXBiaWduZ2tSZXc3bjAKLS0tIEp6NWpIMlhoSEtvQ3IyNXJNVnE1\nb1lSczR2eG1JY1NScnkyNWMxWWN0aWcKrnfv9dGrWpmBjt8u+FdtwojU5hLDyV/Z\n6vgaW35SvFYLYR53Zo18MPkYbqGcaNldyr68qbYMLxqVdQUJwv3LSg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-07T19:41:19Z", + "mac": "ENC[AES256_GCM,data:joT4cUsVDxTVJqF9OJyETkC0lxQ6sT3XonBIjy80/PZ6cs7lcEyboWWSVuBcG+CTPzcUv1uXmdNjUBNc/TDdF8P0vEGnMBgmNRnSrxb0OwENW+c08GOB+c4AJev58H+V1wmzmyr9NJAKxpvQaE/cWIS1wS7c5QdiKAj8HsYd2ns=,iv:H2xSAU0jTH0bKS+P5W+FwbOtzl/Wb5xTfirkZMmtPq8=,tag:o+b9ESO3d8XnIU/bcH09zw==,type:str]", + "version": "3.12.2" + } +} diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny new file mode 120000 index 0000000..48e5c60 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-identity-secret/users/danny @@ -0,0 +1 @@ +../../../../../../sops/users/danny \ No newline at end of file diff --git a/vars/per-machine/foreign-port/zerotier/zerotier-ip/value b/vars/per-machine/foreign-port/zerotier/zerotier-ip/value new file mode 100644 index 0000000..79b3db0 --- /dev/null +++ b/vars/per-machine/foreign-port/zerotier/zerotier-ip/value @@ -0,0 +1 @@ +fdd5:53a2:de33:d269:6499:9389:9b18:6c52 \ No newline at end of file From f8a873bd06b106a69e5d62eb1b4bd0efac968143 Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 8 Jun 2026 22:27:32 +0200 Subject: [PATCH 8/9] nixos: add tdpixi service (port 8093) + vps-relay vhost Idle Tower Defence Mini App by @plasmagoat forked from github.com/plasmagoat/TDPixi. Pure static FastAPI serve, no DB. Proxied at tdpixi.dannydannydanny.me. Co-Authored-By: Claude Sonnet 4.6 --- nixos/hosts/phantom-ship.nix | 29 ++++++++++++++++++++++++++--- nixos/hosts/vps-relay.nix | 4 ++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index e018a73..99bec0e 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -50,11 +50,11 @@ in # KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator # (:8083), Forgejo (:3000), Escape Hormuz (:8090), bon (:8091), - # notes (:8092) are reachable only over the ZeroTier mesh — the - # vps-relay Caddy reverse-proxies into them. Same pattern as + # notes (:8092), TDPixi (:8093) are reachable only over the ZeroTier mesh — + # the vps-relay Caddy reverse-proxies into them. Same pattern as # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side # stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 ]; + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 8093 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -527,6 +527,29 @@ in }; }; + # TDPixi — Idle Tower Defence Telegram Mini App by @plasmagoat. + # Pure static serve, no DB. Code rsync'd to /home/danny/tdpixi/. + # Upstream: https://github.com/plasmagoat/TDPixi + systemd.services.tdpixi = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + ]); + in { + description = "tdpixi — Idle Tower Defence Mini App"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + serviceConfig = { + WorkingDirectory = "/home/danny/tdpixi"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8093"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Hara morning heartbeat — daily email check + Telegram good-morning ping. # Runs claude in print mode with the Gmail MCP, then sends output via Bot API. # Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin). diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 3387aa5..207e8b8 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -142,6 +142,10 @@ "bon.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8091 ''; + # TDPixi — Idle Tower Defence Mini App by @plasmagoat, port 8093. + "tdpixi.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8093 + ''; # notes — markdown blog (notes.X) + apex landing (X). Same backend # service on phantom :8092 routes by Host header. "notes.dannydannydanny.me".extraConfig = '' From 0eab0d47ae1c7c3ff3fbc5f058df653a40a636bb Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Mon, 8 Jun 2026 23:25:34 +0200 Subject: [PATCH 9/9] nixos: add bananasimulator-beta service + vhost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cheat instance for the bananasim project. Mirrors the production service on phantom-ship but: - own DB at /home/danny/.local/share/bananasimulator-beta/ - own working dir /home/danny/bananasimulator-beta/ - port 8084 (added to zt+ firewall allowlist + new vps-relay vhost at bananasimulator-beta.dannydannydanny.me) - BS_RIPE_MIN_PER_STAGE=0.2 so a banana cycles in ~3 min (testable) - BS_BETA_MODE=1 so the server exposes /api/cheat/* + sets beta:true in /api/me, which makes the frontend render the 🧪 cheat menu Same code base; deploy with the same tar-over-ssh ritual into the sibling dir. apps.json gets a private 'bananasim (beta)' entry that only my user sees. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/phantom-ship.nix | 37 +++++++++++++++++++++++++++++++++++- nixos/hosts/vps-relay.nix | 5 +++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/nixos/hosts/phantom-ship.nix b/nixos/hosts/phantom-ship.nix index 99bec0e..26b3e90 100644 --- a/nixos/hosts/phantom-ship.nix +++ b/nixos/hosts/phantom-ship.nix @@ -54,7 +54,7 @@ in # the vps-relay Caddy reverse-proxies into them. Same pattern as # sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side # stays closed. - networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8090 8091 8092 8093 ]; + networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8084 8090 8091 8092 8093 ]; hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware @@ -176,6 +176,7 @@ in "d /home/danny/.local/share/shelfish 0755 danny users - -" "d /home/danny/.local/share/scuttle 0755 danny users - -" "d /home/danny/.local/share/bananasimulator 0755 danny users - -" + "d /home/danny/.local/share/bananasimulator-beta 0755 danny users - -" "d /home/danny/.local/share/komtolk 0755 danny users - -" "d /home/danny/.local/share/escape_hormuz 0755 danny users - -" "d /home/danny/.local/share/scuttle/tiles 0755 danny users - -" @@ -385,6 +386,40 @@ in }; }; + # Bananasimulator BETA — cheat-instance for testing the full progression + # end-to-end. Separate DB, exposes /api/cheat/* (gated by BS_BETA_MODE=1) + # so the frontend cheat menu can seed canonical states and reset. + # Faster ripening (0.2 min/stage = ~3 min to compost) so cycles are + # testable in real time. Same code base; deploy to a sibling dir. + # vhost in vps-relay.nix → bananasimulator-beta.dannydannydanny.me. + systemd.services.bananasimulator-beta = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + fastapi + uvicorn + httpx + python-telegram-bot + ]); + in { + description = "Bananasimulator BETA (cheat instance) FastAPI server"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv ]; + environment = { + SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard"; + BS_DB_PATH = "/home/danny/.local/share/bananasimulator-beta/bananasimulator.db"; + BS_RIPE_MIN_PER_STAGE = "0.2"; # ~3 min to compost — testable in real time + BS_BETA_MODE = "1"; # exposes /api/cheat/* + flips beta=true in /api/me + }; + serviceConfig = { + WorkingDirectory = "/home/danny/bananasimulator-beta"; + ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8084"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + # Escape Hormuz — turn-based boat-race Mini App (Hara's first build). # Code lives at /home/danny/escape_hormuz/. Same vps-relay-fronted ZT path # as the others; binds :: so the ZeroTier IPv6 address is reachable. diff --git a/nixos/hosts/vps-relay.nix b/nixos/hosts/vps-relay.nix index 207e8b8..cedcbfa 100644 --- a/nixos/hosts/vps-relay.nix +++ b/nixos/hosts/vps-relay.nix @@ -124,6 +124,11 @@ "bananasimulator.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8083 ''; + # Bananasimulator BETA — separate service on port 8084 with + # BS_BETA_MODE=1 (cheat menu + faster ripening for testing). + "bananasimulator-beta.dannydannydanny.me".extraConfig = '' + reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8084 + ''; # KomTolk (formerly translate-platform) — same backend, port 8080. "komtolk.dannydannydanny.me".extraConfig = '' reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8080