Merge pull request 'Quarantine what the gate could not see; delete what the repo only pretended to own' (#41) from fix/40-dotfiles-hygiene into main
This commit was merged in pull request #41.
This commit is contained in:
+87
-10
@@ -1,25 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# Secret gate: block any commit whose staged diff contains live-secret shapes.
|
||||
# This repo is pushed to a Gitea remote; anything committed is instantly visible
|
||||
# Secret gate: block any commit whose staged diff contains live-secret shapes or
|
||||
# credential assignments. This repo IS the deployment and every workstation
|
||||
# pulls it, so a committed secret is instantly in every agent's context window
|
||||
# on the fleet. Deliberate override: ALLOW_SECRETS=1 git commit ...
|
||||
#
|
||||
# Two rules, deliberately different in scope:
|
||||
#
|
||||
# 1. Token SHAPES are scanned across the ENTIRE staged diff, whatever the file
|
||||
# type. The first version of this gate whitelisted extensions
|
||||
# (*.md *.yml *.yaml *.json *.toml *.sh *.ts *.js), so a `postgres.env`, a
|
||||
# `.container` quadlet, an `.ini`, or an extension-less dotfile — exactly
|
||||
# the shapes the dotfiles tier is made of — was never read at all.
|
||||
# 2. Credential ASSIGNMENTS (PASSWORD=…, secret_key: …) are scanned only in
|
||||
# non-prose files. The auto-learned memory tier is prose ABOUT credentials,
|
||||
# and a gate that fires there gets overridden until it means nothing; the
|
||||
# 2026-10-20 audit measured 7 such prose hits on the tracked tree. Markdown
|
||||
# keeps shape detection only, which is what it had before.
|
||||
#
|
||||
# Bcrypt hashes and Basic-auth headers joined the shape list on 2026-10-20
|
||||
# (issue #40) after a live Caddy basicauth hash plus its plaintext password —
|
||||
# and the Pi-hole internal password — were found in a tracked memory file.
|
||||
set -uo pipefail
|
||||
|
||||
staged=$(git diff --cached --diff-filter=ACM -U0 -- '*.md' '*.yml' '*.yaml' '*.json' '*.toml' '*.sh' '*.ts' '*.js')
|
||||
[ -z "$staged" ] && exit 0
|
||||
shapes='sk-[A-Za-z0-9]{18,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|glpat-[A-Za-z0-9_-]{20}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[A-Z0-9]{16}|ASIA[A-Z0-9]{16}|BEGIN [A-Z ]*PRIVATE KEY|aes_[A-Za-z0-9/+]{40}|eyJ[A-Za-z0-9_-]{20,}\.ey|\$2[aby]\$[0-9]{2}\$[./A-Za-z0-9]{20,}|Authorization:[[:space:]]*[Bb]asic[[:space:]]+[A-Za-z0-9+/=]{24,}'
|
||||
|
||||
patterns='sk-[A-Za-z0-9]{18,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|glpat-[A-Za-z0-9_-]{20}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[A-Z0-9]{16}|ASIA[A-Z0-9]{16}|BEGIN [A-Z ]*PRIVATE KEY|aes_[A-Za-z0-9/+]{40}|eyJ[A-Za-z0-9_-]{20,}\.ey'
|
||||
# `newpass1234`, `secret_key: "rotated-value"` — an assignment with a literal
|
||||
# value. `$VAR`, `${VAULT}`, <placeholders> and empty values cannot match.
|
||||
assign='[A-Za-z0-9_]*(PASSW|SECRET|TOKEN|API_?KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIAL)[A-Za-z0-9_]*[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9/+._@-]{8,}'
|
||||
placeholders='changeme|change[-_]|placeholder|redacted|example|sample|dummy|nokey|no[-_]key|not[-_]needed|your[-_]|localhost|xxxx|yyyy|\$\{|<[a-z]|\*\*\*|%s|null|false|true'
|
||||
|
||||
# Exemptions: a variable naming a location (`…_FILE=`, `…_PATH=`) or a value
|
||||
# that is a filesystem path is a pointer, not a credential. Flagging those is
|
||||
# how a gate gets permanently overridden.
|
||||
assign_exempt='(_FILE|_PATH|_DIR|_URL|_ENDPOINT|_SOCKET)[[:space:]]*[:=]|[:=][[:space:]]*["'"'"']?(/|\.\./)'
|
||||
# Fail closed: a gate that cannot read the staged diff must not report success.
|
||||
if ! files=$(git diff --cached --diff-filter=ACM --name-only 2>&1); then
|
||||
{
|
||||
echo "SECRET GATE: cannot read the staged diff — refusing to commit."
|
||||
printf ' %s\n' "$files"
|
||||
echo "Override deliberately: ALLOW_SECRETS=1 git commit ..."
|
||||
} >&2
|
||||
[ -n "${ALLOW_SECRETS:-}" ] && exit 0
|
||||
exit 1
|
||||
fi
|
||||
# stderr is captured so the failure branch can quote it; on success it must not
|
||||
# leak a `warning:` line into the file list and have it mistaken for a path.
|
||||
files=$(printf '%s\n' "$files" | grep -vE '^(warning|fatal|error):' || true)
|
||||
[ -z "${files//[[:space:]]/}" ] && exit 0
|
||||
|
||||
report=""
|
||||
exempt=""
|
||||
count=0
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
# Self-exemption, deliberately an exact filename list and not `tests/`: the
|
||||
# gate's fixtures must contain credential-shaped strings or there is nothing to
|
||||
# test against, and this hook documents the shapes it looks for. A secret parked
|
||||
# in any other test file is still caught.
|
||||
case "$f" in
|
||||
.githooks/pre-commit|tests/secret-gate.bats) exempt="$exempt $f"; continue ;;
|
||||
esac
|
||||
diff=$(git diff --cached -U0 -- "$f")
|
||||
[ -z "$diff" ] && continue
|
||||
# Only ADDED lines can leak. Matching removals too blocks the very commit that
|
||||
# deletes a credential — which is exactly what happened on 2026-10-20, when the
|
||||
# gate refused the redaction of the password it exists to catch.
|
||||
added=$(printf '%s\n' "$diff" | grep -E '^\+' | grep -vE '^\+\+\+' | sed 's/^+//' || true)
|
||||
[ -z "$added" ] && continue
|
||||
hits=$(printf '%s\n' "$added" | grep -Ei "$shapes" || true)
|
||||
if [ "${f##*.}" != "md" ]; then
|
||||
assigns=$(printf '%s\n' "$added" | grep -Ei "$assign" | grep -Ev "$assign_exempt" | grep -Eiv "$placeholders" || true)
|
||||
hits=$(printf '%s\n%s\n' "$hits" "$assigns" | sed '/^[[:space:]]*$/d')
|
||||
fi
|
||||
if [ -n "$hits" ]; then
|
||||
n=$(printf '%s\n' "$hits" | wc -l)
|
||||
count=$((count + n))
|
||||
report+=" $f"$'\n'"$(printf '%s\n' "$hits" | cut -c1-100 | sed 's/^/ /')"
|
||||
fi
|
||||
done <<< "$files"
|
||||
|
||||
# Say so out loud: an exemption that prints nothing is how a carve-out becomes
|
||||
# invisible, and invisible is the property a secret wants.
|
||||
if [ -n "$exempt" ]; then
|
||||
printf 'secret-gate: self-test fixtures exempt:%s\n' "$exempt" >&2
|
||||
fi
|
||||
|
||||
hits=$(printf '%s\n' "$staged" | grep -Ei "$patterns" || true)
|
||||
if [ -n "${ALLOW_SECRETS:-}" ]; then
|
||||
[ -n "$hits" ] && printf 'secret-gate: ALLOW_SECRETS set, allowing %s secret-shaped string(s) through.\n' "$(printf '%s\n' "$hits" | wc -l)"
|
||||
[ -n "$report" ] && printf 'secret-gate: ALLOW_SECRETS set, allowing %s secret-shaped string(s) through.\n' "$count"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$hits" ]; then
|
||||
if [ -n "$report" ]; then
|
||||
{
|
||||
echo "SECRET GATE: staged diff contains $(printf '%s\n' "$hits" | wc -l) secret-shaped string(s):"
|
||||
printf '%s\n' "$hits" | sed 's/^/ /'
|
||||
echo "SECRET GATE: staged diff contains $count secret-shaped string(s):"
|
||||
printf '%s\n' "$report"
|
||||
echo "Fix it, or override deliberately: ALLOW_SECRETS=1 git commit ..."
|
||||
echo "A credential that reaches a tracked file is already in history on every"
|
||||
echo "machine that pulls this repo — deleting it later does not un-leak it."
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+13
@@ -26,6 +26,10 @@ auth.json
|
||||
**/auth.json
|
||||
.env
|
||||
**/.env
|
||||
*.env
|
||||
# `*.env` is the rule that matters: `.env` alone does not match `postgres.env`
|
||||
# or `frank-job-hunt.env`, and those are the files that actually carry
|
||||
# POSTGRES_PASSWORD / API keys on this fleet (issue #40).
|
||||
trust.json
|
||||
**/trust.json
|
||||
*.pem
|
||||
@@ -74,3 +78,12 @@ wt/
|
||||
# A session run inside a git worktree mints a bucket named after the worktree
|
||||
# path; the worktree is deleted, the bucket is orphan state, not config.
|
||||
agent/memories/--home-blank-.omp-wt-*
|
||||
|
||||
# --- theme media: content the pipeline consumes, not configuration ---------
|
||||
# 23 wallpapers, 80 MiB. Tracked until 2026-10-20; every clone of a config repo
|
||||
# was carrying a library no diff can review. The files stay on disk because
|
||||
# ~/.local/share/noctalia/wallpapers symlinks into here, and every filename
|
||||
# carries its wallhaven ID, so a fresh box restores them with
|
||||
# `curl -L -o <name> https://wallhaven.cc/w/<id>` + deploy.sh.
|
||||
# Law: omp-docs/tools/dotfiles-deploy.md
|
||||
dotfiles/noctalia/.local/share/noctalia/wallpapers/
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: command-center-dotfiles-import
|
||||
description: Runbook for importing client env-layer configs into the command-center repo and deploying them via stow on new fleet machines; decisions agreed with Frank 2026-09-09 (pre-execution — verify execution state before assuming done).
|
||||
---
|
||||
|
||||
Design agreed with Frank for importing client application/environment configs into the `~/.omp` command-center repo and deploying them on fresh machines. **Status: plan agreed, not yet executed** — check repo state (does `dotfiles/` or `scripts/deploy.sh` exist?) before re-deriving; if already implemented, this becomes the operating model, not a to-do.
|
||||
|
||||
## Agreed decisions (each was an explicit user choice)
|
||||
1. **Target**: unknown/TBD fresh machine → design stays host-agnostic; deploy derives overrides from `$(hostname)` at run time. Nothing hardcodes a target.
|
||||
2. **Scope**: environment layer only — niri (+cfg includes), gtk-3.0/4.0 settings, ghostty, alacritty, shell rc files (`.zshrc`, `.bashrc`, `.profile`, `.p10k.zsh`), fish, systemd user units, autostart, dconf. App *data* (browser profiles, vaults, DBs) never imported.
|
||||
3. **Layout**: stow-style package dirs `dotfiles/<pkg>/<home-relative path>` inside the repo; repo clones to `~/.omp` on every host that runs the harness (full command-center clone, not env-layer only).
|
||||
4. **Deploy mechanism**: symlink farm — `stow -t ~` from `~/.omp/dotfiles`; relative symlinks within `$HOME` work because repo root lives inside `$HOME`.
|
||||
5. **Dogfood order**: convert the workstation itself (real dirs → symlinks via adopt) BEFORE any target import; iterate where the compositor reload is observable.
|
||||
6. **Secrets policy**: never commit secret values. Split rc files into committed logic + untracked sourced-if-exists stub (`~/.config/environment.d/secrets.env` or `.zshrc.local`); real values provisioned out-of-band only on machines that need them. Pre-commit secret scanner stays as safety net, not gate of first resort.
|
||||
7. **Divergence**: `zz-<hostname>` override package stowed LAST with `--override`; only per-host diffs live there. (Stow descends into conflicting symlinked dirs — per-file overrides only work via this last-stowed `--override` pattern.)
|
||||
8. **Deploy trigger**: explicit `scripts/deploy.sh` invocation, human- or agent-invoked. NO auto-deploy on pull — pull and deploy are separate decisions; `--check` = drift report mode.
|
||||
9. **Ownership**: Frank is sole push authority; exactly one editing agent at a time → no multi-writer race machinery needed. Each host's agent edits only its own `zz-<hostname>` package.
|
||||
10. **Conflict policy**: pre-existing regular file that differs from repo truth → move to timestamped `.quarantine/` dir on the target (never `rm`, same convention as this repo's trash rule), symlink anyway, print every replacement in deploy output and `--check` report. Repo truth always wins; divergence recoverable from quarantine.
|
||||
11. **First-clone auth**: human types credential once console-side (URL-with-token or prompt); the new machine's secrets exist only there afterward.
|
||||
|
||||
## Implementation gotchas baked into the design
|
||||
- `core.hooksPath` is a per-clone local setting — does NOT survive clone; bootstrap/deploy must set it on every fresh clone or the secret-gate pre-commit hook silently doesn't run there.
|
||||
- dconf/gsettings are databases, not files: import as `dconf dump /` text into repo; deploy runs `dconf load` instead of symlinking.
|
||||
- p10k installer and toolchain hooks append to rc files at install time — expect conflicts on `.zshrc`-class files at first adopt; quarantine-and-replace handles it, but diff live-vs-repo before adopting so pre-existing drift is seen, not silently adopted.
|
||||
- Known secret at import time: `GITEA_TOKEN` export lived in `.zshrc` (line ~22) on the workstation — quarantine BEFORE importing rc files; consider token rotation at cutover since it had been exposed in plaintext rc + remote URL.
|
||||
|
||||
## Deploy script spec agreed
|
||||
`scripts/deploy.sh`: sets hooksPath → stows all base packages → stows `zz-$(hostname)` with `--override` → first run uses `--adopt` for pre-existing identical files → divergent files quarantined-and-reported; `--check` mode emits drift report (modified tracked files vs deployed symlinks) without mutating.
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: stash-supersession-audit-before-drop
|
||||
description: "Before dropping a stale git stash, prove it's superseded by HEAD via per-file line-set diff of git show stash@{0}:file vs HEAD; archive as an annotated tag and drop only after auditing unique facts — zero-loss alternative to blind drop."
|
||||
---
|
||||
|
||||
# Stash Supersession Audit Before Drop
|
||||
|
||||
Use when a repo (especially a live-deployment checkout where the working tree IS the deployment, e.g. `~/.omp` command-center) has an old stash whose content may have been superseded by later commits, and dropping blindly risks losing unique facts.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. **Inventory the stash** — see which files it touches:
|
||||
```bash
|
||||
git stash show --stat stash@{0}
|
||||
```
|
||||
2. **Probe for unique content per file.** For each touched file, diff stash version against HEAD and count lines that exist only in the stash version:
|
||||
```bash
|
||||
git show "stash@{0}:<path>" > /tmp/stash_copy
|
||||
# compare normalized line sets; report lines present in stash but absent from HEAD
|
||||
```
|
||||
A file with `unique-vs-HEAD = 0` is fully superseded — safe to drop for that file.
|
||||
3. **Classify each unique-content hit**:
|
||||
- *Stale fact* (superseded by a later refactor, e.g. old model/provider naming) → drop is correct.
|
||||
- *Live durable fact* (e.g. an operational invariant never re-recorded) → extract it into the live memory/docs file or flag to the user before dropping.
|
||||
4. **Archive before dropping** — zero-loss: tag the stash commit, then drop from the stash list:
|
||||
```bash
|
||||
git tag archive/stash-<YYYY-MM-DD>-<base-branch>-wip "stash@{0}"
|
||||
git stash drop "stash@{0}"
|
||||
```
|
||||
The tag pins the full stash object (including its index parent); recoverable later via `git show archive/stash-...` or `git checkout archive/stash-...^3 -- <path>` for the untracked-files third parent if present.
|
||||
|
||||
## Why
|
||||
Stashes are invisible in normal history and expire from reflogs (~90 days). A stash that *looks* superseded can still hold the only copy of a durable fact; tagging converts "drop" into a reversible archive with zero workflow cost.
|
||||
@@ -111,12 +111,22 @@ Single-context, inside the existing knowledge base: `omp-docs/glossary.md` and
|
||||
## Secret gate (HARD rule)
|
||||
|
||||
The pre-commit hook (`.githooks/pre-commit`, enabled via
|
||||
`git config core.hooksPath .githooks`) scans every staged diff for live-secret
|
||||
shapes (`sk-…`, `ghp_…`, `github_pat_…`, `glpat-…`, `xox…`, `AKIA…`, PEM headers,
|
||||
JWTs) and blocks the commit. Quarantine list — never tracked, ever: `auth.json`,
|
||||
`.env`, `trust.json`, `*.pem`, `*.key`, `identity*`. Deliberate override:
|
||||
`git config core.hooksPath .githooks`) blocks the commit in two ways. Token
|
||||
**shapes** (`sk-…`, `ghp_…`, `github_pat_…`, `glpat-…`, `xox…`, `AKIA…`, PEM
|
||||
headers, JWTs, bcrypt hashes, `Authorization: Basic` headers) are scanned across
|
||||
the **entire** staged diff — every file type, because the first version of the
|
||||
gate whitelisted extensions and never read a `.env`, `.container`, `.ini`, or
|
||||
extension-less dotfile at all. Credential **assignments** (`PASSWORD=…`,
|
||||
`secret_key: …`) are scanned in non-prose files only; markdown keeps shape
|
||||
detection, because the auto-learned memory tier is prose *about* credentials and a
|
||||
gate that fires there gets overridden until it means nothing. The gate **fails
|
||||
closed**: if it cannot read the staged diff it refuses the commit rather than
|
||||
passing it. Quarantine list — never tracked, ever: `auth.json`, `.env` and
|
||||
`*.env` (`postgres.env`, `frank-job-hunt.env` are the ones that carry real
|
||||
values), `trust.json`, `*.pem`, `*.key`, `identity*`. Deliberate override:
|
||||
`ALLOW_SECRETS=1 git commit …`. Remember why: a secret in a tracked file is a
|
||||
secret in every agent's context window on every machine that pulls this repo.
|
||||
secret in every agent's context window on every machine that pulls this repo, and
|
||||
redacting it later does not un-leak the history.
|
||||
|
||||
## Machine facts (verify before trusting)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
- Hermes (Dankclaw) model routing as of 2026-09-05: main default = frank-large (Dankclaw 192.168.0.143:7770/v1), small/aux routes = frank-small (Dankserver 192.168.0.235:7770/v1), vision + title_generation route to the exact FLM registry tag gemma4-it:e4b (192.168.0.143:7771/v1). The pre-rename aliases frank-default-large/-small/-sightsound are dead since the 2026-09-03 rename cutover — never route on them. Live config lives at /home/dank/Containers/hermes/data/config.yaml (owned by uid 109999; needs sudo to read/edit); the repo's dankclaw/hermes/config.yaml.example is a sanitized backup and can lag live, so diff live first before editing either. Signal adapter is currently disabled in live config (platforms.signal.enabled: false).
|
||||
- On the guarded franksdank.cloud vhosts (Caddy → Anubis :8923 → Authentik outpost), every unauthenticated request — roots and deep paths alike — is answered with HTTP **200** and an HTML challenge page (titleMaking sure you're not a bot!/title), not a 4xx. Verifying guard health by status code alone is meaningless; assert on leakage markers instead (copyparty UI title, homepage id="grid", services.json "category" JSON shape). Non-JS clients (curl/wget/WebDAV) can never solve the PoW challenge, so they are permanently parked at the challenge page — this is the entire security boundary for copyparty, which serves authenticated traffic with anonymous read perms (-v /w::r:ra,dank). Pinned by tests/e2e/specs/15-unauth-fetch.spec.ts (guest project, empty storage state): any guard-route regression that lets app bytes answer fails loudly; any gate implementation passes.
|
||||
- On Dankclaw's Pi-hole (FTL v6.7, quadlet at /home/dank/Containers/pihole), /etc/pihole/hosts/custom.list (host path /home/dank/Containers/pihole/etc-pihole/hosts/custom.list) is a LIVE-ONLY Custom-DNS source NOT tracked in the repo — it held ~22 A records pointing every *.franksdank.cloud vhost at 192.168.0.143, including names with no public Porkbun record. gravity.db's domainlist table has zero custom-DNS rows on this build, and the pihole CLI's sqlite path (pihole-FTL sqlite3) works but the web API auth was failing at discovery time, so retiring a name from LAN DNS requires editing custom.list directly (sudo sed -i '/name/d') followed by podman exec pihole pihole restartdns reload; deleting only the repo-managed addn-hosts record is not sufficient. Verify with dig +short name @192.168.0.143 → empty answer.
|
||||
- Pi-hole admin panel at pihole.franksdank.cloud has two auth layers: Caddy basicauth (edge, username dankuser + hash) and Pi-hole internal password (CivetWeb, set via pihole setpassword). The Pi-hole internal password was the actual failure point, not the Caddy hash. Pi-hole resolves to 192.168.0.143 via addn-hosts (all 28 names point here), making it a LAN service behind Caddy's public TLS edge. Options to remove public exposure: (1) remove the Caddy vhost entirely — LAN clients hit Pi-hole quadlet directly on :8083; (2) keep Caddy vhost but remove the Porkbun A record so external DNS won't resolve pihole.franksdank.cloud. Current state: Caddy basicauth hash = $2b$14$cJm.hbvDi1dEh7x0XRMnseaIdvIrRyH.AcTL56jGJf3wd54qvEb9K (dankuser, password pihole2026); Pi-hole internal password = pihole.2026 (set via pihole setpassword, survives restart).
|
||||
- Pi-hole admin panel at pihole.franksdank.cloud has two auth layers: Caddy basicauth (edge, username dankuser + hash) and Pi-hole internal password (CivetWeb, set via pihole setpassword). The Pi-hole internal password was the actual failure point, not the Caddy hash. Pi-hole resolves to 192.168.0.143 via addn-hosts (all 28 names point here), making it a LAN service behind Caddy's public TLS edge. Options to remove public exposure: (1) remove the Caddy vhost entirely — LAN clients hit Pi-hole quadlet directly on :8083; (2) keep Caddy vhost but remove the Porkbun A record so external DNS won't resolve pihole.franksdank.cloud. Current state: Caddy basicauth hash and the Pi-hole internal password are BOTH redacted (2026-10-20, issue #40) — they sat here in plaintext, which means they are in git history on every machine that ever pulled this repo, so both need rotation before this note is trustworthy; the live values are the Dankclaw host `.env` (`CADDY_BASICAUTH_HASH`) and `pihole setpassword`, never this file.
|
||||
- Pi-hole admin at pihole.franksdank.cloud has two auth layers: (1) Caddy basicauth edge (username dankuser, hash in rendered Caddyfile from CADDY_BASICAUTH_HASH in .env), (2) Pi-hole internal CivetWeb password (stored in cli_pw as bcrypt hash, set via pihole setpassword CLI). The env file WEBPASSWORD is used at container start but the CLI hash is what the running web server validates. When "I can't get into Pi-hole admin" returns 403 even after Caddy basicauth passes, the Pi-hole internal password is the likely culprit. Fix: podman exec pihole pihole setpassword newpw to update the CLI hash, then verify podman exec pihole cat /etc/pihole/cli_pw shows updated value. Verify by curling with basicauth: curl -sk -H 'Authorization: Basic ZGFua3VzZXI6...' https://pihole.franksdank.cloud/admin/ should return 302 (not 401 or 403).
|
||||
- Pi-hole admin panel at pihole.franksdank.cloud uses Caddy basicauth (not Pi-hole's own CivetWeb login). The hash is shared across services (RomM, Copyparty, Pi-hole) via CADDY_BASICAUTH_HASH in the host .env. The rendered Caddyfile at /home/dank/Containers/caddy/Caddyfile is mounted into the Caddy container — updates are picked up by Caddy's file watcher without restart. Password reset: generate bcrypt hash with python3 + bcrypt (hashpw + gensalt), update the rendered Caddyfile line directly or update .env + deploy. Caddy accepts both $2a$ and $2b$ bcrypt variants. _(context: Pi-hole admin password reset session: discovered Caddy basicauth (not Anubis), could not find matching password in common candidates, reset to pihole2026, updated rendered Caddyfile directly, synced repo .env files, Caddy file watcher handled hot-reload.)_
|
||||
- Pi-hole admin panel at pihole.franksdank.cloud uses Caddy basicauth (not Pi-hole's own CivetWeb login). The hash is shared across services (RomM, Copyparty, Pi-hole) via CADDY_BASICAUTH_HASH in the host .env. The rendered Caddyfile at /home/dank/Containers/caddy/Caddyfile is mounted into the Caddy container — updates are picked up by Caddy's file watcher without restart. Password reset: generate bcrypt hash with python3 + bcrypt (hashpw + gensalt), update the rendered Caddyfile line directly or update .env + deploy. Caddy accepts both $2a$ and $2b$ bcrypt variants. _(context: Pi-hole admin password reset session: discovered Caddy basicauth (not Anubis), could not find matching password in common candidates, reset to the current password [redacted 2026-10-20, issue #40], updated rendered Caddyfile directly, synced repo .env files, Caddy file watcher handled hot-reload.)_
|
||||
- On the FrankBook-Pro *arr fleet (current lsio images: Sonarr v4/Radarr v5 API v3, Lidarr v2/Readarr API v1), the download-client validation endpoints are NOT usable via API: POST /api/{v}/downloadclient/test/{id} and .../test?forceTest=true both return 405, and POST .../downloadclient/action/getTest exists but 400s without a bespoke body. To prove download-client health after changing a qB host/port, skip the API test route and prove behaviorally instead: podman exec arr curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/ (expect 200) plus a real qB login from inside the container (curl --data-urlencode username=admin --data-urlencode password=$QBIT_PW .../api/v2/auth/login, expect 200), then confirm /api/{v}/health shows no DownloadClient-*/wbrrelated warnings. Also: arr APIs redact stored client passwords as ******** (sonarr/radarr/lidarr) — PUT the object back with the sentinel untouched to preserve the stored value; readarr (v1) returns the plaintext. When editing via the edit tool, body rows must be prefixed with +, not : — : prefixes get written literally into the file. _(context: Capture turn after the arr→qB loopback repoint (2026-09-04): app-side download-client test endpoints were unreachable, so client health was proven behaviorally instead.)_
|
||||
- The *arr fleet (Prowlarr, Sonarr, Radarr, Lidarr, Readarr, qBittorrent) runs ONLY on FrankBook-Pro (192.168.0.112, Mac podman machine) since 2026-09-04 — the dankserver compose copy was removed (config roots in ~/.AGENTTRASH/20260904-dankserver-arr-teardown, 22 MB, awaiting purge approval). Fleet invariant, enforced by tests/frankbook_quadlet.bats: ALL six containers share gluetun's netns (Network=gluetun.container, Wants/After gluetun.service) and publish NO ports; the gluetun unit publishes the union (8080, 6881 tcp+udp, 9696, 8989, 7878, 8686, 8787), so all egress exits ProtonVPN by construction (AllowedIPs=0.0.0.0/0; proven: netns egress 51.179.177.14 vs residential 71.227.227.172). deploy-plan.py couples gluetun → all six joiners (a gluetun recreate kills the whole family). Consequences: (1) the old arr→qB addressing hack 10.88.0.1:8080 (podman-bridge gateway) routes INTO the tunnel from inside the netns — download clients must use 127.0.0.1:8080 (done via arr APIs, verified by real qB login from inside sonarr's container); same rule for any future Prowlarr Application rows (baseUrl 127.0.0.1:arr port). (2) Caddy arr vhosts and homepage probes target 192.168.0.112:port (host-side published ports unaffected by netns changes). (3) Known defects as of 2026-09-04: Prowlarr's Applications table is EMPTY (the four rows from 2026-08-30 were lost — prowlarr→arr sync broken until rebuilt); readarr's qB proxy auth defect needs retest post-move; EZTV/1337x "CloudFlare-blocked" parking is stale (egress now VPN — retest before enabling). (4) E2E lesson: playwright specs 11-admin/12-crawl now derive expected open-link counts from services.json (showLink) instead of hardcoded counts — keep new registry entries consistent. Also: deploy.sh --service X dispatches ONLY X on frankbook (path-coupling applies to changed-path planning); gluetun restart self-heals all netns joiners via Restart=always so a single gluetun deploy converges the family. _(context: 2026-09-04 operator decision session: dankserver arr compose copy (booted that morning) removed after Frank chose FrankBook-Pro as the sole arr deployment; netns consolidation + test seam + loopback repoint executed same session. Commits 4c7465d, df4339a.)_
|
||||
- Rootless podman on Dankserver (5.7.0, verified 2026-09-04 via /proc/pid/uid_map across 7 containers): the DEFAULT user namespace maps container uid 0 → host uid 1000 (dank) and container uid N → host 100000+N-1 (e.g. calibre-web PUID=1000 writes as 100999). Consequences: (1) PUID=0 + data roots owned 1000:1000 is the complete fleet recipe — NO UserNS/userns_mode key is needed on quadlets or compose services; the plex quadlet works without one. (2) NEVER add userns_mode: host to a podman-compose service: podman-compose 1.5.0 runs every compose service inside a pod and podman hard-rejects the combo (--userns and --pod cannot be set together), leaving the stack down with no recreate path. (3) Diagnostics: podman exec c id always shows uid=0 inside any mapping — meaningless; ground truth is /proc/container-pid/uid_map, host-side ps -o user= -p pid, or ownership of freshly-written files. (4) *arr boot-crash signature: UnauthorizedAccessException: Access to the path '/config/...' at logger init = data root not writable (e.g. root-owned dir created by sudo mkdir); the app exits before binding its port, so the port probe reads "dead" rather than "crashed" — check podman logs tail for "Press enter to exit" before assuming an outage has an infra cause. (5) NEVER pkill -f rootlessport on these hosts: it kills the port-forward helper of EVERY published container simultaneously (2026-09-03 outage); restart the owning systemd units instead. _(context: Dankserver *arr stack restore session, 2026-09-04: stack committed 2026-08-29 never booted (Sentry crash, dead ports read as outage); userns_mode: host fix was a red herring — rejected by podman-compose pods; real fix was chown 1000:1000 of /opt config dirs. Commit e454159 (revert), 8a63624 (docs).)_
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// ────────────── Output Configuration ──────────────
|
||||
output "Samsung Electric Company Odyssey G75F HNTY601056" {
|
||||
mode "3840x2160@60"
|
||||
scale 1
|
||||
transform "normal"
|
||||
position x=0 y=0
|
||||
}
|
||||
|
||||
output "DP-1" {
|
||||
transform "90"
|
||||
position x=0 y=0
|
||||
}
|
||||
|
||||
output "DP-3" {
|
||||
transform "270"
|
||||
position x=1200 y=0
|
||||
}
|
||||
|
||||
output "eDP-1" {
|
||||
// no position — auto-placed to the right of DP-* outputs
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
// ────────────── Startup Applications ──────────────
|
||||
// https://github.com/YaLTeR/niri/wiki/Configuration:-Miscellaneous#spawn-sh-at-startup
|
||||
|
||||
spawn-at-startup "noctalia"
|
||||
@@ -1,142 +0,0 @@
|
||||
binds {
|
||||
|
||||
// ────────────── Keybindings ──────────────
|
||||
// https://github.com/YaLTeR/niri/wiki/Configuration:-Key-Bindings
|
||||
|
||||
// ─── noctalia-shell keybinds ───
|
||||
// https://docs.noctalia.dev/getting-started/keybinds/
|
||||
|
||||
Mod+Shift+Escape { show-hotkey-overlay; }
|
||||
|
||||
// ─── Applications ───
|
||||
Mod+Return hotkey-overlay-title="Open Terminal: Ghostty" { spawn "ghostty"; }
|
||||
Mod+CTRL+Return hotkey-overlay-title="Open App Launcher: noctalia launcher" { spawn-sh "noctalia msg panel-toggle launcher"; }
|
||||
Mod+B hotkey-overlay-title="Open Browser: firefox" { spawn "firefox"; }
|
||||
Mod+ALT+L hotkey-overlay-title="Lock Screen: noctalia lock" { spawn-sh "noctalia msg session lock"; }
|
||||
Mod+Shift+Q hotkey-overlay-title="Session Menu: noctalia session" { spawn-sh "noctalia msg panel-toggle session"; }
|
||||
|
||||
// Please choose your own file manager.
|
||||
Mod+E hotkey-overlay-title="File Manager: Nautilus" { spawn "nautilus"; }
|
||||
|
||||
// ─── Media Controls ───
|
||||
XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "noctalia msg volume-up"; }
|
||||
XF86AudioLowerVolume allow-when-locked=true { spawn-sh "noctalia msg volume-down"; }
|
||||
XF86AudioMute allow-when-locked=true { spawn-sh "noctalia msg volume-mute"; }
|
||||
XF86AudioMicMute allow-when-locked=true { spawn-sh "noctalia msg mic-mute"; }
|
||||
XF86AudioNext allow-when-locked=true { spawn-sh "noctalia msg media next"; }
|
||||
XF86AudioPrev allow-when-locked=true { spawn-sh "noctalia msg media previous"; }
|
||||
XF86AudioPlay allow-when-locked=true { spawn-sh "noctalia msg media playPause"; }
|
||||
XF86AudioPause allow-when-locked=true { spawn-sh "noctalia msg media playPause"; }
|
||||
|
||||
// ─── Brightness Controls ───
|
||||
XF86MonBrightnessUp allow-when-locked=true { spawn-sh "noctalia msg brightness-up"; }
|
||||
XF86MonBrightnessDown allow-when-locked=true { spawn-sh "noctalia msg brightness-down"; }
|
||||
|
||||
// ─── Window Movement and Focus ───
|
||||
Mod+Q { close-window; }
|
||||
|
||||
Mod+Left { focus-column-left; }
|
||||
Mod+H { focus-column-left; }
|
||||
Mod+Right { focus-column-right; }
|
||||
Mod+L { focus-column-right; }
|
||||
Mod+Up { focus-window-up; }
|
||||
Mod+K { focus-window-up; }
|
||||
Mod+Down { focus-window-down; }
|
||||
Mod+J { focus-window-down; }
|
||||
|
||||
// Win+Ctrl consume/expel windows to/from columns
|
||||
Mod+CTRL+Left { consume-or-expel-window-left; }
|
||||
Mod+CTRL+H { consume-or-expel-window-left; }
|
||||
Mod+CTRL+Right { consume-or-expel-window-right; }
|
||||
Mod+CTRL+L { consume-or-expel-window-right; }
|
||||
Mod+CTRL+UP { expel-window-from-column; }
|
||||
Mod+CTRL+K { expel-window-from-column; }
|
||||
Mod+CTRL+Down { expel-window-from-column; }
|
||||
Mod+CTRL+J { expel-window-from-column; }
|
||||
Mod+Home { focus-column-first; }
|
||||
Mod+End { focus-column-last; }
|
||||
Mod+CTRL+Home { move-column-to-first; }
|
||||
Mod+CTRL+End { move-column-to-last; }
|
||||
|
||||
Mod+Shift+Left { focus-monitor-left; }
|
||||
Mod+Shift+Right { focus-monitor-right; }
|
||||
Mod+Shift+UP { focus-monitor-up; }
|
||||
Mod+Shift+Down { focus-monitor-down; }
|
||||
|
||||
Mod+Shift+CTRL+Left { move-column-to-monitor-left; }
|
||||
Mod+Shift+CTRL+Right { move-column-to-monitor-right; }
|
||||
Mod+Shift+CTRL+UP { move-column-to-monitor-up; }
|
||||
Mod+Shift+CTRL+Down { move-column-to-monitor-down; }
|
||||
|
||||
// ─── Workspace Switching ───
|
||||
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
||||
Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
|
||||
Mod+CTRL+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; }
|
||||
Mod+CTRL+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; }
|
||||
|
||||
Mod+WheelScrollRight { focus-column-right; }
|
||||
Mod+WheelScrollLeft { focus-column-left; }
|
||||
Mod+CTRL+WheelScrollRight { move-column-right; }
|
||||
Mod+CTRL+WheelScrollLeft { move-column-left; }
|
||||
|
||||
Mod+Shift+WheelScrollDown { focus-column-right; }
|
||||
Mod+Shift+WheelScrollUp { focus-column-left; }
|
||||
Mod+CTRL+Shift+WheelScrollDown { move-column-right; }
|
||||
Mod+CTRL+Shift+WheelScrollUp { move-column-left; }
|
||||
|
||||
Mod+1 { focus-workspace 1; }
|
||||
Mod+2 { focus-workspace 2; }
|
||||
Mod+3 { focus-workspace 3; }
|
||||
Mod+4 { focus-workspace 4; }
|
||||
Mod+5 { focus-workspace 5; }
|
||||
Mod+6 { focus-workspace 6; }
|
||||
Mod+7 { focus-workspace 7; }
|
||||
Mod+8 { focus-workspace 8; }
|
||||
Mod+9 { focus-workspace 9; }
|
||||
|
||||
Mod+CTRL+1 { move-column-to-workspace 1; }
|
||||
Mod+CTRL+2 { move-column-to-workspace 2; }
|
||||
Mod+CTRL+3 { move-column-to-workspace 3; }
|
||||
Mod+CTRL+4 { move-column-to-workspace 4; }
|
||||
Mod+CTRL+5 { move-column-to-workspace 5; }
|
||||
Mod+CTRL+6 { move-column-to-workspace 6; }
|
||||
Mod+CTRL+7 { move-column-to-workspace 7; }
|
||||
Mod+CTRL+8 { move-column-to-workspace 8; }
|
||||
Mod+CTRL+9 { move-column-to-workspace 9; }
|
||||
|
||||
Mod+TAB { focus-workspace-previous; }
|
||||
|
||||
// ─── Layout Controls ───
|
||||
Mod+CTRL+F { expand-column-to-available-width; }
|
||||
Mod+C hotkey-overlay-title="Calculator" { spawn "gnome-calculator"; }
|
||||
Mod+CTRL+C { center-visible-columns; }
|
||||
Mod+Minus { set-column-width "-10%"; }
|
||||
Mod+Equal { set-column-width "+10%"; }
|
||||
Mod+Shift+Minus { set-window-height "-10%"; }
|
||||
Mod+Shift+Equal { set-window-height "+10%"; }
|
||||
|
||||
// ─── Split / Consume / Expel ───
|
||||
Mod+Shift+H { consume-or-expel-window-left; }
|
||||
Mod+Shift+L { consume-or-expel-window-right; }
|
||||
Mod+Shift+J { expel-window-from-column; }
|
||||
|
||||
// ─── Modes ───
|
||||
Mod+T { toggle-window-floating; }
|
||||
Mod+F { fullscreen-window; }
|
||||
Mod+W { toggle-column-tabbed-display; }
|
||||
|
||||
// ─── Screenshots ───
|
||||
CTRL+Shift+1 hotkey-overlay-title="Screenshot: flameshot full" { spawn-sh "flameshot full"; }
|
||||
CTRL+Shift+2 hotkey-overlay-title="Screenshot: flameshot screen" { spawn-sh "flameshot screen"; }
|
||||
CTRL+Shift+3 hotkey-overlay-title="Screenshot: flameshot region" { spawn-sh "flameshot gui"; }
|
||||
|
||||
// ─── Emergency Escape Key ───
|
||||
// Use this when a fullscreen app blocks your keybinds.
|
||||
// It disables any active keyboard shortcut inhibitor, restoring control.
|
||||
Mod+ESCAPE allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
|
||||
|
||||
// ─── Exit / Power ───
|
||||
CTRL+ALT+Delete { quit; } // Also quits Niri
|
||||
Mod+Shift+P { power-off-monitors; } // Turn off screens (useful for OLED or privacy)
|
||||
Mod+O repeat=false { toggle-overview; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[Unit]
|
||||
Description=swayidle idle lock (locks via noctalia-shell IPC, suspends on lid)
|
||||
Documentation=https://github.com/swaywm/swayidle
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
# Locks via the noctalia v5 shell; `noctalia msg` resolves the running instance
|
||||
# through the D-Bus session bus, so no WAYLAND_DISPLAY pinning is needed in unit context.
|
||||
# deploy.sh enables it only where the ExecStart binary resolves, so it stays inert until swayidle is installed.
|
||||
ExecStart=/usr/bin/swayidle -w \
|
||||
timeout 300 '/usr/bin/noctalia msg session lock' \
|
||||
before-sleep '/usr/bin/noctalia msg session lock' \
|
||||
after-resume '/usr/bin/noctalia msg session lock'
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,241 +1,190 @@
|
||||
---
|
||||
type: reference
|
||||
title: Home Directory Configuration Census
|
||||
description: Complete inventory of userspace program configurations on the workstation — top-level dotfiles, ~/.config, ~/.local/share state, and special directories (~/.pi, ~/.omp).
|
||||
tags: [inventory, unverified, census, dotfiles, config]
|
||||
title: Home Configuration Census (blankpad)
|
||||
description: Per-file tracked-vs-box-only census of ~/.config on blankpad — the input for the dotfiles pruning/adoption session.
|
||||
tags: [inventory, census, dotfiles, config]
|
||||
created: 2026-08-28
|
||||
census-date: 2026-08-28
|
||||
source: Workstation CachyOS laptop
|
||||
census-date: 2026-10-20
|
||||
verified: 2026-10-20
|
||||
source: blankpad (CachyOS, /home/blank)
|
||||
---
|
||||
|
||||
## Scope & Methodology
|
||||
This page is the input for the dotfiles pruning/adoption session: for every
|
||||
`~/.config` entry it separates what the repo already tracks from what lives only
|
||||
on this box, and sorts the box-only half into adopt / never-track / prune.
|
||||
|
||||
Census covers four areas: (1) top-level dotfiles/dotdirs in `/home/dank` (excluding `.omp` which is documented separately here), (2) every entry under `~/.config`, (3) non-cache state dirs under `~/.local/share`, (4) special directories `~/.pi` and `~/.omp`. Each entry assessed for installed status (`command -v`), size (`du -sh`), and age (mtime). Uninstalled programs flagged as `prune-candidate`.
|
||||
## Method
|
||||
|
||||
All measurements are live — no invented numbers.
|
||||
Classification is per **FILE**, not per directory: the tracked unit is a symlink
|
||||
into `~/.omp/dotfiles/`. A directory can look "real" (a plain folder, not itself
|
||||
a symlink) while every file inside it is a tracked symlink — `niri` and `gtk-3.0`
|
||||
do exactly that. Conversely a directory can be a whole symlink into `dotfiles`
|
||||
(`lazyvim`) and be entirely tracked. So a directory-level verdict lies; only the
|
||||
leaf symlink target is ground truth.
|
||||
|
||||
---
|
||||
- **tracked** = a file whose fully-resolved target (`readlink -f`) is under
|
||||
`/home/blank/.omp/dotfiles/`.
|
||||
- **box-only** = a regular file, or a symlink that resolves elsewhere.
|
||||
|
||||
## 1. Top-Level Dotfiles / Dotdirs
|
||||
Rerun this classifier rather than trusting the table below:
|
||||
|
||||
> 38 entries in `/home/dank` beginning with `.` (excluding `.omp`).
|
||||
```bash
|
||||
DOT=/home/blank/.omp/dotfiles; CFG=/home/blank/.config
|
||||
for d in "$CFG"/*; do
|
||||
[ -L "$d" ] && case "$(readlink -f "$d")" in "$DOT"/*) echo "$(basename "$d"): tracked=ALL box-only=0"; continue;; esac
|
||||
[ -d "$d" ] || { echo "$(basename "$d"): FILE (top-level)"; continue; }
|
||||
tr=0; bo=0; names=""
|
||||
while IFS= read -r -d '' f; do
|
||||
if [ -L "$f" ]; then case "$(readlink -f "$f")" in "$DOT"/*) tr=$((tr+1)); continue;; esac; fi
|
||||
bo=$((bo+1)); names+=" ${f#"$CFG"/}"
|
||||
done < <(find "$d" -mindepth 1 \( -type f -o -type l \) -print0 2>/dev/null)
|
||||
printf '%s: tracked=%d box-only=%d%s\n' "$(basename "$d")" "$tr" "$bo" "$names"
|
||||
done
|
||||
```
|
||||
|
||||
| Path | What it is | Installed? | Size | Age (mtime) | Prune? |
|
||||
|---|---|---|---|---|---|
|
||||
| `.bash_history` | Bash command history | yes (bash) | 12K | 2026-08-27 | — |
|
||||
| `.bash_logout` | Bash logout script | yes (bash) | 4.0K | 2026-07-03 | — |
|
||||
| `.bash_profile` | Bash login script | yes (bash) | 4.0K | 2026-07-03 | — |
|
||||
| `.bashrc` | Bash config | yes (bash) | 4.0K | 2026-07-12 | — |
|
||||
| `.bun` | Bun runtime install | yes | 2.3G | 2026-07-05 | — |
|
||||
| `.cache` | XDG cache directory | system | 6.4G | 2026-08-28 | — |
|
||||
| `.claude` | Claude Code config | **no** | 6.4M | 2026-07-12 | **prune-candidate** |
|
||||
| `.config` | XDG config directory | system | 574M | 2026-08-25 | — |
|
||||
| `.env` | Environment variables | system | 4.0K | 2026-08-21 | — |
|
||||
| `.git-credentials` | Git auth cache | yes (git) | 4.0K | 2026-08-28 | — |
|
||||
| `.gitconfig` | Git global config | yes (git) | 4.0K | 2026-08-05 | — |
|
||||
| `.local` | XDG local directory | system | 552G | 2026-07-24 | — |
|
||||
| `.npm` | Node package manager cache | yes (node) | 1.7G | 2026-07-23 | — |
|
||||
| `.nv` | nvim (Neovim) config | **no** | 8.0K | 2026-07-03 | **prune-candidate** |
|
||||
| `.nvidia-settings-rc` | NVIDIA Settings config | yes | 4.0K | 2026-07-04 | — |
|
||||
| `.nvm` | Node Version Manager | **no** | 256M | 2026-07-06 | **prune-candidate** |
|
||||
| `.openpeon` | OMP Peon session state | yes (omp) | 4.4M | 2026-07-16 | — |
|
||||
| `.p10k.zsh` | Powerlevel10k zsh theme | yes (zsh) | 96K | 2026-07-03 | — |
|
||||
| `.paradoxlauncher` | Paradox Launcher state | **no** | 1.1G | 2026-07-31 | **prune-candidate** |
|
||||
| `.pi` | Legacy PI agent (git repo) | — | 2.5G | 2026-08-17 | see §4 |
|
||||
| `.pki` | System PKI certs | system | 120K | 2026-07-03 | — |
|
||||
| `.pulse-cookie` | PulseAudio session | system | 4.0K | 2026-07-03 | — |
|
||||
| `.python_history` | Python REPL history | yes (python3) | 4.0K | 2026-08-06 | — |
|
||||
| `.ssh` | SSH keys & config | yes | 28K | 2026-08-24 | — |
|
||||
| `.steam` | Steam runtime + config | yes | 44K | 2026-08-26 | — |
|
||||
| `.steampath` | Steam executable path | yes (steam) | 4.0K | 2026-07-31 | — |
|
||||
| `.steampid` | Steam PID file | yes (steam) | 4.0K | 2026-07-31 | — |
|
||||
| `.tmux-resurrect` | Tmux resurrect state | yes (tmux) | 0 | 2026-08-25 | — |
|
||||
| `.tmux.conf` | Tmux configuration | yes (tmux) | 4.0K | 2026-07-18 | — |
|
||||
| `.wget-hsts` | Wget HSTS database | yes (wget) | 4.0K | 2026-07-06 | — |
|
||||
| `.zcompdump-<host>-5.9.1` | Zsh completions v5.9.1 | yes (zsh) | 56K | 2026-07-12 | — |
|
||||
| `.zcompdump-<host>-5.9.1.zwc` | Zsh compiled completions v5.9.1 | yes (zsh) | 120K | 2026-07-12 | — |
|
||||
| `.zcompdump-<host>-5.9.2` | Zsh completions v5.9.2 | yes (zsh) | 56K | 2026-08-19 | — |
|
||||
| `.zcompdump-<host>-5.9.2.lock` | Zsh completions lock | yes (zsh) | 0 | 2026-08-25 | — |
|
||||
| `.zcompdump-<host>-5.9.2.zwc` | Zsh compiled completions v5.9.2 | yes (zsh) | 120K | 2026-08-19 | — |
|
||||
| `.zsh_history` | Zsh command history | yes (zsh) | 68K | 2026-08-28 | — |
|
||||
| `.zshrc` | Zsh configuration | yes (zsh) | 4.0K | 2026-08-25 | — |
|
||||
## ~/.config — tracked vs box-only
|
||||
|
||||
---
|
||||
Profile/browser dirs (`mozilla` 5471 files, `obsidian` 99, `FreeTube` 56,
|
||||
`nirimod` 116, `google-chrome-for-testing` 5) are app state, never tracked, and
|
||||
are omitted as rows. Box-only bulk that is regenerated-by-the-program is named
|
||||
by category, not file-by-file.
|
||||
|
||||
## 2. ~/.config — Program Configuration Directory
|
||||
| `~/.config/` dir | tracked | box-only | box-only files |
|
||||
|---|--:|--:|---|
|
||||
| alacritty | 1 | 1 | `themes/noctalia.toml` (generated) |
|
||||
| autostart | 0 | 3 | `Flameshot.desktop`, `cachyos-hello.desktop`, `.AGENTTRASH/Handy.desktop` |
|
||||
| bat | 0 | 2 | `config`, `themes/noctalia.tmTheme` (generated) |
|
||||
| btop | 0 | 2 | `btop.conf`, `themes/noctalia.theme` (generated) |
|
||||
| cachyos | 0 | 1 | `cachyos-hello/cachyos-hello.log` (state) |
|
||||
| cava | 0 | 1 | `themes/noctalia` (generated) — program absent, prune |
|
||||
| crush | 0 | 37 | `skills/<37>` — skills-CLI install |
|
||||
| containers | 0 | 2 | `systemd/franksign-db.container`, `systemd/postgres.env` (see §Owned-elsewhere / secret tier) |
|
||||
| dconf | 0 | 1 | `user` (compiled dconf db, state) |
|
||||
| devin | 0 | 37 | `skills/<37>` — skills-CLI install |
|
||||
| fastfetch | 0 | 1 | `themes/noctalia.jsonc` (generated) |
|
||||
| fish | 2 | 1 | `fish_variables` (fish var store, state) |
|
||||
| flameshot | 0 | 1 | `flameshot.ini` |
|
||||
| foot | 0 | 2 | `foot.ini`, `themes/noctalia` (generated) — program absent, prune |
|
||||
| fzf | 0 | 2 | `themes/noctalia.sh`, `themes/noctalia.fish` (generated) |
|
||||
| ghostty | 2 | 1 | `themes/noctalia` (generated) |
|
||||
| glow | 0 | 2 | `glow.yml`, `noctalia.json` (generated) |
|
||||
| goose | 0 | 37 | `skills/<37>` — skills-CLI install |
|
||||
| gtk-3.0 | 3 | 0 | — |
|
||||
| gtk-4.0 | 2 | 1 | `servers` (KDE-generated) |
|
||||
| helix | 0 | 1 | `themes/noctalia.toml` (generated) — `hx` absent, prune |
|
||||
| herdr | 0 | 1 | `noctalia-colors.toml` (generated) |
|
||||
| hypr | 0 | 2 | `hyprland.conf`, `noctalia.conf` (generated) — `hyprland` absent, prune |
|
||||
| kimchi | 0 | 37 | `harness/skills/<37>` — skills-CLI install |
|
||||
| kitty | 0 | 2 | `kitty.conf`, `themes/noctalia.conf` (generated) — program absent, prune |
|
||||
| labwc | 0 | 2 | `rc.xml`, `noctalia.conf` (generated) — program absent, prune |
|
||||
| lazygit | 0 | 2 | `config.yml`, `themes/noctalia.yml` (generated) |
|
||||
| lazyvim | ALL | 0 | — (whole dir symlink → `dotfiles/lazyvim`) |
|
||||
| mango | 0 | 2 | `config.conf`, `noctalia.conf` (generated) — program absent, prune |
|
||||
| micro | 0 | 154 | adopt: `settings.json`, `bindings.json`; generated: `colorschemes/noctalia.micro`; bundled/state: `syntax/*` (146), `buffers/history` |
|
||||
| mpv | 0 | 0 | — (empty) |
|
||||
| nautilus | 0 | 0 | — (empty) |
|
||||
| niri | 9 | 1 | 8 live KDL includes (5 from `dotfiles/niri`, 3 overridden by the `zz-blankpad` tier, whose own 9 niri files are counted in that tier) + 1 tracked relic, see *Never track*. Box-only: `noctalia.kdl`, a generated include — [tools/dotfiles-deploy](/tools/dotfiles-deploy.md) |
|
||||
| noctalia | 3 | 2672 | Tracked and live-linked: `config.toml`, `palettes/Gruber Red.json`, `state/noctalia/settings.toml`. Everything else is app state plus noctalia's downloaded catalog: `community-palettes/` 66, `community-templates/` 80, `wallhaven/thumbs`, `clipboard/`, `plugins/`, `state.toml`, `usage_counts.json`. The 23 wallpapers #41 untracks stay on disk — the theme reads them |
|
||||
| nvim | 0 | 0 | — (empty) |
|
||||
| pywalfox | 0 | 1 | `config.json` — program absent, prune |
|
||||
| qt5ct | 0 | 1 | `colors/noctalia.conf` (generated) — program absent, prune |
|
||||
| qt6ct | 0 | 1 | `colors/noctalia.conf` (generated) — program absent, prune |
|
||||
| rofi | 0 | 1 | `noctalia.rasi` (generated) — program absent, prune |
|
||||
| scroll | 0 | 2 | `config`, `noctalia` (generated) — program absent, prune |
|
||||
| secrets | 0 | 4 | `gitea`, `hf_token`, `hf_token_write`, `kagi_api_key` (names only — secret tier) |
|
||||
| shelly | 0 | 2 | `config.json`, `settings.json` |
|
||||
| sway | 0 | 2 | `config`, `noctalia` (generated) — program absent, prune |
|
||||
| systemd | 9 | 3 | `user/fib-watchdog.service`, `user/fib-watchdog.timer`, `user/timers.target.wants/fib-watchdog.timer` |
|
||||
| tmux | 0 | 1 | `themes/noctalia.conf` (generated) |
|
||||
| turborepo | 0 | 1 | `telemetry.json` — `turbo` absent, prune |
|
||||
| umbriel | 0 | 2 | `config.toml`, `noctalia.toml` (generated) — program absent, prune |
|
||||
| unity3d | 0 | 235 | RimWorld state: `Saves` (201), `Config` (21), other (13) — never track |
|
||||
| wezterm | 0 | 1 | `colors/Noctalia.toml` (generated) — program absent, prune |
|
||||
| yay | 0 | 0 | — (empty) |
|
||||
| ytm-player | 0 | 1 | `theme.toml` — program absent, prune |
|
||||
|
||||
> 38 entries (30 directories + 8 standalone files) under `~/.config`. Files like `mimeapps.list`, `user-dirs.dirs`, `background`, etc. are XDG desktop integration files, not program configs.
|
||||
### Top-level `~/.config` files (not directories)
|
||||
|
||||
### Programs (directories with config files)
|
||||
All regular files, none symlinked into `dotfiles`:
|
||||
`starship.toml` (prune — `starship` absent), `frank-job-hunt.env` (secret tier),
|
||||
`kdeglobals`, `partitionmanagerrc`, `pavucontrol.ini`, `cachyos-hello.json`,
|
||||
`user-dirs.dirs`, `user-dirs.locale` (XDG/desktop state).
|
||||
|
||||
| Path | What it is | Installed? | Size | Files | Age | Prune? |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `.config/alacritty` | Alacritty terminal emulator | yes | 4.0K | 1 | 2026-07-24 | — |
|
||||
| `.config/btop` | btop system monitor | yes | 12K | 1 | 2026-08-11 | — |
|
||||
| `.config/cachyos` | CachyOS distro config | system | 8.0K | 3 | 2026-07-03 | — |
|
||||
| `.config/chromium` | Chromium browser | yes | 158M | 423 | 2026-07-15 | — |
|
||||
| `.config/chromium-headless` | Chromium headless | **no** | 2.6M | 165 | 2026-07-20 | **prune-candidate** |
|
||||
| `.config/dconf` | Dconf database | yes | 4.0K | 1 | 2026-07-03 | — |
|
||||
| `.config/fish` | Fish shell config | yes | 24K | 3 | 2026-07-06 | — |
|
||||
| `.config/gh` | GitHub CLI config | yes | 8.0K | 2 | 2026-07-12 | — |
|
||||
| `.config/ghostty` | Ghostty terminal emulator | yes | 24K | 3 | 2026-08-24 | — |
|
||||
| `.config/glow` | Glow markdown renderer | yes | 4.0K | 1 | 2026-07-03 | — |
|
||||
| `.config/google-chrome-for-testing` | Google Chrome (testing) | **no** | 4.0K | 1 | 2026-07-03 | **prune-candidate** |
|
||||
| `.config/gtk-3.0` | GTK 3.0 theme settings | system | 12K | 3 | 2026-07-03 | — |
|
||||
| `.config/gtk-4.0` | GTK 4.0 theme settings | system | 12K | 3 | 2026-07-03 | — |
|
||||
| `.config/micro` | Micro text editor | yes | 696K | 154 | 2026-08-17 | — |
|
||||
| `.config/mozilla` | Firefox preferences | yes | 334M | 2,918 | 2026-08-24 | — |
|
||||
| `.config/nautilus` | Nautilus file manager | yes | 4.0K | 0 | 2026-07-03 | — |
|
||||
| `.config/niri` | Niri Wayland WM | yes | 44K | 9 | 2026-07-15 | — |
|
||||
| `.config/noctalia` | Noctalia game config | yes | 212K | 28 | 2026-07-05 | — |
|
||||
| `.config/nvim` | Neovim config | yes | 64K | 13 | 2026-07-23 | — |
|
||||
| `.config/obsidian` | Obsidian note-taking app | yes | 79M | 139 | 2026-08-24 | — |
|
||||
| `.config/pulse` | PulseAudio config | system | 4.0K | 1 | 2026-07-03 | — |
|
||||
| `.config/qBittorrent` | qBittorrent torrent client | yes | 24K | 5 | 2026-08-05 | — |
|
||||
| `.config/qmd` | QMD (bun-based CLI) | yes | 4.0K | 1 | 2026-07-03 | — |
|
||||
| `.config/rpiv-web-tools` | RPi web tools config | yes | 4.0K | 1 | 2026-07-03 | — |
|
||||
| `.config/shelly` | Shelly home automation | yes | 8.0K | 2 | 2026-07-03 | — |
|
||||
| `.config/unity3d` | Unity game engine config | yes | 188K | 3 | 2026-07-05 | — |
|
||||
| `.config/vlc` | VLC media player | yes | 96K | 2 | 2026-07-03 | — |
|
||||
## Adoption candidates (program passes `command -v`)
|
||||
|
||||
### Desktop / XDG Integration (no owning program)
|
||||
Pre-vetted box-only files worth moving into `dotfiles`. Authorship is from the
|
||||
file contents, read directly.
|
||||
|
||||
| Path | What it is | Installed? | Size | Age |
|
||||
|---|---|---|---|---|
|
||||
| `.config/autostart` | XDG autostart entries | system | 0 | 2026-07-03 |
|
||||
| `.config/background` | Desktop background setting | system | 0 | 2026-07-03 |
|
||||
| `.config/cachyos-hello.json` | CachyOS welcome JSON | system | 0 | 2026-07-03 |
|
||||
| `.config/menus` | XDG menu spec | system | 0 | 2026-07-03 |
|
||||
| `.config/mimeapps.list` | MIME handler associations | system | 0 | 2026-07-03 |
|
||||
| `.config/paradox-launcher-v2` | Paradox Launcher v2 config | **no** | 0 | 2026-08-05 | **prune-candidate** |
|
||||
| `.config/pavucontrol.ini` | PulseAudio Volume Control config | system | 0 | 2026-07-03 |
|
||||
| `.config/spectaclerc` | Spectacle screenshot tool config | system | 0 | 2026-07-03 |
|
||||
| `.config/systemd` | User systemd config | system | 16K | 2026-07-06 | — |
|
||||
| `.config/user-dirs.dirs` | XDG user directory dirs | system | 0 | 2026-07-03 |
|
||||
| `.config/user-dirs.locale` | XDG locale | system | 0 | 2026-07-03 |
|
||||
|
||||
---
|
||||
|
||||
## 3. ~/.local/share — Non-Cache State & Config
|
||||
|
||||
> 26 entries. Excluded pure caches; included game data dirs, launcher configs, and application state.
|
||||
|
||||
| Path | What it is | Installed? | Size | Age | Prune? |
|
||||
|---|---|---|---|---|---|
|
||||
| `.local/share/Colossal Order` | Cities: Skylines 2 game data | via steam | 27M | 2026-08-26 | — |
|
||||
| `.local/share/Larian Studios` | Baldur's Gate 3 game data | via steam | 1.5G | 2026-08-25 | — |
|
||||
| `.local/share/Paradox Interactive` | Paradox game saves/config | via steam/flatpak | 34M | 2026-08-26 | — |
|
||||
| `.local/share/Shelly` | Shelly home automation data | yes | 4.0K | 2026-07-03 | — |
|
||||
| `.local/share/Steam` | Steam library + runtime | yes | 550G | 2026-08-28 | — |
|
||||
| `.local/share/Trash` | Desktop trash | system | 424M | 2026-08-27 | — |
|
||||
| `.local/share/Tropico6` | Tropico 6 game data | via steam | 3.1M | 2026-08-26 | — |
|
||||
| `.local/share/applications` | Desktop entry files | system | 56K | 2026-07-03 | — |
|
||||
| `.local/share/com.pais.handy` | Handy app (likely flatpak) | unknown | 140K | 2026-08-25 | — |
|
||||
| `.local/share/desktop-directories` | XDG desktop dirs | system | 0 | 2026-07-03 | — |
|
||||
| `.local/share/fish` | Fish shell data | yes | 4.0K | 2026-07-03 | — |
|
||||
| `.local/share/flatpak` | Flatpak runtime/pkgs | yes | 8.0K | 2026-07-03 | — |
|
||||
| `.local/share/gvfs-metadata` | GVFS metadata | system | 156K | 2026-08-25 | — |
|
||||
| `.local/share/icons` | XDG icon theme data | system | 1.1M | 2026-07-03 | — |
|
||||
| `.local/share/inxi` | Inxi system info data | yes | 0 | 2026-07-03 | — |
|
||||
| `.local/share/keyrings` | System keyring data | system | 8.0K | 2026-07-03 | — |
|
||||
| `.local/share/nautilus` | Nautilus state | yes | 164K | 2026-07-03 | — |
|
||||
| `.local/share/noctalia` | Noctalia game state | yes | 48K | 2026-08-25 | — |
|
||||
| `.local/share/nvim` | Neovim local data | yes | 186M | 2026-08-25 | — |
|
||||
| `.local/share/qBittorrent` | qBittorrent download state | yes | 8.5M | 2026-08-05 | — |
|
||||
| `.local/share/sddm` | SDDM display manager state | system | 4.0K | 2026-07-03 | — |
|
||||
| `.local/share/shelly-icons` | Shelly icon assets | yes | 67M | 2026-07-03 | — |
|
||||
| `.local/share/uv` | UV (Python) package data | yes | 59M | 2026-07-03 | — |
|
||||
| `.local/share/vlc` | VLC cache/state | yes | 4.0K | 2026-07-03 | — |
|
||||
| `.local/share/vulkan` | Vulkan shader cache | system | 16K | 2026-07-03 | — |
|
||||
|
||||
---
|
||||
|
||||
## 4. Special Directories
|
||||
|
||||
### ~/.pi — Legacy PI Agent (git repo)
|
||||
|
||||
> 31 entries. **2.5G total.** Remote: `dankclaw:3000/dank/agent.git`. Last commit: `eda2edb docs: update frank-default-large model to Qwen3.8-Flash-Next Uncensored ROCmFP4 STRIX_LEAN`.
|
||||
|
||||
**Top-level contents:**
|
||||
|
||||
| Path | What it is | Size | Prune? |
|
||||
|---|---|---|---|
|
||||
| `agent/` | PI agent core — git-tracked code, sessions, node_modules | 2.2G | — |
|
||||
| `browser-profile/` | Chromium browser profile (artifacts) | 200M | **prune-candidate** |
|
||||
| `trash/` | Agent session trash | 1.8M | **prune-candidate** |
|
||||
| `docs/` | Documentation | 684K | — |
|
||||
| `npm/` | NPM global packages | 368K | — |
|
||||
| `knowledge/` | Knowledge base | 120K | — |
|
||||
| `skills/` | Agent skills | 112K | — |
|
||||
| `scripts/` | Agent scripts | 92K | — |
|
||||
| `extensions/` | Agent extensions | 60K | — |
|
||||
| `system-agent-prompts-guide.md` | Agent prompt guide | 56K | — |
|
||||
| `research/` | Research artifacts | 44K | **prune-candidate** |
|
||||
| `okf-*.md` | OKF project documents (6 files) | ~96K total | — |
|
||||
| `web-search.json` | Web search config | 4.0K | — |
|
||||
| `trust.json` | Trust registry | 4.0K | — |
|
||||
| `skills-lock.json` | Skills lock file | 4.0K | — |
|
||||
| `settings.json` / `settings.json.save` | Agent settings | 8.0K total | — |
|
||||
| `peon` | Peon integration | 4.0K | — |
|
||||
| `oldAGENTS.md` | Legacy AGENTS.md | 4.0K | — |
|
||||
| `keybindings.json` | Key bindings | 4.0K | — |
|
||||
| `PROMPTS` | Prompt templates | 4.0K | — |
|
||||
| `AGENTS.md` | Agent config | 4.0K | — |
|
||||
| `web-search-cache` | Search cache (empty) | 0 | **prune-candidate** |
|
||||
|
||||
**Size offenders inside `agent/` (≥10M):**
|
||||
|
||||
| File | Size |
|
||||
|---|---|
|
||||
| `agent/npm/node_modules/onnxruntime-node/bin/.../libonnxruntime_providers_cuda.so` | 302M |
|
||||
| `agent/npm/node_modules/@lancedb/lancedb-linux-x64-gnu/lancedb.linux-x64-gnu.node` | 137M |
|
||||
| `.git/objects/pack/pack-ff7cc5418a978a5839d587472bec385f4c9ce72d.pack` | 81M |
|
||||
| `agent/npm/node_modules/onnxruntime-node/bin/napi-v6/...` (multi-platform binaries) | 56M |
|
||||
| `agent/npm/node_modules/webgpu/dist/...` (multi-platform .node) | 55M |
|
||||
| `agent/npm/node_modules/pdf-oxide-wasm/...` (Wasm bundles) | 51M |
|
||||
| `agent/npm/node_modules/onnxruntime-web/dist/` (Wasm runtime) | 43M |
|
||||
| `agent/sessions/--home-dank-Projects-*/...` (session JSONL files) | 80M+ |
|
||||
| `agent/npm/node_modules/playwright-core/lib/` (browser bundle) | 6.1M |
|
||||
|
||||
**Prune candidates flagged by task scope:**
|
||||
- `research/` (44K) — research artifacts, likely stale
|
||||
- `browser-profile/` (200M) — browser profile, not needed for agent
|
||||
- `trash/` (1.8M) — agent session trash, purgeable
|
||||
|
||||
### ~/.omp — Command Center
|
||||
|
||||
> **411M.** Git repo, 7,726,074 commits. Remote: `192.168.0.143:3000/dank/command-center.git`. Last commit: `7726074 chore: birth — state-out ignore policy + secret-gate pre-commit hook`. Active configuration center for the OMP harness — **do not prune.**
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| **Total config items** | 133 (38 top-level + 38 ~/.config + 26 ~/.local/share + 27 ~/.pi top-level entries + 1 ~/.omp) |
|
||||
| **Orphaned configs** (owning program uninstalled) | **7** (.claude, .nv, .nvm, .paradoxlauncher at top-level; chromium-headless, google-chrome-for-testing, paradox-launcher-v2 in ~/.config) |
|
||||
| **Total storage for configs** | ~567M (dotfiles) + 574M (~/.config) + 765M (~/.local/share excl. Steam) + 2.5G (~/.pi) = **~4.4G** |
|
||||
| **Steam library** | 550G (not counted in config total — it's game data) |
|
||||
|
||||
### Top 10 Largest Offenders by du -sh
|
||||
|
||||
| Rank | Path | Size | Notes |
|
||||
|---|---|---|---|
|
||||
| 1 | `~/.local/share/Steam/` | 550G | Game library (not config) |
|
||||
| 2 | `~/.cache/` | 6.4G | System cache |
|
||||
| 3 | `~/.pi/` | 2.5G | Legacy agent (git repo) |
|
||||
| 4 | `~/.bun/` | 2.3G | Bun runtime |
|
||||
| 5 | `~/.npm/` | 1.7G | Node modules cache |
|
||||
| 6 | `~/.local/share/Larian Studios/` | 1.5G | Baldur's Gate 3 (via Steam) |
|
||||
| 7 | `~/.omp/` | 411M | Command center (keep) |
|
||||
| 8 | `~/.local/share/Trash/` | 424M | Desktop trash |
|
||||
| 9 | `~/.config/mozilla/` | 334M | Firefox profile |
|
||||
| 10 | `~/.local/share/nvim/` | 186M | Neovim data/cache |
|
||||
|
||||
### Prune Summary
|
||||
|
||||
| Category | Count | Entries |
|
||||
| File | Program (path) | Authored or default |
|
||||
|---|---|---|
|
||||
| Full program uninstalled | 7 | .claude, .nv, .nvm, .paradoxlauncher, chromium-headless, google-chrome-for-testing, paradox-launcher-v2 |
|
||||
| Stale session/trash data | 3 | .pi/trash, .pi/browser-profile, .pi/research |
|
||||
| **Total recommended for review** | **10** | — |
|
||||
| `~/.gitconfig` | git (`/usr/bin/git`) | Authored — single setting line: `credential.helper = store --file ~/.config/secrets/gitea` (points at the secret tier; adopt the line, never the file it names) |
|
||||
| `~/.config/bat/config` | bat (`/usr/bin/bat`) | Authored — one line `--theme=noctalia` |
|
||||
| `~/.config/lazygit/config.yml` | lazygit (`/usr/bin/lazygit`) | Authored — 28-line custom noctalia color theme |
|
||||
| `~/.config/micro/settings.json` | micro (`/usr/bin/micro`) | Authored — `{"colorscheme":"noctalia"}` |
|
||||
| `~/.config/micro/bindings.json` | micro (`/usr/bin/micro`) | Default — empty `{}`, no custom bindings |
|
||||
| `~/.config/flameshot/flameshot.ini` | flameshot (`/usr/bin/flameshot`) | Authored — `contrastOpacity=188`, `startupLaunch=true` |
|
||||
| `~/.config/glow/glow.yml` | glow (`/usr/bin/glow`) | Default — shipped template, all default values (noctalia look comes from the generated `noctalia.json`, not this file) |
|
||||
| `~/.config/autostart/Flameshot.desktop` | flameshot (`/usr/bin/flameshot`) | Authored — `Exec=flameshot`, `X-GNOME-Autostart-enabled=true` |
|
||||
|
||||
Every `~/.config` box-only row above lists each file, so the session can extend
|
||||
beyond these seven (e.g. `btop/btop.conf`, `shelly/*` are also installed-program
|
||||
configs).
|
||||
|
||||
## Never track (box-only)
|
||||
|
||||
| Reason | Contents |
|
||||
|---|---|
|
||||
| **Generated palette expansion** | Every `themes/noctalia*`, `*/noctalia.*`, `*/colors/noctalia*` / `Noctalia*` (24 files outside app-profile dirs). Machine-written by matugen/noctalia, not hand-authored: the `stat -c '%y %n'` cluster `2026-10-14 19:05:40.330…40.813 -0700` writes 24 files in one second (`alacritty/themes/noctalia.toml` at `.330212397`, `btop/themes/noctalia.theme` at `.334212456`, `tmux/themes/noctalia.conf` at `.813219586`, …); later re-runs re-stamp singles — `bat/themes/noctalia.tmTheme` `2026-10-18 17:11:22`, `niri/noctalia.kdl` `2026-10-18 17:36:27`, `mango/noctalia.conf` `2026-10-17 18:23:37`. A same-second mass-write is generation, never an edit. |
|
||||
| **App state / profiles** | `mozilla` (5471), `unity3d` RimWorld state (235), `nirimod` (116), `obsidian` (99), `FreeTube` (56), `google-chrome-for-testing` (5); plus per-program state inside tracked dirs: `dconf/user`, `fish/fish_variables`, `micro/syntax/*`+`micro/buffers/history`, `gtk-4.0/servers`, `cachyos/cachyos-hello.log`. |
|
||||
| **Skills-CLI installs** | `crush` (37), `devin` (37), `goose` (37), `kimchi/harness` (37) skill trees — provisioned by the skills CLI, owned by the store, not this repo. See [skills/store-wiring](/skills/store-wiring.md). |
|
||||
| **Secret tier** | `~/.config/secrets/` (`gitea`, `hf_token`, `hf_token_write`, `kagi_api_key` — names only), top-level `frank-job-hunt.env`, `containers/systemd/postgres.env`. Never tracked, values never printed. |
|
||||
| **Trash committed inside the tree** | `dotfiles/niri/.config/niri/cfg/.AGENTTRASH/display.kdl.superseded` — a retired output config committed 2026-10-09, five days before `.gitignore:54` gained `.AGENTTRASH/` (2026-10-14). Tracked-then-ignored: an ignore rule never reaches an already-tracked path, so the only repair is `git rm --cached`, done by #41. The rule does match at depth — `git check-ignore --no-index -v dotfiles/niri/.config/niri/cfg/.AGENTTRASH/x` resolves to `.gitignore:54` — so a fresh in-tree trash dir is ignored from birth and this does not recur on its own. |
|
||||
|
||||
## Owned-elsewhere (do not adopt here)
|
||||
|
||||
- `~/.config/containers/systemd/franksign-db.container` — header:
|
||||
*"Installed by `infra/setup-dev.sh` into `~/.config/containers/systemd/`."*
|
||||
Owned by the franksign repo; its quadlet, not this box's config.
|
||||
- `~/.config/systemd/user/fib-watchdog.service` + `.timer` — box-only.
|
||||
`ExecStart=/usr/bin/bun src/cli.ts worker` with
|
||||
`WorkingDirectory=/home/blank/Projects/frank-intelligence-bench`.
|
||||
`systemctl --user is-active fib-watchdog.timer` → **inactive**. This box holds
|
||||
the only copy. Do **not** adopt it into `dotfiles`: `ExecStart` and
|
||||
`WorkingDirectory` are the bench project's internals, so that repo owns the
|
||||
unit — land it there and this row becomes "deployed by the bench repo". Until
|
||||
then it is a reinstall risk recorded here, not a dotfiles gap.
|
||||
- **Hygiene finding, fixed 2026-10-20:** the enable-symlink
|
||||
`~/.config/systemd/user/default.target.wants/blankpad-desk-autoswitch.service`
|
||||
pointed into `~/.omp/wt/de-redesign-20261009/…`, a worktree the heartbeat had
|
||||
already reaped — so desk auto-switching was silently off while
|
||||
`systemctl --user is-enabled` still answered `enabled`. Relinked to
|
||||
`dotfiles/zz-blankpad/.config/systemd/user/blankpad-desk-autoswitch.service`
|
||||
and `daemon-reload`ed; left `inactive` on purpose, because starting it mid-
|
||||
session applies a desk profile and the box is in use. Law and diagnosis:
|
||||
[dotfiles-deploy](/tools/dotfiles-deploy.md).
|
||||
|
||||
## Prune list (owning program absent from `command -v`)
|
||||
|
||||
All verified with `command -v <prog>` (git resolves to `/usr/bin/git`; a session
|
||||
alias can mask it, so probe in a clean shell).
|
||||
|
||||
| Config | Program checked |
|
||||
|---|---|
|
||||
| `sway`, `hypr`, `labwc`, `mango`, `scroll`, `umbriel` | `sway` `hyprland` `labwc` `mango` `scroll` `umbriel` — all ABSENT |
|
||||
| `wezterm`, `kitty`, `foot` | `wezterm` `kitty` `foot` — all ABSENT |
|
||||
| `rofi`, `helix`, `cava`, `ytm-player`, `starship.toml` | `rofi` `hx` `cava` `ytm-player` `starship` — all ABSENT |
|
||||
| `pywalfox`, `qt5ct`, `qt6ct`, `turborepo` | `pywalfox` `qt5ct` `qt6ct` `turbo` — all ABSENT |
|
||||
|
||||
The first three rows are the expected set (all 14 confirmed absent). **My check
|
||||
disagrees with the expected set by extending it**: `pywalfox`, `qt5ct`, `qt6ct`,
|
||||
`turborepo` are also installed-nowhere config dirs and belong in the prune list.
|
||||
Note the orphan dirs `cava`, `rofi`, `helix`, `wezterm`, `qt5ct`, `qt6ct` contain
|
||||
*only* a generated noctalia palette file — pruning them removes no authored
|
||||
config. `crush`/`devin`/`goose`/`kimchi` also fail `command -v` but are the
|
||||
skills-CLI tier above, not this session's prune targets.
|
||||
|
||||
## Links
|
||||
|
||||
- [skills/store-wiring](/skills/store-wiring.md) — owns the skills-CLI tier
|
||||
claim (`crush`/`devin`/`goose`/`kimchi`).
|
||||
- [KB index](/index.md) — knowledge-base entry point.
|
||||
|
||||
@@ -16,6 +16,32 @@ lesson, a few lines. Full incident narratives are recoverable from git
|
||||
(`git log --follow omp-docs/log.md`); knowledge that outlives an entry lives
|
||||
on its concept page, linked here. Never re-expand an entry in place.
|
||||
|
||||
## 2026-10-20 — the secret gate was reading the wrong files, and one credential had already escaped
|
||||
|
||||
Found while deciding what else belongs in `dotfiles/`. (1) The gate whitelisted
|
||||
extensions (`*.md *.yml *.json *.toml *.sh *.ts *.js`), so a `.env`, `.container`,
|
||||
`.ini` or extension-less dotfile was never read — exactly the shapes the dotfiles
|
||||
tier is made of. It now scans the whole staged diff for token shapes and, in
|
||||
non-prose files, for credential assignments, and it fails closed when `git diff`
|
||||
itself errors. That last clause earned its place: the rewrite's first attempt
|
||||
passed `-I` to `git diff`, which is not valid there, got an empty file list, and
|
||||
reported success on every commit — that is what a blind gate looks like, and it
|
||||
looks identical to a clean tree. (2) A live credential had already escaped into
|
||||
tracked content: a Caddy basicauth bcrypt hash, its plaintext password, and the
|
||||
Pi-hole internal password, in an auto-learned memory file. Redacted — but both
|
||||
credentials need **rotation**, because redaction does not un-leak history that
|
||||
every machine has already pulled. (3) `home/dank/.omp/dotfiles/**` and
|
||||
`.pre-pull-backup/**` were tracked fiction invisible to `deploy.sh`, and 23
|
||||
wallpaper JPEGs (80 MiB) rode along in every clone. Laws: a gate must fail closed,
|
||||
since a silently-passing gate is worse than none — it buys the belief of safety;
|
||||
auto-learned memory is written by a process that has read your terminal, so it is
|
||||
a leak surface and gets gated like any config; `systemctl --user enable`
|
||||
snapshots an absolute path, so enabling from a worktree means the seven-day reap
|
||||
silently kills the unit while `is-enabled` goes on saying `enabled`
|
||||
(`blankpad-desk-autoswitch.service` was dangling that way). Details:
|
||||
[dotfiles-deploy](tools/dotfiles-deploy.md),
|
||||
[home-config-census](inventory/home-config-census.md).
|
||||
|
||||
## 2026-10-20 — engineering skills configured against this KB
|
||||
|
||||
`setup-matt-pocock-skills` ran, and its output moved out of the skill's default
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
type: reference
|
||||
title: Dotfiles deployment tier
|
||||
description: How dotfiles/ packages become live configuration — stow-equivalent symlinks, host override packages, and the two laws about theme media and systemd enable symlinks.
|
||||
tags:
|
||||
- dotfiles
|
||||
- deployment
|
||||
- systemd
|
||||
- noctalia
|
||||
timestamp: 2026-10-20
|
||||
verified: 2026-10-20
|
||||
---
|
||||
|
||||
# Dotfiles deployment tier
|
||||
|
||||
`dotfiles/` holds home-relative trees and `scripts/deploy.sh` is a stow-equivalent: the
|
||||
repo checkout **is** the deployment, and deploying replaces live files with symlinks into
|
||||
the repo, so `git pull` + `deploy.sh` is the whole propagation story. One directory per
|
||||
tool (`niri`, `ghostty`, `gtk-3.0`, `systemd-user`, …); a package named `zz-$(hostname)`
|
||||
is stowed last and its files replace symlinks coming from base packages.
|
||||
|
||||
| Mode | Effect |
|
||||
|---|---|
|
||||
| `deploy.sh --check` | dry run — adopts identicals, reports what would be linked, lists divergents, writes nothing. Run this first, always |
|
||||
| `deploy.sh` (sync) | adopt identicals, link new, quarantine divergents into `.quarantine/` and replace them |
|
||||
| `deploy.sh --adopt` | like sync, but divergents are reported and skipped rather than quarantined |
|
||||
|
||||
Two package shapes are not symlinks: `dconf/dump.txt` is loaded with `dconf load /`, and
|
||||
`systemd-user` units get `daemon-reload` plus `enable --now` only when their `ExecStart`
|
||||
binary resolves on the current machine, so a unit belonging to another box stays inert
|
||||
instead of crash-looping.
|
||||
|
||||
**The tracked unit is the file, not the directory.** A live directory can be a real
|
||||
directory and still be fully tracked, because every file inside it is a symlink into the
|
||||
repo (`niri` is 9 tracked / 1 box-only; `gtk-3.0` is 3/0). Any audit that classifies by
|
||||
directory mislabels the tree — classify per file with `readlink -f`. The live census and
|
||||
its classifier command are in [home-config-census](/inventory/home-config-census.md).
|
||||
|
||||
## Law: media the theme pipeline consumes is content, not config
|
||||
|
||||
Wallpapers live at `dotfiles/noctalia/.local/share/noctalia/wallpapers/` and are
|
||||
gitignored. They were tracked until 2026-10-20 — 23 files, 80 MiB — which meant every
|
||||
clone of a *configuration* repo carried a media library no diff can review, and the
|
||||
payload only ever grows because deleting a tracked blob from the tree does not delete it
|
||||
from history. [Noctalia](noctalia-shell.md) reads
|
||||
`~/.local/share/noctalia/wallpapers/`, which is a set of symlinks into that directory, so
|
||||
the files stay on disk and untracking them broke nothing on this box.
|
||||
|
||||
Restore on a fresh box: every filename is `wallhaven_<id>.<ext>` and the ID is the whole
|
||||
address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` puts the library back, then
|
||||
`deploy.sh` relinks it. Losing the set is an acceptable outcome; 80 MB per clone forever
|
||||
is not.
|
||||
|
||||
## Law: never `systemctl --user enable` a unit from a worktree
|
||||
|
||||
`enable` writes `~/.config/systemd/user/default.target.wants/<unit>` as a symlink to the
|
||||
unit path it was handed, and that path is absolute. The heartbeat reaps `wt/*` worktrees
|
||||
older than seven days ([live-sync](live-sync.md)), so a unit enabled from a worktree turns
|
||||
into a dangling symlink while `systemctl --user is-enabled` still answers `enabled` — the
|
||||
unit is dead and the status lies about it. Found on 2026-10-20:
|
||||
`blankpad-desk-autoswitch.service` pointed into the reaped `wt/de-redesign-20261009`, so
|
||||
desk auto-switching had been silently off since the reap.
|
||||
|
||||
Check the link target before the unit file:
|
||||
|
||||
```bash
|
||||
ls -l ~/.config/systemd/user/default.target.wants/
|
||||
# every target must resolve; a dangling one is a reaped worktree, not a config bug
|
||||
```
|
||||
|
||||
Fix is to relink into the deployment checkout (`~/.omp/dotfiles/…`), `systemctl --user
|
||||
daemon-reload`, then confirm with `is-active` — `is-enabled` proves nothing.
|
||||
|
||||
## Law: a generated file must never be a required include
|
||||
|
||||
`~/.config/niri/config.kdl` ends with `include "noctalia.kdl"` — no `optional=true`.
|
||||
That file is noctalia's palette output (`#ff8c00`, `#f43841`, one re-stamp
|
||||
`2026-10-18 17:36:27`), so it is machine state and correctly untracked. But on a fresh
|
||||
box `deploy.sh` links `config.kdl` first, and niri then refuses to start until noctalia
|
||||
has run once and written the file — the compositor is bricked by the order of operations,
|
||||
not by a bad config. Close it at the include, which survives someone skipping the theme
|
||||
pipeline: mark generated includes `optional=true`, as the `display.kdl` and
|
||||
`switch-events.kdl` lines in the same file already are.
|
||||
|
||||
## Desk choice is live state, never config
|
||||
|
||||
`~/.config/niri/cfg/display.kdl` is a relative symlink to whichever profile is active
|
||||
(`desk-1-home.kdl`, `desk-2-portable.kdl`), written by `desk.sh`. Both profiles are
|
||||
tracked in `dotfiles/zz-blankpad/`; the pointer is not, because which desk the box sits on
|
||||
is not a fact about the repo. A fresh box therefore starts with no output configuration —
|
||||
`config.kdl` includes `display.kdl` with `optional=true`, so niri comes up usable but with
|
||||
default monitors until `desk.sh` is run once.
|
||||
@@ -9,6 +9,7 @@ Configuration guides and reference material for installed software. Each tool ha
|
||||
* [applications](applications.md) — Default userspace applications on CachyOS with Niri — browsers, editor, terminal, file manager, audio, screenshots, gaming.
|
||||
* [cachyos](cachyos.md) — Arch-based rolling release with performance-optimized kernels, repositories, and gaming stack. Running on the workstation.
|
||||
* [comfyui](comfyui/index.md) — Node-based AI image generation engine on dankclaw. FLUX.2 klein 4B, CPU backend, systemd managed.
|
||||
* [dotfiles-deploy](dotfiles-deploy.md) — How `dotfiles/` packages become live config: `deploy.sh` modes, host override packages, and why theme media and worktree-enabled units are traps.
|
||||
* [ghostty](ghostty/index.md) — GPU-accelerated terminal emulator with extensive configuration, keybindings, and shaders.
|
||||
* [gitea](gitea/index.md) — Private Git hosting on dankclaw. Quadlet-managed containers: Gitea, PostgreSQL, Caddy.
|
||||
* [handy](handy.md) — Offline speech-to-text transcription with WebKitGTK backend and Tauri v2 UI.
|
||||
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bats
|
||||
# tests/secret-gate.bats — black-box tests for .githooks/pre-commit
|
||||
#
|
||||
# Contract: a commit whose staged content contains a credential is blocked
|
||||
# (non-zero exit, stderr names the file); a commit whose content is benign
|
||||
# configuration or prose about credentials is allowed. The gate is copied into a
|
||||
# scratch repo so git itself invokes it — no sourcing, no internal state.
|
||||
|
||||
setup() {
|
||||
HOOK="$BATS_TEST_DIRNAME/../.githooks/pre-commit"
|
||||
REPO="$(mktemp -d)/g"
|
||||
mkdir -p "$REPO/.git/hooks"
|
||||
git init -q "$REPO"
|
||||
git -C "$REPO" config user.email gate@test
|
||||
git -C "$REPO" config user.name gate
|
||||
cp "$HOOK" "$REPO/.git/hooks/pre-commit"
|
||||
chmod +x "$REPO/.git/hooks/pre-commit"
|
||||
}
|
||||
|
||||
teardown() { rm -rf "$(dirname "$REPO")"; }
|
||||
|
||||
# try_commit <file> <content> -> sets status/output from the real commit attempt
|
||||
try_commit() {
|
||||
local file="$1" content="$2"
|
||||
mkdir -p "$REPO/$(dirname "$file")"
|
||||
printf '%s\n' "$content" > "$REPO/$file"
|
||||
run git -C "$REPO" add -A
|
||||
run git -C "$REPO" commit -q -m "attempt: $file"
|
||||
}
|
||||
|
||||
@test "blocks a low-entropy password in a .env file" {
|
||||
try_commit "postgres.env" 'POSTGRES_PASSWORD=hunter2corp2026'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"SECRET GATE"* ]]
|
||||
[[ "$output" == *"postgres.env"* ]]
|
||||
}
|
||||
|
||||
@test "blocks a credential in a file type the extension whitelist used to skip" {
|
||||
try_commit "app.conf" '[app]
|
||||
secret_key = "rotatedvalue99"'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"app.conf"* ]]
|
||||
}
|
||||
|
||||
@test "blocks a token shape in a plain text file" {
|
||||
try_commit "notes.txt" "backup remote: ghp_$(printf 'a%.0s' {1..36})"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"notes.txt"* ]]
|
||||
}
|
||||
|
||||
@test "blocks a bcrypt hash" {
|
||||
try_commit "dump.txt" 'basicauth: $2b$14$'"$(printf 'a%.0s' {1..53})"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"dump.txt"* ]]
|
||||
}
|
||||
|
||||
@test "blocks a credential in a quadlet unit" {
|
||||
try_commit "db.container" '[Container]
|
||||
Environment=POSTGRES_PASSWORD=devpostgres2026'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"db.container"* ]]
|
||||
}
|
||||
|
||||
@test "prose about credentials in markdown is still allowed" {
|
||||
try_commit "docs/runbook.md" 'The GITEA_TOKEN moved out of .zshrc into secrets.env.
|
||||
Set password: user.set_password(value) and api_key = "sk-no-key-required".'
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "placeholder and indirected values in config files are allowed" {
|
||||
try_commit "app.conf" '[db]
|
||||
PASSWORD = CHANGEME
|
||||
SECRET_KEY = ${VAULT_SECRET}
|
||||
api_key = "sk-no-key-required"
|
||||
token_file = <operator_token>'
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "environment variable indirection is not a secret" {
|
||||
try_commit "start.sh" '#!/bin/sh
|
||||
export GITEA_TOKEN="$GITEA_TOKEN"
|
||||
export API_KEY_FILE=/run/secrets/api_key'
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "binary content never trips the gate" {
|
||||
head -c 4096 /dev/urandom > "$REPO/wall.jpg"
|
||||
run git -C "$REPO" add -A
|
||||
run git -C "$REPO" commit -q -m "binary"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "ALLOW_SECRETS still overrides, and says so" {
|
||||
try_commit "postgres.env" 'POSTGRES_PASSWORD=hunter2corp2026'
|
||||
[ "$status" -ne 0 ]
|
||||
mkdir -p "$REPO/.git/hooks"
|
||||
run env ALLOW_SECRETS=1 git -C "$REPO" commit -q -m "override"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"ALLOW_SECRETS set"* ]]
|
||||
}
|
||||
|
||||
@test "this repo's ignore policy quarantines env files at any name" {
|
||||
repo="$BATS_TEST_DIRNAME/.."
|
||||
run git -C "$repo" check-ignore -q --no-index dotfiles/db/postgres.env
|
||||
[ "$status" -eq 0 ]
|
||||
run git -C "$repo" check-ignore -q --no-index dotfiles/app/frank-job-hunt.env
|
||||
[ "$status" -eq 0 ]
|
||||
run git -C "$repo" check-ignore -q --no-index dotfiles/bat/config
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "the gate refuses to pass when it cannot read the staged diff" {
|
||||
# A gate that swallows a git error reports success on every commit. This is not
|
||||
# hypothetical: the 2026-10-20 rewrite passed `-I` to git diff, which is not a
|
||||
# valid flag there, and the empty file list made every secret commit sail through.
|
||||
nodir="$(mktemp -d)"
|
||||
run bash -c "cd '$nodir' && '$HOOK'"
|
||||
rm -rf "$nodir"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"cannot read the staged diff"* ]]
|
||||
}
|
||||
|
||||
@test "deleting a credential is never blocked" {
|
||||
# The commit that removes a secret must always be possible, or the gate holds a
|
||||
# leak hostage. It did exactly that on 2026-10-20: matching removed lines made
|
||||
# the redaction commit look like the leak. Seed with the documented override,
|
||||
# then remove with an ordinary commit.
|
||||
printf 'POSTGRES_PASSWORD=hunter2corp2026\n' > "$REPO/db.conf"
|
||||
run git -C "$REPO" add -A
|
||||
run env ALLOW_SECRETS=1 git -C "$REPO" commit -q -m "seed a leak"
|
||||
[ "$status" -eq 0 ]
|
||||
printf '[db]\nPASSWORD = ${VAULT_SECRET}\n' > "$REPO/db.conf"
|
||||
run git -C "$REPO" add -A
|
||||
run git -C "$REPO" commit -q -m "redact the leaked password"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "the gate can commit itself" {
|
||||
# Fixtures have to look like credentials, so a gate that scans its own test file
|
||||
# without exemption can never be committed — it holds its own maintenance
|
||||
# hostage. The exemption is two exact paths, and it must announce itself.
|
||||
mkdir -p "$REPO/.githooks" "$REPO/tests"
|
||||
cp "$HOOK" "$REPO/.githooks/pre-commit"
|
||||
printf 'POSTGRES_PASSWORD=hunter2corp2026\n' > "$REPO/tests/secret-gate.bats"
|
||||
run git -C "$REPO" add -A
|
||||
run git -C "$REPO" commit -q -m "gate and its fixtures"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"self-test fixtures exempt"* ]]
|
||||
}
|
||||
|
||||
@test "a credential hidden in some other test file is still caught" {
|
||||
# The exemption is a filename list, not the tests/ directory.
|
||||
mkdir -p "$REPO/tests"
|
||||
printf 'POSTGRES_PASSWORD=hunter2corp2026\n' > "$REPO/tests/other.bats"
|
||||
run git -C "$REPO" add -A
|
||||
run git -C "$REPO" commit -q -m "smuggle"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"tests/other.bats"* ]]
|
||||
}
|
||||
Reference in New Issue
Block a user