CT-101 Samba: dark2devil-Korrektur, ACL-Abgleich und AGENTS.md-Grundwissen

- dark2devil (UID 1002) aus der gesamten deklarierten State entfernt:
  derselbe Benutzer wie debugofen (Betreiberklärung 2026-10-28); GNAI-019
  Entscheidung 11 revidiert, gn-media-Mitgliedschaft nur ghost
- Share-ACLs im Inventar an die live smb.conf angeglichen (GNAI-018 R4):
  admin users auf elf Shares ergänzt, write list Dokumente/Extern ergänzt,
  Serien2 ohne force user/group; Template [printers] um browseable=no ergänzt
- passdb-Asserts und Audit-Loops auf die drei deklarierten Medien-User
  umgestellt; Platzhalterzahl 11; provision-Script-Hash aktualisiert
- AGENTS.md (Repo-Root) um Grundwissen für Agenten ergänzt:
  ghost-agent-Convention, Account-Landscape/Inventory-Pfade,
  Operator-Run-Steps, LXC-Konventionen, Repo-Konventionen

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-10-28 16:30:33 +01:00
parent 61f2158508
commit 8b21797502
25 changed files with 671 additions and 208 deletions
+92
View File
@@ -5,3 +5,95 @@ Before infrastructure work, read `.codex/LOCAL_MEMORY.md` if it exists. It conta
For read-only access to the KI server, use the tested SSH profile `ghostnet-ai-agent`, which logs in as the unprivileged account `ghost-agent` on `10.0.0.90`. On this workstation, invoke it as `ssh -F ~/.ssh/config ghostnet-ai-agent` because the system-wide SSH client configuration currently has an ownership/permissions error. Do not assume sudo access or make server changes without an explicit user request.
The KI server at `10.0.0.90` is deliberately a fresh base system. The operator plans to install the LLM application stack in a later Ansible sprint. Do not install applications or change server configuration as part of documentation review or access checks unless the operator explicitly requests that work.
## Agent access convention: `ghost-agent`
`ghost-agent` is the standard unprivileged account for the AI agent on the
Proxmox LXCs and the KI server (operator-confirmed 2026-10-28: "on the KI
server and many other Proxmox LXCs"). It is a convention, not a per-host
exception.
- Login is key-based with a **passphrase-less** Ed25519 key kept in the
operator's `~/.ssh` (`id_ed25519_ghostnet_<host>_agent`); `~/.ssh/config`
carries the matching `ghostnet-<host>-agent` aliases, which also work with
`ssh -o BatchMode=yes`. Private keys never enter the repository.
- `ghost-agent` has **no sudo** (verified on CT 100, CT 101, CT 103 and
ghostnet-ki): treat every agent session as read-only unless the operator
explicitly authorizes a change.
- Known aliases (verify live state before use; the WireGuard LXC has no
`ghost-agent` alias and uses the legacy `admin` account via alias `lxc`):
| Alias | System | IP |
| --- | --- | --- |
| `ghostnet-ct100-agent` | CT 100, Pi-hole/Split-DNS `dns` | 10.0.0.14 |
| `ghostnet-ct101-agent` | CT 101, Samba file server | 10.0.0.9 |
| `ghostnet-ct102-agent` | CT 102, Keycloak | 10.0.0.4 |
| `ghostnet-ct103-agent` | CT 103, reverse proxy (NPM) | 10.0.0.10 |
| `ghostnet-ct104-agent` | CT 104, Keycloak | 10.0.0.16 |
| `ghostnet-ai-agent` | ghostnet-ki, LLM/AI server | 10.0.0.90 |
- Invoke ssh as `ssh -F ~/.ssh/config <alias>`: a bare `ssh` currently rejects
`/etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf` because of its
ownership/permissions; `-F` deliberately uses the local profile without
loading that broken system configuration.
- Dated per-host observations (host-key fingerprints, verified identities,
account details) live in `.codex/LOCAL_MEMORY.md`, not here.
## Account landscape and Ansible inventory paths
| Account | Role | Key (operator `~/.ssh`) | sudo |
| --- | --- | --- | --- |
| `ghost-agent` | AI agent, read-only | `id_ed25519_ghostnet_<host>_agent` (no passphrase) | no |
| `ghost` | human operator/admin | `id_ed25519_ghostnet_<host>_ghost` (passphrase-protected) | yes, password-gated |
| `ansible-deploy` | Ansible deployment; created by the project's access role during adoption | `id_ed25519_ghostnet_<host>_ansible_deploy` (no passphrase) | `NOPASSWD: ALL` (treat the key as root-equivalent) |
Each Ansible LXC project (`proxmox/lxc/<ct>/ansible/`) maps these accounts to
three inventories: `agent` (`ghost-agent`, read-only preflight), `bootstrap`
(`ghost`, `--ask-become-pass`), `production` (`ansible-deploy`, NOPASSWD).
Legacy exceptions exist (CT 103 is root-only and bootstrapped via the Proxmox
console; the WireGuard LXC uses `admin`) — see `.codex/LOCAL_MEMORY.md`.
## Operator-run steps
- The workstation has **no Proxmox host access** (no `pct`): PVE-side commands
(`pct exec`, `pct reboot`, `vzdump`, editing `/etc/pve/lxc/*.conf`) are run by
the operator as root@pve. The agent prepares exact commands and reviews the
pasted output.
- Interactive secret steps are operator-run: `passwd`, `smbpasswd -a`, playbook
runs with `--ask-become-pass`, and key passphrase prompts. The agent hands
over copy-paste commands; the operator executes them.
- Secrets never enter the repository, inventories, playbooks or logs: SMB
passwords, the Samba passdb, the `ghost` password, private SSH keys, Podman
secrets. Only public keys belong in the repo (`ansible/files/ssh/`).
## LXC / Proxmox conventions
- Reboot caveat (GNAI-016 section 7): in LXC, `/proc/sys/kernel/random/boot_id`
belongs to the PVE host and does **not** change on a guest reboot — never use
boot_id-based waiters; reboot via PVE and verify independently over SSH.
- Unprivileged LXCs: keep the empty `lxc.seccomp.profile:` line (rootful
Podman/Ganesha containers need `open_by_handle_at`), mask
`sys-kernel-config.mount` (configfs is unavailable), and expect
`uid_map 0 100000 65536` — host UIDs appear as 65534 (nobody) inside the
guest.
- In-place dist-upgrades: take a `vzdump` safety net first and use
`--force-confold` so productive configuration files survive.
## Repository conventions
- Documentation: `docs/platform/proxmox-server/lxc/<ct>/GNAI-*.md` (German;
decisions carry operator/date stamps, findings are lettered a), b), …).
Ansible project READMEs are English. The central index is
`docs/GNAI-000-DOC_Index.md`.
- Ansible projects are self-contained under `proxmox/lxc/<ct>/ansible/`
(`ansible.cfg`, `inventories/{agent,bootstrap,production}`, `playbooks/`,
`roles/`, `files/ssh/`), use the repository-wide control venv (`.venv`, built
from `shared/ansible/requirements-control.txt`) and the shared roles in
`shared/ansible/roles/`.
- Change discipline: productive changes only with explicit operator go; preview
with `--check --diff` first; convergence must be idempotent (a second run
reports `changed=0`); commit only when the operator asks.
- Communicate with the operator in German.
- Verify account/identity assumptions with the operator before declaring them
(CT 101 example: `dark2devil` turned out to be the same person as
`debugofen`; GNAI-019 decision 11 was revised accordingly).
@@ -10,8 +10,9 @@
CT 101 (`10.0.0.9`) ist der Samba-Dateiserver des Proxmox-Kontexts und trägt
den Medienspeicher (Filme, Serien, Anime, Musik, Software, Downloads) auf
mehreren Platten (`/media/daten1`, `/media/daten2`, `/media/daten3`,
`/media/datenhdd2`, `/media/datenhdd3`). Bislang ist die LXC nicht im
mehreren Platten (Mounts `/media/daten1`, `/media/daten2`,
`/media/daten3`, `/media/datenhdd1`, `/media/datenhdd2/extern`,
`/media/datenhdd3`; erfasst am 27.10.2026). Bislang ist die LXC nicht im
Infrastructure-Repository verwaltet; am 19.10.2026 wurde beschlossen, sie
aufzunehmen (Betreiberentscheidung).
@@ -26,9 +27,10 @@ offenen Punkte fest. Bestehende Shares bleiben unberührt.
| Container-ID | CT 101 |
| IPv4 | 10.0.0.9 |
| Samba-Rolle | standalone server, Workgroup `Ghost.net` (aus `smb.conf`) |
| SMB-Benutzer | `ghost`, `daidalaw`, `debugofen` |
| SMB-Benutzer | `ghost`, `daidalaw`, `debugofen` (die passdb enthält zusätzlich den Alt-Eintrag `dark2devil`, UID 1002 — dieselbe Person wie `debugofen`, nicht deklariert; siehe Abschnitt 6.8 (e) und GNAI-019 Entscheidung 11) |
| Betriebssystem | Debian 13 (trixie) seit In-Place-Upgrade durch den Betreiber am 21.10.2026 (manuell; nicht via `scripts/upgrade-ct101-to-debian13.sh`) |
| Hostname | nicht erfasst — `preflight.yml` meldet ihn vor der Adoption (Inventory-Platzhalter `samba_expected_hostname`) |
| Hostname | `Samba` (großgeschrieben; FQDN `Samba.fritz.box`), erfasst am 27.10.2026; Inventory-Platzhalter `samba_expected_hostname` |
| Ressourcen-Profil | 1 CPU (cpuset), 2048 MiB RAM, 2048 MiB Swap, 4 GiB Rootfs (lxcfs-/cgroup-Werte im Gast, 27.10.2026) |
## 3. Bestand: Shares (aus `smb.conf`, Stand 19.10.2026)
@@ -37,7 +39,7 @@ offenen Punkte fest. Bestehende Shares bleiben unberührt.
| `[homes]` | Home-Verzeichnisse | read-only, `valid users = %S` | Debian-Standard |
| `[printers]` | `/var/tmp` | printable, read-only | **Abschnitt doppelt vorhanden** (siehe Abschnitt 6) |
| `[print$]` | `/var/lib/samba/printers` | read-only | Druckertreiber |
| `[Dokumente]` | `/media/daten1/Dokumente` | RO, nur `ghost` | — |
| `[Dokumente]` | `/media/daten1/Dokumente` | RO, nur `ghost`, write `ghost` | — |
| `[Filme]` | `/media/daten1/Filme` | RO, `ghost`/`daidalaw`/`debugofen`, write `ghost` | — |
| `[Filme2]` | `/media/datenhdd3/Filme` | wie `[Filme]` | — |
| `[Serien]` | `/media/daten2/Serien` | RO, alle drei, write `ghost` | `force user/group = root` |
@@ -85,9 +87,12 @@ PVE-Skripte `scripts/upgrade-ct101-to-debian13.sh` (Adoption) und
- Gruppe `gn-media` mit **GID 970** auf CT-101 anlegen (identisch zu
`ghostnet-ki`).
- Benutzer `ghost` in `gn-media` aufnehmen: Schreib-/Löschrecht auf den
Pipeline-Verzeichnissen, damit die Abholung per `mv` aus `[Encode]`
funktioniert. `daidalaw`/`debugofen` optional für reinen Lesezugriff.
- Benutzer `ghost` in `gn-media` aufnehmen (GNAI-018 R3):
Schreib-/Löschrecht auf den Pipeline-Verzeichnissen, damit die Abholung per
`mv` aus `[Encode]` und der `.dlc`-Drop in `[Encode-In]` funktionieren.
`daidalaw`/`debugofen` benötigen keine Mitgliedschaft — reiner Lesezugriff
genügt. **Korrektur 28.10.2026:** Die zunächst geplante Aufnahme von
`dark2devil` entfällt (GNAI-019 Entscheidung 11 revidiert).
- Die Platte `datenhdd3` erscheint im Monitoring über den NFS-Mount auf
`ghostnet-ki` (Disk-Metrik des NFS-Exports, GNMON-001).
@@ -147,9 +152,12 @@ GNENC-002 Abschnitt 4): `input/`, `jd/`, `queue/`, `work/`, `output/`,
`proxmox/lxc/ct-101-samba/` ist gebaut (Design in
[GNAI-019](GNAI-019-DEC_Ansible-Migration.md), Requirements in
[GNAI-018](GNAI-018-REQ_Encoding-Pipeline-Integration.md)) und lokal
verifiziert (Syntax-Checks, Check-Mode-Smoke). **Ausstehend:** Adoption des
produktiven LXC gemäß Runbook in `ansible/README.md` (Dist-Upgrade auf
Debian 13, elf Inventory-Platzhalter, Schlüssel, Convergence, Verifikation).
verifiziert (Syntax-Checks, Check-Mode-Smoke). **In Arbeit (Stand
27.10.2026):** Adoption gemäß Runbook in `ansible/README.md` — Schlüssel
installiert, SSH-Zugang für `ghost-agent` (UID 1004) und `ghost` (UID 1000)
hergestellt; die elf Inventory-Platzhalter wurden am 27.10.2026 aus
preflight/live gefüllt (alle drei Inventare byte-identisch); ausstehend:
audit, bootstrap, Konvergenz, Verifikation.
2. **`[printers]` doppelt** in der `smb.conf` — erledigt: Das Duplikat (inkl.
Kommentarblöcke) ist im templated `smb.conf` bereinigt; `testparm` ist als
`validate:`-Gate im Rollen-Flow verankert (GNAI-019 Entscheidung 7).
@@ -158,7 +166,7 @@ GNENC-002 Abschnitt 4): `input/`, `jd/`, `queue/`, `work/`, `output/`,
GNAI-019 Entscheidung 7).
4. **Backup** — entschieden: `backup.yml` (one-shot) sichert `/etc/samba` und
`/var/lib/samba` nach `/var/backups/ghostnet/samba/` vor der ersten
Convergence; alle fünf Medien-Mounts tragen `backup=no`; das Upgrade-Skript
Convergence; alle sechs Medien-Mounts tragen `backup=no`; das Upgrade-Skript
fährt vor dem Dist-Upgrade einen `vzdump --mode stop` (GNAI-019 Entscheidung
6). **Offen:** hostunabhängiger Restore-Test (GNAI-000 Abschnitt 4 Punkt 4).
5. **System-Baseline** — erfasst: OS-Version (Debian 13 nach Upgrade, GNAI-019
@@ -184,3 +192,62 @@ GNENC-002 Abschnitt 4): `input/`, `jd/`, `queue/`, `work/`, `output/`,
den Verlust verhindert. Bei der kommenden Ansible-Adoption wird die
`smb.conf` planmäßig durch das Template ersetzt (zusätzlich
`[Encode]`/`[Encode-In]`, `[printers]` bereinigt).
8. **Adoption (27.10.2026): Befunde und Korrekturen vor der Konvergenz** —
erledigt: (a) Hostname erfasst: `Samba` (großgeschrieben, FQDN
`Samba.fritz.box`); die Defaults in `samba_lxc_baseline` und
`provision-ct101-samba.sh` standen fälschlich auf klein `samba` und wurden
korrigiert (Rolle-Identity-Assert, Fresh-State-Rebuild). (b)
`sys-kernel-config.mount` war auf CT 101 `failed` (configfs ist in
unprivileged LXC nicht verfügbar); CT 100/102/103 maskieren die Unit seit
ihrer Erstellung. Die Maske ist jetzt in `samba_lxc_baseline` und
`provision-ct101-samba.sh` deklariert und wurde auf CT 101 manuell gesetzt
(`preflight.yml` verlangt „no failed units"). (c) Das manuelle
Dist-Upgrade vom 21.10. hatte den leeren `lxc.seccomp.profile:` nicht
gesetzt (Seccomp-Filter aktiv, `Seccomp: 2`); am 27.10.2026 nachgetragen
und per CT-Neustart wirksam (GNAI-019 Entscheidung 2). (d) Das Konto
`ghost` hatte das Home-Feld `/home/ghost/` (Trailing Slash) und die Shell
`/bin/false`; vor der Baseline auf `/home/ghost` und `/bin/bash`
normalisiert (27.10.2026). (e) Konto `dark2devil` (UID 1002, Lars Peuser,
Home `/home/dark2devil`, seit 11.03.2024) — **Korrektur 28.10.2026:** Der
Betreiber klärte, dass `dark2devil` und `debugofen` (UID 1003) dieselbe
Person sind und `debugofen` der valide Account ist; `dark2devil` ist ein
Alt-Account. Die am 27.10.2026 beschlossene Gleichstellung mit `ghost`
(GNAI-019 Entscheidung 11) wurde zurückgenommen: `dark2devil` ist nicht in
`samba_media_users` deklariert, nicht Mitglied von `gn-media` und in keiner
Share-ACL eingetragen (die live `smb.conf` nennt ihn nirgends). Konto und
passdb-Eintrag bleiben auf der Adoption unangetastet (keine produktive
Account-Löschung ohne ausdrückliche Betreiberentscheidung); optionaler
Cleanup, Betreiberentscheidung: `smbpasswd -x dark2devil` auf CT 101.
(f) Der private
`ghost`-SSH-Key erhielt am 27.10.2026 eine Passphrase (GNAI-011 Abschnitt
3.3); Entsperrung über den lokalen ssh-agent. (g) Mount-Topologie und
Ressourcen-Profil erfasst (27.10.2026, nach dem Reboot): Sechs Medien-Mounts
sind produktiv eingehängt — `/media/daten1`, `/media/daten2`,
`/media/daten3` (btrfs), `/media/datenhdd1` (ext4, leer, Verzeichnis auf der
Host-Root-Platte), `/media/datenhdd2/extern` (btrfs; die Extern-Platte ist
am Unterverzeichnis eingehängt, nicht an `/media/datenhdd2` selbst) und
`/media/datenhdd3` (btrfs). Die geplante Mount-Liste wich davon ab (fünf
Einträge mit `/media/datenhdd2`) und wurde korrigiert: Inventory
`samba_media_mount_points`, `samba_media`-Defaults,
`provision-ct101-samba.sh` (mp0–mp5) und `upgrade-ct101-to-debian13.sh`
(GNAI-019 entsprechend). Profil des produktiven CT: 1 CPU, 2048 MiB RAM,
2048 MiB Swap, 4 GiB Rootfs — die Defaults in `provision-ct101-samba.sh`
wurden darauf korrigiert (zuvor 2 Cores / 32 GiB / 1024 MiB Swap).
`/media/datenhdd3/Encoding` existiert produktiv noch nicht; die Konvergenz
legt es als `root:gn-media 2775` an (GNAI-019 Entscheidung 2). (h) Die
Gruppe `gn-media` existiert produktiv noch nicht (GID 970 frei; `ghost`
nicht Mitglied) — `samba_media` legt sie bei der Konvergenz an und nimmt
`ghost` auf; `preflight.yml` toleriert das Fehlen (Report statt Abbruch).
(i) **Share-ACLs gegen live abgeglichen (28.10.2026):** Das Inventory
deklarierte die `admin users`-Zeilen der live `smb.conf` nicht (betroffen:
Dokumente, Filme, Filme2, Serien2, Animation, Anime, Extern, Musik,
Audiobooks, Software, Downloads), das `write list` von `[Dokumente]` und
`[Extern]` fehlte, und `[Serien2]` trug ein `force user/group = root`, das
live nicht existiert. Nach GNAI-018 R4 (funktionsgleicher Erhalt) deklariert
das Inventory die ACLs nun exakt wie die live `smb.conf` (Erfassung
28.10.2026); die Konvergenz ändert damit keine produktiven Share-Rechte
außer der planmäßigen Ergänzung von `[Encode]`/`[Encode-In]`. Die
Normalisierung `public = yes` → `guest ok = no` auf den Medien-Shares ist
funktionsneutral (mit `valid users` ist Gastzugriff ohnehin verweigert).
Ebenfalls angeglichen: `[printers]` trägt im Template nun `browseable = no`
wie live (Sichtbarkeit im anonymen Listing).
@@ -20,9 +20,10 @@ und GNAI-018 R1. Es ist das Referenzdokument für das ausführende Projekt
## 2. Ausgangslage und Randbedingungen
- Produktiver, unprivileged LXC (Proxmox VE), Debian 12 (bookworm), manuell
verwaltet; Samba-Dateiserver mit 14 Shares auf fünf Host-Mounts
(`/media/daten1`, `/media/daten2`, `/media/daten3`, `/media/datenhdd2`,
`/media/datenhdd3`).
verwaltet; Samba-Dateiserver mit 12 produktiven Shares auf sechs
Host-Mounts (`/media/daten1`, `/media/daten2`, `/media/daten3`,
`/media/datenhdd1`, `/media/datenhdd2/extern`, `/media/datenhdd3`) plus zwei
geplanten Pipeline-Shares (`[Encode]`, `[Encode-In]`).
- Produktiver Samba-State: `smb.conf` plus `/var/lib/samba` inkl.
`private/passdb.tdb`; SMB-Benutzer `ghost`, `daidalaw`, `debugofen` mit
produktiven Passwörtern.
@@ -47,7 +48,7 @@ ist als Jinja2-Template im Repository gepflegt (Entscheidung 8).
**Begründung:**
- Der produktive Bestand umfasst gewachsene POSIX-Rechte auf fünf Host-Mounts
- Der produktive Bestand umfasst gewachsene POSIX-Rechte auf sechs Host-Mounts
und einen Samba-State (tdb/passdb). Ein Container-Wechsel würde die
Datei- und ID-Mapping-Semantik verändern, ohne einen Sicherheitsgewinn zu
bringen: Der LXC bleibt unprivileged, und Samba ist der Dienst, der die
@@ -145,6 +146,9 @@ Reboot: `ghostnet-samba-nfs.service` statt `nfs-kernel-server`).
`/usr/sbin/nologin` und ohne Home-Anlage).
- Mitgliedschaft in `gn-media`: nur `ghost` (GNAI-018 R3). `daidalaw` und
`debugofen` benötigen keine Mitgliedschaft — reiner Lesezugriff genügt.
**Update 28.10.2026:** Die Gleichstellung von `dark2devil` wurde
zurückgenommen (Entscheidung 11 revidiert) — `dark2devil` und `debugofen`
sind dieselbe Person; nur `ghost` ist Mitglied von `gn-media`.
**Begründung:** Die UID-Stabilität ist Voraussetzung der Pipeline
(AUTH_SYS-Credentials, `mv`-Abholung aus `[Encode]`, `.dlc`-Drop in
@@ -183,7 +187,7 @@ Ziel generiert bzw. operator-seitig gesetzt).
assertet das Vorhandensein dieses pre-migration Backups
(`samba_verify_require_premigration_backup: true`; Fresh-State-Rebuild:
`-e samba_verify_require_premigration_backup=false`).
- **Medien-Mounts:** Alle fünf Mounts tragen `backup=no` — `vzdump` archiviert
- **Medien-Mounts:** Alle sechs Mounts tragen `backup=no` — `vzdump` archiviert
die Medienbibliothek nicht.
- **Upgrade:** `scripts/upgrade-ct101-to-debian13.sh` fährt vor dem
Dist-Upgrade einen `vzdump --mode stop --storage local --compress zstd` als
@@ -236,7 +240,7 @@ genau so wieder auf. Der dokumentierte Ablauf (Runbook in
`ansible/README.md`):
1. `scripts/provision-ct101-samba.sh` auf dem Proxmox-Host: frischer CT 101 aus
einer Debian-13-Vorlage mit dem aufgezeichneten Profil, fünf Medien-Mounts
einer Debian-13-Vorlage mit dem aufgezeichneten Profil, sechs Medien-Mounts
(`backup=no`), leerer Seccomp-Eintrag, Baseline-Hook mit den
aufgezeichneten Konto-UIDs.
2. Operator auf der PVE-Konsole: `passwd ghost`, öffentliche Schlüssel aus
@@ -245,8 +249,8 @@ genau so wieder auf. Der dokumentierte Ablauf (Runbook in
3. Erster `site.yml`-Lauf mit `-e samba_smb_preserve_passdb=false`
(`samba_media` legt fehlende Konten mit den aufgezeichneten UIDs an,
`samba_smb` installiert Samba und die templated `smb.conf`).
4. Operator: `smbpasswd -a` für `ghost`, `daidalaw`, `debugofen` (Secrets nie
im Repo).
4. Operator: `smbpasswd -a` für `ghost`, `daidalaw` und
`debugofen` (Secrets nie im Repo).
5. Zweiter `site.yml`-Lauf mit Default: volle passdb-Guardrails, erwartet
`changed=0` (Idempotenz).
6. `verify-production.yml` mit
@@ -268,15 +272,35 @@ Debian 13); frische Paketbasis für die Migration; die Baseline-Rolle erwartet
Debian 13.
**Ablauf (Skript):** Guards (CT läuft, unprivileged, Gast Debian 12, Dienste
aktiv, IPv4, uid_map, `backup=no` auf allen fünf Mounts) → `vzdump --mode stop`
aktiv, IPv4, uid_map, `backup=no` auf allen sechs Mounts) → `vzdump --mode stop`
→ laufender Zustand gesichert (leerer Seccomp-Eintrag) → In-Guest-Upgrade
(`bookworm` → `trixie` in deb822- und One-Line-Quellen, `dist-upgrade` mit
`--force-confold` — hält die produktive `smb.conf`, `autoremove`,
wtmpdb-Cleanup analog Baseline) → Reboot → Post-Verify (Debian 13, Dienste,
keine failed units, Listener 22/139/445, anonyme `smbclient -L`-Abfrage,
uid_map intakt, Seccomp deaktiviert, fünf Mounts eingehängt).
uid_map intakt, Seccomp deaktiviert, sechs Mounts eingehängt).
## 13. Verworfene Alternativen
## 13. Entscheidung 11: `dark2devil` — Alt-Account, keine Gleichstellung (revidiert 28.10.2026)
**Entscheidung (revidiert):** Die am 27.10.2026 beschlossene Gleichstellung
von `dark2devil` mit `ghost` wird zurückgenommen. Der Betreiber klärte am
28.10.2026, dass `dark2devil` (UID 1002) und `debugofen` (UID 1003) dieselbe
Person sind (Lars Peuser) und `debugofen` der valide Account ist;
`dark2devil` ist ein Alt-Account ohne produktive Share-Berechtigung (die live
`smb.conf` nennt ihn nirgends). Konkret: `dark2devil` ist nicht in
`samba_media_users` deklariert, nicht Mitglied von `gn-media` und in keiner
Share-ACL eingetragen; die passdb-Asserts verlangen nur `ghost`, `daidalaw`
und `debugofen`. Konto und passdb-Eintrag bleiben auf der Adoption
unangetastet (keine produktive Account-Löschung ohne ausdrückliche
Betreiberentscheidung). Optionaler Cleanup (Betreiberentscheidung, nicht Teil
der Migration): `smbpasswd -x dark2devil` auf CT 101.
**Begründung:** Die Gleichstellung beruhte auf der Annahme, `dark2devil` sei
ein aktiver privater Benutzer. Nach der Betreiberklärung ist die Person über
den validen Account `debugofen` angebunden; eine zweite Identität für
dieselbe Person auf Medien-/SMB-Ebene ist nicht erforderlich.
## 14. Verworfene Alternativen
| Alternative | Grund der Ablehnung |
| --- | --- |
@@ -287,7 +311,7 @@ uid_map intakt, Seccomp deaktiviert, fünf Mounts eingehängt).
| SMB auch aus dem Admin-VPN (`10.8.0.0/24`) öffnen | Medien-Clients leben im LAN; Erweiterung ist ausdrückliche Betreiberentscheidung (GNAI-018 R7) |
| Medien-Mounts mit `backup=yes` | `vzdump` würde die Medienbibliothek archivieren (Ressourcen, Datenschutz); `backup=no` plus gezieltes State-Backup (Entscheidung 6) |
## 14. Auswirkungen auf GNAI-017 und GNAI-018
## 15. Auswirkungen auf GNAI-017 und GNAI-018
- **GNAI-017 Abschnitt 4.1** (`nfs-kernel-server`, `/etc/exports`) ist durch
Entscheidung 2 und 3 überholt und wird entsprechend aktualisiert.
@@ -304,16 +328,36 @@ uid_map intakt, Seccomp deaktiviert, fünf Mounts eingehängt).
(Entscheidung 5), Backup-Ziel `/var/backups/ghostnet/samba/` plus vzdump im
Upgrade-Skript (Entscheidung 6), `usershare allow guests` beibehalten
(Entscheidung 7), System-Baseline via preflight-Platzhalter (Entscheidung 9).
- **GNAI-017 Abschnitt 1/2** (Mount-Topologie): korrigiert auf die sechs
produktiv eingehängten Mounts (inkl. `/media/datenhdd1` und
`/media/datenhdd2/extern`); `provision-ct101-samba.sh` attachiert `mp0`–`mp5`
mit dem aufgezeichneten Profil (1 CPU, 2048 MiB RAM, 2048 MiB Swap, 4 GiB
Rootfs).
- **Korrektur 28.10.2026 (GNAI-018 R4):** GNAI-017 Abschnitt 2
(SMB-Benutzer-Tabelle) und Abschnitt 4.2 (`gn-media`-Mitgliedschaft) werden
korrigiert — `dark2devil` ist nicht deklariert (Entscheidung 11
revidiert). Das Inventory deklariert die Share-ACLs exakt wie die live
`smb.conf` (inkl. `admin users` auf elf Shares, `write list` auf
`[Dokumente]`/`[Extern]`; `[Serien2]` ohne `force user/group`), damit die
Konvergenz keine produktiven Share-Rechte ändert.
## 15. Offene Punkte
## 16. Offene Punkte
1. Adoption des produktiven CT 101 gemäß Runbook in
`proxmox/lxc/ct-101-samba/ansible/README.md` (erfordert Betreiber-Autorisierung).
2. Elf Inventory-Platzhalter mit den Ist-Werten füllen (`preflight.yml` /
`audit-production.yml` liefern sie).
`audit-production.yml` liefern sie) — **erledigt 27.10.2026**: preflight und
Live-Abfragen lieferten die Werte; alle drei Inventare sind byte-identisch
gefüllt (Hostname `Samba`, 1 vCPU, 2048 MiB RAM, Rootfs-Mindestwerte
4 GB/1 GB, Medien-UIDs 1000/1001/1003 — der Alt-Account `dark2devil`,
UID 1002, ist nicht deklariert, siehe Entscheidung 11 —, admin 1000,
agent 1004, deploy 1005).
3. Drei Ed25519-Schlüsselpaare generieren (Operator); nur die öffentlichen
Schlüssel liegen im Repo unter `ansible/files/ssh/`.
4. Dist-Upgrade auf Debian 13 ausführen (`scripts/upgrade-ct101-to-debian13.sh`).
Schlüssel liegen im Repo unter `ansible/files/ssh/` — **erledigt**: agent und
deploy ohne Passphrase, ghost mit Passphrase (GNAI-011 Abschnitt 3.3).
4. Dist-Upgrade auf Debian 13 ausführen (`scripts/upgrade-ct101-to-debian13.sh`) —
**erledigt 21.10.2026**, manuell durch den Betreiber (GNAI-017 Abschnitt 6
Punkt 7).
5. Reboot-Test nach der Convergence (GNAI-016 Abschnitt 7, LXC-boot_id-Caveat).
6. Hostunabhängiger Backup-/Restore-Test (GNAI-000 Abschnitt 4 Punkt 4).
7. Kapazitäts-Monitoring der daten-Platten auf CT 101 (GNAI-017 Abschnitt 6
+16 -8
View File
@@ -59,8 +59,8 @@ role argument specs reject them before any task runs). `preflight.yml` on the
`samba_minimum_root_available_bytes` (group_vars)
- `samba_access_admin_uid`, `samba_access_agent_uid`,
`samba_access_deploy_uid` (host_vars)
- `samba_media_users[ghost|daidalaw|debugofen].uid` (group_vars; the recorded
productive UIDs — the pipeline depends on them, GNAI-018 R1)
- `samba_media_users[ghost|daidalaw|debugofen].uid` (group_vars; the
recorded productive UIDs — the pipeline depends on them, GNAI-018 R1)
## Playbooks
@@ -87,13 +87,21 @@ keeps the healthy file server and its Samba passdb intact:
2. On the Proxmox host, run `scripts/upgrade-ct101-to-debian13.sh` (see
`scripts/README.md`): in-place Debian 12 → 13 dist-upgrade of the existing
CT 101 with a pre-upgrade `vzdump` and the empty `lxc.seccomp.profile`.
When the dist-upgrade was performed manually instead of via the script,
ensure the empty `lxc.seccomp.profile:` line is present in
`/etc/pve/lxc/101.conf` and reboot the CT — the Ganesha container needs it
(GNAI-019 decision 2).
3. On the PVE console of CT 101: capture the productive `ghost` UID, pick a
free UID for `ghost-agent`, then run the shared baseline
`ghostnet-lxc-baseline.sh 101` with `GHOSTNET_AGENT_UID`/`GHOSTNET_ADMIN_UID`
(validates the unprivileged mapping, creates the locked `ghost-agent`,
prepares `.ssh`, adds `ghost` to `sudo`, disables `ssh.socket`). Then set
`passwd ghost` and paste the `ghost-agent` and `ghost` public keys into the
respective `authorized_keys`.
prepares `.ssh`, adds `ghost` to `sudo`, disables `ssh.socket`). Also mask
`sys-kernel-config.mount` (configfs is unavailable in unprivileged LXC; CT
100/102/103 convention) and clear its failed state — `preflight.yml`
requires no failed units:
`pct exec 101 -- systemctl mask sys-kernel-config.mount && pct exec 101 -- systemctl reset-failed sys-kernel-config.mount`.
Then set `passwd ghost` and paste the `ghost-agent` and `ghost` public
keys into the respective `authorized_keys`.
4. On the workstation: add the `ghostnet-ct101-agent` / `ghostnet-ct101-ghost`
ssh aliases and seed `~/.ssh/known_hosts_ghostnet_ct101` (verify the
fingerprint on the PVE console first, GNAI-011 section 3.3).
@@ -122,11 +130,11 @@ and installs; the only state that cannot be declared is secret material.
1. Prerequisites: the inventory is fully filled (all 11 placeholders carry the
recorded productive values), the three `.pub` files are committed, and the
five media host paths are known.
six media host paths are known.
2. On the Proxmox host, run `scripts/provision-ct101-samba.sh` with
`GHOSTNET_AGENT_UID`, `GHOSTNET_ADMIN_UID` and `GHOSTNET_MP0_HOST` …
`GHOSTNET_MP4_HOST` (see `scripts/README.md`). This creates CT 101 from a
Debian 13 template with the recorded profile, attaches the five media
`GHOSTNET_MP5_HOST` (see `scripts/README.md`). This creates CT 101 from a
Debian 13 template with the recorded profile, attaches the six media
mounts (`backup=no`), disables the LXC seccomp filter and runs the baseline
hook, which creates `ghost-agent` and `ghost` with the recorded UIDs.
3. Operator: `pct exec 101 -- passwd ghost`, then install the `ghost-agent` and
@@ -8,15 +8,16 @@ ansible_ssh_common_args: >-
samba_expected_distribution: Debian
samba_expected_distribution_major_version: "13"
# Live hostname of CT 101; copy from preflight.yml (SMB1 is disabled, so the
# live server does not answer NetBIOS names):
samba_expected_hostname: REPLACE-WITH-PREFLIGHT-HOSTNAME
# Live hostname of CT 101 (recorded 2026-10-27 from preflight.yml; SMB1 is
# disabled, so the live server does not answer NetBIOS names):
samba_expected_hostname: Samba
samba_expected_virtualization_type: lxc
# Expected Podman version after the dist-upgrade (trixie), analog to CT 100/102:
samba_expected_podman_version: "5.4.2"
# Resource minimums; copy from preflight.yml:
samba_minimum_root_size_bytes: REPLACE-WITH-PREFLIGHT-ROOT-SIZE
samba_minimum_root_available_bytes: REPLACE-WITH-PREFLIGHT-ROOT-AVAIL
# Resource minimums (recorded 2026-10-27; round values at/below the live
# capacity, CT 100/102 convention):
samba_minimum_root_size_bytes: 4000000000
samba_minimum_root_available_bytes: 1000000000
samba_firewall_management_ipv4:
- 10.0.0.0/24
@@ -35,48 +36,64 @@ samba_access_source_networks: 10.0.0.0/24,10.8.0.0/24
samba_media_group_name: gn-media
samba_media_group_gid: 970
# Media accounts with their productive UIDs; copy from preflight.yml /
# audit-production.yml (fresh-state rebuild: missing accounts are created with
# exactly these UIDs; existing accounts are only validated on the UID):
# Media accounts with their productive UIDs (recorded 2026-10-27 from
# preflight.yml / audit-production.yml; fresh-state rebuild: missing accounts
# are created with exactly these UIDs; existing accounts are only validated on
# the UID). The legacy account dark2devil (UID 1002) exists on the productive
# CT 101 but is deliberately NOT declared: it belongs to the same person as
# debugofen (operator clarification 2026-10-28, GNAI-019 decision 11) and is
# not referenced by any share in the live smb.conf.
samba_media_users:
- name: ghost
uid: REPLACE-WITH-AUDIT-UID-GHOST
uid: 1000
shell: /bin/bash
home: /home/ghost
create_home: true
- name: daidalaw
uid: REPLACE-WITH-AUDIT-UID-DAIDALAW
uid: 1001
shell: /usr/sbin/nologin
home: /home/daidalaw
create_home: false
- name: debugofen
uid: REPLACE-WITH-AUDIT-UID-DEBUGOFEN
uid: 1003
shell: /usr/sbin/nologin
home: /home/debugofen
create_home: false
samba_media_group_members:
- ghost
# Live media mount topology of the productive CT 101 (recorded 2026-10-27):
# six Proxmox mount points. /media/datenhdd1 is an empty ext4 directory from
# the host root filesystem; the Extern disk is mounted at the subdirectory
# /media/datenhdd2/extern (not at /media/datenhdd2 itself).
samba_media_mount_points:
- /media/daten1
- /media/daten2
- /media/daten3
- /media/datenhdd2
- /media/datenhdd1
- /media/datenhdd2/extern
- /media/datenhdd3
samba_smb_workgroup: Ghost.net
samba_smb_service_units:
- smbd.service
- nmbd.service
# Complete share target state from GNAI-017 section 3 plus [Encode] and
# [Encode-In] from GNAI-018 R2. Comments only where the live system shows them
# (Debian defaults and the new pipeline shares); the media shares had empty
# comments in the anonymous listing and stay without comments.
# Complete share target state: the twelve productive shares mirror the live
# smb.conf ACLs exactly (valid users / write list / admin users / force
# user+group; recorded 2026-10-28, GNAI-018 R4 funktionsgleicher Erhalt), plus
# [Encode] and [Encode-In] per GNAI-017 section 4.3 / GNAI-018 R2. Comments
# only where the live system shows them (Debian defaults and the new pipeline
# shares); the media shares had empty comments in the anonymous listing and
# stay without comments.
samba_smb_media_shares:
- name: Dokumente
path: /media/daten1/Dokumente
read_only: "yes"
valid_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
- name: Filme
path: /media/daten1/Filme
read_only: "yes"
@@ -86,6 +103,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Filme2
path: /media/datenhdd3/Filme
read_only: "yes"
@@ -95,6 +114,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Serien
path: /media/daten2/Serien
read_only: "yes"
@@ -115,8 +136,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
force_user: root
force_group: root
admin_users:
- ghost
- name: Animation
path: /media/daten1/Animation
read_only: "yes"
@@ -126,6 +147,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Anime
path: /media/daten3/Anime
read_only: "yes"
@@ -135,11 +158,17 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Extern
path: /media/datenhdd2/extern
read_only: "no"
valid_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
- name: Musik
path: /media/daten1/Musik
read_only: "yes"
@@ -149,6 +178,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Audiobooks
path: /media/daten1/Audiobooks
read_only: "yes"
@@ -158,6 +189,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Software
path: /media/datenhdd3/Software
read_only: "yes"
@@ -168,6 +201,9 @@ samba_smb_media_shares:
write_list:
- ghost
- daidalaw
admin_users:
- ghost
- daidalaw
- name: Downloads
path: /media/datenhdd3/Downloads
read_only: "no"
@@ -175,6 +211,10 @@ samba_smb_media_shares:
- ghost
- daidalaw
- debugofen
admin_users:
- ghost
- daidalaw
- debugofen
- name: Encode
comment: "Encoding-Pipeline: fertige Encodes (Abholung per mv)"
path: /media/datenhdd3/Encoding/output
@@ -183,10 +223,10 @@ samba_smb_media_shares:
- ghost
- daidalaw
- debugofen
admin_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
force_group: gn-media
create_mask: "0664"
directory_mask: "0775"
@@ -196,10 +236,10 @@ samba_smb_media_shares:
read_only: "no"
valid_users:
- ghost
admin_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
force_group: gn-media
create_mask: "0664"
directory_mask: "0775"
@@ -6,11 +6,13 @@ samba_expected_ipv4: 10.0.0.9
# (GNAI-011 section 3.3):
samba_expected_ssh_host_key_fingerprint: >-
SHA256:+MUO4J4x3TCYLc1iei95UcPTzkPwDPOzovfaIieuOKs
# Live values; copy from preflight.yml:
samba_expected_processor_vcpus: REPLACE-WITH-PREFLIGHT-VCPUS
samba_minimum_memory_mb: REPLACE-WITH-PREFLIGHT-MEMORY-MB
# UID of the existing ghost account; copy from audit-production.yml:
samba_access_admin_uid: REPLACE-WITH-AUDIT-GHOST-UID
# Free UIDs to pick from audit-production.yml (ghost-agent / ansible-deploy):
samba_access_agent_uid: REPLACE-WITH-FREE-AGENT-UID
samba_access_deploy_uid: REPLACE-WITH-FREE-DEPLOY-UID
# Live values (recorded 2026-10-27 from preflight.yml; lxcfs presents the
# configured profile inside the guest):
samba_expected_processor_vcpus: 1
samba_minimum_memory_mb: 2048
# UID of the existing ghost account (audit-production.yml):
samba_access_admin_uid: 1000
# Recorded UIDs (audit-production.yml): ghost-agent exists with 1004;
# ansible-deploy takes the lowest free UID >= 1000:
samba_access_agent_uid: 1004
samba_access_deploy_uid: 1005
@@ -8,15 +8,16 @@ ansible_ssh_common_args: >-
samba_expected_distribution: Debian
samba_expected_distribution_major_version: "13"
# Live hostname of CT 101; copy from preflight.yml (SMB1 is disabled, so the
# live server does not answer NetBIOS names):
samba_expected_hostname: REPLACE-WITH-PREFLIGHT-HOSTNAME
# Live hostname of CT 101 (recorded 2026-10-27 from preflight.yml; SMB1 is
# disabled, so the live server does not answer NetBIOS names):
samba_expected_hostname: Samba
samba_expected_virtualization_type: lxc
# Expected Podman version after the dist-upgrade (trixie), analog to CT 100/102:
samba_expected_podman_version: "5.4.2"
# Resource minimums; copy from preflight.yml:
samba_minimum_root_size_bytes: REPLACE-WITH-PREFLIGHT-ROOT-SIZE
samba_minimum_root_available_bytes: REPLACE-WITH-PREFLIGHT-ROOT-AVAIL
# Resource minimums (recorded 2026-10-27; round values at/below the live
# capacity, CT 100/102 convention):
samba_minimum_root_size_bytes: 4000000000
samba_minimum_root_available_bytes: 1000000000
samba_firewall_management_ipv4:
- 10.0.0.0/24
@@ -35,48 +36,64 @@ samba_access_source_networks: 10.0.0.0/24,10.8.0.0/24
samba_media_group_name: gn-media
samba_media_group_gid: 970
# Media accounts with their productive UIDs; copy from preflight.yml /
# audit-production.yml (fresh-state rebuild: missing accounts are created with
# exactly these UIDs; existing accounts are only validated on the UID):
# Media accounts with their productive UIDs (recorded 2026-10-27 from
# preflight.yml / audit-production.yml; fresh-state rebuild: missing accounts
# are created with exactly these UIDs; existing accounts are only validated on
# the UID). The legacy account dark2devil (UID 1002) exists on the productive
# CT 101 but is deliberately NOT declared: it belongs to the same person as
# debugofen (operator clarification 2026-10-28, GNAI-019 decision 11) and is
# not referenced by any share in the live smb.conf.
samba_media_users:
- name: ghost
uid: REPLACE-WITH-AUDIT-UID-GHOST
uid: 1000
shell: /bin/bash
home: /home/ghost
create_home: true
- name: daidalaw
uid: REPLACE-WITH-AUDIT-UID-DAIDALAW
uid: 1001
shell: /usr/sbin/nologin
home: /home/daidalaw
create_home: false
- name: debugofen
uid: REPLACE-WITH-AUDIT-UID-DEBUGOFEN
uid: 1003
shell: /usr/sbin/nologin
home: /home/debugofen
create_home: false
samba_media_group_members:
- ghost
# Live media mount topology of the productive CT 101 (recorded 2026-10-27):
# six Proxmox mount points. /media/datenhdd1 is an empty ext4 directory from
# the host root filesystem; the Extern disk is mounted at the subdirectory
# /media/datenhdd2/extern (not at /media/datenhdd2 itself).
samba_media_mount_points:
- /media/daten1
- /media/daten2
- /media/daten3
- /media/datenhdd2
- /media/datenhdd1
- /media/datenhdd2/extern
- /media/datenhdd3
samba_smb_workgroup: Ghost.net
samba_smb_service_units:
- smbd.service
- nmbd.service
# Complete share target state from GNAI-017 section 3 plus [Encode] and
# [Encode-In] from GNAI-018 R2. Comments only where the live system shows them
# (Debian defaults and the new pipeline shares); the media shares had empty
# comments in the anonymous listing and stay without comments.
# Complete share target state: the twelve productive shares mirror the live
# smb.conf ACLs exactly (valid users / write list / admin users / force
# user+group; recorded 2026-10-28, GNAI-018 R4 funktionsgleicher Erhalt), plus
# [Encode] and [Encode-In] per GNAI-017 section 4.3 / GNAI-018 R2. Comments
# only where the live system shows them (Debian defaults and the new pipeline
# shares); the media shares had empty comments in the anonymous listing and
# stay without comments.
samba_smb_media_shares:
- name: Dokumente
path: /media/daten1/Dokumente
read_only: "yes"
valid_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
- name: Filme
path: /media/daten1/Filme
read_only: "yes"
@@ -86,6 +103,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Filme2
path: /media/datenhdd3/Filme
read_only: "yes"
@@ -95,6 +114,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Serien
path: /media/daten2/Serien
read_only: "yes"
@@ -115,8 +136,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
force_user: root
force_group: root
admin_users:
- ghost
- name: Animation
path: /media/daten1/Animation
read_only: "yes"
@@ -126,6 +147,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Anime
path: /media/daten3/Anime
read_only: "yes"
@@ -135,11 +158,17 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Extern
path: /media/datenhdd2/extern
read_only: "no"
valid_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
- name: Musik
path: /media/daten1/Musik
read_only: "yes"
@@ -149,6 +178,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Audiobooks
path: /media/daten1/Audiobooks
read_only: "yes"
@@ -158,6 +189,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Software
path: /media/datenhdd3/Software
read_only: "yes"
@@ -168,6 +201,9 @@ samba_smb_media_shares:
write_list:
- ghost
- daidalaw
admin_users:
- ghost
- daidalaw
- name: Downloads
path: /media/datenhdd3/Downloads
read_only: "no"
@@ -175,6 +211,10 @@ samba_smb_media_shares:
- ghost
- daidalaw
- debugofen
admin_users:
- ghost
- daidalaw
- debugofen
- name: Encode
comment: "Encoding-Pipeline: fertige Encodes (Abholung per mv)"
path: /media/datenhdd3/Encoding/output
@@ -183,10 +223,10 @@ samba_smb_media_shares:
- ghost
- daidalaw
- debugofen
admin_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
force_group: gn-media
create_mask: "0664"
directory_mask: "0775"
@@ -196,10 +236,10 @@ samba_smb_media_shares:
read_only: "no"
valid_users:
- ghost
admin_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
force_group: gn-media
create_mask: "0664"
directory_mask: "0775"
@@ -6,11 +6,13 @@ samba_expected_ipv4: 10.0.0.9
# (GNAI-011 section 3.3):
samba_expected_ssh_host_key_fingerprint: >-
SHA256:+MUO4J4x3TCYLc1iei95UcPTzkPwDPOzovfaIieuOKs
# Live values; copy from preflight.yml:
samba_expected_processor_vcpus: REPLACE-WITH-PREFLIGHT-VCPUS
samba_minimum_memory_mb: REPLACE-WITH-PREFLIGHT-MEMORY-MB
# UID of the existing ghost account; copy from audit-production.yml:
samba_access_admin_uid: REPLACE-WITH-AUDIT-GHOST-UID
# Free UIDs to pick from audit-production.yml (ghost-agent / ansible-deploy):
samba_access_agent_uid: REPLACE-WITH-FREE-AGENT-UID
samba_access_deploy_uid: REPLACE-WITH-FREE-DEPLOY-UID
# Live values (recorded 2026-10-27 from preflight.yml; lxcfs presents the
# configured profile inside the guest):
samba_expected_processor_vcpus: 1
samba_minimum_memory_mb: 2048
# UID of the existing ghost account (audit-production.yml):
samba_access_admin_uid: 1000
# Recorded UIDs (audit-production.yml): ghost-agent exists with 1004;
# ansible-deploy takes the lowest free UID >= 1000:
samba_access_agent_uid: 1004
samba_access_deploy_uid: 1005
@@ -8,15 +8,16 @@ ansible_ssh_common_args: >-
samba_expected_distribution: Debian
samba_expected_distribution_major_version: "13"
# Live hostname of CT 101; copy from preflight.yml (SMB1 is disabled, so the
# live server does not answer NetBIOS names):
samba_expected_hostname: REPLACE-WITH-PREFLIGHT-HOSTNAME
# Live hostname of CT 101 (recorded 2026-10-27 from preflight.yml; SMB1 is
# disabled, so the live server does not answer NetBIOS names):
samba_expected_hostname: Samba
samba_expected_virtualization_type: lxc
# Expected Podman version after the dist-upgrade (trixie), analog to CT 100/102:
samba_expected_podman_version: "5.4.2"
# Resource minimums; copy from preflight.yml:
samba_minimum_root_size_bytes: REPLACE-WITH-PREFLIGHT-ROOT-SIZE
samba_minimum_root_available_bytes: REPLACE-WITH-PREFLIGHT-ROOT-AVAIL
# Resource minimums (recorded 2026-10-27; round values at/below the live
# capacity, CT 100/102 convention):
samba_minimum_root_size_bytes: 4000000000
samba_minimum_root_available_bytes: 1000000000
samba_firewall_management_ipv4:
- 10.0.0.0/24
@@ -35,48 +36,64 @@ samba_access_source_networks: 10.0.0.0/24,10.8.0.0/24
samba_media_group_name: gn-media
samba_media_group_gid: 970
# Media accounts with their productive UIDs; copy from preflight.yml /
# audit-production.yml (fresh-state rebuild: missing accounts are created with
# exactly these UIDs; existing accounts are only validated on the UID):
# Media accounts with their productive UIDs (recorded 2026-10-27 from
# preflight.yml / audit-production.yml; fresh-state rebuild: missing accounts
# are created with exactly these UIDs; existing accounts are only validated on
# the UID). The legacy account dark2devil (UID 1002) exists on the productive
# CT 101 but is deliberately NOT declared: it belongs to the same person as
# debugofen (operator clarification 2026-10-28, GNAI-019 decision 11) and is
# not referenced by any share in the live smb.conf.
samba_media_users:
- name: ghost
uid: REPLACE-WITH-AUDIT-UID-GHOST
uid: 1000
shell: /bin/bash
home: /home/ghost
create_home: true
- name: daidalaw
uid: REPLACE-WITH-AUDIT-UID-DAIDALAW
uid: 1001
shell: /usr/sbin/nologin
home: /home/daidalaw
create_home: false
- name: debugofen
uid: REPLACE-WITH-AUDIT-UID-DEBUGOFEN
uid: 1003
shell: /usr/sbin/nologin
home: /home/debugofen
create_home: false
samba_media_group_members:
- ghost
# Live media mount topology of the productive CT 101 (recorded 2026-10-27):
# six Proxmox mount points. /media/datenhdd1 is an empty ext4 directory from
# the host root filesystem; the Extern disk is mounted at the subdirectory
# /media/datenhdd2/extern (not at /media/datenhdd2 itself).
samba_media_mount_points:
- /media/daten1
- /media/daten2
- /media/daten3
- /media/datenhdd2
- /media/datenhdd1
- /media/datenhdd2/extern
- /media/datenhdd3
samba_smb_workgroup: Ghost.net
samba_smb_service_units:
- smbd.service
- nmbd.service
# Complete share target state from GNAI-017 section 3 plus [Encode] and
# [Encode-In] from GNAI-018 R2. Comments only where the live system shows them
# (Debian defaults and the new pipeline shares); the media shares had empty
# comments in the anonymous listing and stay without comments.
# Complete share target state: the twelve productive shares mirror the live
# smb.conf ACLs exactly (valid users / write list / admin users / force
# user+group; recorded 2026-10-28, GNAI-018 R4 funktionsgleicher Erhalt), plus
# [Encode] and [Encode-In] per GNAI-017 section 4.3 / GNAI-018 R2. Comments
# only where the live system shows them (Debian defaults and the new pipeline
# shares); the media shares had empty comments in the anonymous listing and
# stay without comments.
samba_smb_media_shares:
- name: Dokumente
path: /media/daten1/Dokumente
read_only: "yes"
valid_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
- name: Filme
path: /media/daten1/Filme
read_only: "yes"
@@ -86,6 +103,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Filme2
path: /media/datenhdd3/Filme
read_only: "yes"
@@ -95,6 +114,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Serien
path: /media/daten2/Serien
read_only: "yes"
@@ -115,8 +136,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
force_user: root
force_group: root
admin_users:
- ghost
- name: Animation
path: /media/daten1/Animation
read_only: "yes"
@@ -126,6 +147,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Anime
path: /media/daten3/Anime
read_only: "yes"
@@ -135,11 +158,17 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Extern
path: /media/datenhdd2/extern
read_only: "no"
valid_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
- name: Musik
path: /media/daten1/Musik
read_only: "yes"
@@ -149,6 +178,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Audiobooks
path: /media/daten1/Audiobooks
read_only: "yes"
@@ -158,6 +189,8 @@ samba_smb_media_shares:
- debugofen
write_list:
- ghost
admin_users:
- ghost
- name: Software
path: /media/datenhdd3/Software
read_only: "yes"
@@ -168,6 +201,9 @@ samba_smb_media_shares:
write_list:
- ghost
- daidalaw
admin_users:
- ghost
- daidalaw
- name: Downloads
path: /media/datenhdd3/Downloads
read_only: "no"
@@ -175,6 +211,10 @@ samba_smb_media_shares:
- ghost
- daidalaw
- debugofen
admin_users:
- ghost
- daidalaw
- debugofen
- name: Encode
comment: "Encoding-Pipeline: fertige Encodes (Abholung per mv)"
path: /media/datenhdd3/Encoding/output
@@ -183,10 +223,10 @@ samba_smb_media_shares:
- ghost
- daidalaw
- debugofen
admin_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
force_group: gn-media
create_mask: "0664"
directory_mask: "0775"
@@ -196,10 +236,10 @@ samba_smb_media_shares:
read_only: "no"
valid_users:
- ghost
admin_users:
- ghost
write_list:
- ghost
admin_users:
- ghost
force_group: gn-media
create_mask: "0664"
directory_mask: "0775"
@@ -6,11 +6,13 @@ samba_expected_ipv4: 10.0.0.9
# (GNAI-011 section 3.3):
samba_expected_ssh_host_key_fingerprint: >-
SHA256:+MUO4J4x3TCYLc1iei95UcPTzkPwDPOzovfaIieuOKs
# Live values; copy from preflight.yml:
samba_expected_processor_vcpus: REPLACE-WITH-PREFLIGHT-VCPUS
samba_minimum_memory_mb: REPLACE-WITH-PREFLIGHT-MEMORY-MB
# UID of the existing ghost account; copy from audit-production.yml:
samba_access_admin_uid: REPLACE-WITH-AUDIT-GHOST-UID
# Free UIDs to pick from audit-production.yml (ghost-agent / ansible-deploy):
samba_access_agent_uid: REPLACE-WITH-FREE-AGENT-UID
samba_access_deploy_uid: REPLACE-WITH-FREE-DEPLOY-UID
# Live values (recorded 2026-10-27 from preflight.yml; lxcfs presents the
# configured profile inside the guest):
samba_expected_processor_vcpus: 1
samba_minimum_memory_mb: 2048
# UID of the existing ghost account (audit-production.yml):
samba_access_admin_uid: 1000
# Recorded UIDs (audit-production.yml): ghost-agent exists with 1004;
# ansible-deploy takes the lowest free UID >= 1000:
samba_access_agent_uid: 1004
samba_access_deploy_uid: 1005
@@ -107,8 +107,9 @@
samba_media_users | map(attribute='name') | list is
subset(samba_audit_pdbedit.stdout_lines)
fail_msg: >-
The productive SMB users ghost, daidalaw and debugofen are missing
from the passdb. Stop the adoption; the passdb must not be at risk.
The productive SMB users ghost, daidalaw and debugofen
are missing from the passdb. Stop the adoption; the passdb must not
be at risk.
- name: Validate the effective SSH policy
ansible.builtin.command:
@@ -197,10 +198,7 @@
- "{{ item }}"
check_mode: false
changed_when: false
loop:
- ghost
- daidalaw
- debugofen
loop: "{{ samba_media_users | map(attribute='name') | list }}"
register: samba_audit_password_states
- name: Read the complete account database for UID slot picking
@@ -224,10 +222,7 @@
- name: Stat the media account authorized keys
ansible.builtin.stat:
path: "/home/{{ item }}/.ssh/authorized_keys"
loop:
- ghost
- daidalaw
- debugofen
loop: "{{ samba_media_users | map(attribute='name') | list }}"
register: samba_audit_keys_stats
- name: Read the existing authorized keys
@@ -8,6 +8,7 @@
- hardware
- network
- virtual
- mounts
tasks:
- name: Verify required inventory safeguards are defined
@@ -57,6 +58,32 @@
- ansible_facts["hostname"] == samba_expected_hostname
when: samba_expected_hostname is not match('^REPLACE-')
- name: Read the effective CPU allocation (nproc reflects the configured cores)
ansible.builtin.command:
argv:
- /usr/bin/nproc
check_mode: false
changed_when: false
register: samba_preflight_nproc
- name: Verify the declared CPU allocation when the placeholder is already replaced
ansible.builtin.assert:
that:
- samba_preflight_nproc.stdout | int == samba_expected_processor_vcpus | int
fail_msg: >-
The guest reports {{ samba_preflight_nproc.stdout }} CPUs, but the
inventory declares {{ samba_expected_processor_vcpus }} vCPUs.
when: samba_expected_processor_vcpus is not match('^REPLACE-')
- name: Verify the declared memory minimum when the placeholder is already replaced
ansible.builtin.assert:
that:
- ansible_facts["memtotal_mb"] | int >= samba_minimum_memory_mb | int
fail_msg: >-
The guest reports {{ ansible_facts["memtotal_mb"] }} MiB RAM, below
the declared minimum {{ samba_minimum_memory_mb }} MiB.
when: samba_minimum_memory_mb is not match('^REPLACE-')
- name: Read the guest user-namespace ID mapping
ansible.builtin.command:
argv:
@@ -182,6 +209,18 @@
The pre-migration listener set is unexpected: SSH and SMB must be
served and NFS (2049) must not be active yet.
- name: Verify the declared media mount points are mounted
ansible.builtin.assert:
that:
- >-
ansible_facts["mounts"] | map(attribute="mount") | list is
superset(samba_media_mount_points)
fail_msg: >-
Not all declared media mount points are mounted on CT 101. The Samba
shares and the NFS export depend on the complete media library; stop
the adoption until the storage topology is complete.
success_msg: All declared media mount points are mounted.
- name: Read the Samba server version
ansible.builtin.command:
argv:
@@ -200,7 +239,7 @@
changed_when: false
register: samba_preflight_smb_conf
- name: Read the live anonymous Samba share listing
- name: Read the live anonymous Samba share listing (smbclient is installed by samba_smb during convergence)
ansible.builtin.command:
argv:
- /usr/bin/smbclient
@@ -209,6 +248,7 @@
- -N
check_mode: false
changed_when: false
failed_when: false
register: samba_preflight_shares
- name: Read the media account entries
@@ -237,7 +277,7 @@
label: "{{ item.name }}"
register: samba_preflight_media_uids
- name: Read the media group entry for the inventory placeholder
- name: Read the media group entry (created by samba_media during convergence)
ansible.builtin.command:
argv:
- /usr/bin/getent
@@ -245,6 +285,7 @@
- "{{ samba_media_group_name }}"
check_mode: false
changed_when: false
failed_when: false
register: samba_preflight_media_group
- name: Read the complete account database for UID slot picking
@@ -279,6 +320,23 @@
changed_when: false
register: samba_preflight_root_capacity
- name: Verify the declared root filesystem minimums when the placeholders are replaced
ansible.builtin.assert:
that:
- >-
samba_preflight_root_capacity.stdout.split()[0] | int >=
samba_minimum_root_size_bytes | int
- >-
samba_preflight_root_capacity.stdout.split()[1] | int >=
samba_minimum_root_available_bytes | int
fail_msg: >-
The root filesystem does not meet the declared minimums
({{ samba_minimum_root_size_bytes }} bytes size,
{{ samba_minimum_root_available_bytes }} bytes available).
when:
- samba_minimum_root_size_bytes is not match('^REPLACE-')
- samba_minimum_root_available_bytes is not match('^REPLACE-')
- name: Read the media mount capacities
ansible.builtin.command:
argv:
@@ -354,6 +412,7 @@
{{ ansible_facts['virtualization_type'] }}
{{ ansible_facts['virtualization_role'] }}
host_visible_processor_vcpus: "{{ ansible_facts['processor_vcpus'] }}"
configured_vcpus_nproc: "{{ samba_preflight_nproc.stdout }}"
memory_mib: "{{ ansible_facts['memtotal_mb'] }}"
cgroup_cpu_max: "{{ samba_preflight_cpu_max.stdout }}"
cgroup_memory_max: "{{ samba_preflight_memory_max.stdout }}"
@@ -367,7 +426,10 @@
map(attribute='stdout') | list }}
samba_version: "{{ samba_preflight_samba_version.stdout }}"
smb_conf_sha256: "{{ samba_preflight_smb_conf.stdout.split()[0] }}"
smb_share_listing: "{{ samba_preflight_shares.stdout }}"
smb_share_listing: >-
{{ samba_preflight_shares.stdout
if samba_preflight_shares.rc == 0 else
'smbclient not installed yet (samba_smb installs it during convergence)' }}
media_accounts: >-
{{ samba_preflight_media_accounts.results |
map(attribute='stdout') | list }}
@@ -376,7 +438,10 @@
map(attribute='item.name') |
zip(samba_preflight_media_uids.results | map(attribute='stdout')) |
list }}
media_group: "{{ samba_preflight_media_group.stdout }}"
media_group: >-
{{ samba_preflight_media_group.stdout
if samba_preflight_media_group.rc == 0 else
'missing (samba_media creates it during convergence)' }}
agent_uid: "{{ samba_preflight_agent_uid.stdout }}"
all_accounts: "{{ samba_preflight_all_accounts.stdout }}"
all_groups: "{{ samba_preflight_all_groups.stdout }}"
@@ -387,14 +452,14 @@
{{ samba_expected_ssh_host_key_fingerprint }}
placeholder_sources: >-
samba_expected_hostname=hostname,
samba_expected_processor_vcpus=host_visible_processor_vcpus,
samba_expected_processor_vcpus=configured_vcpus_nproc (nproc; configured cores),
samba_minimum_memory_mb=memory_mib,
samba_minimum_root_size_bytes=root_size_bytes,
samba_minimum_root_available_bytes=root_available_bytes,
samba_minimum_root_size_bytes=round minimum at/below root_size_bytes,
samba_minimum_root_available_bytes=round minimum at/below root_available_bytes,
samba_access_admin_uid=UID of ghost in media_accounts,
samba_access_agent_uid=agent_uid,
samba_access_deploy_uid=free UID from all_accounts,
samba_media_users[ghost].uid=uid of ghost in media_user_uids,
samba_media_users[daidalaw].uid=uid of daidalaw in media_user_uids,
samba_media_users[debugofen].uid=uid of debugofen in media_user_uids,
samba_media_group_gid=gid of gn-media in media_group
samba_media_group_gid=recorded 970 (GID slot free; samba_media creates the group)
@@ -235,7 +235,7 @@
- /usr/bin/findmnt
- --noheadings
- --output
- SOURCE,FSTYPE,OPTIONS
- TARGET,SOURCE,FSTYPE,OPTIONS
- --target
- "{{ item }}"
check_mode: false
@@ -245,11 +245,17 @@
label: "{{ item }}"
register: samba_production_mounts
- name: Require every declared media mount to be mounted
- name: Require every declared media mount to be mounted at its declared path
ansible.builtin.assert:
that:
- samba_production_mounts.results | map(attribute='rc') | unique | list == [0]
fail_msg: Not all declared media mount points are mounted.
- item.rc == 0
- item.stdout.split()[0] == item.item
fail_msg: >-
{{ item.item }} is not a mount point; findmnt resolved it to
{{ item.stdout.split()[0] }}.
loop: "{{ samba_production_mounts.results }}"
loop_control:
label: "{{ item.item }}"
- name: Read the declared media accounts
ansible.builtin.command:
@@ -381,9 +387,9 @@
samba_media_users | map(attribute='name') | list is
subset(samba_production_pdbedit.stdout_lines)
fail_msg: >-
The productive SMB users ghost, daidalaw and debugofen are missing
from the passdb (GNAI-018 R5). On a fresh-state rebuild, set them
with smbpasswd -a <user> after the first convergence.
The productive SMB users ghost, daidalaw and debugofen
are missing from the passdb (GNAI-018 R5). On a fresh-state rebuild,
set them with smbpasswd -a <user> after the first convergence.
- name: Read the anonymous Samba share listing
ansible.builtin.command:
@@ -3,17 +3,17 @@ samba_access_source_networks: 10.0.0.0/24,10.8.0.0/24
samba_access_public_key_root: "{{ playbook_dir }}/../files/ssh"
samba_access_agent_user: ghost-agent
samba_access_agent_uid: 1000
samba_access_agent_uid: 1004
samba_access_agent_public_key_file: >-
{{ samba_access_public_key_root }}/samba-ghost-agent.pub
samba_access_admin_user: ghost
samba_access_admin_uid: 1001
samba_access_admin_uid: 1000
samba_access_admin_public_key_file: >-
{{ samba_access_public_key_root }}/samba-ghost.pub
samba_access_deploy_user: ansible-deploy
samba_access_deploy_uid: 1002
samba_access_deploy_uid: 1005
samba_access_deploy_public_key_file: >-
{{ samba_access_public_key_root }}/samba-ansible-deploy.pub
@@ -22,6 +22,7 @@ samba_access_sudoers_file: /etc/sudoers.d/90-ghostnet-ansible-deploy
# CT 101 is an adopted legacy LXC: the personal administrator account ghost
# already exists with its productive UID and is only validated here, never
# recreated. ghost-agent and ansible-deploy are created with fixed UIDs that
# the operator picks from the audit output because the media accounts
# ghost/daidalaw/debugofen already occupy the low UID range.
# recreated. ghost-agent (1004) and ansible-deploy (1005) are created with
# fixed UIDs because the existing accounts ghost/daidalaw/dark2devil/
# debugofen already occupy the low UID range (recorded 2026-10-27; the
# inventory host_vars carry the same values).
@@ -1,9 +1,12 @@
# `samba_lxc_baseline`
Declaratively manages the CT 101 Samba LXC baseline: guest identity
(hostname, Debian 13, `10.0.0.9`, LXC guest, systemd), the unprivileged
(hostname `Samba`, Debian 13, `10.0.0.9`, LXC guest, systemd), the unprivileged
ID-mapping premise, baseline administration packages, the `Europe/Berlin`
timezone and the protected Ghostnet configuration/data/backup directories.
It also masks `sys-kernel-config.mount` (configfs is unavailable in
unprivileged LXC; CT 100/102/103 convention) and clears a stale failed state,
so the no-failed-units gate of `playbooks/preflight.yml` holds.
The role refuses to run while the inventory still contains
`REPLACE-WITH-*` placeholders; `playbooks/preflight.yml` on the `agent`
@@ -1,5 +1,5 @@
---
samba_lxc_baseline_expected_hostname: samba
samba_lxc_baseline_expected_hostname: Samba
samba_lxc_baseline_expected_distribution: Debian
samba_lxc_baseline_expected_distribution_major_version: "13"
samba_lxc_baseline_expected_ipv4: 10.0.0.9
@@ -56,6 +56,34 @@
(GNAI-019) depends on the standard Proxmox ID mapping; a privileged
conversion changes the UID/GID semantics of the media library.
- name: Mask the configfs mount unit (configfs is unavailable in unprivileged LXC)
ansible.builtin.systemd_service:
name: sys-kernel-config.mount
masked: true
register: samba_lxc_baseline_configfs_mask
- name: Probe the configfs mount unit failure state
ansible.builtin.command:
argv:
- /usr/bin/systemctl
- is-failed
- sys-kernel-config.mount
check_mode: false
changed_when: false
failed_when: false
register: samba_lxc_baseline_configfs_failed
- name: Clear a stale failed state of the configfs mount unit
ansible.builtin.command:
argv:
- /usr/bin/systemctl
- reset-failed
- sys-kernel-config.mount
when: >-
not ansible_check_mode and
(samba_lxc_baseline_configfs_mask.changed or
samba_lxc_baseline_configfs_failed.rc == 0)
- name: Install baseline administration packages
ansible.builtin.apt:
name: "{{ samba_lxc_baseline_packages }}"
@@ -4,12 +4,13 @@ Declaratively manages the CT 101 media prerequisites for the Samba shares and
the NFS export (GNAI-018 R1/R3):
- Verifies that all declared media mount points (`/media/daten1`,
`/media/daten2`, `/media/daten3`, `/media/datenhdd2`, `/media/datenhdd3`)
are actually mounted. Mount facts are gathered on demand when the playbook
fact subset omits them.
- Validates the productive media accounts `ghost`, `daidalaw` and `debugofen`
against their recorded UIDs (`samba_media_users`; the `REPLACE-WITH-*` UID
placeholders fail closed in the argument spec before any task runs).
`/media/daten2`, `/media/daten3`, `/media/datenhdd1`,
`/media/datenhdd2/extern`, `/media/datenhdd3`) are actually mounted. Mount
facts are gathered on demand when the playbook fact subset omits them.
- Validates the productive media accounts `ghost`, `daidalaw`
and `debugofen` against their recorded UIDs (`samba_media_users`; the
`REPLACE-WITH-*` UID placeholders fail closed in the argument spec before
any task runs).
Existing accounts are only validated, never modified. On a fresh-state
rebuild, missing accounts are created with exactly the declared UIDs
(primary group `gid=uid`, `password_lock: true`; shell/home/create_home come
@@ -18,10 +19,11 @@ the NFS export (GNAI-018 R1/R3):
- Creates the group `gn-media` with the pipeline GID **970** — the numeric
identity must match `ghostnet-ki` (GNENC-002). The role fails closed when
the GID slot or the group name is occupied by a different identity.
- Adds `ghost` to `gn-media` (required for the `mv` pickup from `[Encode]` and
the `.dlc` drop into `[Encode-In]`, because the pipeline directories carry
group `gn-media` with mode 2770). `daidalaw`/`debugofen` deliberately get no
membership (read-only world access is sufficient, GNAI-018 R3).
- Adds `ghost` to `gn-media` (required for the `mv` pickup
from `[Encode]` and the `.dlc` drop into `[Encode-In]`, because the pipeline
directories carry group `gn-media` with mode 2770; GNAI-018 R3).
`daidalaw`/`debugofen` deliberately get no membership (read-only world
access is sufficient).
Post-state verification (converged group entry, effective memberships) runs
on real executions and on check-mode reruns of an already converged host;
@@ -5,7 +5,8 @@ samba_media_group_gid: 970
# audit-production.yml). shell/home/create_home are only used when creating
# missing accounts (fresh-state rebuild); existing accounts are only validated
# on the UID and never modified. The UID placeholders stop the role
# validation fail-closed until the live values are entered.
# validation fail-closed until the live values are entered. The legacy account
# dark2devil (UID 1002) is deliberately not declared (GNAI-019 decision 11).
samba_media_users:
- name: ghost
uid: REPLACE-WITH-AUDIT-UID-GHOST
@@ -24,9 +25,12 @@ samba_media_users:
create_home: false
samba_media_group_members:
- ghost
# Live media mount topology of the productive CT 101 (recorded 2026-10-27):
# six Proxmox mount points; the Extern disk is mounted at /media/datenhdd2/extern.
samba_media_mount_points:
- /media/daten1
- /media/daten2
- /media/daten3
- /media/datenhdd2
- /media/datenhdd1
- /media/datenhdd2/extern
- /media/datenhdd3
@@ -7,7 +7,8 @@ decision 1: Debian package, no container — see there for the rationale).
`samba_smb_preserve_passdb: true`, `/var/lib/samba/private/passdb.tdb` must
exist before anything converges, the `pdbedit -L` user list is read before
and after the convergence and must be identical, and the productive users
`ghost`, `daidalaw`, `debugofen` must be present. On a fresh-state rebuild
`ghost`, `daidalaw`, `debugofen` must be present. On a
fresh-state rebuild
the first `site.yml` run passes `-e samba_smb_preserve_passdb=false` (no
passdb exists yet); the operator then sets the SMB passwords interactively
(`smbpasswd -a`), and the second run validates the populated passdb with the
@@ -114,7 +114,8 @@
subset(samba_smb_passdb_after.stdout_lines)
fail_msg: >-
The Samba user database changed during convergence. The productive
users ghost, daidalaw and debugofen must be preserved (GNAI-018 R5).
users ghost, daidalaw and debugofen must be preserved
(GNAI-018 R5).
when: samba_smb_preserve_passdb | bool
- name: Read the anonymous Samba share listing
@@ -33,6 +33,7 @@
[printers]
comment = All Printers
browseable = no
path = /var/tmp
printable = yes
guest ok = no
+21 -18
View File
@@ -14,7 +14,7 @@ Upgrades the productive CT 101 from Debian 12 (bookworm) to Debian 13
productive `smb.conf` survive the upgrade (`--force-confold` keeps the
productive `smb.conf`). Sequence:
1. Preflight: CT 101 is running, is unprivileged (GNAI-015), all five media
1. Preflight: CT 101 is running, is unprivileged (GNAI-015), all six media
mounts carry `backup=no` (otherwise `vzdump` would archive the media
library), and in the guest Debian 12, `ssh`/`smbd`/`nmbd` are active and the
IPv4 matches.
@@ -29,7 +29,7 @@ productive `smb.conf`). Sequence:
`wtmpdb-rotate.timer` cleanup rule from `ghostnet-lxc-baseline.sh`.
5. Reboot and post-verify: Debian 13, `ssh`/`smbd`/`nmbd` active, no failed
units, listeners 22/139/445, anonymous `smbclient -L` query successful,
unprivileged uid_map intact, seccomp filter disabled, all five media
unprivileged uid_map intact, seccomp filter disabled, all six media
mounts mounted.
Before the run, all media mounts in the productive `pct config 101` must carry
@@ -51,32 +51,35 @@ file-server service; the host key is unchanged, so the workstation does not
have to replace a known host key.
The reviewed repository copy has SHA-256
`f7adfbde7378708620975a64c30bb3de8727b96915ad8c58100893a52c2c2d6e`.
`99f34cd991143d7d11dc1f64a49d29e966dee9db23737d081b06b67c1e409aac`.
## `provision-ct101-samba.sh` (fresh-state rebuild)
Creates a fresh CT 101 from a Debian 13 template with the recorded profile,
attaches the five media mounts, disables the LXC seccomp filter and then runs
the reviewed baseline hook with the recorded account UIDs. The script refuses
to run when CT 101 already exists, and it names every material setting
explicitly.
attaches the six media mounts, disables the LXC seccomp filter and then runs
the reviewed baseline hook with the recorded account UIDs. After the hook it
masks `sys-kernel-config.mount` (configfs is unavailable in unprivileged LXC;
CT 100/102/103 convention) and clears a stale failed state, so the fresh CT
passes the no-failed-units gate of `playbooks/preflight.yml`. The script
refuses to run when CT 101 already exists, and it names every material
setting explicitly.
| Setting | Value |
| --- | --- |
| CT ID / hostname | `101` / `samba` (overridable via `GHOSTNET_CT101_HOSTNAME`) |
| CT ID / hostname | `101` / `Samba` (overridable via `GHOSTNET_CT101_HOSTNAME`) |
| OS | Debian 13, amd64 (newest `debian-13-*amd64*` template in the local cache, otherwise `GHOSTNET_OS_TEMPLATE`) |
| Container type | unprivileged LXC with `nesting=1,keyctl=1` |
| CPU / RAM / swap | 2 cores / 2048 MiB / 1024 MiB (adjustable via env) |
| Rootfs | 32 GiB on `local-lvm` (adjustable via env) |
| CPU / RAM / swap | 1 core / 2048 MiB / 2048 MiB (recorded profile, adjustable via env) |
| Rootfs | 4 GiB on `local-lvm` (recorded profile, adjustable via env) |
| Network | `vmbr0`, `10.0.0.9/24`, gateway `10.0.0.1`, DNS `10.0.0.14`, no IPv6 |
| Boot / timezone | `onboot: 1`, `Europe/Berlin` |
| Seccomp | empty `lxc.seccomp.profile:` (Ganesha `open_by_handle_at`) |
| Media mounts | `mp0`–`mp4` → `/media/daten1`, `/media/daten2`, `/media/daten3`, `/media/datenhdd2`, `/media/datenhdd3`, each with `backup=no` |
| Media mounts | `mp0`–`mp5` → `/media/daten1`, `/media/daten2`, `/media/daten3`, `/media/datenhdd1`, `/media/datenhdd2/extern`, `/media/datenhdd3`, each with `backup=no` |
| Baseline hook | `/usr/local/sbin/ghostnet-lxc-baseline.sh` (SHA-256 pinned) |
The resource defaults are conservative starting values; for an exact rebuild of
the productive profile, set the values from `pct config 101` of the productive
CT 101 via the env variables.
The resource defaults are the recorded productive profile (captured 2026-10-27
inside the guest via lxcfs/cgroup and `findmnt`); override them via the env
variables only deliberately.
Mandatory environment variables (the recorded values from the Ansible inventory
or the productive `pct config 101`):
@@ -85,7 +88,7 @@ or the productive `pct config 101`):
| --- | --- |
| `GHOSTNET_AGENT_UID` | UID of `ghost-agent` (must match `samba_access_agent_uid`) |
| `GHOSTNET_ADMIN_UID` | UID of `ghost` (must match `samba_access_admin_uid`) |
| `GHOSTNET_MP0_HOST` … `GHOSTNET_MP4_HOST` | Host paths of the five media mounts (must exist) |
| `GHOSTNET_MP0_HOST` … `GHOSTNET_MP5_HOST` | Host paths of the six media mounts (must exist) |
Optional variables: `GHOSTNET_CT101_HOSTNAME`, `GHOSTNET_CT101_CORES`,
`GHOSTNET_CT101_MEMORY_MB`, `GHOSTNET_CT101_ROOTFS_GB`, `GHOSTNET_CT101_SWAP_MB`,
@@ -100,8 +103,8 @@ install -o root -g root -m 0700 \
GHOSTNET_AGENT_UID=<uid> GHOSTNET_ADMIN_UID=<uid> \
GHOSTNET_MP0_HOST=/path/daten1 GHOSTNET_MP1_HOST=/path/daten2 \
GHOSTNET_MP2_HOST=/path/daten3 GHOSTNET_MP3_HOST=/path/datenhdd2 \
GHOSTNET_MP4_HOST=/path/datenhdd3 \
GHOSTNET_MP2_HOST=/path/daten3 GHOSTNET_MP3_HOST=/path/datenhdd1 \
GHOSTNET_MP4_HOST=/path/datenhdd2-extern GHOSTNET_MP5_HOST=/path/datenhdd3 \
/usr/local/sbin/provision-ct101-samba.sh
```
@@ -112,7 +115,7 @@ steps (set the ghost password, install the public keys from
placeholders, rebuild sequence from `ansible/README.md`).
The reviewed repository copy has SHA-256
`ac5d613fb3c3526135c96f02efff9fd7d8dc01952e3d4701b9b5a4a511fff1cd`.
`f40f3da836d3b0b31773c8293fa245eb970fae079dfe6189c91c699a48c21306`.
## `ghostnet-lxc-baseline.sh`
@@ -2,9 +2,11 @@
# Fresh-state rebuild of CT 101 (Samba file server) on the Proxmox VE host.
# Creates the container from a Debian 13 template with the recorded profile,
# attaches the five media mounts, disables the LXC seccomp filter (required
# attaches the six media mounts, disables the LXC seccomp filter (required
# by the rootful NFS-Ganesha container, GNAI-019) and runs the reviewed
# generic LXC baseline hook with the recorded account UIDs.
# generic LXC baseline hook with the recorded account UIDs. It then masks the
# configfs mount unit (configfs is unavailable in unprivileged LXC; CT
# 100/102/103 convention) and clears a stale failed state.
#
# The script never replaces or destroys an existing CT 101. It contains no
# password, private key or SMB secret. The SMB passwords are set by the
@@ -14,7 +16,7 @@ set -Eeuo pipefail
umask 077
readonly CTID='101'
readonly HOSTNAME="${GHOSTNET_CT101_HOSTNAME:-samba}"
readonly HOSTNAME="${GHOSTNET_CT101_HOSTNAME:-Samba}"
readonly IPV4_CIDR='10.0.0.9/24'
readonly GATEWAY='10.0.0.1'
readonly NAMESERVER='10.0.0.14'
@@ -24,10 +26,10 @@ readonly CONTAINER_STORAGE='local-lvm'
readonly BASELINE_HOOK="${GHOSTNET_BASELINE_HOOK:-/usr/local/sbin/ghostnet-lxc-baseline.sh}"
# Reviewed repository copy of proxmox/shared/scripts/ghostnet-lxc-baseline.sh:
readonly BASELINE_HOOK_SHA256='111f1b70e47e3a2f6a1bcce22cb0ec50aacd8fbcff37d87fbcf853a318381b10'
readonly CORES="${GHOSTNET_CT101_CORES:-2}"
readonly CORES="${GHOSTNET_CT101_CORES:-1}"
readonly MEMORY_MB="${GHOSTNET_CT101_MEMORY_MB:-2048}"
readonly ROOTFS_GB="${GHOSTNET_CT101_ROOTFS_GB:-32}"
readonly SWAP_MB="${GHOSTNET_CT101_SWAP_MB:-1024}"
readonly ROOTFS_GB="${GHOSTNET_CT101_ROOTFS_GB:-4}"
readonly SWAP_MB="${GHOSTNET_CT101_SWAP_MB:-2048}"
# Empty: auto-detect the newest debian-13 amd64 template in the local cache.
readonly OS_TEMPLATE="${GHOSTNET_OS_TEMPLATE:-}"
# The recorded account UIDs must match samba_access_agent_uid and
@@ -35,13 +37,15 @@ readonly OS_TEMPLATE="${GHOSTNET_OS_TEMPLATE:-}"
readonly AGENT_UID="${GHOSTNET_AGENT_UID:-}"
readonly ADMIN_UID="${GHOSTNET_ADMIN_UID:-}"
# Guest mount points of the media library (GNAI-017 section 2); the host
# paths are supplied per environment variable and must already exist.
# Guest mount points of the live media library (GNAI-017 section 1, recorded
# 2026-10-27); the host paths are supplied per environment variable and must
# already exist. The Extern disk is mounted at /media/datenhdd2/extern.
readonly -a MOUNT_GUEST_PATHS=(
'/media/daten1'
'/media/daten2'
'/media/daten3'
'/media/datenhdd2'
'/media/datenhdd1'
'/media/datenhdd2/extern'
'/media/datenhdd3'
)
readonly -a MOUNT_HOST_PATHS=(
@@ -50,6 +54,7 @@ readonly -a MOUNT_HOST_PATHS=(
"${GHOSTNET_MP2_HOST:-}"
"${GHOSTNET_MP3_HOST:-}"
"${GHOSTNET_MP4_HOST:-}"
"${GHOSTNET_MP5_HOST:-}"
)
log() {
@@ -101,7 +106,7 @@ if pct status "$CTID" >/dev/null 2>&1; then
die "CT ${CTID} is still registered"
fi
[[ "$HOSTNAME" =~ ^[a-z0-9][a-z0-9-]*$ ]] ||
[[ "$HOSTNAME" =~ ^[A-Za-z0-9][A-Za-z0-9-]*$ ]] ||
die "invalid hostname: ${HOSTNAME}"
require_numeric GHOSTNET_CT101_CORES "$CORES"
require_numeric GHOSTNET_CT101_MEMORY_MB "$MEMORY_MB"
@@ -114,7 +119,7 @@ require_numeric GHOSTNET_ADMIN_UID "$ADMIN_UID"
for index in "${!MOUNT_HOST_PATHS[@]}"; do
[[ -n "${MOUNT_HOST_PATHS[$index]}" ]] ||
die "GHOSTNET_MP${index}_HOST is not set; all five media mount host paths are required"
die "GHOSTNET_MP${index}_HOST is not set; all six media mount host paths are required"
[[ -d "${MOUNT_HOST_PATHS[$index]}" ]] ||
die "media mount host path does not exist: ${MOUNT_HOST_PATHS[$index]}"
done
@@ -236,6 +241,10 @@ GHOSTNET_AGENT_UID="$AGENT_UID" GHOSTNET_ADMIN_UID="$ADMIN_UID" \
"$BASELINE_HOOK" "$CTID" ||
die 'the baseline hook failed; inspect the output above before any rerun'
log 'masking the configfs mount unit (configfs is unavailable in unprivileged LXC)'
pct exec "$CTID" -- systemctl mask sys-kernel-config.mount
pct exec "$CTID" -- systemctl reset-failed sys-kernel-config.mount
pct exec "$CTID" -- /bin/bash -s -- \
"$HOSTNAME" "${IPV4_CIDR%/*}" "$AGENT_UID" "$ADMIN_UID" \
"${MOUNT_GUEST_PATHS[@]}" <<'GUEST_VERIFY' ||
@@ -268,8 +277,12 @@ ip -o -4 address show scope global | grep -Eq "[[:space:]]inet[[:space:]]+${ipv4
test "$(id -u ghost-agent)" = "$agent_uid"
test "$(id -u ghost)" = "$admin_uid"
systemctl is-active --quiet ssh.service
if systemctl --failed --no-legend --plain | grep -q .; then
die 'failed systemd units are present after the baseline'
fi
for mount_point in "${mounts[@]}"; do
findmnt --noheadings --target "$mount_point" >/dev/null
awk -v target="$mount_point" '$2 == target { found = 1 } END { exit(found ? 0 : 1) }' /proc/mounts ||
die "media mount point is not mounted: $mount_point"
done
# The empty lxc.seccomp.profile must disable the container seccomp filter.
test "$(awk '$1 == "Seccomp:" { print $2 }' /proc/self/status)" = '0'
@@ -21,11 +21,14 @@ readonly EXPECTED_IPV4='10.0.0.9'
readonly CONFIRMATION_PHRASE='DIST-UPGRADE_CT101_TO_DEBIAN_13'
readonly BACKUP_STORAGE="${GHOSTNET_BACKUP_STORAGE:-local}"
readonly BACKUP_DUMP_DIR="${GHOSTNET_BACKUP_DUMP_DIR:-/var/lib/vz/dump}"
# Live media mount topology of the productive CT 101 (recorded 2026-10-27):
# six Proxmox mount points; the Extern disk is mounted at /media/datenhdd2/extern.
readonly -a MEDIA_MOUNT_POINTS=(
'/media/daten1'
'/media/daten2'
'/media/daten3'
'/media/datenhdd2'
'/media/datenhdd1'
'/media/datenhdd2/extern'
'/media/datenhdd3'
)
@@ -254,7 +257,7 @@ grep -q ':445 ' <<<"$listeners" || die 'SMB listener 445 is missing'
test "$(awk '$1 == "Seccomp:" { print $2 }' /proc/self/status)" = '0' ||
die 'the container seccomp filter is still active'
for mount_point in "${mounts[@]}"; do
findmnt --noheadings --target "$mount_point" >/dev/null ||
awk -v target="$mount_point" '$2 == target { found = 1 } END { exit(found ? 0 : 1) }' /proc/mounts ||
die "media mount is not mounted: ${mount_point}"
done