diff --git a/.agents/skills/nix-config-desktop/SKILL.md b/.agents/skills/nix-config-desktop/SKILL.md
index 2c9e4e40..7a59ce64 100644
--- a/.agents/skills/nix-config-desktop/SKILL.md
+++ b/.agents/skills/nix-config-desktop/SKILL.md
@@ -49,7 +49,7 @@ Store (needs the user to run `just niri`):
- Noctalia per-host override: `home/hosts/linux/
/noctalia.toml` (`host-.toml`)
- fcitx5 profile and addons: `home/linux/gui/base/fcitx5/`
- vinput (voice input): `home/linux/gui/base/vinput/` (tag-pinned: the `fcitx5-vinput` row in
- WORKAROUNDS.md §Pins)
+ docs/WORKAROUNDS.md §Pins)
- Interface a net-speed widget reads: `home/hosts/linux//noctalia.toml`
(`[widget.net_rx]`/`[widget.net_tx]` `interface`)
- `$HOME` state that must survive: `hosts//preservation.nix` (tmpfs root; `12kingdoms-shoukei`
diff --git a/.agents/skills/nix-config-new-host/SKILL.md b/.agents/skills/nix-config-new-host/SKILL.md
index b3048207..baf68259 100644
--- a/.agents/skills/nix-config-new-host/SKILL.md
+++ b/.agents/skills/nix-config-new-host/SKILL.md
@@ -38,9 +38,9 @@ in [hosts/README.md](../../../hosts/README.md).
`outputs/x86_64-linux/src/k3s-test-1-worker-1.nix`
A MicroVM guest is also registered in its VM host's `microvm.nix`; deploy it with the procedure in
-WA-026 of [WORKAROUNDS.md](../../../WORKAROUNDS.md), not `just microvm-deploy`. On a VM host with
-the `br0` bridge it also needs a `systemd.network.networks` unit that attaches the guest's tap to
-`br0`; the tap name comes from the guest IP (`192.168.5.116` to `vm116`). Some guest outputs also
+WA-026 of [WORKAROUNDS.md](../../../docs/WORKAROUNDS.md), not `just microvm-deploy`. On a VM host
+with the `br0` bridge it also needs a `systemd.network.networks` unit that attaches the guest's tap
+to `br0`; the tap name comes from the guest IP (`192.168.5.116` to `vm116`). Some guest outputs also
expose a Colmena node for evaluation or other workflows; do not assume the physical-host deployment
is done through Colmena.
diff --git a/.agents/skills/nix-config-update/SKILL.md b/.agents/skills/nix-config-update/SKILL.md
index a25ea2a1..0d6a1201 100644
--- a/.agents/skills/nix-config-update/SKILL.md
+++ b/.agents/skills/nix-config-update/SKILL.md
@@ -36,8 +36,8 @@ deploying the result to any host.
- `just test` is green _before_ the update, so you have a baseline to compare against. It and every
eval fetch the private `mysecrets` input over SSH; without that access they hang instead of
failing.
-- Check [WORKAROUNDS.md](../../../WORKAROUNDS.md) for the inputs and recipes you are about to use;
- an active row that names one of them replaces its command here.
+- Check [WORKAROUNDS.md](../../../docs/WORKAROUNDS.md) for the inputs and recipes you are about to
+ use; an active row that names one of them replaces its command here.
- Enough disk for the new closures: `df -h /nix/store`. Broad nixpkgs bumps pull a lot.
- Know what will move and choose the narrowest recipe that does the job.
diff --git a/.agents/skills/nixpkgs-patched/SKILL.md b/.agents/skills/nixpkgs-patched/SKILL.md
index 4e3d2ea2..1e2e54dc 100644
--- a/.agents/skills/nixpkgs-patched/SKILL.md
+++ b/.agents/skills/nixpkgs-patched/SKILL.md
@@ -3,7 +3,7 @@ name: nixpkgs-patched
description:
Use when temporarily carrying an unmerged nixpkgs pull request or commit in the personal
ryan4yin/nixpkgs fork, updating the nixos-unstable-patched branch, or consuming that branch from
- this nix-config flake, including a WORKAROUNDS.md row whose removal condition is carrying the
+ this nix-config flake, including a docs/WORKAROUNDS.md row whose removal condition is carrying the
patch.
---
diff --git a/AGENTS.md b/AGENTS.md
index d0e71ce6..1a34010f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -81,13 +81,14 @@ for disclosure of AI-assisted contributions:
## Security
Security architecture, control limitations, rollout checks, and prioritized work are documented in
-[SECURITY.md](./SECURITY.md). Keep it consistent with changes to security boundaries or defaults.
+[SECURITY.md](./docs/SECURITY.md). Keep it consistent with changes to security boundaries or
+defaults.
## Workarounds
Temporary workarounds, version pins, carried patches, and known gaps are recorded in
-[WORKAROUNDS.md](./WORKAROUNDS.md), each with a removal condition. Add a row in the same change that
-introduces one, and re-evaluate entries when their `Revisit` trigger comes up.
+[WORKAROUNDS.md](./docs/WORKAROUNDS.md), each with a removal condition. Add a row in the same change
+that introduces one, and re-evaluate entries when their `Revisit` trigger comes up.
## Command Hazards
@@ -107,8 +108,8 @@ introduces one, and re-evaluate entries when their `Revisit` trigger comes up.
narrower recipe that matches the intended host scope.
- `just microvm-deploy ` installs one MicroVM guest's runner on the physical
host and restarts its unit there. A later host activation re-points the guest at the runner baked
- into the host's system; see WA-026 in [WORKAROUNDS.md](./WORKAROUNDS.md) before deploying. Deploy
- guests serially and check the guest Node and host services after each activation.
+ into the host's system; see WA-026 in [WORKAROUNDS.md](./docs/WORKAROUNDS.md) before deploying.
+ Deploy guests serially and check the guest Node and host services after each activation.
- VM hosts (`shoryu`, `shushou`, `youko`) carry the `br0` bridge for their guests. Use the
`boot`-based host deployment procedure for network stack or broad nixpkgs changes; see
[hosts/README.md](./hosts/README.md#deploying-vm-hosts).
@@ -145,11 +146,12 @@ repository, and cross-link them — no need to be told.
## Further Context
+- [Documentation index](./docs/README.md)
- [Repository overview](./README.md)
-- [Workarounds & known gaps](./WORKAROUNDS.md)
+- [Workarounds & known gaps](./docs/WORKAROUNDS.md)
- [Outputs and tests](./outputs/README.md)
- [Hosts](./hosts/README.md), [system modules](./modules/README.md), and
[Home Manager](./home/README.md)
-- [Secrets](./secrets/README.md), [backups](./BACKUP.md), and
+- [Secrets](./secrets/README.md), [backups](./docs/BACKUP.md), and
[hardened app wrappers](./hardening/README.md)
- [Installing NixOS from the ISO](./nixos-installer/README.md)
diff --git a/Justfile b/Justfile
index 4149e3a4..62d003a9 100644
--- a/Justfile
+++ b/Justfile
@@ -212,7 +212,7 @@ col tag mode="switch":
# Deploy one microVM guest: copy its runner to the physical host, then restart the
# guest unit there. microvm.nix's sshSwitch step is not used: it switches the guest over
-# SSH and dies on the read-only virtiofs /nix/store (WA-026 in WORKAROUNDS.md).
+# SSH and dies on the read-only virtiofs /nix/store (WA-026 in docs/WORKAROUNDS.md).
# The host is the physical machine running the guest.
[linux]
[group('homelab')]
diff --git a/README.md b/README.md
index 219b7b99..bf9dfcd9 100644
--- a/README.md
+++ b/README.md
@@ -30,9 +30,10 @@ This repository is home to the Nix code that builds all of my systems:
3. **NixOS servers** — three physical mini PCs running the VMs (libvirt and microVMs), the K3s test
cluster and the self-hosted services.
-See [./hosts](./hosts) for the host inventory, [./outputs](./outputs) for how the flake outputs are
-composed, [./docs](./docs) for the BIOS and Wake-on-LAN runbooks, [./BACKUP.md](./BACKUP.md) for the
-backup design, and [./AGENTS.md](./AGENTS.md) for the repository conventions.
+See [./docs](./docs/README.md) for the documentation index, [./hosts](./hosts) for the host
+inventory, [./outputs](./outputs) for how the flake outputs are composed,
+[BACKUP.md](./docs/BACKUP.md) for the backup design, and [./AGENTS.md](./AGENTS.md) for the
+repository conventions.
## Why NixOS & Flakes?
@@ -98,9 +99,9 @@ See [./secrets](./secrets) for details.
## Security & Hardening
-See [./SECURITY.md](./SECURITY.md) for the threat model, security architecture, controls and audit
-status, [./hardening/README.md](./hardening/README.md) for application sandboxing and Linux
-hardening, and [./WORKAROUNDS.md](./WORKAROUNDS.md) for temporary exceptions and known gaps.
+See [SECURITY.md](./docs/SECURITY.md) for the threat model, security architecture, controls and
+audit status, [./hardening/README.md](./hardening/README.md) for application sandboxing and Linux
+hardening, and [WORKAROUNDS.md](./docs/WORKAROUNDS.md) for temporary exceptions and known gaps.
## How to Deploy this Flake?
diff --git a/agents/README.md b/agents/README.md
index 7e67fcc4..1abee5b2 100644
--- a/agents/README.md
+++ b/agents/README.md
@@ -120,8 +120,8 @@ recorded in [#428](https://github.com/ryan4yin/nix-config/pull/428).
- **Adapt `wayfinder` in the fork** so its map and tickets default to local Markdown and research
stays local, instead of GitHub issues and pushed `research/` branches.
- **Adapt `domain-modeling` in the fork** to record decisions in the repository's existing documents
- (here `AGENTS.md`, `WORKAROUNDS.md`, `SECURITY.md`, READMEs) and create `GLOSSARY.md` or ADRs only
- where none exist.
+ (here `AGENTS.md`, `docs/WORKAROUNDS.md`, `docs/SECURITY.md`, READMEs) and create `GLOSSARY.md` or
+ ADRs only where none exist.
- **Borrow small gstack ideas into the rules**: completion statuses (done, done with concerns,
blocked, needs context); never call a failure pre-existing without running the same check on the
base branch; a subagent with no one to ask takes the recommended option but never a destructive or
diff --git a/BACKUP.md b/docs/BACKUP.md
similarity index 90%
rename from BACKUP.md
rename to docs/BACKUP.md
index 9f2a5de3..c0aec487 100644
--- a/BACKUP.md
+++ b/docs/BACKUP.md
@@ -13,11 +13,11 @@ How this fleet is backed up, where the data lives, and how to restore it.
## Layers
-1. **btrbk — local snapshots** ([`modules/nixos/base/btrbk.nix`](./modules/nixos/base/btrbk.nix)).
+1. **btrbk — local snapshots** ([`modules/nixos/base/btrbk.nix`](../modules/nixos/base/btrbk.nix)).
Same-filesystem btrfs snapshots of `@persistent`, run on Tue/Sat 03:45:20, kept for 7 days
(minimum 2). Cheap rollback for mistakes; it does **not** survive disk loss.
2. **restic — encrypted copies on youko**
- ([`modules/nixos/base/restic-backup.nix`](./modules/nixos/base/restic-backup.nix)). Encryption
+ ([`modules/nixos/base/restic-backup.nix`](../modules/nixos/base/restic-backup.nix)). Encryption
happens on the client, so the server only ever stores ciphertext. youko runs `restic-rest-server`
with `--private-repos`, authenticated by htpasswd, published behind caddy as
`restic.writefor.fun`. Deliberately not append-only: a desktop holds both the plaintext and the
@@ -152,12 +152,12 @@ Restoring a btrbk snapshot (offline; stop writers first):
## Components
-| Path | Role |
-| -------------------------------------------------------------------------------- | ---------------------------------------------------- |
-| [`modules/nixos/base/restic-backup.nix`](./modules/nixos/base/restic-backup.nix) | client module: repository, paths, excludes, schedule |
-| [`modules/nixos/base/btrbk.nix`](./modules/nixos/base/btrbk.nix) | local btrfs snapshots |
-| `hosts/12kingdoms-youko/default.nix` | the REST server and youko's own backup |
-| `hosts/12kingdoms-youko/homelab-services/restic-server-snapshots.nix` | hourly read-only snapshots of the REST store |
-| `hosts/12kingdoms-youko/homelab-services/caddy.nix` | the `restic.writefor.fun` vhost |
-| `secrets/nixos.nix` | which secret is defined on which host |
-| `hosts/idols-ai/restic.nix`, `hosts/12kingdoms-youko/default.nix` | per-host backup configuration |
+| Path | Role |
+| --------------------------------------------------------------------------------- | ---------------------------------------------------- |
+| [`modules/nixos/base/restic-backup.nix`](../modules/nixos/base/restic-backup.nix) | client module: repository, paths, excludes, schedule |
+| [`modules/nixos/base/btrbk.nix`](../modules/nixos/base/btrbk.nix) | local btrfs snapshots |
+| `hosts/12kingdoms-youko/default.nix` | the REST server and youko's own backup |
+| `hosts/12kingdoms-youko/homelab-services/restic-server-snapshots.nix` | hourly read-only snapshots of the REST store |
+| `hosts/12kingdoms-youko/homelab-services/caddy.nix` | the `restic.writefor.fun` vhost |
+| `secrets/nixos.nix` | which secret is defined on which host |
+| `hosts/idols-ai/restic.nix`, `hosts/12kingdoms-youko/default.nix` | per-host backup configuration |
diff --git a/docs/README.md b/docs/README.md
index ff56d29a..6ec32076 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,5 +1,89 @@
# Documentation
-- [BIOS settings](./bios.md)
-- [Wake-on-LAN](./wake-on-lan.md)
-- [NixOS modernization options](./nixos-modernization-options.md)
+Canonical index for this repository. Standalone reference material lives in this directory.
+Component runbooks stay next to the code they describe so they remain visible during maintenance;
+every human-facing document is linked here.
+
+`README.md` and `AGENTS.md` remain at the repository root because GitHub and coding agents require
+those entry points.
+
+## Repository
+
+- [Repository overview](../README.md) — systems, components, deployment and validation quick start.
+- [Agent guide](../AGENTS.md) — repository conventions, command hazards and validation rules.
+- [NixOS modernization options](nixos-modernization-options.md) — design options and decisions.
+
+## Operations and policy
+
+- [Security](SECURITY.md) — threat model, trust boundaries, implemented controls and audits.
+- [Backups](BACKUP.md) — backup layers, password model, retention and restore procedure.
+- [Workarounds and known gaps](WORKAROUNDS.md) — pins, temporary workarounds and removal conditions.
+- [BIOS settings](bios.md) — firmware settings for the physical hosts.
+- [Wake-on-LAN](wake-on-lan.md) — firmware prerequisites, NixOS setup and wake commands.
+- [Runtime hardening validation](../hardening/tests/runtime-validation.md).
+
+## Hosts and services
+
+- [Host inventory and deployment notes](../hosts/README.md).
+- AI desktop: [host runbook](../hosts/idols-ai/README.md) and [VR notes](../hosts/idols-ai/VR.md).
+- Akane: [host runbook](../hosts/idols-akane/README.md).
+- Aquamarine: [host runbook](../hosts/idols-aquamarine/README.md),
+ [iGPU notes](../hosts/idols-aquamarine/IGPU.md) and
+ [ROCm notes](../hosts/idols-aquamarine/ROCM.md).
+- Kana: [host runbook](../hosts/idols-kana/README.md).
+- Ruby: [host runbook](../hosts/idols-ruby/README.md).
+- Shoukei: [host runbook](../hosts/12kingdoms-shoukei/README.md).
+- Youko homelab: [services overview](../hosts/12kingdoms-youko/homelab-services/README.md),
+ [Immich](../hosts/12kingdoms-youko/homelab-services/immich/README.md),
+ [monitoring](../hosts/12kingdoms-youko/homelab-services/monitoring/README.md),
+ [alert rules](../hosts/12kingdoms-youko/homelab-services/monitoring/alert_rules/README.md),
+ [recording rules](../hosts/12kingdoms-youko/homelab-services/monitoring/recording_rules/README.md)
+ and [Grafana dashboards](../hosts/12kingdoms-youko/homelab-services/grafana/dashboards/README.md).
+- Kubernetes cluster: [k8s host notes](../hosts/k8s/README.md) and
+ [disko configuration](../hosts/k8s/disko-config/README.md).
+- Infrastructure: [infra overview](../infra/README.md), [RustFS](../infra/rustfs/README.md),
+ [Loki storage](../infra/rustfs/loki/README.md) and
+ [Terraform S3 backend](../infra/rustfs/tf-s3-backend/README.md).
+- NixOS installation: [installer guide](../nixos-installer/README.md) and
+ [Shoukei notes](../nixos-installer/README.shoukei.md).
+- Certificates: [certificate setup](../certs/README.md).
+
+## Configuration structure
+
+- [NixOS and nix-darwin modules](../modules/README.md), including
+ [Darwin modules](../modules/darwin/README.md),
+ [Mihomo networking](../modules/nixos/desktop/networking/mihomo/README.md) and
+ [Sunshine](../modules/nixos/desktop/networking/sunshine/README.md).
+- [Home Manager modules](../home/README.md).
+- [Flake outputs and tests](../outputs/README.md).
+- [Secrets management](../secrets/README.md).
+- [Overlays](../overlays/README.md), [shared library helpers](../lib/README.md) and
+ [shared variables](../vars/README.md).
+- [Hardening wrappers and policy](../hardening/README.md).
+
+## Desktop and Home Manager details
+
+- [Darwin](../home/darwin/README.md), [per-host Home Manager profiles](../home/hosts/README.md) and
+ [Linux](../home/linux/README.md).
+- [Linux desktop stack](../home/linux/gui/README.md): [base GUI](../home/linux/gui/base/README.md),
+ [Fcitx5](../home/linux/gui/base/fcitx5/README.md),
+ [hypridle](../home/linux/gui/base/hypridle/README.md),
+ [vinput](../home/linux/gui/base/vinput/README.md) and [i3](../home/linux/gui/i3/README.md).
+- [Base Home Manager profile](../home/base/README.md):
+ [editors](../home/base/core/editors/README.md),
+ [editor glossary](../home/base/core/editors/Glossary.md),
+ [Helix](../home/base/core/editors/helix/README.md),
+ [Neovim](../home/base/core/editors/neovim/README.md), [Rime](../home/base/gui/rime/README.md) and
+ [terminal](../home/base/gui/terminal/README.md).
+- Terminal tools: [agents](../home/base/tui/agents/README.md) and
+ [dsh notes](../home/base/tui/agents/dsh.md), [editors](../home/base/tui/editors/README.md),
+ [encryption](../home/base/tui/encryption/README.md), [GPG](../home/base/tui/gpg/README.md) and
+ [password store](../home/base/tui/password-store/README.md).
+
+## Agent resources
+
+- [Global rules, skills and evaluations](../agents/README.md).
+- [Skill catalog](../agents/skills/README.md).
+- [Global agent rules](../agents/global-rules.md),
+ [evaluation guide](../agents/evals/global-rules.md) and
+ [tooling installation notes](../agents/install-tooling.md).
diff --git a/SECURITY.md b/docs/SECURITY.md
similarity index 95%
rename from SECURITY.md
rename to docs/SECURITY.md
index b08d49b1..683bd5fc 100644
--- a/SECURITY.md
+++ b/docs/SECURITY.md
@@ -4,8 +4,8 @@ This is a personal infrastructure repository, not a supported security product.
describes the intended architecture, controls present in source, their limitations, and remaining
work. A configured control is not evidence that it is active on a running machine.
-Related documents: [application hardening](hardening/README.md) for sandbox wrappers and Linux
-hardening notes, and [WORKAROUNDS.md](WORKAROUNDS.md) for temporary exceptions and known gaps.
+Related documents: [application hardening](../hardening/README.md) for sandbox wrappers and Linux
+hardening notes, and [WORKAROUNDS.md](./WORKAROUNDS.md) for temporary exceptions and known gaps.
## Reporting problems
@@ -46,12 +46,12 @@ small, reversible controls and narrow exceptions; benchmark before adopting expe
| Supply chain | Locked flake inputs; explicit binary-cache trust keys on NixOS; normal user is not a Nix trusted-user | A trusted cache or imported Nix module can supply code executed as root; a lock file is not a security audit |
| Recovery | NixOS generations, btrbk/restic, hourly read-only snapshots of the REST store, and documented backup procedures | A rollback does not undo compromised data or secrets; backup configuration alone does not prove restore works; the offsite immutable copy is still planned |
-Sources: [host layout](hosts/README.md), [k3s deployment](hosts/k8s/README.md),
-[firewall](modules/nixos/base/networking/firewall.nix), [SSH](modules/nixos/base/ssh.nix),
-[Nix trust](modules/base/nix.nix), [secrets](secrets/README.md), and [backup](BACKUP.md). Cluster
-resources are maintained in the separate k8s-gitops repository.
+Sources: [host layout](../hosts/README.md), [k3s deployment](../hosts/k8s/README.md),
+[firewall](../modules/nixos/base/networking/firewall.nix), [SSH](../modules/nixos/base/ssh.nix),
+[Nix trust](../modules/base/nix.nix), [secrets](../secrets/README.md), and [backup](./BACKUP.md).
+Cluster resources are maintained in the separate k8s-gitops repository.
-The Macs set `nix.enable = false` ([nix-core.nix](modules/darwin/nix-core.nix)), so nix-darwin
+The Macs set `nix.enable = false` ([nix-core.nix](../modules/darwin/nix-core.nix)), so nix-darwin
writes no `/etc/nix/nix.conf`: the substituters, trusted keys and access-token include declared here
do not reach them. Their Nix settings come from the Determinate installation, which this repository
does not manage; those settings were not verified on the Macs.
@@ -59,7 +59,7 @@ does not manage; those settings were not verified on the Macs.
## Kernel and process baseline
The following explicit, overridable baseline is implemented in
-[kernel-hardening.nix](hardening/kernel-hardening.nix), which holds the values; the table below
+[kernel-hardening.nix](../hardening/kernel-hardening.nix), which holds the values; the table below
summarizes them. It has Linux eval coverage and explicit ARM/RISC-V server imports. Source
implementation is not runtime verification. Some values are already kernel/systemd/NixOS defaults;
Darwin does not receive Linux sysctls.
@@ -97,8 +97,8 @@ an advisory and affected kernel versions before adding CVE claims; search snippe
### Boot integrity and mandatory access control
-- ai configures Lanzaboote in [secureboot.nix](hosts/idols-ai/secureboot.nix). Confirm firmware key
- enrollment and actual Secure Boot state with `sbctl status`; do not assume all hosts have it.
+- ai configures Lanzaboote in [secureboot.nix](../hosts/idols-ai/secureboot.nix). Confirm firmware
+ key enrollment and actual Secure Boot state with `sbctl status`; do not assume all hosts have it.
- Secure Boot, signed boot images, kernel lockdown and module signature enforcement are distinct
controls. Test custom kernels and NVIDIA/out-of-tree modules before extending enforcement.
- AppArmor is enabled, but the only explicit policy, sudo's, is in complain mode. Complain mode logs
@@ -106,17 +106,18 @@ an advisory and affected kernel versions before adding CVE claims; search snippe
broad enforced application confinement.
- Systemd service sandboxing is per-service, not a global policy. The exposed services nixpkgs
leaves weakly sandboxed (caddy, rustfs, vmalert, whisper-asr) use the preset in
- [systemdHardening.nix](lib/systemdHardening.nix). Review capabilities, writable paths, network
+ [systemdHardening.nix](../lib/systemdHardening.nix). Review capabilities, writable paths, network
access and syscall requirements before tightening an individual service.
-See [application hardening](hardening/README.md) for wrapper details.
+See [application hardening](../hardening/README.md) for wrapper details.
### Audit trail
-`modules.security.audit` ([auditd.nix](modules/nixos/base/auditd.nix)) runs auditd on youko, shoryu,
-shushou and aquamarine. It records, for login sessions only (SSH, and sudo or su from one): every
-root command (`root-session`), access to decrypted secrets and SSH host keys (`credential-access`),
-writes to SSH, account/PAM, Nix trust and audit configuration, module loads, and denials.
+`modules.security.audit` ([auditd.nix](../modules/nixos/base/auditd.nix)) runs auditd on youko,
+shoryu, shushou and aquamarine. It records, for login sessions only (SSH, and sudo or su from one):
+every root command (`root-session`), access to decrypted secrets and SSH host keys
+(`credential-access`), writes to SSH, account/PAM, Nix trust and audit configuration, module loads,
+and denials.
- Services are not audited: a `never,task` rule gives tasks without a login uid no audit context, so
virtiofsd, hypervisors, NFS, PostgreSQL and Nix builds skip the per-syscall audit path. A
@@ -228,7 +229,7 @@ checks remain necessary after deployment.
reaching the export, but a root user on an allowed client still has server-root write access.
`root_squash` is deferred: the existing PVC subdirectories are root-owned (observed
`drwxr-xr-x root root` and `drwxrwsr-x root 107`), so enabling it needs a one-time ownership
- migration and a maintenance window. See WORKAROUNDS.md.
+ migration and a maintenance window. See docs/WORKAROUNDS.md.
- **Remote consoles:** the libvirt VNC consoles listen on `127.0.0.1` only, and the k3s firewall
drops 5900-5903 from pod sources. Reach a console through an SSH tunnel.
- **Pod-to-host firewall:** two independent layers deny pod access to the credential-bearing host
@@ -324,7 +325,7 @@ isolation. Source review and socket metadata—not reads of private credentials
alias or GitHub destination. Do not treat forwarding as enabled for every LAN alias, or remove it
without checking deployment/build workflows.
- All nine desktop wrappers use shared nixpak policies: cleared environment, filtered session D-Bus
- and selected mounts. The [permission inventory](hardening/README.md#app-permissions) lists each
+ and selected mounts. The [permission inventory](../hardening/README.md#app-permissions) lists each
app's file, network, credential and media grants.
- Only Firefox/Chromium share host networking. Chromium is an intentional local-development
exception; all other apps isolate loopback with pasta but can still reach the LAN/Internet.
@@ -348,7 +349,7 @@ isolation. Source review and socket metadata—not reads of private credentials
Chromium, Brave Origin, Edge, QQ, Telegram and Zoom; the Chromium smoke test also validates
renderer sandboxing, environment clearing, bind scope and profile persistence. WeChat's
interactive Wayland check remains pending. See the
- [runtime validation](hardening/tests/runtime-validation.md).
+ [runtime validation](../hardening/tests/runtime-validation.md).
#### Agent-first diagnostics by host role
@@ -414,7 +415,7 @@ separate verification.
| P2 | Validate narrower sandbox/agent boundaries where needed | Source and selected SSH/journal metadata audited; use disposable data/socket probes and preserve core-desktop diagnosis, browserpass and Homelab root workflows |
| P3 | Improve security-event alerts and configuration drift checks | Actionable redacted alerts and periodic runtime control readback |
-Record temporary exceptions in [WORKAROUNDS.md](WORKAROUNDS.md) with a removal condition. Revisit
+Record temporary exceptions in [WORKAROUNDS.md](./WORKAROUNDS.md) with a removal condition. Revisit
this architecture after a new exposed service, network/storage change, host addition or significant
kernel/nixpkgs update. No control here guarantees containment after host root compromise.
diff --git a/WORKAROUNDS.md b/docs/WORKAROUNDS.md
similarity index 99%
rename from WORKAROUNDS.md
rename to docs/WORKAROUNDS.md
index 3e6bd54d..0203468d 100644
--- a/WORKAROUNDS.md
+++ b/docs/WORKAROUNDS.md
@@ -86,7 +86,7 @@ An unfinished feature or gap.
- Security architecture, threat model and audit status: [SECURITY.md](./SECURITY.md).
- Unmerged **nixpkgs** PRs are carried via the `nixpkgs-patched` input and the
- [`.agents/skills/nixpkgs-patched`](./.agents/skills/nixpkgs-patched/SKILL.md) workflow (currently
+ [`.agents/skills/nixpkgs-patched`](../.agents/skills/nixpkgs-patched/SKILL.md) workflow (currently
none).
- Routine version pins (colmena, agenix, disko, lanzaboote, haumea, home-manager, `nixpkgs-stable`,
`nixpkgs-darwin`, …) are the versions in use, not workarounds, so they are not listed here.
diff --git a/docs/nixos-modernization-options.md b/docs/nixos-modernization-options.md
index 88c353c8..e465f94e 100644
--- a/docs/nixos-modernization-options.md
+++ b/docs/nixos-modernization-options.md
@@ -39,7 +39,7 @@ fallback, and a root-readable Cloudflare token in agenix.
**Pros:** certificates become automatic and every tailnet device trusts them (today a phone without
`ecc-ca` gets a certificate error); removes the Cloudflare token and the DNS-01 propagation
workaround from those services; access control moves from "IP trust zone" to tailnet identity/ACL,
-which is exactly the limitation `SECURITY.md` records; `--tcp=` can also expose raw TCP.
+which is exactly the limitation `docs/SECURITY.md` records; `--tcp=` can also expose raw TCP.
**Cons:** Serve routes by mount path on one name, or (via the nixpkgs module) as Tailscale Services
with `tcp:` endpoints only — there is no Host/SNI vhost equivalent, so path-sensitive apps
@@ -285,10 +285,10 @@ flake input is maintained separately for Secure Boot.
the lanzaboote flake input; checksum validation of kernel and initrd; nicer menu.
**Cons:** different trust model — it signs **limine itself** and validates artifacts by checksum,
-rather than shipping a firmware-verified UKI, so `SECURITY.md` and the threat model must be updated;
-key enrollment is a manual firmware dance (Setup Mode + `sbctl enroll-keys`) and is the one change
-in this repository that can leave a machine unbootable; the existing lzpt bundle may or may not be
-reusable; less NixOS mileage than systemd-boot.
+rather than shipping a firmware-verified UKI, so `docs/SECURITY.md` and the threat model must be
+updated; key enrollment is a manual firmware dance (Setup Mode + `sbctl enroll-keys`) and is the one
+change in this repository that can leave a machine unbootable; the existing lzpt bundle may or may
+not be reusable; less NixOS mileage than systemd-boot.
**Reversibility:** low on the physical desktop (firmware + ESP state), high on a VM.
@@ -466,7 +466,7 @@ prevent boot or login. **Recommendation for the group: evaluate in a disposable
| D4 | `system.nixos-init.enable` | `activationScripts` | bashless init | makes `activationScripts` a silent no-op — youko's `data-ro-backing`, `installCaddyWallpapers` and `installHomepageDashboardConfig` would stop running without an error | lab only |
| D5 | `security.pam.services..rules` | per-service booleans | freeform PAM config | upstream marks it subject to breaking changes; a mistake locks you out | lab only |
| D6 | `security.shadow.enable = false` + greetd | shadow suite provides login/su | fewer setuid programs | assertion couples the two; changes VT login semantics | lab only |
-| D7 | `security.apparmor.policies` | `policies.sudo` already in use (`hardening/apparmor/default.nix`); the old `profiles` option is removed upstream | policy set is now data | writing profiles is real work and breakage-prone; `SECURITY.md` explicitly warns against untested profiles | lab only |
+| D7 | `security.apparmor.policies` | `policies.sudo` already in use (`hardening/apparmor/default.nix`); the old `profiles` option is removed upstream | policy set is now data | writing profiles is real work and breakage-prone; `docs/SECURITY.md` explicitly warns against untested profiles | lab only |
---
@@ -520,7 +520,7 @@ header) should then be trimmed to **adopt** items only.
`services/system/{userborn,nix-daemon,zram-generator}.nix`,
`services/monitoring/prometheus/exporters.nix`.
- NixOS 26.05 release notes; Nix manual, Experimental Features.
-- Repo context: `SECURITY.md` (trust zones and control limits), `WORKAROUNDS.md` (WA-001, WA-008,
- WA-009, WA-023), `modules/nixos/base/networking/mdns.nix`,
+- Repo context: `docs/SECURITY.md` (trust zones and control limits), `docs/WORKAROUNDS.md` (WA-001,
+ WA-008, WA-009, WA-023), `modules/nixos/base/networking/mdns.nix`,
`hosts/12kingdoms-youko/homelab-services/caddy.nix`,
`hosts/12kingdoms-youko/homelab-services/README.md`.
diff --git a/flake.nix b/flake.nix
index 40d195fa..d643a91a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -145,7 +145,7 @@
# x86/x86_64 emulation on 16K-page ARM64 (Apple Silicon). FEX cannot run
# natively with 16K pages, so this wraps FEX in a 4K-page muvm microVM and
# exposes an `x86pkgs` set plus Steam/Wine launchers. Used by shoukei; see
- # hosts/12kingdoms-shoukei and WORKAROUNDS.md (WA-021).
+ # hosts/12kingdoms-shoukei and docs/WORKAROUNDS.md (WA-021).
#
# Pinned to our fork of rowanG077/nix-x86-on-aarch64 at the reviewed commit
# 5d69793a. To update, fetch upstream into the fork, review the new commits,
diff --git a/hardening/README.md b/hardening/README.md
index ea4cd376..fa409b03 100644
--- a/hardening/README.md
+++ b/hardening/README.md
@@ -1,7 +1,7 @@
# Linux Hardening
Deployed and interactively validated on the Linux desktop host (2026-10-31). Policy and limits:
-[SECURITY.md](../SECURITY.md).
+[SECURITY.md](../docs/SECURITY.md).
## Why these apps are sandboxed
diff --git a/hardening/kernel-hardening.nix b/hardening/kernel-hardening.nix
index 269d4453..13b14e44 100644
--- a/hardening/kernel-hardening.nix
+++ b/hardening/kernel-hardening.nix
@@ -2,7 +2,7 @@
{
# Keep this baseline independent of a particular kernel CVE. User namespaces
# remain available for Nix builds, rootless containers and application sandboxes.
- # See SECURITY.md for compatibility constraints and post-deployment checks.
+ # See docs/SECURITY.md for compatibility constraints and post-deployment checks.
boot.kernel.sysctl = {
"kernel.dmesg_restrict" = lib.mkDefault 1;
# NixOS already sets mkDefault 1; use a stronger default, still overridable
diff --git a/hosts/12kingdoms-shoukei/README.md b/hosts/12kingdoms-shoukei/README.md
index 1dd7ed0e..d426e2eb 100644
--- a/hosts/12kingdoms-shoukei/README.md
+++ b/hosts/12kingdoms-shoukei/README.md
@@ -20,11 +20,11 @@ inside a 4 KiB `muvm` microVM and registers both ELF handlers:
- `steam` and `x86-arm-wine` for games and Windows programs.
The microVM is capped at 6 GiB (`memoryMiB`) on this 16 GiB machine, and needs `/dev/kvm` plus a
-running systemd user session. See [WORKAROUNDS.md](../../WORKAROUNDS.md) WA-021.
+running systemd user session. See [WORKAROUNDS.md](../../docs/WORKAROUNDS.md) WA-021.
Because the host no longer registers qemu-user binfmt, `podman run --platform=linux/amd64`, x86
chroots, and local builds of uncached x86 derivations need a remote x86_64 builder (see
-[WORKAROUNDS.md](../../WORKAROUNDS.md) WA-022).
+[WORKAROUNDS.md](../../docs/WORKAROUNDS.md) WA-022).
Related:
diff --git a/hosts/12kingdoms-shoukei/apple-silicon.nix b/hosts/12kingdoms-shoukei/apple-silicon.nix
index f4eecae4..4b9770a3 100644
--- a/hosts/12kingdoms-shoukei/apple-silicon.nix
+++ b/hosts/12kingdoms-shoukei/apple-silicon.nix
@@ -31,7 +31,7 @@
# BCM4378/brcmfmac: disable Wi-Fi power saving. Powersave is a common source
# of intermittent association failures / authentication timeouts, which NM
# mistakes for a bad key, re-prompts, and lets the client (Noctalia, nmtui,
- # nmcli) save as a duplicate " 1" profile. See WORKAROUNDS.md WA-024/025.
+ # nmcli) save as a duplicate " 1" profile. See docs/WORKAROUNDS.md WA-024/025.
networking.networkmanager.wifi.powersave = false;
# Specify path to peripheral firmware files.
diff --git a/hosts/12kingdoms-shoukei/game/default.nix b/hosts/12kingdoms-shoukei/game/default.nix
index ab0111d6..9863f2b0 100644
--- a/hosts/12kingdoms-shoukei/game/default.nix
+++ b/hosts/12kingdoms-shoukei/game/default.nix
@@ -2,7 +2,7 @@
#
# FEX cannot run on the 16K pages this machine boots with, so
# nix-x86-on-aarch64 runs FEX inside a 4K-page muvm microVM and owns the
-# x86_64/i686 ELF handlers. Pinned to our fork; see WORKAROUNDS.md WA-021.
+# x86_64/i686 ELF handlers. Pinned to our fork; see docs/WORKAROUNDS.md WA-021.
{ nix-x86-on-aarch64, ... }:
{
imports = [ nix-x86-on-aarch64.nixosModules.default ];
diff --git a/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix b/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix
index 3db60a1c..717f57c9 100644
--- a/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix
+++ b/hosts/12kingdoms-youko/homelab-services/grafana/datasources.nix
@@ -91,7 +91,7 @@
# Disabled: the `playground` role has no password and remote logins
# require scram-sha-256, so this datasource could never authenticate.
# Re-enable once a `playground` DB password is provisioned (the PostgreSQL
- # per-role password follow-up in SECURITY.md).
+ # per-role password follow-up in docs/SECURITY.md).
/*
{
# https://grafana.com/docs/grafana/latest/datasources/postgres/configure/
diff --git a/hosts/12kingdoms-youko/homelab-services/transmission.nix b/hosts/12kingdoms-youko/homelab-services/transmission.nix
index b3aebc06..374a6404 100644
--- a/hosts/12kingdoms-youko/homelab-services/transmission.nix
+++ b/hosts/12kingdoms-youko/homelab-services/transmission.nix
@@ -59,7 +59,7 @@ in
{
# transmission 4.1.4 takes an fcntl lock on its config-dir lock file, but the
# AppArmor profile shipped by nixpkgs grants rw without k (lock), so the
- # daemon exits with "Another process is already using". See WORKAROUNDS.md.
+ # daemon exits with "Another process is already using". See docs/WORKAROUNDS.md.
security.apparmor.includes."local/bin.transmission-daemon" = ''
/var/lib/transmission/.config/transmission-daemon/lock rwk,
'';
diff --git a/hosts/12kingdoms-youko/nfs-golden.nix b/hosts/12kingdoms-youko/nfs-golden.nix
index e5ed2e81..b36acaf8 100644
--- a/hosts/12kingdoms-youko/nfs-golden.nix
+++ b/hosts/12kingdoms-youko/nfs-golden.nix
@@ -39,5 +39,5 @@ in
# NFSv4 only needs 2049/tcp (no rpcbind/mountd/statd). The export is limited
# to the hosts above, not the whole LAN. `no_root_squash` stays until the
# existing root-owned PVC subdirectories are migrated for `root_squash`; see
- # SECURITY.md.
+ # docs/SECURITY.md.
}
diff --git a/hosts/idols-aquamarine/asr.nix b/hosts/idols-aquamarine/asr.nix
index 89ed94c4..f91ec26a 100644
--- a/hosts/idols-aquamarine/asr.nix
+++ b/hosts/idols-aquamarine/asr.nix
@@ -13,7 +13,7 @@ let
# OpenVINO publishes static IRs for Whisper only, and the NPU compiler accepts
# nothing less: any other ASR family keeps a dynamic time axis and aborts it
- # (WORKAROUNDS.md WA-037). Pinned by content because the NPU caches the
+ # (docs/WORKAROUNDS.md WA-037). Pinned by content because the NPU caches the
# compiled graph; a version bump should be deliberate.
modelFiles = {
"added_tokens.json" = "sha256-PFH2bEwh+eEmlwB48RrnenjHSu6N9gbunauobkZxCOA=";
diff --git a/hosts/idols-aquamarine/asr/server.py b/hosts/idols-aquamarine/asr/server.py
index c94a1f24..e21ef493 100644
--- a/hosts/idols-aquamarine/asr/server.py
+++ b/hosts/idols-aquamarine/asr/server.py
@@ -4,7 +4,7 @@ vinput records raw PCM (S16_LE, mono, 16 kHz), wraps it in a WAV container and
POSTs it to /v1/audio/transcriptions; this service is that endpoint. Whisper is
the only ASR family OpenVINO publishes fully static IRs for, and a static graph
is what the NPU compiler accepts -- every other family keeps a dynamic time axis
-and aborts the compiler, see WORKAROUNDS.md WA-037.
+and aborts the compiler, see docs/WORKAROUNDS.md WA-037.
"""
import io
diff --git a/hosts/k8s/README.md b/hosts/k8s/README.md
index 90b0f950..526ffda8 100644
--- a/hosts/k8s/README.md
+++ b/hosts/k8s/README.md
@@ -37,9 +37,9 @@ just microvm-deploy
For example, `just microvm-deploy k3s-test-1-master-3 shushou`. The recipe copies the runner to the
physical host, then restarts `microvm@` there; microvm.nix's SSH switch is not used because
it cannot write the guest's read-only `/nix/store` (WA-026 in
-[WORKAROUNDS.md](../../WORKAROUNDS.md)). A later activation of the physical host re-points the guest
-at the runner baked into the host's system, so switch the host after installing a guest from a newer
-tree.
+[WORKAROUNDS.md](../../docs/WORKAROUNDS.md)). A later activation of the physical host re-points the
+guest at the runner baked into the host's system, so switch the host after installing a guest from a
+newer tree.
Use `just build-microvm ` to build a runner without deploying it. Preserve the guest's
`etc.img`, `var.img`, and `home.img` when updating it; they contain its identity and K3s state.
diff --git a/modules/README.md b/modules/README.md
index ab9b3cbd..e3f15993 100644
--- a/modules/README.md
+++ b/modules/README.md
@@ -38,7 +38,7 @@ modules/
│ ├── networking/ # Network configuration
│ ├── nix.nix # Nix settings
│ ├── packages.nix # System packages
- │ ├── restic-backup.nix # Encrypted off-host backups (see ../BACKUP.md)
+ │ ├── restic-backup.nix # Encrypted off-host backups (see ../docs/BACKUP.md)
│ ├── ssh.nix # SSH daemon configuration
│ ├── trash.nix # Trash retention cleanup
│ ├── user-group.nix # User and group management
diff --git a/modules/nixos/base/auditd.nix b/modules/nixos/base/auditd.nix
index 45ce14e5..699d6617 100644
--- a/modules/nixos/base/auditd.nix
+++ b/modules/nixos/base/auditd.nix
@@ -10,7 +10,7 @@ let
inherit (config.services) openssh;
# Audit trail for login sessions, so that a stolen deployment key leaves a
- # record of what each session did; see SECURITY.md ("Audit trail").
+ # record of what each session did; see docs/SECURITY.md ("Audit trail").
# `auditctl -R` aborts at the first watch whose path is missing, leaving
# auditing off, so every watched path must exist when the rules load.
diff --git a/outputs/README.md b/outputs/README.md
index fada8371..eae00642 100644
--- a/outputs/README.md
+++ b/outputs/README.md
@@ -72,7 +72,7 @@ Problems:
agenix secrets.
- [ ] Maybe it's better to test every service separately, not the whole host?
-Full-host tests are tracked as WA-013 in [WORKAROUNDS.md](../WORKAROUNDS.md).
+Full-host tests are tracked as WA-013 in [WORKAROUNDS.md](../docs/WORKAROUNDS.md).
`outputs/x86_64-linux/nixos-tests/idols-ruby.nix` is exported as
`packages.x86_64-linux.ruby-nixos-tests`, but it is not a `checks` entry, so CI never builds it and
nothing else runs it. What runs today is the eval set above with `just test`, plus one NixOS-driver
@@ -122,5 +122,5 @@ All the outputs of this flake are defined here.
The tree lists the per-host sources and tests. The flake also exposes a `checks` output: every
system has `eval-tests` and `pre-commit-check`, and `x86_64-linux` adds `security-exporters`, which
-imports the top-level `tests/security-exporters.nix`. See [`../SECURITY.md`](../SECURITY.md) for
-when to run it.
+imports the top-level `tests/security-exporters.nix`. See
+[`../docs/SECURITY.md`](../docs/SECURITY.md) for when to run it.
diff --git a/outputs/default.nix b/outputs/default.nix
index 2af9b60f..e5b7d5b6 100644
--- a/outputs/default.nix
+++ b/outputs/default.nix
@@ -136,7 +136,7 @@ let
deployment
# Colmena evaluates with nixpkgs' plain lib, which lacks the version the
# flake's lib reports; without this the toplevel differs (`26.11pre-git`).
- # Recorded as WA-042 in WORKAROUNDS.md.
+ # Recorded as WA-042 in docs/WORKAROUNDS.md.
{
system.nixos.versionSuffix = lib.trivial.versionSuffix;
system.nixos.revision = lib.trivial.revisionWithDefault null;
diff --git a/scripts/shoukei-home-wifi-static.nu b/scripts/shoukei-home-wifi-static.nu
index bcf5a63d..3e821f2a 100644
--- a/scripts/shoukei-home-wifi-static.nu
+++ b/scripts/shoukei-home-wifi-static.nu
@@ -11,7 +11,7 @@
#
# just shoukei-home-wifi
#
-# See hosts/12kingdoms-shoukei/README.md and WORKAROUNDS.md WA-024/025.
+# See hosts/12kingdoms-shoukei/README.md and docs/WORKAROUNDS.md WA-024/025.
# ---------------------------------------------------------------------------
# Configuration