docs: Document the deployment

Add a top-level README mapping the boxes and a full docs/ tree: topic
pages (architecture, networking, deployment), per-site box pages for
colony and home with containers nested under their hosts, remote and
mobile boxes, the installer, and the home switch fabric reference
(folded in from home-switches.md, with AGENTS.md and code comments
retargeted to its new home). Box pages carry marked assignment tables
that CI regenerates from nixos.allAssignments.

AGENTS.md points at the new docs and keeps its terse agent version of
the mechanics, referring to the topic pages for depth.
This commit is contained in:
2026-07-24 00:24:31 +01:00
parent 9b582bec7b
commit d51f2d62b6
45 changed files with 3072 additions and 6 deletions
@@ -0,0 +1,45 @@
# chatterbox
The Matrix homeserver for `nul.ie` (Synapse) and its bridges to other chat networks.
[middleman](middleman.md) fronts it as `matrix.nul.ie` for clients and on `:8448` for
federation.
- **Source:** [`shill/containers/chatterbox.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/chatterbox.nix)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **matrix-synapse** — `server_name = "nul.ie"`, `public_baseurl = https://matrix.nul.ie`,
Element at `element.nul.ie` as the web client. Listens on `[::]:8008` (client + federation
resources, `x_forwarded`) with a localhost manhole on `:9000`. Registration and guest access
are disabled; uploads up to 1024M with dynamic thumbnails and URL previews enabled (previews
are limited to [middleman](middleman.md)'s addresses as the fetch proxy).
- **heisenbridge** — IRC bridge, owner `@dev:nul.ie`, exclusive `@irc_*` user namespace.
- **mautrix-whatsapp** — WhatsApp bridge (appservice `whatsapp2`, `!wa` commands).
- **mautrix-meta** — two instances, `messenger` (`fbm2_*`, `!fbm`) and `instagram` (`ig_*`,
`!ig`), both with backfill enabled.
- All three mautrix bridges use Postgres on [colony-psql](colony-psql.md) (URIs in their secret
env files), require end-to-end encryption by default, and double-puppet onto `nul.ie` via the
shared `doublepuppet.yaml` appservice registration (an age secret).
- The firewall allows `8008` (Synapse) and `8009` besides netdata.
## Network assignments
<!-- assignments: chatterbox -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| chatterbox-ctr | internal | `10.100.2.5/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::5/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
## Notes
- Synapse's real database config lives in the `chatterbox/synapse.yaml` age secret — options
only merge at the top level, so the base config carries a dummy `sqlite3` block to satisfy
the module defaults. The signing key is also an age secret.
- `olm-3.2.16` is allowed via `permittedInsecurePackages` (a nixpkgs E2EE library issue).
- The bridge services get `ffmpeg` on their `PATH` for GIF→video conversion.
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/chatterbox.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/chatterbox.nix) — container definition, Synapse settings and all bridge configuration
@@ -0,0 +1,43 @@
# colony-psql
The shared PostgreSQL instance for colony services. Rather than each service running its own
database, the containers (and the `git` VM) connect here over the `ctrs` network.
- **Source:** [`shill/containers/colony-psql.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/colony-psql.nix)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **PostgreSQL 14** with TCP/IP enabled, reachable from the whole colony (`10.100.0.0/16` and
`2a0e:97c0:4d2:10::/60`, `md5` auth). The firewall allows `5432`.
- Local `peer` auth maps `postgres`, `root`, `netdata` and `dev` to the `postgres` superuser via
the ident map.
- **netdata** with the Python PostgreSQL collector.
- Consumers wait for the database to accept connections with the `lib.my.systemdAwaitPostgres`
helper (e.g. `sharry`, `atticd`, `mastodon-init-db`, and `middleman`'s nginx as a DNS
bootstrap hack).
## Network assignments
<!-- assignments: colony-psql -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| colony-psql-ctr (colony-psql) | internal | `10.100.2.4/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::4/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
The assignment also has the alt name `colony-psql` (no `-ctr` suffix), which is what consumers
use as the database hostname.
## Consumers
- [object](object.md) — `sharry` and `hedgedoc` (and `atticd` when enabled) over
`colony-psql:5432`
- [toot](toot.md) — Mastodon's database (Mastodon currently disabled)
- [chatterbox](chatterbox.md) — the mautrix bridges (WhatsApp, Messenger, Instagram) via
Postgres URIs in their secret env files
- `git` VM — Gitea
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/colony-psql.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/colony-psql.nix) — container definition and PostgreSQL configuration
+27
View File
@@ -0,0 +1,27 @@
# gam
A game-server container — the lightweight counterpart to the OCI game servers on `whale2`,
running servers directly as NixOS services. Currently it runs a single Terraria server.
- **Source:** [`shill/containers/gam.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/gam.nix)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **terraria** — dedicated server (`services.terraria`): world at
`/var/lib/terraria/NotWorld.wld`, auto-created large worlds, MOTD "sup gamers", UPnP off.
Extra settings (e.g. password) come from the `gam/terraria.conf` age secret used as the config
file. `openFirewall` is on, and `estuary` port-forwards TCP and UDP `7777` to this container.
## Network assignments
<!-- assignments: gam -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| gam-ctr | internal | `10.100.2.11/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::b/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/gam.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/gam.nix) — container definition and the Terraria service
@@ -0,0 +1,62 @@
# jackflix
The media stack — acquisition, library, streaming and photos. Torrent traffic is routed through
an AirVPN WireGuard tunnel so downloads only flow while the VPN is up.
- **Source:** [`shill/containers/jackflix/`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix)
(`default.nix`, `networking.nix`)
- **Host:** NixOS container on [`shill`](../../shill.md) (bind-mounts `/mnt/media` read-write)
## Role
| Service | Port | Purpose |
| --- | --- | --- |
| Jellyfin | `8096` | streaming, published as `jackflix.nul.ie` |
| Transmission | `9091` | BitTorrent client (`transmission_4`), published as `torrents.nul.ie` (SSO) |
| Jackett | `9117` | indexer aggregator, `jackett.nul.ie` (SSO) |
| FlareSolverr | — | Cloudflare challenge solver for Jackett |
| Radarr | `7878` | movies, `radarr.nul.ie` (SSO) |
| Sonarr | `8989` | TV, `sonarr.nul.ie` (SSO) |
| Jellyseerr (`seerr`) | `5055` | request portal, `gib.nul.ie` (`openFirewall` on) |
| PhotoPrism | `2342` | photos, `photos.nul.ie`; password auth, sqlite DB, originals/import under `/mnt/media/photoprism` |
| copyparty | `3923` | file sharing, `stuff.nul.ie`; serves `/mnt/media/public` (read-only to everyone) and `/priv``/mnt/media/stuff` (admin for `dev`), share creation, indexing (`e2dsa`/`e2t`), file-magic checks |
All published through [middleman](middleman.md) as shown. A shared `media` group (gid 2000) plus
`UMask=0002` on Radarr/Sonarr gives the apps coordinated access to the media volume.
## Network assignments
<!-- assignments: jackflix -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| jackflix-ctr | internal | `10.100.2.6/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::6/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
## VPN download path
[`networking.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix)
defines a `vpn` WireGuard netdev to **AirVPN NL** (key + PSK from age secrets, MTU 1320 per
AirVPN, fwmark `42`, route table `51820`):
- Policy routing keeps colony traffic on the main table (from/to `prefixes.all` v4/v6 rules at
priority 100, plus a default-route suppression) while everything else falls through to the VPN
table — so the services stay reachable on the `ctrs` network while outbound torrent traffic
exits via AirVPN. `DNSDefaultRoute` is disabled on `host0`; the VPN provides DNS.
- `transmission` and `jackett` `bindsTo` `systemd-networkd-wait-online@vpn.service` — they only
run while the tunnel is up.
- AirVPN forwards peer port `28457` to Transmission (`peer-port`); the firewall accepts it and
drops other new inbound TCP from `vpn`, while non-VPN input is limited to the service ports
(netdata, Transmission, Jackett, Radarr, Sonarr, Jellyfin, PhotoPrism) plus copyparty's `3923`
from the base config and Jellyseerr's `5055`.
## Storage
Media lives on the shared `/mnt/media` volume (bind-mounted read-write from `shill`); Transmission
downloads into `/mnt/media/downloads/torrents` with a `.incomplete` dir, 28 MB/s up/down limits
and a seed ratio limit of 2.0.
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/jackflix/default.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/default.nix) — container definition and the media services
- [`nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/jackflix/networking.nix) — AirVPN WireGuard netdev, policy routing and VPN firewall rules
@@ -0,0 +1,94 @@
# middleman
The front-end reverse proxy for the colony's public web services — the single ingress that
`estuary` DNATs HTTP/HTTPS (and Matrix federation on `:8448`) to. Terminates TLS with wildcard
certificates it issues itself, provides nginx-sso for gated vhosts, and runs a librespeed
backend.
- **Source:** [`shill/containers/middleman/`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman)
(`default.nix`, `vhosts.nix`)
- **Host:** NixOS container on [`shill`](../../shill.md) (`my.containers` ephemeral nspawn on the
`ctrs` bridge; bind-mounts `/mnt/media` read-only for the static file vhosts)
## Role
- **nginx** reverse proxy with the `vts` and `fancyindex` modules, Brotli, kTLS, a proxy cache,
and a dynamic resolver pointed at `estuary` (`proxyResolveWhileRunning`) so upstreams re-resolve
at runtime. All vhosts are declared in
[`vhosts.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix);
upstreams are addressed by their internal container/VM hostnames under `ams1.int.nul.ie`.
nginx is made to wait for `colony-psql` (`systemdAwaitPostgres`) before starting — a hack for
it getting stuck on DNS early in boot.
- **ACME** — issues the wildcard certificates its own vhosts are served with (it is not a shared
CA for the other boxes):
- `ams1.int.nul.ie` + `*.ams1.int.nul.ie` via a lego `exec` DNS challenge that SSHes to
`pdns-file-records@estuary-vm` to add/remove records in the internal PowerDNS zone. This is
the default cert (`useACMEHost`) for vhosts on the internal domain.
- `nul.ie` + `*.nul.ie` + `*.s3.nul.ie` via the Cloudflare DNS provider. A `postRun` hook
`scp`s the renewed key/cert to the `mail` (Mailcow) VM and runs `mailcow-ssl-reload` there.
- Certs reload `nginx` on renewal; the `acme` group owns the secret files and `nginx` is a
member.
- **nginx-sso** — single sign-on at `sso.nul.ie` (one instance, `generic`), with Google OAuth as
the default method and a simple username/password provider. Cookie domain is `.nul.ie`.
SSO-gated vhosts below include `server-generic.conf` / `location-generic.conf` from
`/etc/nginx/includes/sso/`.
- **librespeed** — speed test, backend + frontend, published as `speed.nul.ie` and
`librespeed.ams1.int.nul.ie` (both proxy to `localhost:8989`).
## Network assignments
<!-- assignments: middleman -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| middleman-ctr | internal | `10.100.2.2/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::2/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
The firewall allows `http`, `https` and `8448` (Matrix federation). A small nftables SNAT rule
rewrites outbound IPv6 to the container's own address on `host0`.
## Published vhosts
Everything is under `*.nul.ie` with the public wildcard cert unless noted; defaults applied to
all vhosts are `onlySSL`, kTLS and HTTP/2. "SSO" = gated behind nginx-sso (`generic` instance).
| Host | Upstream | Notes |
| --- | --- | --- |
| `nul.ie` (`_`, default) | static | landing page (`index.html`, CV PDF, SSH pubkey); serves Matrix `.well-known`s and redirects `webfinger`/`nodeinfo`/`host-meta``toot.nul.ie`, `atproto-did``pds.nul.ie`; `forceSSL` (plain HTTP redirects to HTTPS) |
| `localhost` | — | loopback-only VTS status page at `/status` (scraped by netdata); plain HTTP |
| `sso.nul.ie` | `localhost:8082` | nginx-sso endpoint |
| `netdata-colony.nul.ie` | `<host>.ams1.int.nul.ie:19999` | netdata fan-out over `vm`, `fw`, `ctr`, `oci`, `http`, `jackflix-ctr`, `chatterbox-ctr`, `colony-psql-ctr`; **SSO** |
| `pass.nul.ie` | `vaultwarden-ctr:8080` | [vaultwarden](vaultwarden.md); `/notifications/hub` proxied with websockets |
| `matrix.nul.ie` | `chatterbox-ctr:8008` | [chatterbox](chatterbox.md) Synapse client + federation; also listens on `:8448` as federation `default_server`; `= /` redirects to Element; serves Matrix `.well-known`s |
| `element.nul.ie` | static `element-web` | Element configured for the `nul.ie` homeserver |
| `torrents.nul.ie` | `jackflix-ctr:9091` | Transmission ([jackflix](jackflix.md)); **SSO** |
| `jackett.nul.ie` | `jackflix-ctr:9117` | **SSO** |
| `radarr.nul.ie` | `jackflix-ctr:7878` | **SSO**; websockets |
| `sonarr.nul.ie` | `jackflix-ctr:8989` | **SSO**; websockets |
| `gib.nul.ie` | `jackflix-ctr:5055` | Jellyseerr requests |
| `jackflix.nul.ie` | `jackflix-ctr:8096` | Jellyfin; `/socket` websockets; `/` redirects to `/web/` |
| `toot.nul.ie` | `toot-ctr:80` | Mastodon — **upstream currently disabled**, see [toot](toot.md) |
| `pds.nul.ie` | `toot-ctr:3000` | Bluesky PDS ([toot](toot.md)); websockets |
| `share.nul.ie` | `object-ctr:9090` | Sharry ([object](object.md)); websockets |
| `stuff.nul.ie` | `jackflix-ctr:3923` | copyparty |
| `public.nul.ie` (+ alias `p.nul.ie`) | static `/mnt/media/public` | fancyindex file listing; `addSSL` so plain HTTP also works |
| `mc-map.nul.ie` | `simpcraft-oci:8100` | Minecraft map (OCI container on `whale2`) |
| `mc-rail.nul.ie` | `simpcraft-oci:3876` | Minecraft railway map |
| `mc-map-kink.nul.ie` | `kinkcraft-oci:8100` | Minecraft map |
| `speed.nul.ie` | `localhost:8989` | librespeed |
| `librespeed.ams1.int.nul.ie` | `localhost:8989` | librespeed on the internal domain (internal wildcard cert) |
| `md.nul.ie` | `object-ctr:3000` | HedgeDoc; websockets |
| `pb.nul.ie` | `object-ctr:8088` | wastebin |
| `photos.nul.ie` | `jackflix-ctr:2342` | PhotoPrism; websockets |
| `pront.nul.ie` | `stream-hi.h.nul.ie:5000` | OctoPrint on the home network; `/webcam/``:5050`; **SSO** |
| `hass.nul.ie` | `hass-ctr.h.nul.ie:8123` | Home Assistant (home network); websockets |
| `hass-john.nul.ie` | `john-valorant-tun.ams1.int.nul.ie:8123` | remote Home Assistant over the point-to-point tunnel; websockets |
| `minio.nul.ie` | `object-ctr:9001` | MinIO console; `/ws` websockets |
| `s3.nul.ie` (+ `*.s3.nul.ie`) | `object-ctr:9000` | MinIO S3 API (virtual-host style via the `*.s3` wildcard cert); `/gitea/packages/` has a hack forcing the correct `Content-Type` for Docker image manifests |
| `nix-cache.nul.ie` | `object-ctr:5000` | Harmonia Nix binary cache; `.narinfo`/`nar/`/`serve/` paths get immutable `Cache-Control`/`Expires` headers |
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/middleman/default.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/default.nix) — container definition: nginx, ACME, nginx-sso, librespeed, secrets
- [`nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/vhosts.nix) — all virtual hosts, the SSO include helpers, and the `.well-known` tree
- [`nixos/boxes/colony/vms/shill/containers/middleman/default.html`](../../../../../nixos/boxes/colony/vms/shill/containers/middleman/default.html) — default vhost landing page
@@ -0,0 +1,43 @@
# object
Object storage and the Nix binary cache, plus a few small self-hosted web apps (Sharry,
HedgeDoc, wastebin).
- **Source:** [`shill/containers/object.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/object.nix)
- **Host:** NixOS container on [`shill`](../../shill.md) (bind-mounts `/mnt/minio` and
`/mnt/nix-cache` read-write)
## Role
| Service | Port | Purpose |
| --- | --- | --- |
| MinIO | `9000` (S3) / `9001` (console) | S3-compatible object storage, `s3.nul.ie` + `*.s3.nul.ie` (virtual-host style via `MINIO_DOMAIN`), console at `minio.nul.ie`; region `eu-central-1`; data on the `/mnt/minio` XFS volume |
| Harmonia | `5000` | Nix binary cache at `nix-cache.nul.ie``harmonia-dev` cache serves `shill`'s `/nix/store` out of a dedicated store view rooted at `/var/lib/harmonia` (bind-mounted from `/mnt/nix-cache`), signed with the `nix-cache.key` secret; a `harmonia` user with authorized keys exists for cache pushes; priority 30 |
| Sharry | `9090` | file sharing at `share.nul.ie`; Postgres on [colony-psql](colony-psql.md), files stored in the `share` MinIO bucket; fixed `dev` account + invite signup; mail via `mail.nul.ie`; max share size 128G |
| HedgeDoc | `3000` | collaborative markdown notes at `md.nul.ie`; Postgres on [colony-psql](colony-psql.md); anonymous edits but no anonymous notes, email login, no open email registration |
| wastebin | `8088` | pastebin at `pb.nul.ie` |
| atticd | `8069` | **currently disabled** (`services.atticd.enable = false`) — an alternative Nix cache that would store locally and sit behind `nix-cache.nul.ie`; config (including the `object/atticd.env` secret) is kept around |
Everything public is fronted by [middleman](middleman.md) (see its vhost table). The
`minio-client` is installed and the user's `~/.mc/config.json` points at an age-secret config.
`minio-2025-10-15T17-29-55Z` is allowlisted via `permittedInsecurePackages` (flagged as a TODO).
## Network assignments
<!-- assignments: object -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| object-ctr | internal | `10.100.2.7/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::7/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
## Backing services
- [colony-psql](colony-psql.md) — Sharry and HedgeDoc databases (atticd too, when enabled).
- MinIO buckets back other boxes' services: Gitea LFS/packages (with the `middleman` MIME hack
for Docker manifests), Mastodon's `mastodon` bucket and the Bluesky PDS `pds` bucket on
[toot](toot.md), and Sharry's `share` bucket.
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/object.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/object.nix) — container definition and all services
@@ -0,0 +1,39 @@
# qclk
A WireGuard management appliance for the `qclk` network — it terminates the `management`
tunnel and routes/NATs the `qclk` prefix. No service daemon is currently defined in the config;
the container provides the network plumbing and opens the API port.
- **Source:** [`shill/containers/qclk/`](../../../../../nixos/boxes/colony/vms/shill/containers/qclk)
(`default.nix`)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **WireGuard `management` interface** — listens on UDP `51821` (`lib.my.c.colony.qclk.wgPort`,
allowed through the firewall; `estuary` port-forwards it here) with the private key from the
`qclk/wg.key` age secret. Managed devices are static peers, each pinned to its own address in
the `qclk` prefix (`10.100.4.0/24`); the peer list currently has a single entry (host 2).
- **Routing/NAT** — the container itself is host 1 of the `qclk` prefix. `shill` routes
`10.100.4.0/24` to this container, and outbound traffic from `host0` into `management` is
SNATed to the container's `qclk` address. Forwarding into `management` is accepted from the
AS211024 trusted IPv4 ranges (`lib.my.c.as211024.trusted.v4`).
- **API port** — TCP `8080` is accepted on the `management` interface (`apiPort`), but note
`services = { }`: whatever serves the qclk API is not defined in this configuration today.
## Network assignments
<!-- assignments: qclk -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| qclk-ctr | internal | `10.100.2.10/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::a/64` | ams1.int.nul.ie | |
| qclk | qclk | `10.100.4.1/24` | — | — | |
<!-- assignments-end -->
Two assignments: `internal` on the `ctrs` network like the other containers, and `qclk` — host 1
of the `qclk` prefix on the `management` WireGuard interface (IPv4 only, no DNS name).
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/qclk/default.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/qclk/default.nix) — container definition: WireGuard netdev, peer list and firewall rules
@@ -0,0 +1,48 @@
# toot
Federated-social container. Despite the name, the only service actually running is a **Bluesky
PDS** — the Mastodon instance ("toots") is **currently disabled**.
- **Source:** [`shill/containers/toot.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/toot.nix)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **bluesky-pds** — the active service. `pds.nul.ie` on port `3000`, fronted by
[middleman](middleman.md) (which also redirects `/.well-known/atproto-did` here). Invites
required; blob store is the `pds` bucket on [object](object.md)'s MinIO (`s3.nul.ie`,
`eu-central-1`), upload limit 50 MiB; federation settings point at the stock Bluesky
infrastructure (`plc.directory`, `api.bsky.app`, `mod.bsky.app`, `bsky.network` crawlers).
Secrets (including the S3 credentials) come from the `toot/pds.env` age secret. Email from
`pds@nul.ie`.
- **mastodon** — **disabled** (`services.mastodon.enable = false`). The full config is still
present: `LOCAL_DOMAIN = nul.ie` with `WEB_DOMAIN = toot.nul.ie`, Postgres on
[colony-psql](colony-psql.md), local Redis, SMTP via `mail.nul.ie`, media in the `mastodon`
MinIO bucket (`S3_ALIAS_HOST = mastodon.s3.nul.ie`), 4 streaming processes, and media
auto-cleanup after 30 days. [middleman](middleman.md) still proxies `toot.nul.ie`
`toot-ctr:80` and redirects the `webfinger`/`nodeinfo`/`host-meta` well-knowns there, but with
the service off those endpoints are dead. The config notes the removed `otpSecretFile` option
would need addressing before Mastodon can come back.
## Network assignments
<!-- assignments: toot -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| toot-ctr | internal | `10.100.2.8/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::8/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
The firewall allows `http` (the Mastodon nginx vhost) and the PDS port `3000` besides netdata.
## Notes
- The local nginx still carries the Mastodon virtual host (`toot.nul.ie`) with proxy-header
overrides for being behind `middleman` — part of the preserved-but-disabled Mastodon setup.
- `mastodon-init-dirs` appends the S3 secret key to Mastodon's `.secrets_env` (the module has no
option for a secret-key file), and `mastodon-init-db` waits for `colony-psql` — moot while
Mastodon is disabled.
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/toot.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/toot.nix) — container definition; active PDS config and the preserved (disabled) Mastodon config
@@ -0,0 +1,37 @@
# vaultwarden
[Vaultwarden](https://github.com/dani-garcia/vaultwarden), a Bitwarden-compatible password
manager, published as `pass.nul.ie` through [middleman](middleman.md).
- **Source:** [`shill/containers/vaultwarden.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/vaultwarden.nix)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **vaultwarden** — HTTP on `[::]:8080`, WebSocket notifications on `3012` (both allowed through
the firewall). Web vault enabled, signups disabled, Bitwarden push notifications enabled
(`PUSH_ENABLED`). `DOMAIN` is `https://pass.nul.ie`.
- **SMTP** via `mail.nul.ie:587` (STARTTLS) as `pass@nul.ie`; credentials and other sensitive
settings come from the `vaultwarden/config.env` age secret.
- **Backups** — a `borgbackup` job pushes `/var/lib/vaultwarden` to rsync.net
(`zh2855@zh2855.rsync.net:borg/vaultwarden2`), repokey-encrypted (passphrase and SSH key from
secrets), `zstd,10` compression, keeping 7 daily / 4 weekly / all monthly archives.
## Network assignments
<!-- assignments: vaultwarden -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| vaultwarden-ctr | internal | `10.100.2.3/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::3/64` | ams1.int.nul.ie | |
<!-- assignments-end -->
## Persistence
`/var/lib/vaultwarden` is persisted through `my.tmproot.persistence` — like the other
`shill` containers the root is ephemeral and real state lives under `/persist` (bind-mounted
from the host's `/persist/containers/vaultwarden`).
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/vaultwarden.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/vaultwarden.nix) — container definition, service config and the borgbackup job
@@ -0,0 +1,40 @@
# waffletail
The colony Tailscale node: a subnet router and exit node that advertises the colony prefixes
into the tailnet.
- **Source:** [`shill/containers/waffletail.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/waffletail.nix)
- **Host:** NixOS container on [`shill`](../../shill.md)
## Role
- **tailscale** — joins via an auth key from secrets (`tailscale-auth.key`) against the
self-hosted Headscale control plane at `hs.nul.ie`. Runs with `--netfilter-mode=off` (firewall
is managed by the repo's own nftables rules), `--advertise-exit-node`, and
`--advertise-routes` covering the whole colony — `10.100.0.0/16` and `2a0e:97c0:4d2:10::/60`.
Does **not** accept routes itself. Listens on UDP `41641` (`openFirewall`), which `estuary`
port-forwards to this container.
- `shill` routes the Tailscale prefixes (`100.64.0.0/10`, `fd7a:115c:a1e0::/48`) to this
container, so colony hosts can reach tailnet clients and vice versa.
- nftables: `tailscale0` is a trusted interface; forwarding from `host0` into Tailscale is
allowed for the colony source ranges, and tailnet-sourced traffic leaving via `host0` is
SNATed to the container's colony addresses (except when destined to the colony ranges
themselves).
## Network assignments
<!-- assignments: waffletail -->
<!-- assignments-start -->
| Name | Assignment | IPv4 | IPv6 | Domain | Notes |
|---|---|---|---|---|---|
| waffletail-ctr | internal | `10.100.2.9/24 gw 10.100.2.1` | `2a0e:97c0:4d2:12::9/64` | ams1.int.nul.ie | |
| waffletail | tailscale | `100.64.0.5/32` | `fd7a:115c:a1e0::5/128` | — | |
<!-- assignments-end -->
Two assignments: `internal` on the `ctrs` network like the other containers, and `tailscale`
its addresses on the tailnet itself (host 5 of `100.64.0.0/10` and `fd7a:115c:a1e0::/48`; no
DNS name).
## Notable config files
- [`nixos/boxes/colony/vms/shill/containers/waffletail.nix`](../../../../../nixos/boxes/colony/vms/shill/containers/waffletail.nix) — container definition, Tailscale setup and forward/NAT rules