feat(fleet): normalize docker deploy trees to root:docker setgid

Operator ruling: root:docker, not a personal username and not a new admin
account. lkraven is one of three names he uses, so baking it into shared
infrastructure guarantees a stale owner later; a dedicated deploy account
buys nothing the existing docker group doesn't, since that group already
exists on every host holding exactly lkraven + infra-ops.

Applied to nh3-dev, nh3-docker, esh-docker-vm, irv-ml1, ana-docker. All
five now 2775 root:docker on /opt/docker and /opt/docker/compose. Clears
the 0777 on nh3-docker and ana-docker. 55 stack .env files normalized to
root:docker 0640, tightening 43 world-readable ones and opening 31 that
were readable by only one of the two deploy identities. No containers
bounced — inode metadata only, and .env is read at compose up.

Deliberately not a recursive chmod. Three acme.json files and an ssh
private key are mode 0600 and traefik/ssh refuse to start if that widens,
which would have been a delayed failure surfacing at the next restart
rather than now. Protection is both mode-based (0600/0400 untouched) and
name-based (acme.json, *.key, *.pem, *.pfx, id_*); modes are symbolic so
the 53 executable files in these trees keep their exec bit.

Two defects found and fixed mid-rollout. The name list was initially
reported but not enforced, so a .key already at 0644 on esh-docker-vm was
widened to 0664 — reverted, and the list is now enforced in the chgrp and
widening steps. And the exec-bit verify asserted every .sh is executable,
which was never true and false-FAILED irv-ml1; it now compares the
executable-file count against a recorded baseline.
This commit is contained in:
vh
2026-09-14 13:13:56 -07:00
parent ccc0df6870
commit 826a63b00c
2 changed files with 224 additions and 4 deletions
+14 -4
View File
@@ -43,10 +43,20 @@ local Bash already executes here — no SSH-to-self needed for non-privileged wo
`rc=1 permission denied`, sudo retry `rc=0 Container beszel-agent Started`.
A false FAILED in automation output is worse than a quiet one; it trains
readers to skim the failure lines.
- **`/opt/docker/compose` ownership — nh3-dev is the fleet outlier.**
`root:root` here; `lkraven:lkraven` on irv-ml1, nh3-docker, ana-docker and
esh-docker-vm. So "can a project session deploy its own stack" is false only
on the box where sessions actually run. `/opt/docker/compose/talk` was created
- **`/opt/docker` ownership — normalised fleet-wide 2026-09-14 to
`root:docker 2775`** (setgid) via `playbooks/normalize-docker-tree.yaml`,
operator ruling. Was a three-way split: `root:root 755` here, `root:root 777`
on nh3-docker + ana-docker (world-writable, from a 2024 `chmod -R 777` to get
a git clone working), `lkraven 755` elsewhere. Not a personal username
(`lkraven` is one of three the operator uses) and not a new admin account —
the `docker` group already existed on every host holding exactly `lkraven` +
`infra-ops`. ⚠ This is **not** privilege separation: `docker` membership is
root-equivalent. A future non-root deployer needs a dedicated `deploy` group.
Stack `.env` files went to `root:docker 0640` — previously 31 of 74 were
`0600` readable by only ONE of the two deploy identities (varying by file,
which is what false-FAILED beszel during the docker-ce upgrade) and 43 were
world-readable `0644`. **No containers were bounced**; these are inode
metadata changes and `.env` is read only at `compose up`. `/opt/docker/compose/talk` was created
`lkraven`-owned 2026-09-14 and **tts-dev migrated `talk` into it the same
day** — it had been at `~/talk`, a convention violation that hid it from
anything walking `/opt/docker/compose/*/`. Old path parked at