diff --git a/.claude/worktrees/fix-lxc-docker-nfs-mounts b/.claude/worktrees/fix-lxc-docker-nfs-mounts new file mode 160000 index 0000000..75d09d5 --- /dev/null +++ b/.claude/worktrees/fix-lxc-docker-nfs-mounts @@ -0,0 +1 @@ +Subproject commit 75d09d57e3ecc77a38bc284078c987900e762bdf diff --git a/.claude/worktrees/proxmox-remote-build b/.claude/worktrees/proxmox-remote-build new file mode 160000 index 0000000..a5990cc --- /dev/null +++ b/.claude/worktrees/proxmox-remote-build @@ -0,0 +1 @@ +Subproject commit a5990ccf7d93b8e584d760e71be09bf4ef5dfeca diff --git a/flake-target-refactor-spec.md b/flake-target-refactor-spec.md deleted file mode 100755 index 5dca93a..0000000 --- a/flake-target-refactor-spec.md +++ /dev/null @@ -1,151 +0,0 @@ -# Spec: Refactor Flake Targets into Platform × Build-Type Matrix - -**Status: implemented.** `flake.nix`'s `generatedTargets`/`mkTarget` and -`modules/platforms/*`/`modules/build-types/*` are the result of this spec — -kept here for historical rationale only (referenced from `CLAUDE.md`'s -"Composition pattern" section), not as an active or open plan. The "Open -Questions" below were resolved during implementation; don't treat them as -outstanding. A `tailscale-exit-node` build type was added later, beyond this -spec's original scope. - -## Context - -The flake at `~/nixos` currently defines these output targets (flat, ad-hoc naming): - -- `docker` -- `linode-minimal` -- `nix-cache` -- `nix-minimal` -- `nixos` -- `server` -- `pxe-boot` - -Some already follow a `platform-buildtype` convention (`linode-minimal`), most don't. -`~/nix-auto-installer` is a related repo and should be checked for any coupling to -these target names (scripts, docs, CI, or install automation that reference them by -name) before renaming anything. - -## Goal - -Restructure the flake so targets are generated from two orthogonal concepts: - -**Build types** (what the system is for): -- `minimal` -- `nix-cache` -- `server` -- `docker` -- `pxe-boot` -- `gui` - -**Platforms** (what it's deployed on): -- `linode` (Linode VM) -- `proxmox` (Proxmox VM) -- `lxc` (Proxmox LXC container) - -Final targets should be named consistently as `-`, e.g.: - -``` -linode-minimal proxmox-minimal lxc-minimal -linode-nix-cache proxmox-nix-cache lxc-nix-cache -linode-server proxmox-server lxc-server -linode-docker proxmox-docker lxc-docker -linode-pxe-boot proxmox-pxe-boot lxc-pxe-boot -linode-gui proxmox-gui lxc-gui -``` - -That's the full matrix (18 targets) if every build type applies to every platform. -See **Open Questions** below — some combinations may not make sense and should be -confirmed with me before being built out, not silently included or dropped. - -## Migration mapping (old → new) - -| Old target | New target | Notes | -|--------------------|------------------------------------------------------|-------| -| `linode-minimal` | `linode-minimal` | Already correct, keep as-is | -| `nix-minimal` | likely `proxmox-minimal` or a platform-less base module | Ambiguous — see Open Questions | -| `nix-cache` | base module consumed by `linode-nix-cache`, `proxmox-nix-cache`, `lxc-nix-cache` | Currently platform-less; needs to become a build-type module, not a standalone target | -| `server` | base module consumed by `linode-server`, `proxmox-server`, `lxc-server` | Same as above | -| `docker` | base module consumed by `linode-docker`, `proxmox-docker`, `lxc-docker` | Confirm docker actually makes sense as an LXC/VM guest build vs. a standalone container image — see Open Questions | -| `pxe-boot` | TBD — may stay a single target rather than a per-platform one | See Open Questions | -| `nixos` | TBD — unclear what this maps to in the new scheme | See Open Questions | - -## Open Questions (Claude Code: raise these with me before implementing, don't guess) - -1. **`nixos` target** — what is this currently used for (bare metal install, dev - shell, template)? It doesn't obviously map to any of the six build types. -2. **`nix-minimal` vs `linode-minimal`** — are these two different things, or is - `nix-minimal` a leftover/duplicate? -3. **`pxe-boot` and `gui` across all three platforms** — does PXE boot make sense - for an LXC container or a cloud VM (Linode), or is it inherently bare-metal/ - network-boot only and should remain a single non-platform target? Does `gui` - make sense inside an LXC container? -4. **`docker` as a build type** — is this "a NixOS host configured to run Docker" - (which would sensibly have linode/proxmox/lxc variants), or "a Docker container - image built by the flake" (which wouldn't take a platform prefix at all, since - it doesn't run on Linode/Proxmox/LXC as a guest OS)? These are structurally - different and change how it should be wired in. -5. Confirm whether all 18 combinations should actually exist, or whether this is - meant to produce only the combinations that are genuinely useful (e.g. maybe no - one needs `lxc-pxe-boot`). - -## Implementation approach - -1. **Inventory first.** Read the current `flake.nix` and any `nixosConfigurations`/ - `modules` structure. Map every existing target to what module(s) it actually - pulls in. Don't assume — confirm against the real file contents. -2. **Separate build-type and platform into their own module directories**, e.g.: - ``` - modules/build-types/minimal.nix - modules/build-types/nix-cache.nix - modules/build-types/server.nix - modules/build-types/docker.nix - modules/build-types/pxe-boot.nix - modules/build-types/gui.nix - - modules/platforms/linode.nix - modules/platforms/proxmox.nix - modules/platforms/lxc.nix - ``` - Build-type modules should contain only what makes a system "minimal" vs - "server" vs "gui", etc. Platform modules should contain only what's specific - to running as a Linode VM vs Proxmox VM vs LXC container (virtualisation - guest tools, boot method, filesystem/image format, LXC-specific constraints - like no kernel modules, etc). -3. **Generate the target matrix programmatically** in `flake.nix` rather than - hand-writing 18 near-identical `nixosConfigurations` entries — e.g. a small - function that takes a platform name and build-type name, composes the two - modules plus any shared base module, and produces the named output. This - keeps future build types/platforms a one-line addition rather than a copy-paste - job. -4. **Only build combinations we've confirmed make sense** (see Open Questions) — - don't emit all 18 by default if some are structurally invalid. -5. **Preserve existing working configs during the transition.** Don't delete the - old target names until their replacements build successfully — rename/alias - at the end, not the start, so there's no window where the flake is broken. - -## Verification - -For every new target produced: -```bash -nix flake check -nix build .#nixosConfigurations..config.system.build.toplevel -``` -Confirm each builds without evaluation errors before considering it done. If a -target fails to build, report which one and why rather than silently skipping it. - -## Deliverables - -- Refactored `flake.nix` using the composed module + generated-matrix approach. -- New `modules/build-types/*.nix` and `modules/platforms/*.nix` files. -- Old flat target names removed only after their replacements are verified. -- A short `README.md` (or section in existing docs) listing the final target - names and what each one is for. -- A summary at the end of what changed, what was removed, and any of the Open - Questions above that got resolved differently than expected. - -## Out of scope - -- Don't touch `~/nix-auto-installer` contents beyond checking it for references - to the old target names — if changes there are needed, flag them, don't make - them without confirming. -- Don't add new build types or platforms beyond the ones listed here. diff --git a/remove-sensetive-info-refactor.md b/remove-sensetive-info-refactor.md deleted file mode 100644 index 289eaff..0000000 --- a/remove-sensetive-info-refactor.md +++ /dev/null @@ -1,146 +0,0 @@ -# Spec: Remove Sensitive Information from NixOS Flake - -## Goal - -Every secret currently readable in plaintext anywhere in this repo (working tree *and* git history) gets removed, replaced with `sops-nix`-managed encrypted references, and rotated. When this is done, the repo should be safe to make public without exposing anything about the systems it configures. - -Treat this as three sequential milestones. Do not start git history rewriting (Milestone 3) until Milestones 1 and 2 are fully verified and the flake still builds. This should be its own branch (`refactor/secrets`) until fully verified, then merged. - ---- - -## Milestone 1 — Audit - -Before touching anything, produce a complete inventory. Do not guess at scope — grep the whole tree and the whole history. - -1. Run a secret scanner across the working tree and full history. Use both, since they catch different things: - - `gitleaks detect --source . -v --log-opts="--all"` (scans history too) - - `trufflehog git file://. --since-commit=$(git rev-list --max-parents=0 HEAD) --only-verified=false` - If neither is installed, add them via a temporary `nix-shell -p gitleaks trufflehog` — don't install anything globally on the host. - -2. Manually grep for the categories below, since scanners miss config-specific patterns: - - `hashedPassword`, `password`, `initialPassword`, `initialHashedPassword` in any `users.users.*` block - - `age.secrets`, `sops.secrets` (if any partial secrets work already exists — check for it) - - PSK / `preSharedKey`, `privateKeyFile` inline values (vs. file references) for WireGuard - - `authKey`, `apiToken`, `api_key`, `token =`, `secret =` in service modules (Tailscale, Cloudflare, backup tools, etc.) - - SSH private key material: search for `BEGIN OPENSSH PRIVATE KEY` / `BEGIN RSA PRIVATE KEY` literals - - TLS cert/key pairs committed under e.g. `secrets/`, `certs/`, `pki/` - - Real name, personal email, home address, or anything in comments/hostnames that maps a machine to your physical identity or network layout (e.g. hostnames like `wayne-desktop`, static LAN IPs, ISP-identifying info) - - `.env` files, `secrets.nix`, `secrets.yaml`, or any file that looks like it was meant to be gitignored but wasn't - -3. Produce `secrets-inventory.md` (temporary, delete before finishing) listing: file path, line, secret type, and which host/service it belongs to. This becomes the checklist for Milestone 2 — every row must be either migrated to sops or deleted, with nothing left unaccounted for. - ---- - -## Milestone 2 — Migrate to sops-nix - -### 2.1 Set up sops-nix - -1. Add the flake input: - ```nix - sops-nix.url = "github:Mic92/sops-nix"; - sops-nix.inputs.nixpkgs.follows = "nixpkgs"; - ``` -2. Import `sops-nix.nixosModules.sops` into each host's module list (or into a shared `common.nix` if all hosts use it). -3. Generate an age keypair **per host** (not one shared key for everything — a compromised host shouldn't decrypt every other host's secrets): - ``` - nix-shell -p age --run "age-keygen -o /var/lib/sops-nix/key.txt" - ``` - Print the public key (`age-keygen -y`) for each host — you'll need it for `.sops.yaml`. -4. Also generate one age key for yourself (your admin workstation) so you can edit secrets without needing to SSH into a host: store it at `~/.config/sops/age/keys.txt`, back it up somewhere outside this repo (password manager, offline). **If this key is lost, every secret encrypted with it is unrecoverable — losing the age key is equivalent to losing the secrets.** -5. Create `.sops.yaml` at the repo root defining creation rules: which age public keys can decrypt which secrets files, keyed by path regex, so e.g. `secrets/hostA.yaml` is decryptable by your admin key + hostA's key, `secrets/hostB.yaml` by your admin key + hostB's key. - -### 2.2 Migrate each secret category from the inventory - -For each row in `secrets-inventory.md`: - -- **Password hashes**: generate hash with `mkpasswd -m sha-512` (or `bcrypt` if your setup wants that), store under `sops.secrets."/hashedPassword"`, reference via `users.users..hashedPasswordFile = config.sops.secrets."/hashedPassword".path;`. Do not put the *plaintext* password anywhere, only the hash, and only the hash goes into the encrypted sops file. -- **API tokens / auth keys**: move the raw value into the per-host sops YAML, reference in the module via `config.sops.secrets."/token".path` — most NixOS service modules that take a token also accept a `*File` variant (e.g. `environmentFile`, `tokenFile`); use that instead of passing the value directly. -- **Private keys / certs**: move the PEM/key content wholesale into a sops secret, output as a file with appropriate `sops.secrets..path`, `owner`, `mode`, `restartUnits` so the depending service (sshd, wireguard, nginx) reloads when the secret changes. -- **Personal/identifying info**: this doesn't belong in sops (it's not "secret," it's just information you don't want public). Replace real names/emails with placeholders or move to a small untracked `local.nix` that's `.gitignore`'d and imported conditionally, with a documented template (`local.nix.example`) committed instead. - -### 2.3 Verify before moving on - -- `nixos-rebuild dry-build --flake .#` succeeds for every host. -- `sudo nixos-rebuild switch --flake .#` on at least one real machine (or a VM) confirms secrets decrypt and services start. -- Confirm decrypted secrets land under `/run/secrets/` (not the Nix store — anything placed in `/nix/store` is world-readable by design, so sops-nix's runtime-only placement is the whole point; double check no module accidentally pulls a secret path into a store-built config file). -- Re-run the grep/scanner sweep from Milestone 1 against the *working tree only* (not history yet) — it should now come back clean. - ---- - -## Milestone 3 — Scrub git history - -Do this only after Milestone 2 is merged to your main branch and confirmed working, since it rewrites every commit SHA from the point of the earliest offending commit onward. - -**This is destructive and irreversible on your local clone. Back up first:** -``` -cp -r /path/to/nixos-repo /path/to/nixos-repo-backup-$(date +%F) -``` - -1. Install `git-filter-repo` (not the older `git filter-branch` / BFG — filter-repo is the currently maintained, faster, safer tool): - ``` - nix-shell -p git-filter-repo - ``` -2. Use the `secrets-inventory.md` list to build a list of literal strings/paths to strip. Two approaches, use both: - - Path-based: if whole files were secret (e.g. `secrets.nix`, a `.env`, a private key file), remove them entirely from history: - ``` - git filter-repo --path secrets.nix --path .env --invert-paths - ``` - - Value-based: for secrets embedded inline in files you're keeping (not deleting the whole file), use `--replace-text` with a file listing each literal secret string to replace with `***REMOVED***`: - ``` - git filter-repo --replace-text expressions.txt - ``` -3. After filtering, verify: run the Milestone 1 scanners again against full history (`--log-opts="--all"`). They must come back clean. -4. Force-push the rewritten history: - ``` - git push origin --force --all - git push origin --force --tags - ``` -5. **Every other clone of this repo (other machines, WSL instances, CI) must be deleted and re-cloned fresh** — a `git pull` against rewritten history will not work cleanly and risks resurrecting the old commits. Don't try to reconcile old clones; throw them away and re-clone. -6. If this repo has ever been pushed to a public host (GitHub, etc.) or a fork/mirror exists, treat every secret that was ever in history as **permanently compromised regardless of the rewrite** — caches, forks, and Wayback-style archives can retain old commits indefinitely. History scrubbing prevents *future* exposure via `git clone`; it does not undo past exposure. - ---- - -## Milestone 4 — Rotate everything - -Because the secrets were exposed in history (even briefly, even in a private repo), the migration is not complete until every credential in the inventory has been **rotated**, not just re-encrypted. Re-encrypting an already-leaked value protects it going forward but doesn't undo the leak. - -For each row in the original inventory: -- Password hashes → change the actual account password, regenerate the hash, update the sops file. -- API tokens/auth keys → revoke the old token in the issuing service's dashboard (Cloudflare, Tailscale, backup provider, etc.) and generate a new one. -- SSH/WireGuard private keys → generate new keypairs, update the corresponding public key wherever it's trusted (authorized_keys, peer configs, etc.), retire the old ones. -- TLS certs → reissue if the private key was exposed. - -Keep `secrets-inventory.md` open during this step and check off each row as rotated. Delete the file only once every row is checked off — it should not be committed. - ---- - -## Ongoing prevention - -Add a pre-commit hook (or a `nix flake check` step) running `gitleaks protect --staged` so a secret can't be committed again by accident. Document in the repo README (briefly) that new secrets go through `sops ` to edit, never as plaintext in a tracked file. - ---- - -## Definition of done - -**Status as of 2026-07-20:** Milestones 1–3 are done — sops-nix is fully -wired (`.sops.yaml`, `secrets/*.yaml`, referenced via `hashedPasswordFile`/ -`*File`/`sops.secrets.*.path` throughout), and history has been scrubbed -with `git-filter-repo` + force-push (this removed a GitHub fine-grained PAT -that had been committed in plaintext in `flake.nix`/`common/home.nix` -between 2025-07-16 and 2026-02-09, later migrated to sops but never scrubbed -from history until now). **Milestone 4 is not confirmed** — whether that PAT -(or any other historically-plaintext credential) was actually rotated, not -just re-encrypted, isn't something this repo can attest to; that's an -operator action against the issuing service (GitHub, etc.), not a repo -change. Do that before considering this fully closed. - -- [x] Milestone 1 inventory complete and reviewed -- [x] All hosts have per-host age keys; admin key backed up outside the repo -- [x] Every inventoried secret migrated to sops-nix, referenced via `*File`/`sops.secrets.*.path`, nothing plaintext in the working tree -- [x] `nixos-rebuild dry-build` and at least one real `switch` verified per host -- [x] Working-tree scanner sweep clean -- [x] History rewritten with `git-filter-repo`, force-pushed, full-history scanner sweep clean -- [ ] All other clones deleted and re-cloned from the rewritten history — every clone that existed before 2026-07-20's rewrite (any other machine, WSL instance, or CI checkout) needs this -- [ ] Every credential in the original inventory rotated (not just re-encrypted) — **the GitHub PAT found in history specifically still needs this** -- [x] Pre-commit secret scanning hook added (`.githooks/pre-commit`, `gitleaks protect --staged`) -- [x] `secrets-inventory.md` deleted from the working directory (never committed)