Compare commits

..

No commits in common. "d623a01ebd50d227f1297acb129f852993cd111d" and "da37a2dce328ab67ca31695dd1ae80102026f62d" have entirely different histories.

16 changed files with 85 additions and 167 deletions

2
.gitignore vendored
View file

@ -1,2 +0,0 @@
result
result-*

View file

@ -1,14 +1,14 @@
keys:
- &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local)
- &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t
- &cryodev-main_key age1y6hushuapy0k04mrvvpev0t8lq44w904r596jus44nhkflky0yhqgq2xx6
creation_rules:
- path_regex: hosts/cryodev-main/secrets.yaml$
key_groups:
- age:
- *steffen_key
- *admin_key
- *cryodev-main_key
- path_regex: hosts/cryodev-pi/secrets.yaml$
key_groups:
- age:
- *steffen_key
# - *cryodev-pi_key # Add after Pi installation
- *admin_key
# - *pi_key # Add pi key here once obtained

View file

@ -10,8 +10,6 @@ Required DNS records for the cryodev infrastructure.
|----------|------|-------|---------|
| `@` | A | `<SERVER_IP>` | Main server |
| `@` | AAAA | `<SERVER_IPV6>` | Main server (IPv6) |
| `www` | A | `<SERVER_IP>` | www redirect |
| `www` | AAAA | `<SERVER_IPV6>` | www redirect (IPv6) |
| `mail` | A | `<SERVER_IP>` | Mail server |
| `mail` | AAAA | `<SERVER_IPV6>` | Mail server (IPv6) |
@ -31,32 +29,7 @@ Required DNS records for the cryodev infrastructure.
| `@` | MX | `10 mail.cryodev.xyz.` | Mail delivery |
| `@` | TXT | `"v=spf1 mx ~all"` | SPF |
| `_dmarc` | TXT | `"v=DMARC1; p=none"` | DMARC |
| `mail._domainkey` | TXT | *(siehe unten)* | DKIM |
### Reverse DNS (PTR)
Fuer zuverlaessige Mail-Zustellung muss ein **PTR Record** beim Hosting-Provider
konfiguriert werden (nicht im DNS-Panel der Domain):
| IP | PTR Value |
|----|-----------|
| `<SERVER_IP>` | `mail.cryodev.xyz` |
| `<SERVER_IPV6>` | `mail.cryodev.xyz` |
#### Hetzner Robot (Dedicated Server)
1. [robot.hetzner.com](https://robot.hetzner.com) > **Server** > Server auswaehlen
2. **IPs** Tab
3. Bei der IPv4-Adresse auf das **Stift-Symbol** klicken
4. `mail.cryodev.xyz` eintragen und speichern
5. Fuer IPv6: Unter **Subnets** dasselbe fuer die primaere IPv6-Adresse
#### Hetzner Cloud
1. [cloud.hetzner.com](https://cloud.hetzner.com) > Server auswaehlen
2. **Networking** Tab
3. Bei "Primary IP" auf die IP klicken > **Reverse DNS**
4. `mail.cryodev.xyz` eintragen (fuer IPv4 und IPv6)
| `mail._domainkey` | TXT | `"v=DKIM1; k=rsa; p=..."` | DKIM |
## Getting the DKIM Key
@ -68,18 +41,6 @@ sudo cat /var/dkim/cryodev.xyz.mail.txt
Add this as a TXT record for `mail._domainkey.cryodev.xyz`.
## Complete Checklist
- [ ] A/AAAA fuer `@` (Root-Domain)
- [ ] A/AAAA fuer `www`
- [ ] A/AAAA fuer `mail`
- [ ] CNAME fuer `git`, `headscale`, `headplane`, `netdata`
- [ ] MX Record
- [ ] TXT fuer SPF (`v=spf1 mx ~all`)
- [ ] TXT fuer DMARC (`v=DMARC1; p=none`)
- [ ] TXT fuer DKIM (`mail._domainkey` -- nach erstem Deploy)
- [ ] PTR Record beim Hosting-Provider (Reverse DNS)
## Verification
### Check DNS Propagation
@ -99,9 +60,6 @@ dig TXT mail._domainkey.cryodev.xyz
# DMARC
dig TXT _dmarc.cryodev.xyz
# Reverse DNS
dig -x <SERVER_IP>
```
### Online Tools
@ -126,7 +84,7 @@ Ensure these ports are open on `cryodev-main`:
| Port | Protocol | Service |
|------|----------|---------|
| 2299 | TCP | SSH |
| 22 | TCP | SSH |
| 80 | TCP | HTTP (ACME/redirect) |
| 443 | TCP | HTTPS |
| 25 | TCP | SMTP |

View file

@ -166,14 +166,14 @@ Auf dem **Entwicklungsrechner** den neuen Host-Key in `.sops.yaml` eintragen:
```yaml
keys:
- &steffen_key age1e8p... # steffen (lokal)
- &admin_key age1e8p... # Dein lokaler Admin-Key
- &hostname_key age1abc... # Key von Schritt 3.1
creation_rules:
- path_regex: hosts/<hostname>/secrets.yaml$
key_groups:
- age:
- *steffen_key
- *admin_key
- *hostname_key
```
@ -204,8 +204,8 @@ Diese Secrets koennen erst nach Schritt 4 erstellt werden. **Jetzt noch nicht ei
| Secret | Befehl | Voraussetzung |
|--------|--------|---------------|
| `tailscale/auth-key` | Siehe Schritt 4.1-4.2 | Headscale laeuft |
| `headplane/agent_pre_authkey` | Siehe Schritt 4.1-4.2 | Headscale laeuft |
| `tailscale/auth-key` | `sudo headscale preauthkeys create --expiration 99y --reusable --user default` | Headscale laeuft |
| `headplane/agent_pre_authkey` | `sudo headscale users create headplane-agent && sudo headscale preauthkeys create --expiration 99y --user headplane-agent` | Headscale laeuft |
| `forgejo-runner/token` | Forgejo Admin Panel > Actions > Runners > Create Runner | Forgejo laeuft |
#### Beispiel secrets.yaml (Klartext vor Verschluesselung)
@ -243,13 +243,30 @@ Services **ohne externe Abhaengigkeiten** aktivieren:
./sops.nix
# Stufe 2: Erst nach Schritt 4 aktivieren
# ./forgejo-runner.nix # braucht: forgejo-runner/token (Forgejo)
# ./headplane.nix # braucht: headplane/agent_pre_authkey (Headscale)
# ./tailscale.nix # braucht: tailscale/auth-key (Headscale)
];
}
```
Ebenso in `hosts/<hostname>/services/sops.nix` die Secrets-Definitionen wieder
einkommentieren, **aber nur die fuer Stufe-1-Services**:
```nix
sops = {
defaultSopsFile = ../secrets.yaml;
secrets = {
# "forgejo-runner/token" = { }; # Stufe 2
"tailscale/auth-key" = { };
};
};
```
> **Hinweis:** `tailscale/auth-key` muss in `sops.nix` definiert bleiben, da das
> Tailscale-Modul es referenziert. Es wird aber erst in Schritt 4 mit einem
> echten Wert befuellt. Solange Tailscale nicht importiert ist, hat das keinen
> Effekt.
### 3.5 Deployen (Stufe 1)
```bash
@ -265,26 +282,6 @@ NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .#<hostname> \
Nach diesem Deploy laufen Headscale, Forgejo, Mailserver und Nginx.
### 3.6 Forgejo Admin-Account erstellen
Beim ersten Start hat Forgejo noch keine Benutzer. Admin-Account per CLI anlegen
(auf dem **Server**):
```bash
forgejo admin user create \
--username <benutzername> \
--email <email>@<domain> \
--password <passwort> \
--admin
```
> **Hinweis:** Das `forgejo` Shell-Alias wird vom Modul bereitgestellt und fuehrt
> automatisch den Befehl als `forgejo`-User mit der richtigen Config aus.
> Falls der Alias nicht verfuegbar ist, neue Shell starten (`bash` oder `zsh`).
>
> Da `DISABLE_REGISTRATION = true` gesetzt ist, koennen neue Accounts
> nur per CLI erstellt werden.
## Schritt 4: Restliche Secrets generieren und alle Services aktivieren
Nachdem der Server mit Headscale und Forgejo laeuft:
@ -296,28 +293,20 @@ Nachdem der Server mit Headscale und Forgejo laeuft:
sudo headscale users create headplane-agent
```
2. **User-IDs ermitteln** (wird fuer die Preauth-Keys benoetigt):
2. **Preauth-Keys generieren**:
```bash
sudo headscale users list
# Fuer Tailscale
sudo headscale preauthkeys create --expiration 99y --reusable --user default
# Fuer Headplane Agent
sudo headscale preauthkeys create --expiration 99y --user headplane-agent
```
Die Ausgabe zeigt die numerischen IDs (z.B. `1` fuer default, `2` fuer headplane-agent).
3. **Preauth-Keys generieren** (mit den IDs aus Schritt 2):
```bash
# Fuer Tailscale (User-ID von "default" einsetzen)
sudo headscale preauthkeys create --expiration 99y --reusable --user <ID>
# Fuer Headplane Agent (User-ID von "headplane-agent" einsetzen)
sudo headscale preauthkeys create --expiration 99y --user <ID>
```
4. **Forgejo-Runner-Token** ueber das Forgejo Admin Panel erstellen:
3. **Forgejo-Runner-Token** ueber das Forgejo Admin Panel erstellen:
Administration > Actions > Runners > Create new Runner
5. **Secrets ergaenzen**:
4. **Secrets ergaenzen**:
```bash
sops hosts/<hostname>/secrets.yaml
@ -334,13 +323,12 @@ Nachdem der Server mit Headscale und Forgejo laeuft:
agent_pre_authkey: "..."
```
6. **Stufe-2-Services aktivieren** in `hosts/<hostname>/services/default.nix`:
5. **Stufe-2-Services aktivieren** in `hosts/<hostname>/services/default.nix`:
```nix
{
imports = [
./forgejo.nix
./forgejo-runner.nix
./headplane.nix
./headscale.nix
./mailserver.nix
@ -353,6 +341,8 @@ Nachdem der Server mit Headscale und Forgejo laeuft:
}
```
Und in `sops.nix` auch `forgejo-runner/token` einkommentieren.
6. **Erneut deployen**:
```bash

View file

@ -36,10 +36,7 @@ Diese Anleitung beschreibt das Hinzufügen eines **neuen Raspberry Pi Clients**
**Auf cryodev-main** (per SSH):
```bash
# User-ID ermitteln
sudo headscale users list
# Preauth-Key erstellen (User-ID von "default" einsetzen)
sudo headscale preauthkeys create --expiration 99y --reusable --user <ID>
sudo headscale preauthkeys create --expiration 99y --reusable --user default
```
**Ausgabe notieren!** (z.B. `tskey-preauth-abc123...`)
@ -198,7 +195,7 @@ Auf dem Entwicklungsrechner:
```yaml
keys:
- &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local)
- &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t
- &neuer_pi_key age1xyz... # Der neue Key
creation_rules:
@ -207,7 +204,7 @@ creation_rules:
- path_regex: hosts/neuer-pi/secrets.yaml$
key_groups:
- age:
- *steffen_key
- *admin_key
- *neuer_pi_key
```

View file

@ -25,10 +25,9 @@ nix-shell -p openssl --run 'openssl rand -hex 16'
```bash
# First, create a dedicated user
sudo headscale users create headplane-agent
# Find the user ID
sudo headscale users list
# Then create a reusable pre-auth key (use the ID of headplane-agent)
sudo headscale preauthkeys create --expiration 99y --reusable --user <ID>
# Then create a reusable pre-auth key
sudo headscale preauthkeys create --expiration 99y --reusable --user headplane-agent
```
### Add to Secrets
@ -102,7 +101,7 @@ sudo journalctl -u headplane -f
Verify the agent pre-auth key is valid:
```bash
sudo headscale preauthkeys list --user <ID>
sudo headscale preauthkeys list --user headplane-agent
```
If expired, create a new one and update the secrets file.

View file

@ -31,7 +31,7 @@ Add the host key to `.sops.yaml`:
```yaml
keys:
- &steffen_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t # steffen (local)
- &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t
- &main_key age1... # cryodev-main
- &pi_key age1... # cryodev-pi
@ -39,13 +39,13 @@ creation_rules:
- path_regex: hosts/cryodev-main/secrets.yaml$
key_groups:
- age:
- *steffen_key
- *admin_key
- *main_key
- path_regex: hosts/cryodev-pi/secrets.yaml$
key_groups:
- age:
- *steffen_key
- *admin_key
- *pi_key
```
@ -153,7 +153,7 @@ netdata:
| Mailserver password | `nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'` |
| Random hex token | `nix-shell -p openssl --run 'openssl rand -hex 16'` |
| UUID | `uuidgen` |
| Tailscale preauth | `sudo headscale preauthkeys create --expiration 99y --reusable --user <ID>` |
| Tailscale preauth | `sudo headscale preauthkeys create --expiration 99y --reusable --user default` |
## Updating Keys

View file

@ -14,10 +14,7 @@ Tailscale clients connect to the self-hosted Headscale server to join the mesh V
On the Headscale server (cryodev-main):
```bash
# User-ID ermitteln
sudo headscale users list
# Preauth-Key erstellen (User-ID von "default" einsetzen)
sudo headscale preauthkeys create --expiration 99y --reusable --user <ID>
sudo headscale preauthkeys create --expiration 99y --reusable --user default
```
### Add to Secrets
@ -114,7 +111,7 @@ Check the auth key is valid:
```bash
# On Headscale server
sudo headscale preauthkeys list --user <ID>
sudo headscale preauthkeys list --user default
```
Verify the login server URL is correct in the client configuration.

View file

@ -1,10 +1,10 @@
tailscale:
auth-key: ENC[AES256_GCM,data:v5C3DqYJsDKq6oUa/3G6WKxyKeIK4EJLNxWMbKjSbwe5MPtS4sZjFszMviKcEVGW,iv:4G8irABGuVhOYnK15EjbpNQ4B9VY/NdwCrfz+YAMzvA=,tag:0Vhq/TJgx+48frRy30yKFg==,type:str]
auth-key: ENC[AES256_GCM,data:APMZrLYEqywYTmc=,iv:KiFwgR3UXLXCdl9DlR5tJOr8XUyQEeDomPx9hOREhnw=,tag:32quLtu74EIxAgmjH3hvIw==,type:str]
forgejo-runner:
token: ENC[AES256_GCM,data:sdnJcyRiTLxXoZDNbEzJAjpiK+iSUH0gV0XwbEQf94IE/6IZz5/zHw==,iv:py+qqp3VAwBGEpYiQwft3jnQS943JaBlrcckColv4f8=,tag:rtmRwW8rpXB6Pv+LSkp+Fw==,type:str]
token: ENC[AES256_GCM,data:/i9KVMeEXYwQnn0=,iv:pILMNbhDviifDUFRINi6n9dtGSAeqxKMdBgjYwtXXEM=,tag:JCj5v5BZdZteo0MdTVKREw==,type:str]
headplane:
cookie_secret: ENC[AES256_GCM,data:HICF31i6yCLZGNeOFYTR3Bp0a7i0UKOvGAvx/pD3NB4=,iv:ZtK8r1YUWnf5Af0Ls341k0w1mZm+D5Rb0E1uS5z/Gdo=,tag:vwM9+4dpcmnjn/wR6Ty/MQ==,type:str]
agent_pre_authkey: ENC[AES256_GCM,data:QvhPi2lhyP7w6HTeOSS8660NzIY9Q6AOhlOGQXnvz+qYu9vOAMQPOFMZfie5+e8g,iv:X60wVOEUIsTiMHrrd4lId0VpR7VfFDr74p8RGka3+18=,tag:kIvaHrOWIM+VQ+Qz1GiheQ==,type:str]
agent_pre_authkey: ENC[AES256_GCM,data:aYkPZTR4fwArcKQ=,iv:+OhbIpwsyCJ4i4k8eyCKYAHE25F4iUHfdM+CG0+BQd8=,tag:BkT73WPjOv5Lu6dCFBXxWg==,type:str]
mailserver:
accounts:
admin: ENC[AES256_GCM,data:gY2k3x3sA98yGNLcSWUr9aC0566MJM2UXhwLtWPUL3PRvxQt0XOzjeiC7ddgbqTAol4dBNeaV0zbFInD,iv:rxp0M9kHMgD73K+RDC562sUpXaJ067eU1CeciAke+LM=,tag:VKobduo/ZULAk17M9LD3bw==,type:str]
@ -31,7 +31,7 @@ sops:
MEpGbGlQbVRsM1NxN1JxY2J1MVNTTE0KuIvuM2c1VIXKv0LGLb0NwqtSyBYcRcb1
uiIjNV0UzEt/WvnCeUTMPgIXBHk6jWcaKe13v6MHeha+/CVZ9Su/Lw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-14T11:38:57Z"
mac: ENC[AES256_GCM,data:gmxyp3XaHeU/CT2lgo14wIbJsKs/JrZmUPhgHwo1XRN5Sf/Su6lHOpVlQS1M6R3+ZlBnS/oEur+y0gydCCqhJK1C3Y5YuUfPlOWOeQWMVxQBqxWkyemvz5KgGseDc9nG09FpoGEYa4sSeuD1J6vRsGcZiOStaA6s8NICWivdWcQ=,iv:cYILLrScr7cFiLx5INbc9z3BT7LaCjLnCH0wdn3lZ1k=,tag:IIRb/Tu8YqWNiHXH7CSOfQ==,type:str]
lastmodified: "2026-03-14T10:28:25Z"
mac: ENC[AES256_GCM,data:oeT8I9gMIAPnm8wlNUFjn/0UT6qfTA//fLp3USO33FMsNIOWmqt3kB4NsozS+n6ZeMxBVWQZPss8t819DYqv0xQarzfOqQe1idCGCB+7NBFcFP2VLFzkIH+9Wei9AJSlR3BRnzyVaQDi797P6pEXFn/IoQWPWZ8sX8ZKugOfY0w=,iv:RjsKhPcVZBHHLs1W3PDhcseGLV4eawafg0is6KrzhtE=,tag:ifkobUteslEZ78OvkZw8JQ==,type:str]
unencrypted_suffix: _unencrypted
version: 3.11.0

View file

@ -1,17 +1,13 @@
{
imports = [
# Stufe 1: Services ohne externe Abhaengigkeiten
./forgejo.nix
./headplane.nix
./headscale.nix
./mailserver.nix
./netdata.nix
./nginx.nix
./openssh.nix
./sops.nix
# Stufe 2: Erst aktivieren wenn Headscale/Forgejo laufen und echte Secrets existieren
./forgejo-runner.nix # braucht: forgejo-runner/token (Forgejo)
./headplane.nix # braucht: headplane/agent_pre_authkey (Headscale)
./tailscale.nix # braucht: tailscale/auth-key (Headscale)
./tailscale.nix
];
}

View file

@ -1,28 +0,0 @@
{
config,
outputs,
constants,
...
}:
{
imports = [
outputs.nixosModules.forgejo-runner
];
services.forgejo-runner = {
enable = true;
url = "http://127.0.0.1:${toString constants.services.forgejo.port}";
tokenFile = config.sops.templates."forgejo-runner-token".path;
};
sops.secrets."forgejo-runner/token" = {
mode = "0400";
};
sops.templates."forgejo-runner-token" = {
content = ''
TOKEN=${config.sops.placeholder."forgejo-runner/token"}
'';
};
}

View file

@ -8,6 +8,7 @@
{
imports = [
outputs.nixosModules.forgejo
outputs.nixosModules.forgejo-runner
];
services.forgejo = {
@ -31,6 +32,17 @@
};
};
services.forgejo-runner = {
enable = true;
url = "https://${constants.services.forgejo.fqdn}";
tokenFile = config.sops.secrets."forgejo-runner/token".path;
};
sops.secrets."forgejo-runner/token" = {
# gitea-runner user is created by gitea-actions-runner service
mode = "0400";
};
services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = {
forceSSL = true;
enableACME = true;

View file

@ -16,7 +16,6 @@
headscale = {
url = "http://127.0.0.1:${toString constants.services.headscale.port}";
public_url = "https://${constants.services.headscale.fqdn}";
config_strict = false;
};
};
};

View file

@ -13,9 +13,9 @@
sops = {
defaultSopsFile = ../secrets.yaml;
# age.keyFile is not set, sops-nix defaults to using /etc/ssh/ssh_host_ed25519_key
# Secrets fuer Stufe-2-Services werden in deren eigenen Dateien definiert:
# forgejo-runner/token -> forgejo-runner.nix
# tailscale/auth-key -> tailscale.nix (via Modul)
secrets = {
"forgejo-runner/token" = { };
"tailscale/auth-key" = { };
};
};
}

View file

@ -62,7 +62,6 @@ in
acceptTerms = true;
defaults.email = mkDefault "postmaster@${config.networking.domain}";
defaults.webroot = mkDefault "/var/lib/acme/acme-challenge";
defaults.group = mkDefault "nginx";
};
security.dhparams = mkIf cfg.forceSSL {

1
result Symbolic link
View file

@ -0,0 +1 @@
/nix/store/xmcpz8rawfcbzr528rlnm5v0fmnrd8dj-nixos-system-cryodev-main-25.11.20260309.44bae27