From cf0d588291707ec82edd78d7244379552b87302d Mon Sep 17 00:00:00 2001 From: Brandon Schneider Date: Wed, 20 May 2026 18:00:11 -0500 Subject: [PATCH] infra(vps): deploy Authentik, credential vault, age-sops, PQC, ARR plan - LXC 100: Authentik 2025.4 with PostgreSQL 16 + Redis - LXC 100: Credential vault HTTP service on port 9100 (PG-backed) - VPS: Caddy reverse proxy with Let's Encrypt DNS-01 for researchstack.info, auth.researchstack.info, vault.researchstack.info - VPS: Post-quantum SSH (mlkem768x25519-sha256) in sshd_config - Repo: age-sops secret management enabled - Encrypt: .env, restic.env, credentials.json, appflowy.env, tailscale-auth.key, porkbun.env, API KEYS/*.txt - Add .sops.yaml at repo root and update k3s-flake/.sops.yaml - Porkbun DNS: update A records to 46.232.249.226, add vault/auth subdomains - Tailscale: auth key added to credential server (RackNerd microVM) - Docs: PQC posture, VPS status, Authentik setup guide, ARR stack plan Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .sops.yaml | 38 +++ 4-Infrastructure/docs/arr-stack-plan.md | 269 ++++++++++++++++++++ 4-Infrastructure/docs/authentik-setup.md | 117 +++++++++ 4-Infrastructure/docs/pqc-posture.md | 57 +++++ 4-Infrastructure/docs/vps-proxmox-status.md | 107 ++++++++ 4-Infrastructure/k3s-flake/.sops.yaml | 2 +- 4-Infrastructure/storage/restic/restic.env | 94 +++---- 7 files changed, 637 insertions(+), 47 deletions(-) create mode 100644 .sops.yaml create mode 100644 4-Infrastructure/docs/arr-stack-plan.md create mode 100644 4-Infrastructure/docs/authentik-setup.md create mode 100644 4-Infrastructure/docs/pqc-posture.md create mode 100644 4-Infrastructure/docs/vps-proxmox-status.md diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 00000000..53c6b44f --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,38 @@ +# SOPS configuration for Research Stack +# Uses age for encryption. +# Post-quantum: standard age (X25519 + ChaCha20-Poly1305) is NOT post-quantum. +# PQC adoption tracked in 4-Infrastructure/docs/pqc-posture.md +# +# Usage: +# sops --encrypt --in-place +# sops # decrypt, edit, re-encrypt +# sops --decrypt # decrypt to stdout + +keys: + - &primary age1fvm02ruga67vnw5wws9p2ycckdmc0gp83m9s6cyld0ctpxyf8gzqy5wwsr + +creation_rules: + - path_regex: 4-Infrastructure/infra/secrets/.* + key_groups: + - age: + - *primary + + - path_regex: 4-Infrastructure/deploy/.*/.*\.env$ + key_groups: + - age: + - *primary + + - path_regex: 4-Infrastructure/storage/restic/.*\.env$ + key_groups: + - age: + - *primary + + - path_regex: API KEYS/.* + key_groups: + - age: + - *primary + + - path_regex: \.env$ + key_groups: + - age: + - *primary diff --git a/4-Infrastructure/docs/arr-stack-plan.md b/4-Infrastructure/docs/arr-stack-plan.md new file mode 100644 index 00000000..fc0290a4 --- /dev/null +++ b/4-Infrastructure/docs/arr-stack-plan.md @@ -0,0 +1,269 @@ +# ARR Stack Deployment Plan — Research Stack + +## Overview + +The *ARR stack is a suite of media management tools for TV, movies, music, books, and subtitles. All services will run in LXC 100 (or a new LXC) behind Authentik + Caddy. + +## Architecture + +``` +User -> Caddy (TLS) -> Authentik (forward auth) -> ARR Service + | + v + LXC 100 Docker Network +``` + +All services connect through the existing `authentik_default` Docker network in LXC 100. + +## Service Matrix + +| Service | Port | Image | Purpose | +|---------|------|-------|---------| +| **Sonarr** | 8989 | `linuxserver/sonarr:latest` | TV series management | +| **Radarr** | 7878 | `linuxserver/radarr:latest` | Movie management | +| **Lidarr** | 8686 | `linuxserver/lidarr:latest` | Music management | +| **Readarr** | 8787 | `linuxserver/readarr:develop` | Ebook/audiobook management | +| **Prowlarr** | 9696 | `linuxserver/prowlarr:latest` | Indexer manager (feeds the *arrs) | +| **Bazarr** | 6767 | `linuxserver/bazarr:latest` | Subtitle management | +| **Jellyfin** (optional) | 8096 | `jellyfin/jellyfin:latest` | Media server (frontend) | + +## Docker Compose Fragment + +Add to a new file `/opt/arr-stack/docker-compose.yml` in LXC 100: + +```yaml +services: + sonarr: + image: linuxserver/sonarr:latest + container_name: sonarr + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - sonarr-config:/config + - /mnt/media/tv:/tv + - /mnt/media/downloads:/downloads + ports: + - "8989:8989" + networks: + - authentik_default + restart: unless-stopped + + radarr: + image: linuxserver/radarr:latest + container_name: radarr + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - radarr-config:/config + - /mnt/media/movies:/movies + - /mnt/media/downloads:/downloads + ports: + - "7878:7878" + networks: + - authentik_default + restart: unless-stopped + + lidarr: + image: linuxserver/lidarr:latest + container_name: lidarr + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - lidarr-config:/config + - /mnt/media/music:/music + - /mnt/media/downloads:/downloads + ports: + - "8686:8686" + networks: + - authentik_default + restart: unless-stopped + + readarr: + image: linuxserver/readarr:develop + container_name: readarr + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - readarr-config:/config + - /mnt/media/books:/books + - /mnt/media/downloads:/downloads + ports: + - "8787:8787" + networks: + - authentik_default + restart: unless-stopped + + prowlarr: + image: linuxserver/prowlarr:latest + container_name: prowlarr + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - prowlarr-config:/config + ports: + - "9696:9696" + networks: + - authentik_default + restart: unless-stopped + + bazarr: + image: linuxserver/bazarr:latest + container_name: bazarr + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - bazarr-config:/config + - /mnt/media/movies:/movies + - /mnt/media/tv:/tv + ports: + - "6767:6767" + networks: + - authentik_default + restart: unless-stopped + + # Optional: Jellyfin media server + jellyfin: + image: jellyfin/jellyfin:latest + container_name: jellyfin + environment: + PUID: 1000 + PGID: 1000 + TZ: America/Chicago + volumes: + - jellyfin-config:/config + - jellyfin-cache:/cache + - /mnt/media:/media:ro + ports: + - "8096:8096" + networks: + - authentik_default + restart: unless-stopped + +volumes: + sonarr-config: + radarr-config: + lidarr-config: + readarr-config: + prowlarr-config: + bazarr-config: + jellyfin-config: + jellyfin-cache: + +networks: + authentik_default: + external: true +``` + +## Storage Layout + +Create on the host (or in LXC 100 with a bind mount): + +``` +/mnt/media/ +├── tv/ +├── movies/ +├── music/ +├── books/ +└── downloads/ +``` + +The Proxmox host has 104GB free on `/dev/vda3`. For a media library, you may want to: +- Add a secondary disk to LXC 100 +- Use the existing Garage S3 mesh for cold storage +- Or keep it minimal given the 120GB VPS constraint + +## Caddy Additions + +Add to `/opt/caddy/Caddyfile` on the VPS: + +```caddy +sonarr.researchstack.info { + tls { dns porkbun { ... } } + forward_auth 192.168.100.100:9000 { + uri /outpost.goauthentik.io/auth/caddy + copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name + } + reverse_proxy 192.168.100.100:8989 +} + +radarr.researchstack.info { + tls { dns porkbun { ... } } + forward_auth 192.168.100.100:9000 { ... } + reverse_proxy 192.168.100.100:7878 +} + +# Repeat for lidarr, readarr, prowlarr, bazarr, jellyfin +``` + +## DNS Records Needed + +Create A records in Porkbun pointing `46.232.249.226`: + +- `sonarr.researchstack.info` +- `radarr.researchstack.info` +- `lidarr.researchstack.info` +- `readarr.researchstack.info` +- `prowlarr.researchstack.info` +- `bazarr.researchstack.info` +- `jellyfin.researchstack.info` (optional) + +## Resource Estimate + +With all services running in LXC 100 alongside Authentik: + +| Service | RAM (typical) | +|---------|--------------| +| Authentik | 512MB | +| PostgreSQL | 256MB | +| Redis | 64MB | +| Vault | 64MB | +| Sonarr | 256MB | +| Radarr | 256MB | +| Lidarr | 256MB | +| Readarr | 256MB | +| Prowlarr | 256MB | +| Bazarr | 256MB | +| Jellyfin | 512MB | +| **Total** | **~3GB** | + +The VPS has 4GB RAM. This is tight but workable if: +- Jellyfin is skipped (use direct file access or a separate media server) +- LXC 100 memory limits are tuned +- swap is available (but BTRFS + swap = bad, use zram instead) + +## Deployment Order + +1. **Storage**: Create `/mnt/media` and set correct permissions +2. **Prowlarr** first (other *arrs need it for indexers) +3. **Sonarr + Radarr** (the core pair) +4. **Bazarr** (needs Sonarr/Radarr for subtitle matching) +5. **Lidarr + Readarr** (optional, lower priority) +6. **Jellyfin** (optional, only if memory allows) +7. **Authentik providers** for each service +8. **Caddy + DNS** for each service + +## Security Notes + +- All ARR services have **no built-in auth** — they rely entirely on Authentik forward auth +- Ensure the Authentik provider mode is set to `Forward domain` with proper URL matching +- Never expose ARR ports directly; always route through Caddy + Authentik +- Disable ARR external access / API keys where possible (let Authentik handle auth) + +## Next Steps + +1. Configure Authentik forward auth (see `authentik-setup.md`) +2. Decide on media storage strategy (local vs Garage S3) +3. Create `docker-compose.yml` in LXC 100 +4. Deploy Prowlarr first, then Sonarr/Radarr diff --git a/4-Infrastructure/docs/authentik-setup.md b/4-Infrastructure/docs/authentik-setup.md new file mode 100644 index 00000000..ce350ef1 --- /dev/null +++ b/4-Infrastructure/docs/authentik-setup.md @@ -0,0 +1,117 @@ +# Authentik Setup Guide — Research Stack + +## Status: 2026-05-21 + +Authentik is deployed in LXC 100 with local PostgreSQL 16 and Redis. + +### Current State + +- **URL**: `https://auth.researchstack.info` (Caddy reverse proxy -> `192.168.100.100:9000`) +- **Default admin**: `akadmin` / `authentik` (CHANGE THIS IMMEDIATELY) +- **Backend**: PostgreSQL 16 in Docker (`authentik-postgresql`) + +### Step 1: First Login & Bootstrap + +1. Open `https://auth.researchstack.info` (use SSH tunnel if netcup ports blocked) +2. Login with `akadmin` / `authentik` +3. Go to **Admin Interface** (top right) +4. Navigate to **Directory -> Users -> akadmin** +5. Change password to a strong unique password +6. (Optional) Add your email for recovery + +### Step 2: Create Forward Auth Provider (for Caddy) + +This protects `vault.researchstack.info` and future ARR services. + +1. In Admin Interface, go to **Applications -> Providers** +2. Click **Create** +3. Select **Proxy Provider** +4. Fill in: + - **Name**: `caddy-forward-auth` + - **Authorization flow**: `default-provider-authorization-implicit-consent` + - **Internal host**: `http://192.168.100.100:9100` (vault service) + - **External host**: `https://vault.researchstack.info` + - **Mode**: `Forward domain` +5. Save + +### Step 3: Create Application + +1. Go to **Applications -> Applications** +2. Click **Create** +3. Fill in: + - **Name**: `Credential Vault` + - **Slug**: `vault` + - **Provider**: `caddy-forward-auth` +4. Save + +### Step 4: Configure Outpost + +1. Go to **Applications -> Outposts** +2. The **authentik Embedded Outpost** is already running +3. Edit it, add `Credential Vault` to the list of applications +4. Save + +The outpost listens on `192.168.100.100:9000` (same as Authentik). + +### Step 5: Update Caddy Forward Auth + +On the VPS, edit `/opt/caddy/Caddyfile`: + +```caddy +vault.researchstack.info { + tls { + dns porkbun { ... } + } + forward_auth 192.168.100.100:9000 { + uri /outpost.goauthentik.io/auth/caddy + copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name + } + reverse_proxy 192.168.100.100:9100 +} +``` + +Then reload: +```bash +/opt/caddy/caddy reload --config /opt/caddy/Caddyfile +``` + +### Step 6: Test + +1. Open `https://vault.researchstack.info` in an incognito window +2. You should be redirected to Authentik login +3. After logging in, you should see the credential vault JSON response + +### Step 7: Add Users + +1. Go to **Directory -> Users** +2. Create users for anyone who needs access +3. (Optional) Add users to **Directory -> Groups** for role-based access + +### Next: ARR Stack Apps + +For each ARR service, repeat Steps 2-5: + +| Service | Internal Host | External Host | Provider Name | +|---------|--------------|---------------|---------------| +| Sonarr | `http://192.168.100.100:8989` | `https://sonarr.researchstack.info` | `sonarr-forward-auth` | +| Radarr | `http://192.168.100.100:7878` | `https://radarr.researchstack.info` | `radarr-forward-auth` | +| Lidarr | `http://192.168.100.100:8686` | `https://lidarr.researchstack.info` | `lidarr-forward-auth` | +| Readarr | `http://192.168.100.100:8787` | `https://readarr.researchstack.info` | `readarr-forward-auth` | +| Prowlarr| `http://192.168.100.100:9696` | `https://prowlarr.researchstack.info` | `prowlarr-forward-auth` | +| Bazarr | `http://192.168.100.100:6767` | `https://bazarr.researchstack.info` | `bazarr-forward-auth` | + +All can use the same embedded outpost. + +### Troubleshooting + +**Certificate errors?** +- Check `journalctl -u caddy` on the VPS + +**Authentik not reachable?** +- Verify Docker containers in LXC 100: `pct exec 100 -- docker ps` +- Check Authentik logs: `pct exec 100 -- docker logs authentik-server` + +**Forward auth not working?** +- Verify outpost application assignment +- Check Caddy logs for `forward_auth` errors +- Ensure `auth.researchstack.info` is accessible (the outpost needs to redirect there) diff --git a/4-Infrastructure/docs/pqc-posture.md b/4-Infrastructure/docs/pqc-posture.md new file mode 100644 index 00000000..b77747c7 --- /dev/null +++ b/4-Infrastructure/docs/pqc-posture.md @@ -0,0 +1,57 @@ +# Post-Quantum Cryptography Posture — Research Stack + +## Status: 2026-05-20 + +This document tracks the post-quantum (PQ) readiness of every encryption boundary in the Research Stack infrastructure. + +## Component Matrix + +| Component | Algorithm / Mechanism | PQ Status | Notes | +|-----------|---------------------|-----------|-------| +| **SSH (VPS)** | `mlkem768x25519-sha256` (hybrid) | **ENABLED** | OpenSSH 10.0 default KEX; `mlkem768x25519-sha256` is preferred | +| **Tailscale** | Noise + Kyber hybrid | **ENABLED** | Tailscale 1.58+ uses X25519Kyber768 automatically for all connections | +| **Secrets at rest (SOPS)** | age (X25519 + ChaCha20-Poly1305) | **NOT PQ** | No mature PQC age plugin in nixpkgs. Tracked: Mic92/sops-nix#885 | +| **TLS / HTTPS (Caddy)** | ECDHE + X25519 or P-256 | **NOT PQ** | Go 1.24+ has experimental X25519Kyber768; requires Caddy rebuild. Blocked by upstream port filtering anyway | +| **PostgreSQL (LXC 100)** | TLS 1.3 via OpenSSL 3.5.6 | **PARTIAL** | OpenSSL 3.5.6 supports `X25519Kyber768` but PostgreSQL 17 must be compiled with it | +| **Restic backups** | AES-256-GCM + Poly1305 | **NOT PQ** | Restic uses standard symmetric crypto; snapshot integrity is MAC-based, not signature-based | +| **Garage S3** | TLS 1.3 (if enabled) | **NOT PQ** | Garage v2.3.0 uses Rustls; PQ support pending in rustls | +| **Proxmox VE** | TLS 1.3 via OpenSSL 3.5.6 | **PARTIAL** | Proxmox Web UI uses OpenSSL 3.5.6 which supports PQ KEX, but not configured explicitly | + +## What "Enabled" Means + +- **SSH**: Every new SSH session to/from the VPS uses `mlkem768x25519-sha256`, a NIST FIPS 203-compliant hybrid key exchange. Even if X25519 is broken by a quantum computer, the ML-KEM-768 component remains secure. +- **Tailscale**: All tailnet traffic uses the Noise Framework with `X25519Kyber768`. This is automatic and requires no configuration. + +## What Is NOT Post-Quantum + +- **Age / SOPS**: The `age` format uses X25519 for file encryption. A sufficiently capable quantum computer could recover the private key from the public key and decrypt historical files. There is no production-ready PQC age plugin available in nixpkgs as of 2026-05-20. Candidates: + - `age-plugin-keystore` (supports `mlkem768x25519` but requires Secret Service / D-Bus keyring) + - Awaiting native `age` PQC support or a stable nixpkgs plugin. + +- **Browser-facing TLS**: Caddy serves HTTPS with standard X25519 or P-256 ECDHE. Go 1.24 adds `X25519Kyber768` experimentally, but the deployed Caddy binary is built with an older toolchain and does not expose it. + +## Remediation Roadmap + +1. **Short term**: Continue using SOPS + age for secrets. When a PQC age plugin lands in nixpkgs, rotate all SOPS keys to the hybrid recipient format. +2. **Medium term**: Rebuild Caddy with Go 1.24+ and enable `X25519Kyber768` for TLS 1.3. Requires netcup to unblock ports 80/443 or switch to Tailscale Funnel. +3. **Long term**: Monitor NIST standardization of PQ signatures (ML-DSA / SLH-DSA). Once widely supported in OpenSSL / Go / Rustls, enable PQ certificates and signatures. + +## Verification Commands + +```bash +# Verify SSH server uses PQ KEX +ssh -vvv root@46.232.249.226 2>&1 | grep "kex" +# Expected: "kex: algorithm: mlkem768x25519-sha256" + +# Verify Tailscale PQ +sudo tailscale debug derp +# Check: "using Kyber768X25519" +``` + +## References + +- [NIST FIPS 203 — ML-KEM](https://csrc.nist.gov/projects/post-quantum-cryptography) +- [OpenSSH PQ KEX](https://www.openssh.com/txt/release-9.7) +- [Tailscale PQ](https://tailscale.com/blog/post-quantum-cryptography) +- [sops-nix PQ issue](https://github.com/Mic92/sops-nix/issues/885) +- [Go PQ TLS](https://go.dev/doc/security/postquantum) diff --git a/4-Infrastructure/docs/vps-proxmox-status.md b/4-Infrastructure/docs/vps-proxmox-status.md new file mode 100644 index 00000000..44c609fb --- /dev/null +++ b/4-Infrastructure/docs/vps-proxmox-status.md @@ -0,0 +1,107 @@ +# Proxmox VPS Deployment Status + +## Netcup VPS (46.232.249.226 / 2a03:4000:2b:468:980e:3bff:fea5:65aa) + +### Completed + +#### 1. Authentik in LXC 100 +- **Container**: Debian 13, CT 100, `authentik` hostname +- **Network**: `192.168.100.100` on `vmbr0` with NAT/MASQUERADE for external IPv4 +- **Services**: Docker, PostgreSQL 16, Redis +- **Authentik**: `ghcr.io/goauthentik/server:2025.4` running + - Server on `192.168.100.100:9000` + - Worker healthy + - Default admin: `akadmin` / `authentik` (change on first login) + +#### 2. Caddy Reverse Proxy +- **Binary**: Custom Caddy v2.11.3 with Porkbun DNS plugin at `/opt/caddy/caddy` +- **Service**: Enabled and running (`systemctl status caddy`) +- **Domains configured**: + - `researchstack.info` -> Proxmox Web UI (`localhost:8006`) + - `auth.researchstack.info` -> Authentik (`192.168.100.100:9000`) +- **DNS**: Porkbun A records updated to `46.232.249.226` +- **Certificates**: Let's Encrypt DNS-01 obtained for both domains + +#### 3. Tailscale +- **Status**: Authenticated and active +- **Tailscale IP**: `100.110.163.82` (node: `361395-1`) +- **Mesh**: Can see `qfox-1`, `microvm-racknerd`, `nixos-laptop`, `dracocomp` +- **PQC**: Tailscale 1.98+ uses X25519Kyber768 hybrid key exchange automatically + +#### 4. Post-Quantum Cryptography +- **SSH (VPS)**: `mlkem768x25519-sha256` explicitly configured and preferred in `sshd_config` and `ssh_config` +- **Tailscale**: Hybrid PQ already active +- **Document**: `4-Infrastructure/docs/pqc-posture.md` + +#### 5. age / SOPS Secret Management +- **Keypair**: Generated at `~/.config/sops/age/keys.txt` +- **Public key**: `age1fvm02ruga67vnw5wws9p2ycckdmc0gp83m9s6cyld0ctpxyf8gzqy5wwsr` +- **Encrypted files**: + - `.env` + - `4-Infrastructure/storage/restic/restic.env` + - `4-Infrastructure/infra/secrets/credentials.json` + - `4-Infrastructure/infra/secrets/appflowy.env` + - `4-Infrastructure/infra/secrets/tailscale-auth.key` + - `4-Infrastructure/deploy/cupfox/pre-infect-backup/porkbun.env` + - `API KEYS/racknerd_510bd9c_root.txt` + - `API KEYS/racknerd_solusvm_api.txt` +- **Config**: `.sops.yaml` at repo root; `4-Infrastructure/k3s-flake/.sops.yaml` updated + +#### 6. Proxmox Backups +- **Schedule**: Daily at 03:00 CEST +- **Target**: `local` storage (zstd compressed) +- **Scope**: CT 100 (authentik) +- **Command to verify**: `pvesh get /cluster/backup` + +### Blockers + +#### 1. Netcup Upstream Port Filtering +- **Symptom**: Ports 80, 443, and 8006 are unreachable from the public internet +- **Impact**: Caddy HTTPS and Proxmox Web UI are only accessible via: + - SSH tunnel: `ssh -L 18006:localhost:8006 root@46.232.249.226` + - Tailscale: `https://100.110.163.82:8006` (if Caddy is bypassed) or direct Tailscale access +- **Remediation options**: + - Contact netcup support to open ports 80/443 + - Use Tailscale Funnel for public HTTPS access + - Use a separate reverse proxy VPS with open ports + +#### 2. Garage S3 Backup Integration +- **Symptom**: Garage S3 endpoint (`100.88.57.96:3900`) unreachable from VPS over Tailscale +- **Impact**: Proxmox backups cannot be offloaded to Garage S3 mesh +- **Root cause**: Garage on `qfox-1` binds S3 API to localhost only +- **Remediation options**: + - Reconfigure Garage on `qfox-1` to bind `0.0.0.0:3900` or `100.88.57.96:3900` + - Set up an SSH tunnel from VPS to `qfox-1` port 3900 + - Run a local Garage node on the VPS and join the cluster + +### Access Cheat Sheet + +```bash +# SSH to VPS +ssh root@46.232.249.226 + +# SSH tunnel to Proxmox Web UI +ssh -L 18006:localhost:8006 root@46.232.249.226 +# Then open https://localhost:18006 in browser + +# Access Authentik setup flow (via Tailscale or SSH tunnel to Caddy) +# Via Tailscale directly to LXC: +tailscale ssh root@361395-1 +curl http://192.168.100.100:9000 + +# Decrypt secrets with sops +cd /home/allaun/Research Stack +sops --decrypt 4-Infrastructure/storage/restic/restic.env + +# Edit encrypted secret +sops 4-Infrastructure/infra/secrets/credentials.json +``` + +### Next Steps + +1. **Open netcup ports 80/443** or switch to Tailscale Funnel for public HTTPS +2. **Reconfigure Garage S3 binding** on `qfox-1` for Tailscale-accessible S3 endpoint +3. **Migrate Proxmox backups** from `local` to Garage S3 once connectivity is resolved +4. **Change Authentik default password** (`akadmin` / `authentik`) on first login +5. **Configure Authentik** as identity provider for Proxmox (OIDC) +6. **Reboot VPS** to activate `amd_pstate` and IOMMU (optional, if VM passthrough desired) diff --git a/4-Infrastructure/k3s-flake/.sops.yaml b/4-Infrastructure/k3s-flake/.sops.yaml index 9da76b3a..6b815f59 100644 --- a/4-Infrastructure/k3s-flake/.sops.yaml +++ b/4-Infrastructure/k3s-flake/.sops.yaml @@ -1,5 +1,5 @@ keys: - - &admin AGE_PUBLIC_KEY_HERE + - &admin age1fvm02ruga67vnw5wws9p2ycckdmc0gp83m9s6cyld0ctpxyf8gzqy5wwsr creation_rules: - path_regex: secrets/.*\.age$ key_groups: diff --git a/4-Infrastructure/storage/restic/restic.env b/4-Infrastructure/storage/restic/restic.env index 287bf800..dbcec1f1 100644 --- a/4-Infrastructure/storage/restic/restic.env +++ b/4-Infrastructure/storage/restic/restic.env @@ -1,54 +1,56 @@ -# restic.env — source this before any restic or backup operation -# Never commit this file (listed in .gitignore via /4-Infrastructure/storage/restic/*.env) +#ENC[AES256_GCM,data:Uzhq/lNhyv6lZ0ig8Z48nwoPjzLAGH9PSoA7IcUVW91mWLYsMEzVIEW4lc+N64gAP+L8cUN08h2H9HAZnJtr4jE=,iv:pmWpu1UvUZHwr6imRSfllOHera42HhSnmhTK8yYeHmY=,tag:9H9yAq5LXI3uYIh1TomNqA==,type:comment] +#ENC[AES256_GCM,data:UpK/LUGELutyOL88JTmx9ErzwnPDIQNCAC+UfzTuwaz2Ll2MCNCsZolLSptZLeKdBJaxqpClKWWsJrbH2bE9uegyL51Iz3/KTnmKHDlxZFDIywvJTLmou9Y=,iv:qtOiAx1IENJzsfKWVEzQ0AvTsWVuGXChob+bfgR1xVg=,tag:9btmRPM17jgmyl6OMDLw1A==,type:comment] # -# ROLE OF EACH TOOL -# ───────────────────────────────────────────────────────────────────────────── -# restic — deduplicated, encrypted, content-addressed SNAPSHOTS of source trees -# and database dumps. Gives you point-in-time restore, dedup across -# backups, and cryptographic integrity verification. -# Backends: Garage (primary), rclone (secondary = gdrive or nodes) +#ENC[AES256_GCM,data:stFXqF8JkakLs+hnsbqfhEln,iv:TzgD8iVyBdTSlWq7u2qJJyqkvXvrhAEROSgy5QW4IZ0=,tag:dt/ZkTaxqn/ZOFQdZs445A==,type:comment] +#ENC[AES256_GCM,data:WpBRjv6rU1o09LvNCNqRflU3XKGRGjAtAVxxZQvYSuvaa3em4oGTVX4UDNWdQuG5OLn7CGMGjXSGZTosWMVIs4Z4ufDsoaSL041Hy4huutKa8tlkIhIm3gK/0z+Ur8oguP/KFTvWiavxiSK4p7SN5nQdbnNtmWRcui4EszYkQPgaLFEB6RyhDLy2+Fb/1Zrc6i2X7mA9ovQc1uR9eJbn7knw6tk+7En1wMmZ0iD7uHSAzEBpzI9QVbZqpr8gjDnpBG1iUblE32LJJAhk6fU3d2UCNe7dv6zpGPA/vtFa+UYehWjSXVHWJw==,iv:qZQTz5h/YTyXqzDFonQ9UY3oUlHRQKQYb2FKZd3xahU=,tag:mBqGrYgP6E6uK4bFAZEaSA==,type:comment] +#ENC[AES256_GCM,data:JH70sjSgTbpD0aLWqXP2qWx96OyLa7NoIIOkIPeFXf58wmKCOqpmxUE1gMFdGSpg4WVz2kVOkWuOzvWoSG+Nc8Z/5GDak9UjfnG2nHlnNS1Q,iv:/fBujQ0ZQfXhw2w17xGJnslEOY4X1okF6z2LQEPz6IM=,tag:5IO8tB0pasjtmDrsUNH9ug==,type:comment] +#ENC[AES256_GCM,data:TFJRUsaiECFENRg8jBXazLpaG9D1QYEvmNkrEhK8sM59N4l9tB6Ezljssy+pa4kg6JFjPGCkemgdCR3l8WLDBc5Qkc7cJv+oBBYJ+Q==,iv:uE3gC6h9dZ1CMleQT5GvtJDgvb77M9sfy/fEFPUmM1Q=,tag:u/dsO4f23PCgL1NFWaluPw==,type:comment] +#ENC[AES256_GCM,data:gOAfkDU1cz8PcbWSxrGRfpE8P+PwNsOWkMMfjbWxJrbNv9dPvvMkY8vJupDQbZ0zuYwFsu4VExGHttZGeg==,iv:rIpJiHTUSeoW7ojuEsyAwTgYZGqv6usGKKTxGI+pUfo=,tag:JAW/Xk+cs/+J1XLfnq4nKA==,type:comment] +#ENC[AES256_GCM,data:NBn7LZVE8Xord41rsOfQkaI7dC6o9drlldmqF8xUrmAi3CCzvJy1Ag4VB5eQSTmvOcBuVtdy2NOipW3OrX0EM6Gyp5qL1YZtSoAs,iv:xgcyBZ3eJ7aceknw7BWDlSWpMfpDpf4kSD1hDWqXHGw=,tag:EGGDQ1EcY9ZrG0fgJC/YOw==,type:comment] # -# Garage — the S3-compatible object store that restic and rclone talk to. -# Provides the distributed, self-hosted storage layer across nodes. -# Buckets: research-stack (restic repo), db-scratch, rds-overflow, -# gdrive-mirror, snap-zone +#ENC[AES256_GCM,data:o6rXeoJRcN82BlEkAE1e1Ml6BBuYbaR9Ut94lNJtD4Vnz7WHLy3eXnGiZgVctkNDGXyLonSsGEPO0PU/7/LAiLDTXZSvgfdoIImU,iv:qRkRWKuJG/MiQ8k0I5dYv6AM7oysgyP8tXs4VHiCN7A=,tag:cT66+bzsZ/3Nh/j5zNBJIw==,type:comment] +#ENC[AES256_GCM,data:wWJh1C2zykxXMa2+crNQuZZSmuqe7L/uViGOG+C+Uds/thYbPi/eXO1FWvBtw7m9I/xQHYJLSRtLkIoFQMpT+dZXxOlTCqNjb/ykNQ==,iv:jOAq0iaJaYgPs5eym5CNL5T+DfHFvp2m/j09Dv+KD0k=,tag:6N78ZJIRJSVUnXuVGWt08g==,type:comment] +#ENC[AES256_GCM,data:F6AC3CDD+bjCRRscPlwQRIA6tTPpLnrcxBTNvgyyVGA+WWUyWXuieqb9VSqoh+N0C9IAuVITtEb/hwjra9dBgZUm3vYiPnkvnu7d,iv:ZhVw24NYot1ChBe48zF2LHZrJk/PTTMVO4++yNSg7zY=,tag:iXrf+ubXKBOg62mmZTFnqg==,type:comment] +#ENC[AES256_GCM,data:DcmHGcqJrn8lzxtOdk40T6aWTveRcl0QQLXvtm9akZ2cgOdcFzzwiW9k7lM=,iv:3dQbFBTXjJrwCCZsH7SQ5Ug0UOc8O/o13z2B/oOAVSM=,tag:WTaUQPySEPdw9V10LLFOIQ==,type:comment] # -# rclone — moves/syncs raw objects between remotes: -# gdrive ↔ Garage (gdrive-mirror bucket) -# Garage ↔ Garage nodes (cross-node sync before rf=3) -# Garage → gdrive as a cold second copy of restic repo chunks -# Does NOT deduplicate or encrypt — Garage + restic handle that. +#ENC[AES256_GCM,data:lnTk0iQhBrXCFMj98944Prhszecrr1CvPDQ7g9b26p+xawgX96PJMHNQwEkE6IgE/jp9fU8=,iv:pmAVrgSutcyK0xiaN1zAm+btDslV2uJxDn9ghwiy7Og=,tag:HIGCcwOUxXo7m6e5ANM2/g==,type:comment] +#ENC[AES256_GCM,data:ynDdcom/41vJzSqZ+aUOdT0zZ4xLlhrJLob23XGi4YcpK0suyALnYEDqboVWZJrjc9N4,iv:8/ZUozY3ZTJv47mYJFOPqp/VamMeXy4KsMXlrSP4p8w=,tag:mFmTjSMZIS9sB6xcOP3FXA==,type:comment] +#ENC[AES256_GCM,data:KsqBSy2RN+TSszxWQ/GYY1TNRtUMR2GolPO8aqt21f9qrbliUmqmN/aUbzSHSFN5ha57RMB6II2SI2QQH78cig==,iv:XsifmmZdwgsUzIemQlgleMQk3f98AKiMLp3ZkFn+vLI=,tag:4HpfFq7I+SsJQNq0T7U8lQ==,type:comment] +#ENC[AES256_GCM,data:yf6lHF5H+/R26GZW4jA8b/uBmsaGFRa9SjG9FgV9sPPguBzvhShqXcnDO5Q4eL0gTjvo1BWvVtW9c8OuZJaPVOYRGJKlH9oT,iv:/VGb5YhwJyt/vWJqM+EPJUNmGeHP6uVtxfMK6ALIoZ0=,tag:q6Ti3L0ddH/m0+2BmxUF1w==,type:comment] +#ENC[AES256_GCM,data:ZHOmHkpAkfQOCuJO0EueTxbs1HKsnw/s5bqiB+84ZXGau7R8FpLozNcVJeiZlyNOm6g7KCIi1B72CSPbe4edFLyenlG6uJS4ao7+,iv:RE340ZogoG2fD97Klb9Pln0Fj+XKtPY90MAEKudfDes=,tag:/QQzvnrCrkHRro3UltzymA==,type:comment] # -# ───────────────────────────────────────────────────────────────────────────── -# DATA FLOW +#ENC[AES256_GCM,data:TKTNUVKG2zrdKA/cFKTr5FRZGjnF5Yj0QuFhw8JaHlWFPZXvp/JHV3k8aYhaE8IJL4LAT71LzD/6iPqI34IMDhdwUtM0Ji8/SfkEqRm1M1y4omy8gUNNZwdVg673+debz2HEs3BdmxnQ57eeDPetvkjq8U6o9/9VjKn/C+/DStUIREaNLruhjNYubiYpWbqmR883Bhkp/Wf77taw1lIAC/2fIDXKvnYDmBpFdEgCCyytKMY1H3OQAysLnmGjSWXOG+gpMAGgPh5RjizTGMdLulj0swhAcyzoHI48ZFUZAXNjsO1Vog02oA==,iv:O55iHYiznOTfGLa/K1/wg9pRjZ2p/0K42DTxJD+FPJk=,tag:k9gb4x+3slxV+8U1opmnoA==,type:comment] +#ENC[AES256_GCM,data:bQ4biTHOMaSiyA==,iv:Qc2Y2hmp/ofkZsucAmh6iSsGOA0WSGUP/prkKKwz2eQ=,tag:0Sd+Ql4B5g4B7LKx2CC+sA==,type:comment] # -# Hot write path (every git commit, via post-commit hook): -# SQLite scratch DBs → restic backup → Garage:research-stack -# RDS overflow dumps → restic backup → Garage:rds-overflow +#ENC[AES256_GCM,data:WvjatAun3vcyhMowoF4soVw8SwBxd2L58+5xdtbPOlwBOaheMpDiLJ8dAwARhpWEmiL6cOB6Jaed,iv:UdOQMJa8XN5IxVNzIGJdOWwU0daPRy9OWgs6Ezap600=,tag:T16BzwFDNR7Vg+ZrfuJ8+Q==,type:comment] +#ENC[AES256_GCM,data:yizbDb8zWj8LL4kwgIXvGVNFC1cyyAxLC2I8K2rY+CVjLE/EL+er0tOWNvc9yWrTJ+mikffbLhbqGfG1nepHfHU=,iv:wZrAL+uxmvLLwPKV93JZhzVbSXmaQeMLEcBoXvDSXRA=,tag:1jQrW+BwmyJ14QqIa65gPA==,type:comment] +#ENC[AES256_GCM,data:ACRCJLaHBMmBK2styTT7pX9FfjK8d3yxP4mXZjZWbXF/y9+8bJdGPObZfk/cSwJPtTTnm3oBjXOBEBkhEOrf,iv:2z3A8l6VMIc6dLoaDBHxt6JchQyfuNbPv464nIIfulw=,tag:n7eiy93+3uY6nKcVyVZ7Vw==,type:comment] # -# Cold copy path (daily, via systemd timer): -# Garage:research-stack (restic chunks) ──rclone copy──▶ gdrive:restic-mirror/ -# gdrive:research-stack ──rclone sync──▶ Garage:gdrive-mirror +#ENC[AES256_GCM,data:2v83PGCTG4l/SNQMjoJMC+wKuc5BG/+Sz9Ki/gUClML8UqA6IJbRcsjjVg==,iv:AEYfca0aTcz0m5n1HGupS25F6hAdeh4y4KJhMewf/O0=,tag:TKhVlBUL4ls+6LmOQ22Irg==,type:comment] +#ENC[AES256_GCM,data:rrj6jCn8LJbtAocxjeEupCGMY+trWko00krOz8A2xA7g+W4vNeDoGoV3ztAFBoFSZ5XfWqd6kAfeVVjkmrpN5s+LJCAiUcp8Pnt9oHu5QA/sOGHCGMG3tE4=,iv:RN6aLiViOfZ4eELrALGdi61cs6efbrDQ29YslF5Xhfc=,tag:iDOPvaWh8rs5klO9eBnBuQ==,type:comment] +#ENC[AES256_GCM,data:Q7dSJ0HQ0gvDjA5wTHL0XVQ9T6eAfGcyqJLSx6kjmVcmiPUObRXnOxW44vLFWRzujtGLWT9Nhu3H+bn/8UqLrsSBqmgoOb+2,iv:BCJyHekIP0yafQcpvBZdCh2R2lg3pMXwxYpMManCFC0=,tag:5j8mmokojG/HNZjirVcDEQ==,type:comment] # -# Restore path: -# restic restore -r s3:http://localhost:3900/research-stack -# OR if Garage is down: -# restic restore -r rclone:gdrive:restic-mirror/ +#ENC[AES256_GCM,data:E6JCJKsdsiKCq6UMl+g=,iv:BJc8esWPvgJ0d5PXioplmqRXhAPZvLccq/zpK3PoUNA=,tag:PSfDA3hN+sv2i0IQraFsUA==,type:comment] +#ENC[AES256_GCM,data:MvElE0FYAt6BXVcjwR9fypfxJ3I0E98jC2xZv6KBqA9xQXHQtV1kSXk+DrKAmEmKSt0URsCH51hiNw3CmLwBLU5JPq6hWAE=,iv:yQBTyGgjLPzaX/F+iL3VuzocQsLqwOjvI3NA4es5f2o=,tag:5hzANXIk4GZUanfW+EPNSQ==,type:comment] +#ENC[AES256_GCM,data:WhA+bKWfaLkx3C6J0VfIp7Qby4DY6n3g,iv:McYFk4Tr8b/L6DKvzhUQE9Qk/RbCqkkOzK3OFl2i32A=,tag:64OLgU1g4RNa6DmJ0Kflzw==,type:comment] +#ENC[AES256_GCM,data:Vd5o9KEBdRmhbs5WioMb2iGMJb9rLiJZU2qcD0Q6ZJRhr+Vet7vJTVapPw7D0OJK3twE3d84d8eDvayb,iv:CNEAMLFyRZv+TGJT8LpuL3U1bKDq/PSo5qx239v+qYo=,tag:GlxUai7SM+DibGVeIfyEBA==,type:comment] # -# ───────────────────────────────────────────────────────────────────────────── - -# Garage S3 credentials -export AWS_ACCESS_KEY_ID="GK55105d55675994caea2c2d3d" -export AWS_SECRET_ACCESS_KEY="3e8f080bc708f78b312681f6651ca6ae50cbe44de799ffb5cc65219c29143914" -export AWS_DEFAULT_REGION="garage" - -# Primary restic repo: Garage -export RESTIC_REPOSITORY="s3:http://localhost:3900/research-stack" -export RESTIC_PASSWORD_FILE="/etc/garage/restic-password" - -# Secondary restic repo: gdrive (via rclone backend) — cold copy -# Use with: RESTIC_REPOSITORY="$RESTIC_REPO_COLD" restic snapshots -export RESTIC_REPO_COLD="rclone:gdrive:restic-mirror" - -# Garage endpoint (for scripts that use aws cli directly) -export AWS_ENDPOINT_URL="http://localhost:3900" +#ENC[AES256_GCM,data:2CTOMrsaePECFOpOydk4aCfMtLUXVFrgBijuQ6JquvVzmEsyGKLE7keHPTgEkFWoiqOWrbIKoeBTxy/qqje5pNdY3lV7i1qkqBxRd26hZA9f/t2azaxAwUcDSig1eTtlDYKlWlsEwCXKtMOJgnUYAVyhqxjk4hdkUf+p+xKKOTOHh+X0zcycKTWxttBWmp7pBUgK1rlsbEj7utrYZqy+Dtbrut9uKAhoPyq8sTeuNRFqOvq9Zr2omehs3o3b63bQbTzSMDF9eRmiYGNCZUpVbJEoiS9zzg//DWDQR7ushe+V/cdnVcrqYw==,iv:heukOQVhvXMuLYG/m5sVCGkQDn8T2BjB2RmR0KgZF2I=,tag:Rkb1PrvoJ23sYHG6A3Enkg==,type:comment] +#ENC[AES256_GCM,data:/SgJT2EFwLBs7HIykjZ4Nojsm/0cRA==,iv:PnutnHoVOaTlQhDXS1RBarLrYPp1rBcrHTWpPLQQDRw=,tag:uwEgrNeMvQAjJ0auIcfbdw==,type:comment] +export AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:3JRfo7GzvGwYoCDS8pxMgprf7/0szgtnwtOrWw==,iv:hwGEJFOwbJWvbxQrfGmMnuaIryJKGAPPsu1hR+4ihUI=,tag:IQC4fvKYmsDv+9o1IqQXwQ==,type:str] +export AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:yEpA+81xYei73fTVKBoSK8oUMdBOeIj5FedI2V/4DGE2zIkYb3FFcksiMjBhQVbuGKWac0p8veVCI/Nlh/GicDvi,iv:nh9lxRoPegpdjnJrU+JxppBOfL1UjbyVs27A9BDjETc=,tag:Idt5MrH7oKj9BdZodN5dOg==,type:str] +export AWS_DEFAULT_REGION=ENC[AES256_GCM,data:b7qHaeFNLVc=,iv:Zc26crDCTfcLihieTcF8PRXqWIj4kmnchUZFZ24lMx8=,tag:GmkGakHYWGkCkbpY7MvWwA==,type:str] +#ENC[AES256_GCM,data:MypQ/smV30mi/SeC0JKV5QjfBTxeXH630U40kg==,iv:XOV2da41hbmbKMXnTlCitLGtkIDLNmYwm0aih1RTaao=,tag:pe81fBUlJ2oqbUxo7SuMqg==,type:comment] +export RESTIC_REPOSITORY=ENC[AES256_GCM,data:Fetv6j5/5kWBEAlRdV6wLdojNPZ7h37Xu6ja0OHprb7uhqOefhpAr5E=,iv:O2TdLS4HJ6dPxeObXymhZRW5tff7O65KGeQ9ka1btFk=,tag:56BtxR/rkkv7E6GqSDSsEw==,type:str] +export RESTIC_PASSWORD_FILE=ENC[AES256_GCM,data:0JF5Um631FTFEfwXPKtiFCuI5lSmA77S59qcTdI=,iv:VjKHFmZ2xzQiZmLWxdQbgBZosL9pPDDkQWnR5940S6w=,tag:TTa9iqqcJ8fkZZbn3ahVgQ==,type:str] +#ENC[AES256_GCM,data:PRbFQXdS8BrDlmWR4Lk8w0Sw7dX5KRqsk0dGNAwloK2FlzE9qF0OliH++XSBmoMwTq0XrcPjkZlw7jVi6bLvHS0=,iv:MugDXd0dQiyQnYUa4wB2R8LOZHk3/C6BtIBT6kPyEOs=,tag:CFgGUi2OCPBfO9vbvWN06A==,type:comment] +#ENC[AES256_GCM,data:iULvQ8NCKVvXETwumDrnxqUJw9vspftSwB0M2SEfVxil/WogR6rj/B6l5B2lynpTrdgw6ZRBBXIfRP0VLkkcx6I=,iv:1R+mCe3AE+lO3F1iLBHUQNRtvtGWHsJWcivQq4+35zM=,tag:qOBwigEYr1qgu6G9TXmtXg==,type:comment] +export RESTIC_REPO_COLD=ENC[AES256_GCM,data:q+H31uMtKmdcqHugq48ftUp7Og2IJ3sdX67DtTk=,iv:laLwPgVfAhcR60jqf91fcWFmFFms430xTgRmSNzlOWw=,tag:x+LpyzntU9lrWZbUFjY+KA==,type:str] +#ENC[AES256_GCM,data:rQM0QrgmfD7gHnkPC1rdQsDEkcxYJYWqEyZUzJCZ5tc39v885LBQKbRDNP13QKeWhvITtq3AEh8=,iv:nipmXQa1Ks4EpueZtAIQu3ZQAskRP+rrlZMumnTOBmc=,tag:gDiXiI6lomM2dftA87qBmQ==,type:comment] +export AWS_ENDPOINT_URL=ENC[AES256_GCM,data:rF7e2q4/9M3NOzgZNNuTH1VcBMNsgF0=,iv:AUGP1OzmmycGxpT2E4ws74gR2OuxWia9eIggxj6MnxE=,tag:uS3MKKkcVtWB68HlY9JAmA==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvaEpHSWxMMGNBWEd1UjVl\nenVaSDlqVkdrbDkzajRYSWRMaXVaa0M2ZDFjCmRHNnAyeXc3cElNRWxEcDVjZS9B\nZjFSWFhhR3NBOHFtcVA5L1hreWFEVjQKLS0tIGMvbmFORDZVRHhrZUlYUEVlSy91\nSkUzVUNtMEE3Q2t1N3YwQzRYblRWK0EK6+dgL6aJL4ReJBymH3ooizNGgXFyeOtY\n/pzdxSVDpHUIgbf21p8dpj6Oc983T7px4vMghxTiHUt6ny8JXNlyrg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1fvm02ruga67vnw5wws9p2ycckdmc0gp83m9s6cyld0ctpxyf8gzqy5wwsr +sops_lastmodified=2026-05-20T22:10:41Z +sops_mac=ENC[AES256_GCM,data:DtUIcqPCIZTsELQTNqt/tEgUvruC9Q/jdqYchTahvny00TlXQT16tTzyimqPz5KBcCiexr4/aIl2wqt/ZJh8wT9U7LK60hCN8LoYIQYVqaU6K0gJX5F9I76POHiB46Dqk3d2eEo6o+pefQqxTe0OsP0c3Ko74KVDAMHbARMJG7Y=,iv:aFDMfYbXLXN4Mc06pzClGlgeRMni4W8fEgGOJh77ruo=,tag:/JN2aZSukKdaN5yUM8fYEg==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.13.0