diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 94df1def..919e7c74 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -9,8 +9,9 @@ > resteranno nei contratti esistenti. Esempio pratico completo: Policlinico San Donato. > Last updated: 2026-08-21 (DWH per-installation authentication is active in dual-key mode; > the owner deferred Mac acceptance and legacy revocation to the mandatory pre-Project-B gate, -> authorized the read-only survey and Project A private preparation, and did not authorize either -> stopping the legacy stack or starting the new stack). +> approved a clean replacement with no legacy-state migration and no new host account, authorized +> the read-only survey and Project A private preparation, and did not authorize either stopping the +> legacy stack or starting the new stack). > Point a fresh session here ("read PROJECT_STATE.md") before substantial work. ### PSD server deployment program — design approved, execution PENDING (2026-08-20) @@ -39,10 +40,18 @@ balancer can prove an operator-only temporary endpoint. Project B preserves the real user flow `Aritmolab homepage -> sidebar -> load balancer -> Nginx -> ThothII`, with direct ThothII-managed OIDC and no second Nginx `auth_request`. +- **Clean-replacement amendment (owner, 2026-08-21):** no host `thothii` user or group is created. + The image retains its internal numeric UID/GID `10001:10001`; only its dedicated writable bind + trees may carry that unmapped numeric ownership. Old ThothII sessions/configuration are + disposable, but the exact legacy containers, images, source, and data remain intact until the + new Aritmolab journey passes Project B. Shared Omics/LocalLLM networks, ETL Evidence, DWH, + `dwh-auth`, Supabase, Authentik, Superset, and Aritmolab are never cleanup targets. Approved + design and executable amendment: `docs/superpowers/specs/2026-08-21-psd-clean-replacement-design.md` + and `docs/superpowers/plans/2026-08-21-psd-clean-replacement.md`. - **State:** survey `SURVEY_NO_GO` for Project A private; Project A `BLOCKED_BY_SURVEY_AND_MUTATION_GATE`; Project B `BLOCKED_BY_PROJECT_A_AND_PRE_B_GATE`. - Remaining private-scope blockers are legacy rollback/backup, approved installation paths and UID - strategy, dedicated read-only workspace access, a dedicated direct-DWH role/route, and Pi/LLM + Legacy retention and UID strategy are resolved. Remaining private-scope blockers are dedicated + read-only workspace access, a dedicated direct-DWH role/route, and sanitized Pi/LLM metadata. The catalog-only survey proved the currently available `postgres` identity owns `datawarehouse` and has full write/DDL privileges, so it must not be reused by the new core. Pi metadata resolves to 0.80.3, `deepseek/deepseek-v4-pro`, thinking `high`, but the bounded diff --git a/docs/install/server.md b/docs/install/server.md index d0a371ad..fce84281 100644 --- a/docs/install/server.md +++ b/docs/install/server.md @@ -40,53 +40,53 @@ Read [server workspace-registry installation](server-workspace-registry.md), ## Service account and directories -The container runtime identity is fixed at UID/GID 10001. Reserve the same host ID for a dedicated -non-login `thothii` account so bind-mounted ownership is obvious. Stop if either ID is already used -by another account; choose a reviewed host mapping instead of changing the image identity. +The container runtime identity is fixed at UID/GID 10001. It does not require or permit creation of +a matching host account or group. Keep the number unmapped and use numeric ownership only for the +dedicated bind paths that the non-root container must read or write. If either lookup below finds a +host identity, stop and design an explicit remapping before installation. ```sh -getent passwd 10001 -getent group 10001 -sudo useradd --system --uid 10001 --user-group --home-dir /srv/thothii \ - --create-home --shell /usr/sbin/nologin thothii +if getent passwd 10001 >/dev/null || getent group 10001 >/dev/null; then + printf '%s\n' 'UID/GID 10001 is already mapped; stop' >&2 + exit 1 +fi +operator_uid="$(id -u)" +operator_gid="$(id -g)" +test "$operator_uid" -ne 0 +id -nG | tr ' ' '\n' | grep -Fx docker >/dev/null ``` -Use a dedicated `thothii-ops` group for the small set of human operators. A human who runs -`tht` must be in both `thothii-ops` (to traverse operator paths and read declared secret files -for output redaction) and the host `docker` group (to invoke Docker). Docker-group membership is -effectively host-root access; grant both memberships only to reviewed administrators. The -non-login `thothii` account owns files and writable data but does not need Docker access. - -```sh -sudo groupadd --system thothii-ops -sudo usermod --append --groups thothii-ops,docker "$USER" -``` - -Log out and back in before continuing; `id` must show both groups. Do not run `tht` through -`sudo -u thothii`: that account deliberately lacks Docker access. Do not grant the human direct -write access to runtime bind trees. +The invoking, pre-existing administrator owns source and operator files. It must already have the +site-approved Docker access required to run `tht`; this guide never changes group membership. +Docker-group membership is effectively host-root access and must remain limited to reviewed +administrators. Do not grant the operator direct write access to container runtime trees. Create explicit directories. `source` contains the clone; `operator` contains untracked path-only configuration; the three writable trees are bind-mounted into `core`; `secrets` contains regular -files only. Backups are separate from live data. Reset the account home explicitly because -distribution `useradd` defaults may otherwise leave `/srv/thothii` non-traversable by -`thothii-ops`. +files only. The parent is owned by the operator with numeric group 10001 so both the operator and +container can traverse it. Numeric ownership does not add entries to `/etc/passwd` or `/etc/group`. ```sh -sudo install -d -o 10001 -g thothii-ops -m 2750 /srv/thothii -sudo install -d -o 10001 -g thothii-ops -m 2750 /srv/thothii/source -sudo install -d -o 10001 -g thothii-ops -m 2770 /srv/thothii/operator -sudo install -d -o 10001 -g thothii-ops -m 2750 /srv/thothii/secrets +operator_uid="$(id -u)" +operator_gid="$(id -g)" +sudo install -d -o "$operator_uid" -g 10001 -m 0750 /srv/thothii +sudo install -d -o "$operator_uid" -g "$operator_gid" -m 0750 /srv/thothii/source +sudo install -d -o "$operator_uid" -g "$operator_gid" -m 0750 /srv/thothii/operator +sudo install -d -o 10001 -g "$operator_gid" -m 0750 /srv/thothii/secrets sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/data sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/pi-state sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/workspace-registry sudo install -d -o root -g root -m 0700 /srv/thothii-backups +stat -c '%u:%g %a %n' \ + /srv/thothii /srv/thothii/source /srv/thothii/operator /srv/thothii/secrets \ + /srv/thothii/data /srv/thothii/pi-state /srv/thothii/workspace-registry \ + /srv/thothii-backups ``` -Verify `/srv/thothii` is owned by `10001:thothii-ops` with mode `2750`. The human operator can -traverse the parent but can write only `operator`; setgid keeps generated files in `thothii-ops`. -`source`, `secrets`, and all runtime bind trees remain non-group-writable. Do not make -`/srv/thothii` a shared application directory. +Expected: the parent is `operator_uid:10001 750`; source/operator are +`operator_uid:operator_gid 750`; secrets are `10001:operator_gid 750`; the three runtime trees are +`10001:10001 750`; backups are `0:0 700`. Re-run the empty `getent` checks after creation. Do not +make `/srv/thothii` a shared application directory. ## Firewall and network boundaries @@ -187,10 +187,10 @@ Never add those services to ThothII's mandatory Compose files. Follow Clone with LF line endings, then verify before every build: ```sh -sudo -u thothii git -c core.autocrlf=false clone \ +git -c core.autocrlf=false clone \ https://github.example.invalid/your-org/ThothII.git /srv/thothii/source/ThothII cd /srv/thothii/source/ThothII -sudo -u thothii git config --local core.autocrlf false +git config --local core.autocrlf false bash scripts/verify-line-endings.sh sudo /srv/thothii/source/ThothII/scripts/prepare-server-pi-state.sh \ /srv/thothii/pi-state 10001 10001 @@ -206,17 +206,15 @@ after restoring `pi-state`; run it before any `tht start`, Compose render/start, Copy the path-only server environment and installation descriptor: ```sh -sudo -u thothii cp deploy/env/server.env.example /srv/thothii/operator/server.env -sudo -u thothii cp docs/install/examples/thothii-installation.server.yaml \ +cp deploy/env/server.env.example /srv/thothii/operator/server.env +cp docs/install/examples/thothii-installation.server.yaml \ /srv/thothii/operator/thothii-installation.yaml -sudo chown 10001:thothii-ops /srv/thothii/operator/server.env \ - /srv/thothii/operator/thothii-installation.yaml -sudo chmod 0660 /srv/thothii/operator/server.env \ +chmod 0600 /srv/thothii/operator/server.env \ /srv/thothii/operator/thothii-installation.yaml ``` -The named human operator can now edit both placeholder files without `sudo`; use an editor that -preserves the group, or create replacements under `umask 0007` in the setgid operator directory. +The invoking operator owns both placeholder files; use an editor that preserves ownership and mode, +or create replacements under `umask 0077` in the operator directory. Replace every placeholder with an absolute path. Use exactly one Git transport override. For HTTPS, replace `deploy/compose.git-ssh.yaml` with `deploy/compose.git-https.yaml`. Keep the required session-server overlay. Optional host-gateway or pinned @@ -224,17 +222,18 @@ image overrides go after them. Create each installation credential (Pi/application, Git, and session storage) as an independent regular file in `/srv/thothii/secrets`, owned by -UID 10001, group `thothii-ops`, and mode `0640`. Owner access lets the UID 10001 container read a -file mounted under `/run/secrets`; group access lets the reviewed human run `tht`. The +UID 10001, the invoking operator's numeric primary GID, and mode `0640`. Owner access lets the UID +10001 container read a file mounted under `/run/secrets`; group access lets the operator run `tht`. The operator environment records only absolute `*_FILE` or `*_SOURCE` paths for those installation credentials. DWH and Evidence values are entered later through Workspace management and persist as ciphertext under `/data/workspace-secrets`; the frontend receives no secret values. Do not print file contents while testing permissions. ```sh -sudo find /srv/thothii/secrets -type f -exec chown 10001:thothii-ops {} + +operator_gid="$(id -g)" +sudo find /srv/thothii/secrets -type f -exec chown "10001:$operator_gid" {} + sudo find /srv/thothii/secrets -type f -exec chmod 0640 {} + -sudo find /srv/thothii/secrets -type f \( ! -user thothii -o ! -group thothii-ops -o ! -perm 0640 \) -print +sudo find /srv/thothii/secrets -type f \( ! -uid 10001 -o ! -gid "$operator_gid" -o ! -perm 0640 \) -print ``` Configure the remote repository and exactly one read-only Git transport as described in @@ -250,7 +249,7 @@ Compose; copy the reviewed path-only server environment to the launcher's untrac ```sh cd /srv/thothii/source/ThothII -sudo -u thothii cp /srv/thothii/operator/server.env deploy/env/local.env +cp /srv/thothii/operator/server.env deploy/env/local.env bash scripts/build-local.sh ``` @@ -286,12 +285,13 @@ Build the operator binaries with Docker. No Go installation or Go knowledge is r cd /srv/thothii/source/ThothII THT_THT_OUTPUT_DIRECTORY=/srv/thothii/operator/build-output \ bash scripts/build-tht.sh -sudo install -o root -g thothii-ops -m 0750 \ +operator_gid="$(id -g)" +sudo install -o root -g "$operator_gid" -m 0750 \ /srv/thothii/operator/build-output/tht-linux-amd64 \ /srv/thothii/operator/tht ``` -The source checkout stays read-only to the human. The explicit output directory is the only build +The source checkout remains controlled by the invoking operator. The explicit output directory is the only build write boundary; the build script rejects relative or non-canonical output paths. After installation, remove or retain `build-output` according to the site's reviewed artifact policy. diff --git a/docs/operations/psd-server-survey-remediation-checklist.md b/docs/operations/psd-server-survey-remediation-checklist.md index 36de83d9..ec99119a 100644 --- a/docs/operations/psd-server-survey-remediation-checklist.md +++ b/docs/operations/psd-server-survey-remediation-checklist.md @@ -58,7 +58,7 @@ Authentik, Aritmolab o repository esterni. | 4 | Topologia e responsabilità del load balancer | `BLOCKED` | Unassigned | Necessario per Project B/route opzionale | | 5 | Accesso read-only protetto ad Authentik | `BLOCKED` | Unassigned | Necessario per Project B, non per A privato | | 6 | Accesso catalog-only protetto a PostgreSQL | `BLOCKED` | Unassigned | DWH direct read-only ancora da provare | -| 7 | Backup e rollback del vecchio ThothII | `BLOCKED` | Proprietario del progetto | Procedura e destinazione da approvare | +| 7 | Confine temporaneo e cleanup del vecchio ThothII | `PASS` | Proprietario del progetto | Retain fino a Project B PASS; cleanup esatto separato | | 8 | Accesso Git read-only al workspace PSD | `BLOCKED` | Curator da confermare | Checkout/deploy key server mancanti | | 9 | Metadati Pi e LLM verificabili | `BLOCKED` | Unassigned | Policy e reachability redatte mancanti | | 10 | Conservazione evidenze e nuovo survey bounded | `PENDING` | Unassigned | Nuovo report e decisione proprietario | @@ -281,23 +281,22 @@ Authentik, Aritmolab o repository esterni. - Next step: definire con il DBA ruolo, secret-file protetto, TLS/rete e query di grant da ripetere; non creare il ruolo durante il survey. -## Activity 7: Prove legacy backup and rollback +## Activity 7: Bind the disposable legacy boundary and cleanup exclusions -- Status: `BLOCKED` -- Accountable owner: Unassigned -- Objective: dimostrare che il vecchio ThothII possa essere preservato e ripristinato prima di - qualsiasi stop. -- Why this is required: source, container, bind e network sono inventariati, ma backup verificato, - controller e restart recipe non sono disponibili. +- Status: `PASS` +- Accountable owner: Proprietario del progetto (confermato dall'utente) +- Objective: mantenere il vecchio ThothII solo come confine temporaneo di cutover e rimuovere + esclusivamente le sue risorse dopo il PASS reale di Aritmolab. +- Why this is required: Aritmolab usa oggi i container legacy, ma il proprietario ha dichiarato + sacrificabili sessioni, configurazione e dati del vecchio ThothII. - Ordered actions: - 1. identificare owner e meccanismo lifecycle compatibile; - 2. confermare assenza di lavoro utente da preservare; - 3. definire contenuti, destinazione e protezione del backup; - 4. definire checksum e verifica di leggibilità; - 5. scrivere comandi esatti di stop/start e ordine di chiusura/ripristino route; - 6. eseguire il backup soltanto nella successiva fase autorizzata, prima dello stop. -- Required redacted evidence: inventario, percorso backup, checksum, owner, restart recipe e - rollback route; nessun contenuto di secret. + 1. inventariare nuovamente container, image ID, source e data immediatamente prima dello stop; + 2. preservare invariati i due network condivisi e l'Evidence ETL esterna; + 3. chiudere la route e fermare solo i container legacy nel gate Project A autorizzato; + 4. conservare container, immagini, source e data fino al PASS automatico, umano e owner di B; + 5. rimuovere poi soltanto gli exact target sotto un'autorizzazione cleanup separata. +- Required redacted evidence: inventario esatto, owner, restart recipe, esclusioni shared, decisioni + Project A/B e manifest finale di cleanup; nessun contenuto di secret. - Discussion notes: il legacy stack è ancora attivo e invariato. Compose project `thothii` usa `/home/chirone/ThothII/compose.yaml`; i servizi sono `core` e `frontend`, senza named volume. Il solo bind applicativo RW è `/home/chirone/thothii-data` (con i bind Pi annidati); Evidence è @@ -307,13 +306,13 @@ Authentik, Aritmolab o repository esterni. `PI_AUTH_FILE=/home/chirone/thothii-data/pi-config/agent/auth.json` insieme a `--env-file deploy/thothii.env -p thothii -f compose.yaml`; stop individua entrambi i container e start è sintatticamente valido (non trova container arrestati mentre lo stack è ancora attivo). -- Decision: il controller e le radici da archiviare sono identificati; nessun backup o stop è stato - eseguito. Per un rollback normale i container restano esistenti e il controller usa `start`. -- Blockers: scegliere/approvare la destinazione protetta, creare il backup con manifest/checksum, - verificare la leggibilità dell'archivio, definire la gestione della route durante lo stop e - ottenere il consenso separato prima di eseguire stop/start. -- Next step: approvare manifest, destinazione e comandi; eseguire backup e checksum soltanto nel - successivo gate di mutazione. +- Decision: nessun backup legacy è richiesto. I container, immagini, source e data già presenti + restano il solo rollback temporaneo fino al PASS Project B; non si crea alcun utente host. Le + risorse esclusive potranno essere cancellate dopo il collaudo Aritmolab, mentre network shared, + ETL Evidence, Omics, LocalLLM, DWH, `dwh-auth`, Supabase, Authentik e Superset sono esclusi. +- Blockers: nessuno per questa decisione survey. Stop/start, route change e cleanup restano tre + autorizzazioni di mutazione separate e non sono autorizzati da questo PASS. +- Next step: usare il design e piano clean-replacement approvati; non eseguire ancora mutazioni. ## Activity 8: Provide read-only PSD workspace Git access @@ -375,12 +374,13 @@ Authentik, Aritmolab o repository esterni. `/home/chirone/ThothII` at `6ca4275`; the only RW application bind is `/home/chirone/thothii-data`, plus the nested Pi binds. The source is about 1.05 GB and the data bind about 1.9 MB. No backup was created. -- Recovery evidence: existing containers can be inventoried for a stopped-container restart, but - backup destination, checksum/restore procedure and route ordering are not yet approved. -- Identity collision: legacy data and protected subtrees are numerically owned by UID/GID 10001. - The example in `docs/install/server.md` and the Project A Pi preparation use UID 10001 for the - new service. Do not create that host - identity or grant it access until a reviewed isolation/ownership strategy is approved. +- Recovery decision: legacy state is disposable; no backup is required. Existing stopped + containers, images, source and data remain only as the temporary Project A/B rollback boundary. +- Identity decision: neither UID nor GID 10001 maps to a host account. No host identity will be + created. The new image retains numeric `10001:10001`, confined to its distinct writable roots; + the existing operator owns source/configuration. Recheck both `getent` lookups before creating + paths and stop on any new mapping. +- Shared exclusion: never remove the Omics/LocalLLM networks or external ETL Evidence bind. - Candidate paths: documented examples `/srv/thothii` and `/srv/thothii-backups` are absent and therefore only candidates; they have not been created. `127.0.0.1:18080` è il candidato frontend e risultava libero al momento del survey, ma non è riservato e va ricontrollato prima @@ -425,10 +425,10 @@ Authentik, Aritmolab o repository esterni. Un nuovo `SURVEY_GO_PROJECT_A_PRIVATE` richiede: -- owner e autorità di backup/stop/start/rollback identificati per il legacy e Project A; +- owner e autorità di stop/start/rollback identificati per il legacy e Project A; - accessi read-only PostgreSQL, workspace Git e Pi/LLM verificati; - identità DWH dimostrata read-only; -- backup e restart recipe legacy verificabili; +- inventario, restart recipe e cleanup exclusions legacy verificabili; - risorse e percorsi della nuova installazione approvati; - report redatto, secret-scan valido e checksum verificato; - approvazione esplicita del proprietario. @@ -452,3 +452,4 @@ registrata separatamente. |---|---|---|---| | 2026-08-20 | Initial | Creata checklist; Activity 1 aperta, Activity 2–10 pending | Sol | | 2026-08-21 | Sequencing amendment | Activity 1 deferred pre-B; survey ripreso read-only; Project A private resta NO-GO | Owner/Sol | +| 2026-08-21 | Clean replacement | Activity 7 PASS; legacy disposable dopo B; nessun account host 10001 | Owner/Sol | diff --git a/docs/plans/2026-08-20-psd-server-deployment-program-design.md b/docs/plans/2026-08-20-psd-server-deployment-program-design.md index c7caa35f..c9bc469d 100644 --- a/docs/plans/2026-08-20-psd-server-deployment-program-design.md +++ b/docs/plans/2026-08-20-psd-server-deployment-program-design.md @@ -36,8 +36,13 @@ manual-test document for each project. - The new source clone is prepared beside the old source directory. The old and new stacks are not kept running simultaneously: inventory and backup happen first, the old stack is stopped, and only then is the new stack started. -- Preserve the old directory, configuration, and volumes as a recovery source until both projects - pass. Do not migrate legacy application sessions, Qdrant data, Ollama caches, or derived indexes. +- Keep the old directory, configuration, containers, images, and data only as a temporary recovery + boundary until the real Aritmolab journey passes Project B. They are disposable after Project B + automated, human, and owner PASS. Do not migrate legacy application sessions, Qdrant data, + Ollama caches, or derived indexes. +- Do not create a host `thothii` user or group. Keep UID/GID `10001:10001` as the image's unmapped + numeric runtime identity and confine numeric ownership to the new installation's writable bind + trees. Stop if either number becomes mapped to a host account before installation. - Recover configuration only: endpoints, non-secret policy, provider/model selection, relevant paths, and references to protected credentials. Never copy an old setting without validating it against the current contract. @@ -149,8 +154,10 @@ proved safe. pre-Project-B gate. 4. Prepare the new source clone and protected operator/runtime directories beside the old source. 5. Extract only approved configuration facts from the legacy installation. -6. Create and verify backups and a restart recipe for the legacy stack. -7. Stop the legacy stack without deleting its source, configuration, images, or volumes. +6. Record and verify the exact restart recipe for the legacy stack. No data backup is required + because the owner declared legacy sessions and configuration disposable; the still-present + containers, images, source, and data are the temporary rollback boundary. +7. Stop the legacy stack without deleting its source, configuration, images, or data. 8. Build/install the current native `tht` and application images from the frozen source. 9. Configure local authentication, direct DWH bindings, Pi/provider access, and the Git workspace. 10. Start through `tht`, then validate health, authentication, workspace, workflow, and Pi. @@ -313,8 +320,8 @@ current gate is satisfied. ## Rollback boundaries -- **Project A:** stop the new stack and restart the preserved legacy installation. No new volume is - copied into the legacy installation. +- **Project A:** stop the new stack and restart the still-present legacy installation. No new + volume is copied into it and no promise is made to retain it after Project B PASS. - **Project B ingress:** close the public route first, then restore the prior Nginx, load-balancer, certificate reference, and sidebar configuration. - **Project B application:** return to the accepted Project A local-auth operator configuration diff --git a/docs/plans/2026-08-20-psd-server-project-a-standalone.md b/docs/plans/2026-08-20-psd-server-project-a-standalone.md index 007c1f6f..6f2d1397 100644 --- a/docs/plans/2026-08-20-psd-server-project-a-standalone.md +++ b/docs/plans/2026-08-20-psd-server-project-a-standalone.md @@ -4,7 +4,7 @@ **Goal:** Install a clean PSD ThothII stack with local authentication, direct read-only DWH access, internal Qdrant/Ollama, rebuilt preprocessing, and one completed F1-F8 work session. -**Architecture:** Preserve the stopped legacy installation and deploy the current canonical five-service Compose stack from an adjacent clean clone. A reviewed server-local override disables public exposure and uses filesystem work sessions; an optional load-balancer route is permitted only when it is operator-only and its denial boundary is proved first. +**Architecture:** Keep the stopped legacy installation intact only as a temporary rollback boundary and deploy the current canonical five-service Compose stack from an adjacent clean clone. Create no host service identity: the image's UID/GID 10001 remains numeric and unmapped, confined to the new writable bind roots. A reviewed server-local override disables public exposure and uses filesystem work sessions; an optional load-balancer route is permitted only when it is operator-only and its denial boundary is proved first. **Tech Stack:** Git, Docker/Compose, native `tht`, local Argon2id authentication, PSD Supabase PostgreSQL direct transport, Qdrant, Ollama, Pi, Nginx/load-balancer test route where safe. @@ -15,10 +15,15 @@ - Common survey result is `SURVEY_GO_PROJECT_A_PRIVATE` and its digest is recorded. - Every path below is replaced by the exact survey result before execution. - No production Nginx/load-balancer/sidebar/Authentik change is in scope. -- The old stack remains running only until backup verification finishes; old and new stacks never - run together. +- The old stack remains running until its exact inventory and restart recipe are verified; old and + new stacks never run together. - The server's workspace deploy credential remains read-only. A curator with write access publishes the workspace change. +- Owner amendment 2026-08-21 declares legacy sessions/configuration disposable. Retain old resources + only until Project B proves the production Aritmolab journey, then delete them under a separate + exact cleanup authorization. No legacy backup is required. +- Do not create a host user or group for 10001. Immediately before preparing `/srv/thothii`, both + `getent passwd 10001` and `getent group 10001` must return no match. - Owner amendment 2026-08-21 defers live Mac `rest_api` acceptance and `legacy-shared` revocation to the mandatory pre-Project-B gate. It does not authorize stop/start; those require a later explicit owner gate even after private preparation is complete. @@ -56,7 +61,8 @@ Expected: one SHA recorded as the pre-change workspace revision. **Step 3: Check old-stack recoverability** Expected: exact old start/stop procedure, source SHA, Compose identity, volumes/binds, proxy closure -procedure, and backup destination are present in the survey. Stop if any is missing. +procedure, restart recipe, and shared-resource exclusions are present in the survey. Stop if any is +missing. ### Task 2: Publish the multi-transport workspace revision @@ -146,11 +152,12 @@ active user work. Do not use the new `tht` against an incompatible old descripto Change only the surveyed ThothII-specific route using its established mechanism. Validate Nginx and load-balancer configuration before applying. Confirm external requests no longer reach the app. -**Step 3: Create the legacy backup** +**Step 3: Record the disposable legacy boundary** -Use the surveyed, version-compatible backup procedure. Include source/config metadata and all old -runtime volumes/binds needed to restart; store credentials separately under existing protected -custody. Create SHA-256 checksums and verify them. +Record exact container and image IDs plus filesystem device/inode/ownership/size for +`/home/chirone/ThothII` and `/home/chirone/thothii-data`. Do not archive their contents: the owner +declared them disposable. Prove that the external Evidence bind and both shared Docker networks +are excluded from any later cleanup manifest. **Step 4: Stop the old stack** @@ -168,10 +175,12 @@ be stated unambiguously, stop before creating the new stack. - Create: survey-selected new source root - Create: survey-selected operator, secret, data, Pi-state, registry, and backup roots -**Step 1: Create dedicated identities and paths** +**Step 1: Create dedicated paths without creating identities** -Follow `docs/install/server.md` ownership rules using the surveyed available UID/GID. Do not reuse a -UID already owned by another service and do not change image UID 10001 without a reviewed mapping. +Follow `docs/install/server.md` numeric-ownership rules. Do not call `useradd`, `groupadd`, or +`usermod`. The existing operator owns source/operator files; only the new data, Pi-state, and +workspace-registry roots use unmapped numeric `10001:10001`. Stop if UID or GID 10001 resolves to a +host account, and do not change the image identity without a reviewed design amendment. **Step 2: Clone the frozen application source** diff --git a/docs/superpowers/plans/2026-08-21-psd-clean-replacement.md b/docs/superpowers/plans/2026-08-21-psd-clean-replacement.md new file mode 100644 index 00000000..0c7a1251 --- /dev/null +++ b/docs/superpowers/plans/2026-08-21-psd-clean-replacement.md @@ -0,0 +1,259 @@ +# PSD Clean ThothII Replacement Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the disposable PSD ThothII installation without creating host identities and remove only its exclusive resources after the new Aritmolab journey passes. + +**Architecture:** Project A builds a separate `/srv/thothii` installation while the old resources remain an inert rollback boundary. The container keeps numeric UID/GID 10001 without a host account. Project B moves the Aritmolab integration to the accepted stack; exact legacy deletion is a final post-acceptance operation that excludes shared Chirone resources. + +**Tech Stack:** Linux ownership and identity checks, Docker Compose, native `tht`, Nginx/Aritmolab integration, protected evidence journals. + +## Global Constraints + +- Never call `useradd`, `groupadd`, `usermod`, or edit `/etc/passwd`, `/etc/group`, `/etc/shadow`, or `/etc/gshadow`. +- Treat UID/GID `10001:10001` as an unmapped numeric container identity only. +- Never reuse `/home/chirone/thothii-data` for the replacement. +- Never remove `omics_portal_omics_network`, `localllm_default`, `/home/chirone/chirone/etl/docs/evidence`, or any Omics Portal, LocalLLM, DWH, `dwh-auth`, Supabase, Authentik, ETL, or Superset resource. +- Never run `docker system prune`, `docker network prune`, `docker volume prune`, or `docker compose down --volumes`. +- Do not stop the old stack or start the new stack without the separate owner mutation gate already required by Project A. +- Do not delete any legacy target until Project B automated PASS, human PASS, and explicit owner PASS are all recorded. + +--- + +### Task 1: Bind the clean-replacement decision to the surveyed host + +**Files:** +- Read: `/etc/passwd` +- Read: `/etc/group` +- Read: `/home/chirone/ThothII/compose.yaml` +- Read: `/home/chirone/omics_portal/nginx/nginx.conf` +- Record: protected Project A survey journal + +**Interfaces:** +- Consumes: approved design `docs/superpowers/specs/2026-08-21-psd-clean-replacement-design.md`. +- Produces: a redacted exact-target inventory and a `numeric_identity_unmapped=PASS` decision. + +- [ ] **Step 1: Prove that 10001 is not a host identity** + +```bash +if getent passwd 10001 >/dev/null || getent group 10001 >/dev/null; then + printf '%s\n' 'numeric_identity_unmapped=FAIL' + exit 1 +fi +printf '%s\n' 'numeric_identity_unmapped=PASS' +``` + +Expected: one PASS line. Do not continue if either lookup succeeds. + +- [ ] **Step 2: Revalidate the exclusive legacy containers and images** + +```bash +docker inspect --format '{{.Name}} project={{index .Config.Labels "com.docker.compose.project"}} image={{.Image}}' \ + thothii-core-1 thothii-frontend-1 +docker image inspect --format '{{.Id}} {{join .RepoTags ","}}' \ + thothii-core:local thothii-frontend:local +``` + +Expected: exactly the two `thothii` project containers and two immutable image IDs. Record IDs, +not environment variables or container configuration values. + +- [ ] **Step 3: Revalidate shared exclusions** + +```bash +docker network inspect --format '{{.Name}} project={{index .Labels "com.docker.compose.project"}}' \ + omics_portal_omics_network localllm_default +stat -c '%u:%g %a %n' /home/chirone/chirone/etl/docs/evidence +``` + +Expected: both networks exist and the external Evidence path remains outside the legacy data tree. + +- [ ] **Step 4: Record the survey checkpoint** + +Record only names, IDs, modes, ownership numbers, PASS/FAIL decisions, and timestamps in the +protected journal. Never record container environment, auth files, API keys, or raw Nginx output. + +### Task 2: Prepare the replacement roots without host accounts + +**Files:** +- Create: `/srv/thothii/source` +- Create: `/srv/thothii/operator` +- Create: `/srv/thothii/data` +- Create: `/srv/thothii/secrets` +- Create: `/srv/thothii/pi-state` +- Create: `/srv/thothii/workspace-registry` +- Create: `/srv/thothii-backups` +- Test: exact ownership/mode checks below + +**Interfaces:** +- Consumes: `numeric_identity_unmapped=PASS` from Task 1. +- Produces: isolated bind roots compatible with container UID/GID 10001 and operator UID/GID 1013:1006. + +- [ ] **Step 1: Create only the reviewed roots** + +```bash +sudo install -d -o 1013 -g 10001 -m 0750 /srv/thothii +sudo install -d -o 1013 -g 1006 -m 0750 /srv/thothii/source +sudo install -d -o 1013 -g 1006 -m 0750 /srv/thothii/operator +sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/data +sudo install -d -o 10001 -g 1006 -m 0750 /srv/thothii/secrets +sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/pi-state +sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/workspace-registry +sudo install -d -o root -g root -m 0700 /srv/thothii-backups +``` + +Expected: commands create directories only. They do not create identities. + +- [ ] **Step 2: Verify identity databases are unchanged and modes are exact** + +```bash +if getent passwd 10001 >/dev/null || getent group 10001 >/dev/null; then exit 1; fi +stat -c '%u:%g %a %n' \ + /srv/thothii /srv/thothii/source /srv/thothii/operator /srv/thothii/secrets \ + /srv/thothii/data /srv/thothii/pi-state /srv/thothii/workspace-registry \ + /srv/thothii-backups +``` + +Expected ownership/modes, in order: `1013:10001 750`, twice `1013:1006 750`, +`10001:1006 750`, three times `10001:10001 750`, and `0:0 700`. + +- [ ] **Step 3: Commit documentation/code changes before runtime use** + +Replace the account-creation section of `docs/install/server.md` with the numeric ownership model: +the invoking operator UID/GID owns source and operator paths, numeric 10001 owns only secrets and +writable runtime paths, and the manual verifies both `getent` lookups remain empty. Remove every +`useradd`, `groupadd`, `usermod`, `sudo -u thothii`, and `thothii-ops` instruction. Run the existing +documentation gates, obtain review, commit, and push before Project A uses these roots. Runtime +state under `/srv` is never committed. + +### Task 3: Execute Project A with a stopped-but-intact legacy boundary + +**Files:** +- Read: `docs/plans/2026-08-20-psd-server-project-a-standalone.md` +- Record: protected Project A report and journal + +**Interfaces:** +- Consumes: Task 2 roots and a separately approved Project A mutation gate. +- Produces: Project A automated PASS and explicit human PASS while the old resources remain intact. + +- [ ] **Step 1: Close the old ThothII route using the separately approved exact route operation** + +Validate configuration before applying it. Do not change `/dwh/` or unrelated Omics routes. + +- [ ] **Step 2: Stop only the two old ThothII containers** + +Use the surveyed legacy Compose controller. Do not remove containers, images, networks, source, or +data. Confirm Omics Portal, LocalLLM, DWH, `dwh-auth`, Supabase, Authentik, ETL, and Superset remain +running. + +- [ ] **Step 3: Execute and close Project A** + +Follow the Project A plan and manual through its automated and human PASS decisions. On failure, +stop the new stack and restart the still-present old containers; do not delete either installation. + +### Task 4: Cut Aritmolab over in Project B + +**Files:** +- Read: `docs/plans/2026-08-20-psd-server-project-b-authentik.md` +- Read: `docs/testing/psd-server-project-b-manual.md` +- Record: protected Project B report and journal + +**Interfaces:** +- Consumes: accepted Project A candidate and the independent pre-Project-B gate. +- Produces: a production route that no longer depends on the old container names. + +- [ ] **Step 1: Complete the mandatory pre-Project-B gates** + +Mac REST acceptance, the 48-hour/two-ETL observation, and `legacy-shared` revocation must be PASS. +These DWH-key gates are independent from deleting the old ThothII application. + +- [ ] **Step 2: Execute Project B without deleting legacy resources** + +Follow the Project B plan. Keep the old containers stopped and intact until all automated checks +pass. + +- [ ] **Step 3: Run the real Aritmolab acceptance** + +Prove the complete path from Aritmolab home and sidebar through authentication to frontend assets, +API, SSE, and one completed workflow. Confirm the active route no longer resolves the legacy +`thothii-core` or `thothii-frontend` services. + +- [ ] **Step 4: Record human and owner PASS** + +No cleanup command is authorized by technical success alone. Record the Project B human PASS and a +separate owner decision explicitly authorizing the exact cleanup inventory from Task 5. + +### Task 5: Delete only exclusive legacy ThothII resources + +**Files:** +- Delete: `/home/chirone/ThothII` +- Delete: `/home/chirone/thothii-data` +- Record: protected final cleanup report + +**Interfaces:** +- Consumes: Project B automated PASS, human PASS, owner PASS, and immutable IDs from Task 1. +- Produces: no legacy ThothII application resources and no change to shared Chirone resources. + +- [ ] **Step 1: Revalidate the cleanup manifest immediately before deletion** + +Re-run Task 1. Stop if an image has another consumer, a legacy container is running, the production +route still mentions a legacy service, or either filesystem path resolves outside its expected +exact target. Record device/inode, ownership, and size; never record file contents. + +- [ ] **Step 2: Remove only the stopped legacy containers through their Compose project** + +Remove `thothii-core-1` and `thothii-frontend-1` without `--volumes`. Do not remove either shared +network. + +- [ ] **Step 3: Remove only the two unreferenced legacy image IDs** + +Resolve tags to the immutable IDs recorded in Task 1 and prove no container references them before +removal. Do not prune images globally. + +- [ ] **Step 4: Remove the two exact legacy filesystem trees** + +Delete only `/home/chirone/ThothII` and `/home/chirone/thothii-data` after their exact identities +match the approved manifest. This operation is irreversible by design; no legacy session or state +restore is promised after this point. + +- [ ] **Step 5: Verify shared resources and the production journey** + +Confirm both shared Docker networks, the external Evidence path, and every out-of-scope service +still exist. Repeat the Aritmolab sidebar, authentication, frontend, API/SSE, and completed-workflow +checks. Record `CLEAN_REPLACEMENT_PASS` only if all checks pass. + +### Task 6: Commit the durable evidence references + +**Files:** +- Modify: `PROJECT_STATE.md` +- Modify: approved redacted report index only + +**Interfaces:** +- Consumes: Project A, Project B, and cleanup evidence digests. +- Produces: a secret-free durable state summary. + +- [ ] **Step 1: Update the state without secret-bearing evidence** + +Record candidate SHAs, image IDs, workspace SHA, report digests, gate decisions, deleted exact +targets, and preserved shared resources. Protected journals and credentials stay outside Git. + +- [ ] **Step 2: Run documentation and secret gates** + +```bash +bash scripts/test-verify-workspace-install-docs.sh +bash scripts/test-verify-dwh-auth-docs.sh +bash scripts/auth-docs-smoke.sh +git diff --check +``` + +Expected: every script exits zero and the diff check has no output. + +- [ ] **Step 3: Commit and push the secret-free state update** + +```bash +git add PROJECT_STATE.md +git commit -m "docs: record PSD clean replacement completion" +git push origin feat/dwh-rest-installation-auth +``` + +Expected: the push contains no `/srv` state, credential, protected journal, raw Nginx output, or +legacy data. diff --git a/docs/superpowers/specs/2026-08-21-psd-clean-replacement-design.md b/docs/superpowers/specs/2026-08-21-psd-clean-replacement-design.md new file mode 100644 index 00000000..b8769d4d --- /dev/null +++ b/docs/superpowers/specs/2026-08-21-psd-clean-replacement-design.md @@ -0,0 +1,86 @@ +# PSD Clean ThothII Replacement Design + +**Date:** 2026-08-21 +**Status:** Approved by the owner + +## Decision + +The existing PSD ThothII installation is disposable. Its sessions, settings, Pi state, derived +artifacts, indexes, images, source checkout, and application data are not migration inputs for the +new installation. They remain present only until the replacement has passed the real Aritmolab +journey; this temporary retention is a cutover safeguard, not a legacy-support requirement. + +The replacement does not create a host `thothii` user or group. The image keeps its internal +UID/GID `10001:10001`. Host bind trees that the container must write may use that numeric ownership +without corresponding `/etc/passwd` or `/etc/group` entries. Source and operator-controlled files +remain owned by the existing operator `admlocforn1` (UID 1013) and the existing `chirone` group +(GID 1006). + +## Boundaries + +The following old ThothII resources become deletion candidates only after Project B human +acceptance proves the production Aritmolab route: + +- containers `thothii-core-1` and `thothii-frontend-1`; +- images `thothii-core:local` and `thothii-frontend:local`, after proving no other container uses + their immutable IDs; +- checkout `/home/chirone/ThothII`; +- application bind tree `/home/chirone/thothii-data`. + +The following resources are shared and are never deletion candidates in the ThothII cleanup: + +- Docker networks `omics_portal_omics_network` and `localllm_default`; +- `/home/chirone/chirone/etl/docs/evidence` and the ETL project; +- Omics Portal/Aritmolab source, Nginx, web, worker, sidebar, and capability configuration; +- LocalLLM API and vLLM services; +- `dwh-auth`, its credential registry, and the `/dwh/` Nginx route; +- Supabase/DWH, Authentik, Superset, and their data or configuration. + +No global Docker prune, network removal, broad recursive deletion, or Compose volume deletion is +permitted. Cleanup resolves and revalidates every exact target immediately before removal. + +## Sequence + +1. Project A preparation creates a distinct installation under `/srv/thothii`; it does not reuse + `/home/chirone/thothii-data`. +2. Immediately before creating bind roots, verify that UID and GID 10001 still have no host account + mapping. A newly observed mapping is a stop condition requiring owner review. +3. Project A may stop the old containers only under its separate mutation authorization. The old + source, data, containers, and images remain intact while the new private stack is tested. +4. Project B changes the production integration and proves the complete path: + `Aritmolab -> sidebar -> Nginx/load balancer -> ThothII`, including frontend assets, API, SSE, + authentication, and a completed workflow. +5. Only after the Project B automated report, human report, and owner decision are PASS may the + exact old ThothII resources be deleted. + +If Project A fails, stop the new private stack and restart the still-present old containers. If +Project B fails, close the new ingress and restore the prior route while the old resources still +exist. After Project B PASS and legacy deletion there is deliberately no promise to restore old +ThothII sessions or state. + +## Host ownership model + +No command may call `useradd`, `groupadd`, `usermod`, or modify the host identity databases. + +- `/srv/thothii/source` and `/srv/thothii/operator`: `1013:1006`. +- `/srv/thothii/data`, `/srv/thothii/pi-state`, and `/srv/thothii/workspace-registry`: + numeric `10001:10001`. +- protected files mounted read-only by the core: operator-owned with a narrowly selected numeric + group/owner mode that permits UID/GID 10001 to read only the required file. +- `/srv/thothii-backups`: operator/root protected and outside the runtime write boundary. + +Numeric ownership does not create or preserve a host user. It is confined to the new installation +tree and exists solely to match the non-root identity already embedded in the container image. + +## Acceptance + +The design is satisfied only when evidence proves all of the following: + +- no host account or group was created for 10001; +- the new core runs non-root and can write only its intended runtime trees; +- the shared networks and external Evidence tree remain unchanged; +- Project A and Project B pass their independent automated and human gates; +- Aritmolab no longer resolves production traffic to `thothii-core` or `thothii-frontend` legacy + containers before those containers are removed; +- the cleanup inventory contains only exact legacy ThothII targets and excludes every shared + resource listed above. diff --git a/scripts/test-server-operator-permissions.sh b/scripts/test-server-operator-permissions.sh index 42c3ca28..6d7a52dd 100755 --- a/scripts/test-server-operator-permissions.sh +++ b/scripts/test-server-operator-permissions.sh @@ -6,30 +6,25 @@ root="$(cd "$(dirname "$0")/.." && pwd -P)" image='golang:1.26.5-bookworm@sha256:1ecb7edf62a0408027bd5729dfd6b1b8766e578e8df93995b225dfd0944eb651' docker run --rm --volume "$root:/repository:ro" "$image" /bin/bash -ceu ' -groupadd --gid 10001 thothii -useradd --uid 10001 --gid 10001 --home-dir /srv/thothii --create-home --shell /usr/sbin/nologin thothii -# Reproduce the conservative home mode permitted by the documented useradd sequence. -chmod 0700 /srv/thothii groupadd --gid 20001 operator-primary -groupadd --gid 20002 thothii-ops groupadd --gid 20003 docker -useradd --uid 20001 --gid 20001 --groups 20002,20003 --create-home --shell /bin/bash operator +useradd --uid 20001 --gid 20001 --groups 20003 --create-home --shell /bin/bash operator -install -d -o 10001 -g 20002 -m 2750 /srv/thothii -install -d -o 10001 -g 20002 -m 2750 /srv/thothii/source -install -d -o 10001 -g 20002 -m 2770 /srv/thothii/operator -install -d -o 10001 -g 20002 -m 2750 /srv/thothii/secrets +install -d -o 20001 -g 10001 -m 0750 /srv/thothii +install -d -o 20001 -g 20001 -m 0750 /srv/thothii/source +install -d -o 20001 -g 20001 -m 0750 /srv/thothii/operator +install -d -o 10001 -g 20001 -m 0750 /srv/thothii/secrets install -d -o 10001 -g 10001 -m 0750 /srv/thothii/data /srv/thothii/pi-state /srv/thothii/workspace-registry install -d -o 10001 -g 10001 -m 0700 /srv/thothii/data/workspace-secrets -install -d -o 10001 -g 20002 -m 2750 /srv/thothii/source/ThothII /srv/thothii/source/ThothII/scripts -install -o 10001 -g 20002 -m 0750 /repository/scripts/build-tht.sh /srv/thothii/source/ThothII/scripts/build-tht.sh -install -o 10001 -g 20002 -m 0750 /repository/scripts/prepare-server-pi-state.sh /srv/thothii/source/ThothII/scripts/prepare-server-pi-state.sh +install -d -o 20001 -g 20001 -m 0750 /srv/thothii/source/ThothII /srv/thothii/source/ThothII/scripts +install -o 20001 -g 20001 -m 0750 /repository/scripts/build-tht.sh /srv/thothii/source/ThothII/scripts/build-tht.sh +install -o 20001 -g 20001 -m 0750 /repository/scripts/prepare-server-pi-state.sh /srv/thothii/source/ThothII/scripts/prepare-server-pi-state.sh /srv/thothii/source/ThothII/scripts/prepare-server-pi-state.sh /srv/thothii/pi-state 10001 10001 printf "%s\n" "PLACEHOLDER=replace-me" > /srv/thothii/operator/server.env printf "%s\n" "projectDirectory: replace-me" > /srv/thothii/operator/thothii-installation.yaml -chown 10001:20002 /srv/thothii/operator/server.env /srv/thothii/operator/thothii-installation.yaml -chmod 0660 /srv/thothii/operator/server.env /srv/thothii/operator/thothii-installation.yaml +chown 20001:20001 /srv/thothii/operator/server.env /srv/thothii/operator/thothii-installation.yaml +chmod 0600 /srv/thothii/operator/server.env /srv/thothii/operator/thothii-installation.yaml printf "%s\n" \ "#!/bin/bash" \ @@ -45,10 +40,10 @@ printf "%s\n" \ chmod 0755 /usr/local/bin/docker runuser --user operator -- /bin/bash -ceu '\'' -umask 0007 +umask 0077 sed -i "s/replace-me/ready/" /srv/thothii/operator/server.env sed -i "s#replace-me#/srv/thothii/source/ThothII#" /srv/thothii/operator/thothii-installation.yaml -for protected in /srv/thothii /srv/thothii/source /srv/thothii/secrets \ +for protected in /srv/thothii/secrets \ /srv/thothii/data /srv/thothii/pi-state /srv/thothii/workspace-registry; do if touch "$protected/operator-must-not-write" 2>/dev/null; then exit 42; fi done @@ -66,18 +61,29 @@ rm -f "$root_output_error" --installation /srv/thothii/operator/thothii-installation.yaml start '\'' -test "$(stat -c %u:%g /srv/thothii)" = 10001:20002 -test "$(stat -c %a /srv/thothii)" = 2750 +test "$(stat -c %u:%g /srv/thothii)" = 20001:10001 +test "$(stat -c %a /srv/thothii)" = 750 test "$(stat -c %u:%g /srv/thothii/pi-state/agent)" = 10001:10001 test "$(stat -c %a /srv/thothii/pi-state/agent)" = 700 for target in auth.json models.json settings.json; do test "$(stat -c %u:%g /srv/thothii/pi-state/agent/$target)" = 10001:10001 test "$(stat -c %a /srv/thothii/pi-state/agent/$target)" = 600 done -test "$(stat -c %u:%g /srv/thothii/operator/build-output/tht-linux-amd64)" = 20001:20002 +test "$(stat -c %u:%g /srv/thothii/operator/build-output/tht-linux-amd64)" = 20001:20001 +if getent passwd 10001 >/dev/null || getent group 10001 >/dev/null; then + printf "%s\n" "numeric runtime identity unexpectedly mapped on host fixture" >&2 + exit 47 +fi +test "$(stat -c %u:%g /srv/thothii)" = 20001:10001 +test "$(stat -c %a /srv/thothii)" = 750 +test "$(stat -c %u:%g /srv/thothii/source)" = 20001:20001 +test "$(stat -c %u:%g /srv/thothii/operator)" = 20001:20001 +test "$(stat -c %u:%g /srv/thothii/secrets)" = 10001:20001 +test "$(stat -c %a /srv/thothii/operator/server.env)" = 600 +test "$(stat -c %a /srv/thothii/operator/thothii-installation.yaml)" = 600 test "$(stat -c %a /srv/thothii/operator/build-output/tht-linux-amd64)" = 750 test -f /srv/thothii/operator/start.marker -for protected in /srv/thothii /srv/thothii/source /srv/thothii/secrets \ +for protected in /srv/thothii/secrets \ /srv/thothii/data /srv/thothii/pi-state /srv/thothii/workspace-registry; do test ! -e "$protected/operator-must-not-write" done diff --git a/scripts/test-verify-workspace-install-docs.sh b/scripts/test-verify-workspace-install-docs.sh index bba734c9..1a49ae26 100755 --- a/scripts/test-verify-workspace-install-docs.sh +++ b/scripts/test-verify-workspace-install-docs.sh @@ -70,12 +70,15 @@ grep -Fq 'scripts/prepare-server-pi-state.sh /srv/thothii/pi-state 10001 10001' echo "server guide does not initialize nested Pi-state targets before Compose" >&2 exit 1 } -grep -Eq '^sudo install -d -o 10001 -g thothii-ops -m 2750 /srv/thothii$' "$server_guide" || { +grep -Fq 'sudo install -d -o "$operator_uid" -g 10001 -m 0750 /srv/thothii' "$server_guide" || { echo "server operations guide does not set the parent traversal boundary" >&2 exit 1 } for required in \ - 'thothii-ops' \ + 'does not require or permit creation' \ + 'getent passwd 10001' \ + 'getent group 10001' \ + 'chmod 0600 /srv/thothii/operator/server.env' \ 'THT_BACKUP_ROOT=/srv/thothii-backups' \ 'sessions migrate --yes' \ '"pending":[]' \ @@ -90,6 +93,12 @@ for required in \ exit 1 } done +for forbidden in 'sudo useradd' 'sudo groupadd' 'sudo usermod' 'sudo -u thothii' 'thothii-ops'; do + if grep -Fq -- "$forbidden" "$server_guide"; then + echo "server operations guide creates or depends on a host identity: $forbidden" >&2 + exit 1 + fi +done grep -Fq '"$THT_BIN" --help' "$server_guide" || { echo "server guide lacks plain tht --help" >&2 exit 1 @@ -702,7 +711,10 @@ switch (mutation) { changed += "\nFor host-gateway, keep the external service listening on 127.0.0.1.\n"; break; case "server-parent-traversal": - changed = original.replace("sudo install -d -o 10001 -g thothii-ops -m 2750 /srv/thothii\n", ""); + changed = original.replace('sudo install -d -o "$operator_uid" -g 10001 -m 0750 /srv/thothii\n', ''); + break; + case "server-host-account": + changed += "\n```sh\nsudo useradd --system --uid 10001 thothii\n```\n"; break; case "server-raw-remove": changed += "\n```sh\ndocker rm thothii-core thothii-frontend\n```\n"; @@ -885,6 +897,10 @@ expect_guide_rejected \ "server parent traversal boundary" verify_server_guide \ "$root/docs/install/server.md" docs/install/server.md server-parent-traversal \ "server installation guide does not set parent traversal boundary" +expect_guide_rejected \ + "server host account creation" verify_server_guide \ + "$root/docs/install/server.md" docs/install/server.md server-host-account \ + "server installation guide creates or depends on a host identity" expect_guide_rejected \ "server raw container removal" verify_server_guide \ "$root/docs/install/server.md" docs/install/server.md server-raw-remove \ diff --git a/scripts/verify-workspace-install-docs.sh b/scripts/verify-workspace-install-docs.sh index 0e33f703..7bc7f13d 100755 --- a/scripts/verify-workspace-install-docs.sh +++ b/scripts/verify-workspace-install-docs.sh @@ -1233,9 +1233,11 @@ verify_server_guide() { "frontend" \ "core" \ "UID/GID 10001" \ - "thothii-ops" \ - "-m 2770 /srv/thothii/operator" \ - "chmod 0660 /srv/thothii/operator/server.env" \ + "does not require or permit creation" \ + "getent passwd 10001" \ + "getent group 10001" \ + "-m 0750 /srv/thothii/operator" \ + "chmod 0600 /srv/thothii/operator/server.env" \ "THT_THT_OUTPUT_DIRECTORY=/srv/thothii/operator/build-output" \ "/srv/thothii" \ "example operator root" \ @@ -1268,10 +1270,14 @@ verify_server_guide() { "docker compose down --volumes" \ "reverse-proxy-nginx.md" \ "reverse-proxy-caddy.md" - if ! grep -Eq '^sudo install -d -o 10001 -g thothii-ops -m 2750 /srv/thothii$' "$guide"; then + if ! grep -Fq 'sudo install -d -o "$operator_uid" -g 10001 -m 0750 /srv/thothii' "$guide"; then echo "server installation guide does not set parent traversal boundary" >&2 return 1 fi + if grep -Eq '(^|[[:space:]])(sudo[[:space:]]+)?(useradd|groupadd|usermod)([[:space:]]|$)|sudo[[:space:]]+-u[[:space:]]+thothii|thothii-ops' "$guide"; then + echo "server installation guide creates or depends on a host identity" >&2 + return 1 + fi node - "$guide" <<'NODE' const fs = require("fs"); const source = fs.readFileSync(process.argv[2], "utf8");