aboutsummaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md33
1 files changed, 16 insertions, 17 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index e18f301..6817d40 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -18,20 +18,14 @@ Check the flake evaluates without deploying:
nix flake check
```
-Deploy a change (from an x86_64-linux machine):
+Deploy a change:
```bash
-nixos-rebuild switch --flake .#eurovm \
- --target-host admin@<server-ipv4> \
- --elevate=sudo
-```
-
-Deploy from a non-x86_64-linux machine (e.g. Apple Silicon) — the server builds its own closure:
-```bash
-nixos-rebuild switch --flake .#eurovm \
- --target-host admin@<server-ipv4> \
- --build-host admin@<server-ipv4> \
- --elevate=sudo
+./deploy.sh
```
+`deploy.sh` reads `SERVER_IP` from a git-ignored `.env` file (copy `.env.example` to start) —
+the server's Tailscale IP — and runs `nixos-rebuild switch --flake .#eurovm` against it with
+both `--target-host` and `--build-host` set to that address, so the server always builds its
+own closure regardless of the local machine's architecture.
First-time provisioning of a fresh VM (partitions the disk via disko, installs NixOS):
```bash
@@ -71,8 +65,9 @@ service module:
**Service modules (`modules/`)** are one file per concern, each self-contained and imported by
`hosts/eurovm/default.nix`:
-- `common.nix` — SSH hardening (key-only, no root login), firewall (22/80/443/53 only), timezone,
- nix settings (flakes, auto-gc, auto-optimise-store), base packages, disabled autoUpgrade.
+- `common.nix` — SSH hardening (key-only, no root login), firewall (22/80/443/853/8443 only —
+ plain DNS on 53 is deliberately closed, see `adguard.nix`), timezone, nix settings (flakes,
+ auto-gc, auto-optimise-store), base packages, disabled autoUpgrade.
- `sops.nix` — sops-nix wiring. The host's own age key is derived at boot from
`/etc/ssh/ssh_host_ed25519_key`, so no separate host key material needs to be provisioned.
Declares which secrets exist, their owning user/group, and file mode.
@@ -88,9 +83,13 @@ service module:
8080, 8086) are bound to `127.0.0.1` and never exposed directly — Caddy is the public HTTP(S)
entry point for everything except AdGuard's encrypted-DNS ports, and handles Let's Encrypt
automatically for its own certs.
-- `adguard.nix` — AdGuard Home; plain DNS on `0.0.0.0:53`, DNS-over-TLS on `0.0.0.0:853`,
- DNS-over-HTTPS on `0.0.0.0:8443` (path `/dns-query`), web UI on `0.0.0.0:3000` (port 3000 is
- never opened in the firewall, so it's still effectively internal-only, reached via Caddy).
+- `adguard.nix` — AdGuard Home; plain DNS (`dns.port`) is set to `0` and deliberately not opened
+ in the firewall — CERT-Bund/BSI flagged this host as an open DNS resolver abusable for UDP
+ reflection/amplification DDoS (spoofed source IP, no handshake needed), so plain DNS is
+ disabled entirely and DNS-over-TLS (`0.0.0.0:853`) / DNS-over-HTTPS (`0.0.0.0:8443`, path
+ `/dns-query`) — both TLS-handshake-based and therefore not spoofable the same way — are the
+ only supported client paths. Web UI on `0.0.0.0:3000` (port 3000 is never opened in the
+ firewall, so it's still effectively internal-only, reached via Caddy).
`http.address` binds `0.0.0.0` rather than `127.0.0.1` specifically because AdGuard's
HTTPS/DoH listener reuses that same bind host internally (confirmed from AdGuard's source) —
narrowing it back to loopback would silently make DoH unreachable. TLS for DoH/DoT uses the