diff --git a/docs/payload-migration/architecture-proposal.md b/docs/le-app-database-migration/architecture-proposal.md similarity index 94% rename from docs/payload-migration/architecture-proposal.md rename to docs/le-app-database-migration/architecture-proposal.md index 7b7f548..e48cf76 100644 --- a/docs/payload-migration/architecture-proposal.md +++ b/docs/le-app-database-migration/architecture-proposal.md @@ -1,4 +1,4 @@ -# Payload migration architecture proposal +# Laser Everything database transition architecture Status: pre-implementation decision record. No production changes are authorized by this document. @@ -31,7 +31,7 @@ The migration branch initially started from `main` at `0a7ee5f`. A tree and hist - Those commits repair production registration/authentication behavior and add the verified Directus deployment contract, policies, flows, exports, and schema snapshot. - The application was not replaced or reduced in those commits. -Conclusion: `main` is a complete application tree, but `4884b5d` is the correct and more complete pre-utilities production baseline. The dedicated `migration/payload` branch has therefore been fast-forwarded to `4884b5d`. The temporary utilities branch is not an ancestor of the migration work. +Conclusion: `main` is a complete application tree, but `4884b5d` is the correct and more complete pre-utilities production baseline. The dedicated `migration/le-app-database` branch has therefore been fast-forwarded to `4884b5d`. The temporary utilities branch is not an ancestor of the migration work. ## Service boundary @@ -141,7 +141,7 @@ CI builds the frontend, Payload, BGBye, and proxy images once from reviewed comm The permanent production checkout and deployment root is `/var/www/lasereverything.net.db`. It contains the complete versioned stack: frontend, Payload, BGBye, Compose definitions, migrations, deployment scripts, internal gateway contract, health checks, and validation tooling. No production checkout or application deployment may run from `/srv`, `/opt`, a Codex workspace, a temporary worktree, or another project root. Operational Docker layers, declared database/media volumes, and server-wide proxy files may live elsewhere, but the deployment rooted at the canonical production checkout declares and controls their contracts. -The sole persistent nonproduction project root is `/srv/le-payload-dev`, with the checkout at `/srv/le-payload-dev/lasereverything.net.db`. The dedicated account design does not retain the current nested `.worktrees/payload-migration` layout. Temporary worktrees may be created under the canonical nonproduction root for a specific documented task and must be removed when that task ends. +The sole persistent nonproduction project root is `/srv/le-app-codex`, with the checkout at `/srv/le-app-codex/lasereverything.net.db`. The dedicated account design does not retain the current nested `.worktrees/payload-migration` layout. Temporary worktrees may be created under the canonical nonproduction root for a specific documented task and must be removed when that task ends. Production database schema changes use committed Payload migrations as an explicit release job before application rollout. Imports are migration-only jobs and are not part of ordinary application startup. Promotion requires recorded image digests, migration status, validation results, rollback compatibility, and health checks. diff --git a/docs/payload-migration/environment-parity.md b/docs/le-app-database-migration/environment-parity.md similarity index 96% rename from docs/payload-migration/environment-parity.md rename to docs/le-app-database-migration/environment-parity.md index f049f18..68ad642 100644 --- a/docs/payload-migration/environment-parity.md +++ b/docs/le-app-database-migration/environment-parity.md @@ -140,7 +140,7 @@ Local HTTPS is required to test secure cookies. A repository-controlled developm ## Rootless Codex-controlled runtime -Required target: a dedicated rootless Docker daemon owned by the separate `le_payload_dev` account. Future Codex migration sessions must execute as that account (preferred) or through a narrowly scoped account transition; the daemon socket is not shared broadly. Rootless Podman is an acceptable fallback only after Compose/profile behavior is proven; Docker is preferred because the repository and production already use Compose. +Required target: a dedicated rootless Docker daemon owned by the separate `le_app_codex` account. Future Codex migration sessions must execute as that account (preferred) or through a narrowly scoped account transition; the daemon socket is not shared broadly. Rootless Podman is an acceptable fallback only after Compose/profile behavior is proven; Docker is preferred because the repository and production already use Compose. Codex must be able to build images, create/remove only its project containers/networks/volumes, inspect their logs, reset disposable PostgreSQL, run finite jobs, and execute integration/browser tests. It must not access `/var/run/docker.sock`, the production daemon/context, production containers/volumes/networks, `/var/www`, or `/srv/directus-archive`. @@ -154,9 +154,9 @@ Current host observation: Therefore a `sol6_vi`-owned daemon is rejected: it could bind-mount those readable production secrets. Milestone 0 requires the isolated account and host changes described in `host-runtime-plan.md`, and work must stop before performing them. An administrator must approve/provision: -1. dedicated `le_payload_dev` identity and restricted filesystem ACLs; +1. dedicated `le_app_codex` identity and restricted filesystem ACLs; 2. rootless prerequisites (`newuidmap`/`newgidmap`, subordinate UID/GID ranges, rootless networking and overlay helpers as required by the OS); -3. rootless Docker daemon installation for `le_payload_dev` using its dedicated data root and named context; +3. rootless Docker daemon installation for `le_app_codex` using its dedicated data root and named context; 4. optional user-service lingering if stacks must survive logout; 5. deterministic CPU BGBye first; optional GPU access only after core acceptance; 6. user/browser-profile-scoped development CA trust; @@ -231,7 +231,7 @@ Build verification inspects image histories and filesystems for forbidden names │ ├── contracts/ │ ├── e2e/ │ └── fixtures/files/ -├── docs/payload-migration/ +├── docs/le-app-database-migration/ ├── directus/ # retained legacy evidence during migration ├── .nvmrc / .node-version └── Dockerfiles remain beside each build context @@ -239,7 +239,7 @@ Build verification inspects image histories and filesystems for forbidden names The current `app`, `bgbye`, and `dashboard` directories will be moved only in a dedicated mechanical commit after build-context and path references are covered by tests. Milestone 0 may initially implement the target logical boundaries without a risky all-at-once directory move. -The repository tree above exists at exactly one persistent nonproduction checkout, `/srv/le-payload-dev/lasereverything.net.db`, and at the canonical production root, `/var/www/lasereverything.net.db`. Runtime data is never placed inside either Git checkout unless it is a deliberately versioned non-secret fixture. The complete canonical host directory policy and transitional consolidation are defined in `host-runtime-plan.md`. +The repository tree above exists at exactly one persistent nonproduction checkout, `/srv/le-app-codex/lasereverything.net.db`, and at the canonical production root, `/var/www/lasereverything.net.db`. Runtime data is never placed inside either Git checkout unless it is a deliberately versioned non-secret fixture. The complete canonical host directory policy and transitional consolidation are defined in `host-runtime-plan.md`. ## End-to-end test matrix diff --git a/docs/payload-migration/host-runtime-plan.md b/docs/le-app-database-migration/host-runtime-plan.md similarity index 64% rename from docs/payload-migration/host-runtime-plan.md rename to docs/le-app-database-migration/host-runtime-plan.md index 875aa1a..2482f48 100644 --- a/docs/payload-migration/host-runtime-plan.md +++ b/docs/le-app-database-migration/host-runtime-plan.md @@ -26,57 +26,90 @@ Conclusion: rootless Docker owned by `sol6_vi` would protect the production daem | Model | Advantages | Risks / cost | Decision | |---|---|---|---| | `sol6_vi` owns rootless Docker | Minimal provisioning; current workspace already writable | Can bind-mount world-readable production secrets and application files; mixes general Codex work with container authority | Rejected on this host | -| dedicated `le_payload_dev` | Daemon owner has an explicit filesystem allow-list; project storage and cleanup are attributable; production paths can be proven inaccessible | Requires account, ACL, subordinate IDs, separate Git credentials/session, storage, and user service | **Recommended** | +| dedicated `le_app_codex` | Daemon owner has an explicit filesystem allow-list; project storage and cleanup are attributable; production paths can be proven inaccessible | Requires account, ACL, subordinate IDs, separate Git credentials/session, storage, and user service | **Recommended** | -Future autonomous migration work should run in a Codex session whose OS identity is `le_payload_dev`. Do not grant `sol6_vi` generic access to the development daemon socket as a convenience; that weakens accountability. If orchestration must be initiated from another account, use a narrowly audited command wrapper or SSH into `le_payload_dev`, not a shared Docker group and not TCP exposure of the daemon. +Future autonomous migration work should run in a Codex session whose OS identity is `le_app_codex`. Do not grant `sol6_vi` generic access to the development daemon socket as a convenience; that weakens accountability. If orchestration must be initiated from another account, use a narrowly audited command wrapper or SSH into `le_app_codex`, not a shared Docker group and not TCP exposure of the daemon. ## Exact proposed identity and filesystem model Proposed fixed identity: -- user and primary group: `le_payload_dev`; +- user and primary group: `le_app_codex`; - UID/GID: `1200` (verified unused at audit time; recheck immediately before creation); -- shell/home: `/bin/bash`, `/home/le_payload_dev`; +- shell/home: `/bin/bash`, `/srv/le-app-codex/home`; - no supplementary production groups, no `docker` group, no sudo/wheel membership; - subordinate UID and GID range: `165536:65536`, non-overlapping with the observed `sol6_ii:100000:65536` allocation; -- dedicated project root: Btrfs subvolume `/srv/le-payload-dev`, owner `root:le_payload_dev`, mode `0750`; -- sole checkout: `/srv/le-payload-dev/lasereverything.net.db`; -- authoritative scrubbed input: `/srv/le-payload-dev/source`, root-owned and read-only to the development group; -- nonproduction credentials only: `/srv/le-payload-dev/secrets`; -- daemon data root: `/srv/le-payload-dev/runtime/docker`; +- dedicated project root: Btrfs subvolume `/srv/le-app-codex`, owner `root:le_app_codex`, mode `0750`; +- sole checkout: `/srv/le-app-codex/lasereverything.net.db`; +- authoritative scrubbed input: `/srv/le-app-codex/database-source-readonly`, root-owned and read-only to the development group; +- nonproduction credentials only: `/srv/le-app-codex/nonproduction-secrets`; +- daemon data root: `/srv/le-app-codex/container-runtime/docker`; - runtime socket: `/run/user/1200/docker.sock`; -- Docker client/config: `/home/le_payload_dev/.docker` and `/home/le_payload_dev/.config/docker`; -- Compose project prefix: `le_payload_dev`, enforced by wrapper preflight. +- Docker client/config: `/srv/le-app-codex/home/.docker` and `/srv/le-app-codex/home/.config/docker`; +- rootless Docker context: `le-app-codex`; +- default nonproduction Compose project: `le-app-testing`, enforced by wrapper preflight. + +Verified candidate availability at this planning checkpoint: + +- neither account nor group name `le_app_codex` exists; +- UID `1200` is unused in `/etc/passwd` and GID `1200` is unused in `/etc/group`; +- no passwd primary UID/GID or group GID collision exists at `1200`; +- `/etc/subuid` and `/etc/subgid` contain one observed allocation, `100000–165535`, owned by the historical `sol6_ii` entry; +- candidate range `165536–231071` does not overlap any entry in either subordinate-ID file; +- `/srv/le-app-codex` does not exist. + +Therefore the current exact recommendation is UID/GID `1200` and subordinate range `165536:65536`, subject to rerunning the documented collision checks immediately before provisioning. If any check produces output or a non-free result, stop and select new IDs; do not force reuse. + +## Canonical naming replacements + +| Superseded proposal | Canonical replacement | +|---|---| +| account/group `le_payload_dev` | `le_app_codex` | +| nonproduction root `/srv/le-payload-dev` | `/srv/le-app-codex` | +| checkout `/srv/le-payload-dev/lasereverything.net.db` | `/srv/le-app-codex/lasereverything.net.db` | +| source directory `source` | `database-source-readonly` | +| secrets directory `secrets` | `nonproduction-secrets` | +| runtime directory `runtime` | `container-runtime` | +| disposable-data directory `data` | `testing-data` | +| artifact directory `artifacts` | `build-artifacts` | +| Docker context `le-payload-dev` | `le-app-codex` | +| Compose project `le_payload_dev` | `le-app-testing` | +| branch `migration/payload` | `migration/le-app-database` | +| documentation `docs/payload-migration` | `docs/le-app-database-migration` | + +`payload` remains valid only for the actual Payload CMS service, application directory, packages, configuration, and Payload-specific migrations. Historical inventory names such as `le-payload-pg` and `.worktrees/payload-migration` remain unchanged because they identify real transitional resources. Canonical nonproduction tree: ```text -/srv/le-payload-dev/ -├── lasereverything.net.db/ # sole persistent nonproduction Git checkout -├── source/ # root-owned scrubbed authoritative input -├── secrets/ # nonproduction credentials only -├── runtime/ # rootless Docker data/configuration support -├── data/ # disposable PostgreSQL, media, clones, caches, temp -└── artifacts/ # migration reports, logs, browser evidence +/srv/le-app-codex/ +├── home/ # dedicated account home and user configuration +├── lasereverything.net.db/ # sole persistent nonproduction Git checkout +├── database-source-readonly/ # root-owned scrubbed authoritative input +├── nonproduction-secrets/ # testing/development credentials only +├── container-runtime/ # rootless Docker images, layers and configuration +├── testing-data/ # disposable databases, media, models and caches +└── build-artifacts/ # reports, logs and browser/test evidence ``` Ownership and permissions: | Path | Owner:group | Mode/policy | |---|---|---| -| `/srv/le-payload-dev` | `root:le_payload_dev` | `0750`; prevents unrelated users and prevents project-root restructuring by the development account | -| `lasereverything.net.db` | `le_payload_dev:le_payload_dev` | directories `0750`, normal Git files subject to repository modes; sole checkout | -| `source` | `root:le_payload_dev` | directories `0550`, files `0440`; account and daemon may read but cannot alter; no writable symlink target | -| `secrets` | `root:le_payload_dev` | directory `0750`, provisioned files `0640`; nonproduction values only; optional `generated/` is `0700 le_payload_dev` for ephemeral fixture secrets | -| `runtime` | `le_payload_dev:le_payload_dev` | `0700`; rootless Docker layers/configuration only | -| `data` | `le_payload_dev:le_payload_dev` | `0700`; child ownership may use rootless subordinate IDs | -| `artifacts` | `le_payload_dev:le_payload_dev` | `0700`; TTL cleanup; snapshot artifacts treated as sensitive | +| `/srv/le-app-codex` | `root:le_app_codex` | `0750`; prevents unrelated users and prevents project-root restructuring by the development account | +| `home` | `le_app_codex:le_app_codex` | `0700`; account, Docker client, user systemd, NSS/browser configuration | +| `lasereverything.net.db` | `le_app_codex:le_app_codex` | directories `0750`, normal Git files subject to repository modes; sole checkout | +| `database-source-readonly` | `root:le_app_codex` | directories `0550`, files `0440`; account and daemon may read but cannot alter; no writable symlink target | +| `nonproduction-secrets` | `root:le_app_codex` | directory `0750`, provisioned files `0640`; nonproduction values only; optional `generated/` is `0700 le_app_codex` for ephemeral fixture secrets | +| `container-runtime` | `le_app_codex:le_app_codex` | `0700`; rootless Docker images, layers and configuration only | +| `testing-data` | `le_app_codex:le_app_codex` | `0700`; child ownership may use rootless subordinate IDs | +| `build-artifacts` | `le_app_codex:le_app_codex` | `0700`; TTL cleanup; snapshot artifacts treated as sensitive | Only the dedicated account receives: - read/write access to its workspace and runtime/storage subtree; -- read/traverse ACL on the frozen scrubbed source only; -- read ACL on `/srv/codex-secrets/le-payload-migration.env` only; +- group read/traverse access to canonical root-owned `database-source-readonly`; +- group read access to specifically provisioned files under canonical `nonproduction-secrets`; - a separate nonproduction Git credential/deploy key with access limited to the application repository. It receives no ACL or group membership for `/var/www`, production database paths, production sockets/volumes, or `/srv/directus-archive`. @@ -86,10 +119,16 @@ It receives no ACL or group membership for `/var/www`, production database paths These commands are a reviewable proposal for an administrator on Garuda/Arch Linux. Recheck UID/GID and subordinate ranges first. Do not paste secrets into the shell history. ```bash -# 1. Recheck proposed IDs/ranges are unused. +# 1. Recheck names, IDs, and ranges across all four account databases. +getent passwd le_app_codex +getent group le_app_codex getent passwd 1200 getent group 1200 -grep -E '^(le_payload_dev|[^:]+:165536:65536)$' /etc/subuid /etc/subgid +awk -F: '$3 == 1200 || $4 == 1200 { print; found=1 } END { exit found ? 1 : 0 }' /etc/passwd +awk -F: '$3 == 1200 { print; found=1 } END { exit found ? 1 : 0 }' /etc/group +awk -F: 'BEGIN { a=165536; b=231071 } + { x=$2; y=$2+$3-1; if (a<=y && x<=b) { print FILENAME ":" $0; found=1 } } + END { exit found ? 1 : 0 }' /etc/subuid /etc/subgid # 2. Install rootless prerequisites. docker-rootless-extras is an Arch AUR # package and must be built/reviewed under the administrator's normal AUR policy. @@ -97,45 +136,49 @@ sudo pacman -S --needed rootlesskit slirp4netns fuse-overlayfs shadow # Then install the reviewed docker-rootless-extras AUR package by the host's # approved AUR procedure; do not curl a remote install script into a shell. -# 3. Create the isolated identity. -sudo groupadd --gid 1200 le_payload_dev -sudo useradd --uid 1200 --gid le_payload_dev --create-home \ - --home-dir /home/le_payload_dev --shell /bin/bash le_payload_dev -sudo usermod --add-subuids 165536-231071 --add-subgids 165536-231071 le_payload_dev +# 3. Create the isolated primary group and project subvolume. +sudo groupadd --gid 1200 le_app_codex +sudo btrfs subvolume create /srv/le-app-codex +sudo chown root:le_app_codex /srv/le-app-codex +sudo chmod 0750 /srv/le-app-codex -# 4. Create a quota-capable project subvolume and ownership boundary. -sudo btrfs subvolume create /srv/le-payload-dev -sudo chown 1200:1200 /srv/le-payload-dev -sudo chmod 0700 /srv/le-payload-dev -sudo -u le_payload_dev mkdir -p \ - /srv/le-payload-dev/lasereverything.net.db \ - /srv/le-payload-dev/source \ - /srv/le-payload-dev/secrets/generated \ - /srv/le-payload-dev/runtime/docker \ - /srv/le-payload-dev/data/{postgres,media,legacy-uploads,bgbye-models,tmp} \ - /srv/le-payload-dev/artifacts/{browser,migration-reports} +# 4. Create the account with its canonical home inside the project root. +sudo useradd --uid 1200 --gid le_app_codex --create-home \ + --home-dir /srv/le-app-codex/home --shell /bin/bash le_app_codex +sudo usermod --add-subuids 165536-231071 --add-subgids 165536-231071 le_app_codex + +# 5. Create the canonical children; no sibling workspace is created. +sudo mkdir -p \ + /srv/le-app-codex/lasereverything.net.db \ + /srv/le-app-codex/database-source-readonly \ + /srv/le-app-codex/nonproduction-secrets/generated \ + /srv/le-app-codex/container-runtime/docker \ + /srv/le-app-codex/testing-data/{postgres,media,legacy-uploads,bgbye-models,tmp} \ + /srv/le-app-codex/build-artifacts/{browser,migration-reports} # Root owns the boundary and authoritative source; development owns mutable areas. -sudo chown root:le_payload_dev /srv/le-payload-dev -sudo chmod 0750 /srv/le-payload-dev -sudo chown -R le_payload_dev:le_payload_dev \ - /srv/le-payload-dev/lasereverything.net.db \ - /srv/le-payload-dev/runtime /srv/le-payload-dev/data /srv/le-payload-dev/artifacts \ - /srv/le-payload-dev/secrets/generated -sudo chmod 0700 /srv/le-payload-dev/runtime /srv/le-payload-dev/data \ - /srv/le-payload-dev/artifacts /srv/le-payload-dev/secrets/generated -sudo chown root:le_payload_dev /srv/le-payload-dev/source /srv/le-payload-dev/secrets -sudo chmod 0550 /srv/le-payload-dev/source -sudo chmod 0750 /srv/le-payload-dev/secrets +sudo chown root:le_app_codex /srv/le-app-codex +sudo chmod 0750 /srv/le-app-codex +sudo chown le_app_codex:le_app_codex /srv/le-app-codex/home +sudo chmod 0700 /srv/le-app-codex/home +sudo chown -R le_app_codex:le_app_codex \ + /srv/le-app-codex/lasereverything.net.db \ + /srv/le-app-codex/container-runtime /srv/le-app-codex/testing-data /srv/le-app-codex/build-artifacts \ + /srv/le-app-codex/nonproduction-secrets/generated +sudo chmod 0700 /srv/le-app-codex/container-runtime /srv/le-app-codex/testing-data \ + /srv/le-app-codex/build-artifacts /srv/le-app-codex/nonproduction-secrets/generated +sudo chown root:le_app_codex /srv/le-app-codex/database-source-readonly /srv/le-app-codex/nonproduction-secrets +sudo chmod 0550 /srv/le-app-codex/database-source-readonly +sudo chmod 0750 /srv/le-app-codex/nonproduction-secrets -# 5. Apply a 250 GiB ceiling to the project subvolume. +# 6. Apply a 250 GiB ceiling to the project subvolume. sudo btrfs quota enable /srv -sudo btrfs qgroup limit 250G /srv/le-payload-dev +sudo btrfs qgroup limit 250G /srv/le-app-codex -# 6. Transitional source access is used only by the administrator-run copy and +# 7. Transitional source access is used only by the administrator-run copy and # verification procedure below. The final account needs no ACL on /srv/codex-*. -# 7. Remediate production secret readability separately. +# 8. Remediate production secret readability separately. # Root-owned Compose can still read mode 0600 files. Confirm the production # operator before applying because this changes production file permissions. sudo chown root:root /var/www/lasereverything.net.db/app/.env.local @@ -143,21 +186,21 @@ sudo chmod 0600 /var/www/lasereverything.net.db/app/.env.local sudo chown root:root /var/www/lasereverything.net.db/bgbye/.env sudo chmod 0600 /var/www/lasereverything.net.db/bgbye/.env -# 8. As le_payload_dev, configure the rootless daemon with an explicit data root. -sudo -iu le_payload_dev mkdir -p ~/.config/docker +# 9. As le_app_codex, configure the rootless daemon with an explicit data root. +sudo -iu le_app_codex mkdir -p ~/.config/docker # Administrator writes ~/.config/docker/daemon.json as documented below, # then the dedicated user enables the packaged rootless user service. -sudo -iu le_payload_dev systemctl --user enable --now docker.socket +sudo -iu le_app_codex systemctl --user enable --now docker.socket -# 9. Optional only if reviewed stacks must survive logout. -sudo loginctl enable-linger le_payload_dev +# 10. Optional only if reviewed stacks must survive logout. +sudo loginctl enable-linger le_app_codex ``` -Proposed `/home/le_payload_dev/.config/docker/daemon.json`: +Proposed `/srv/le-app-codex/home/.config/docker/daemon.json`: ```json { - "data-root": "/srv/le-payload-dev/runtime/docker", + "data-root": "/srv/le-app-codex/container-runtime/docker", "live-restore": false, "log-driver": "local", "log-opts": { "max-size": "20m", "max-file": "5" } @@ -166,9 +209,40 @@ Proposed `/home/le_payload_dev/.config/docker/daemon.json`: The exact Arch rootless unit name (`docker.service` versus `docker.socket`) must be verified from the reviewed package before enabling. Cgroup v2 is present; after provisioning, verify the rootless/security options and delegated controllers before relying on CPU, memory, or PID limits. +The nonstandard home is compatible with rootless Docker and user-level systemd because it is the account home recorded in `/etc/passwd`, resides on local Btrfs, and is owned by the account. Required environment contract: + +- `HOME=/srv/le-app-codex/home` comes from the login/account record; +- `XDG_RUNTIME_DIR=/run/user/1200` is created by logind/systemd and must not be redirected into `/srv` or `/tmp`; +- `XDG_DATA_HOME=/srv/le-app-codex/home/.local/share`; +- Docker daemon data root is `/srv/le-app-codex/container-runtime/docker`; +- Docker client configuration is `/srv/le-app-codex/home/.docker`; +- user systemd configuration is `/srv/le-app-codex/home/.config/systemd/user`; +- the `le-app-codex` context points only to `unix:///run/user/1200/docker.sock`; +- wrappers export `COMPOSE_PROJECT_NAME=le-app-testing`. + +Proposed `/srv/le-app-codex/home/.config/environment.d/10-le-app-codex.conf`: + +```text +HOME=/srv/le-app-codex/home +XDG_DATA_HOME=/srv/le-app-codex/home/.local/share +DOCKER_CONFIG=/srv/le-app-codex/home/.docker +COMPOSE_PROJECT_NAME=le-app-testing +``` + +`XDG_RUNTIME_DIR` is deliberately absent from this file; logind must create `/run/user/1200` with the correct lifetime and ownership. `DOCKER_HOST` is also omitted so the named context remains the explicit authority and scripts can reject context drift. + +After the user service is installed, create the context as the dedicated account: + +```bash +sudo -iu le_app_codex docker context create le-app-codex \ + --description 'Laser Everything Codex nonproduction rootless runtime' \ + --docker host=unix:///run/user/1200/docker.sock +sudo -iu le_app_codex docker context use le-app-codex +``` + ## Required positive and negative verification -Run as `le_payload_dev`, printing only permissions/status: +Run as `le_app_codex`, printing only permissions/status: ```bash test ! -r /var/www/lasereverything.net.db/app/.env.local @@ -176,10 +250,10 @@ test ! -x /var/lib/docker test ! -x /var/lib/mysql test ! -r /var/run/docker.sock test ! -x /srv/directus-archive -test -r /srv/le-payload-dev/source/README.txt -test ! -w /srv/le-payload-dev/source/README.txt -test -r /srv/le-payload-dev/secrets/migration.env -test ! -w /srv/le-payload-dev/secrets/migration.env +test -r /srv/le-app-codex/database-source-readonly/README.txt +test ! -w /srv/le-app-codex/database-source-readonly/README.txt +test -r /srv/le-app-codex/nonproduction-secrets/migration.env +test ! -w /srv/le-app-codex/nonproduction-secrets/migration.env DOCKER_HOST=unix:///run/user/1200/docker.sock docker info --format '{{json .SecurityOptions}}' ``` @@ -192,12 +266,13 @@ Every stack/job wrapper will fail before Compose evaluation if: - effective UID is not the approved dedicated UID; - Docker endpoint is not exactly the approved rootless Unix socket; - Compose project name lacks the fixed nonproduction prefix; +- active Docker context is not exactly `le-app-codex`; - any resolved bind source is `/var/www`, `/var/lib/docker`, `/var/lib/mysql`, `/srv/directus-archive`, `/var/run`, `/run/docker.sock`, or a descendant; -- the Git checkout is not exactly `/srv/le-payload-dev/lasereverything.net.db` in nonproduction or `/var/www/lasereverything.net.db` in production; +- the Git checkout is not exactly `/srv/le-app-codex/lasereverything.net.db` in nonproduction or `/var/www/lasereverything.net.db` in production; - any non-finite application service receives `.migration.env`, `.migration-source`, a MariaDB source URL, or migration credentials; - destination database host/port is not the approved nonproduction target/service; - production-named secret scopes, SMTP hosts, OAuth variables, Ko-fi secrets, or webhook credentials appear in fixture/snapshot/rehearsal rendered Compose; -- reset/purge targets a bind mount, external volume, unknown project label, or any path outside `/srv/le-payload-dev`. +- reset/purge targets a bind mount, external volume, unknown project label, or any path outside `/srv/le-app-codex`. Tests render each Compose mode and inspect mounts, secrets, environment variable **names/classifications**, networks, contexts, labels, and volume ownership without printing values. @@ -206,12 +281,12 @@ Tests render each Compose mode and inspect mounts, secrets, environment variable - The authoritative MariaDB remains read-only at the database privilege and filesystem layers. - `.migration-source` and `.migration.env` are mounted read-only only into finite `inventory`, `import`, and `validation` jobs. - Frontend, Payload, BGBye, proxy, Mailpit, fixture seed, package installation, build, lint, unit, integration, and ordinary browser-test services never receive those mounts or variables. -- Legacy Directus restores the scrubbed SQL into a separate project-scoped writable MariaDB volume and copies uploads into `/srv/le-payload-dev/data/legacy-uploads` or a project volume. +- Legacy Directus restores the scrubbed SQL into a separate project-scoped writable MariaDB volume and copies uploads into `/srv/le-app-codex/testing-data/legacy-uploads` or a project volume. - Legacy Directus never mounts the source database storage or uploads, even read-only; the finite clone job is the only bridge. - Reset/purge operates only on labeled nonproduction destinations. It does not follow source symlinks and refuses all external/bind volumes. - `/srv/directus-archive` remains inaccessible and is never mentioned as a mount source in executable Compose configuration. -The complete root-only archive remains permanently outside the development boundary at `/srv/directus-archive/20260710-175408`. It is never moved, mounted, linked, or copied into `/srv/le-payload-dev`. +The complete root-only archive remains permanently outside the development boundary at `/srv/directus-archive/20260710-175408`. It is never moved, mounted, linked, or copied into `/srv/le-app-codex`. ## Nonproduction storage plan @@ -219,19 +294,19 @@ The host currently has approximately 650 GiB available on the Btrfs filesystem c | Path | Planning allowance | Ownership | Cleanup / backup | |---|---:|---|---| -| `runtime/docker` | 80 GiB soft budget | `le_payload_dev` | prune only project/unused development images after digest recording; no backup | -| `data/postgres` | 25 GiB | `le_payload_dev` / rootless mapped IDs | disposable; reset by labeled volume; no backup | -| `data/media` | 15 GiB | fixed container UID mapped by rootless daemon | fixture disposable; snapshot copied data disposable; no authoritative backup role | -| `data/legacy-uploads` | 10 GiB | rootless mapped service UID | copied from scrubbed source; disposable; never source of truth | -| `data/bgbye-models` | 35 GiB | BGBye service/rootless mapping | checksummed cache; LRU/manual cleanup; redownloadable | -| `data/tmp` | 10 GiB | service-specific mapped UID | tmpfs or age-based cleanup; no backup | -| `artifacts/browser` | 5 GiB | `le_payload_dev` | TTL 7 days; snapshot artifacts treated as sensitive and never committed | -| `artifacts/migration-reports` | 10 GiB | `le_payload_dev` | detailed reports TTL 30 days; committed summaries structural only | -| workspace/metadata headroom | 10 GiB | `le_payload_dev` | Git remote is source backup; no personal exports committed | +| `container-runtime/docker` | 80 GiB soft budget | `le_app_codex` | prune only project/unused development images after digest recording; no backup | +| `testing-data/postgres` | 25 GiB | `le_app_codex` / rootless mapped IDs | disposable; reset by labeled volume; no backup | +| `testing-data/media` | 15 GiB | fixed container UID mapped by rootless daemon | fixture disposable; snapshot copied data disposable; no authoritative backup role | +| `testing-data/legacy-uploads` | 10 GiB | rootless mapped service UID | copied from scrubbed source; disposable; never source of truth | +| `testing-data/bgbye-models` | 35 GiB | BGBye service/rootless mapping | checksummed cache; LRU/manual cleanup; redownloadable | +| `testing-data/tmp` | 10 GiB | service-specific mapped UID | tmpfs or age-based cleanup; no backup | +| `build-artifacts/browser` | 5 GiB | `le_app_codex` | TTL 7 days; snapshot artifacts treated as sensitive and never committed | +| `build-artifacts/migration-reports` | 10 GiB | `le_app_codex` | detailed reports TTL 30 days; committed summaries structural only | +| checkout/home metadata | 10 GiB | `le_app_codex` | Git remote is source backup; no personal exports committed | The listed soft budgets total 200 GiB. The 250 GiB hard qgroup ceiling reserves 50 GiB emergency headroom. Startup refuses when host free space is below 100 GiB, project usage exceeds 200 GiB, or required free space for a rehearsal cannot be reserved. The production filesystem is not used for spillover. -## Resources intentionally outside `/srv/le-payload-dev` +## Resources intentionally outside `/srv/le-app-codex` | Resource | Permanent location | Why it remains outside | |---|---|---| @@ -264,7 +339,7 @@ Metadata-only audit results at the planning checkpoint: | Resource | Current state | |---|---| | `/srv/codex-work/lasereverything.net.db` | Primary Git checkout, branch `temporary/utilities-only`, commit `305ea41`; owns the current local Git database | -| nested `.worktrees/payload-migration` | Migration worktree, branch `migration/payload`; planning commit begins at `ed1c84e` | +| nested `.worktrees/payload-migration` | Migration worktree, branch `migration/le-app-database`; planning commit begins at `ed1c84e` | | `.migration-source` | Symlink to `/srv/codex-migration/current` | | `.migration.env` | Symlink to `/srv/codex-secrets/le-payload-migration.env` | | `/srv/codex-migration/current` | Symlink to `source-20260710-175408` | @@ -297,7 +372,7 @@ No step deletes or mutates a transitional resource. Consolidation is copy/recrea ### Phase A — manifests and Git preservation -1. Push `migration/payload`, including planning commits, to `origin` and verify the remote commit. +1. Push `migration/le-app-database`, including planning commits, to `origin` and verify the remote commit. 2. Record structural manifests for every transitional path, symlink, owner/mode, aggregate size, Git branch/commit/remote, source checksum manifest, and the administrator container/volume inventory above. 3. Confirm the full root-only archive remains independently present and inaccessible; do not traverse or copy it. 4. Record the exact existing rootful container/volume names and leave them stopped/running exactly as found unless a later approved step says otherwise. @@ -307,18 +382,18 @@ No step deletes or mutates a transitional resource. Consolidation is copy/recrea After account/runtime approval, create one clean checkout as the dedicated user: ```bash -sudo -iu le_payload_dev git clone --branch migration/payload --single-branch \ +sudo -iu le_app_codex git clone --branch migration/le-app-database --single-branch \ ssh://FORGE/makearmy/le-app.git \ - /srv/le-payload-dev/lasereverything.net.db -sudo -iu le_payload_dev git -C /srv/le-payload-dev/lasereverything.net.db \ + /srv/le-app-codex/lasereverything.net.db +sudo -iu le_app_codex git -C /srv/le-app-codex/lasereverything.net.db \ fetch --prune origin -sudo -iu le_payload_dev git -C /srv/le-payload-dev/lasereverything.net.db \ +sudo -iu le_app_codex git -C /srv/le-app-codex/lasereverything.net.db \ status --short --branch -sudo -iu le_payload_dev git -C /srv/le-payload-dev/lasereverything.net.db \ +sudo -iu le_app_codex git -C /srv/le-app-codex/lasereverything.net.db \ rev-parse HEAD ``` -The HEAD must equal the reviewed remote `migration/payload` commit, the worktree must be clean, and no nested permanent worktree may exist. Provision a dedicated repository-scoped Git credential for `le_payload_dev`; do not copy `sol6_vi`'s private key. +The HEAD must equal the reviewed remote `migration/le-app-database` commit, the worktree must be clean, and no nested permanent worktree may exist. Provision a dedicated repository-scoped Git credential for `le_app_codex`; do not copy `sol6_vi`'s private key. ### Phase C — scrubbed source copy @@ -326,7 +401,7 @@ The administrator copies from the resolved source directory, never from the root ```bash source_old=/srv/codex-migration/source-20260710-175408 -source_new=/srv/le-payload-dev/source +source_new=/srv/le-app-codex/database-source-readonly sudo rsync -aHAX --numeric-ids --delete-delay "$source_old/" "$source_new/" @@ -335,18 +410,18 @@ sudo sh -c "cd '$source_new' && sha256sum --check --status CHECKSUMS.sha256" sudo sh -c "cd '$source_new' && sha256sum --check --status uploads.sha256" # Freeze the canonical input against development writes. -sudo chown -R root:le_payload_dev "$source_new" +sudo chown -R root:le_app_codex "$source_new" sudo find "$source_new" -type d -exec chmod 0550 {} + sudo find "$source_new" -type f -exec chmod 0440 {} + ``` -Before using `--delete-delay`, assert `source_new` resolves exactly beneath `/srv/le-payload-dev/source` and is not a symlink. The source checksum files, row-count manifests, schema, dump, extensions, and all uploads must validate. The old copy remains untouched. +Before using `--delete-delay`, assert `source_new` resolves exactly to `/srv/le-app-codex/database-source-readonly` and is not a symlink. The source checksum files, row-count manifests, schema, dump, extensions, and all uploads must validate. The old copy remains untouched. ### Phase D — nonproduction secrets - Preserve both old credential files in place during rollback. - Do not copy `le-payload-migration-admin.env`. -- Generate new fixture/snapshot/rootless database, Payload, Mailpit, and local gateway credentials directly into root-owned `/srv/le-payload-dev/secrets` with mode `0640`. +- Generate new fixture/snapshot/rootless database, Payload, Mailpit, and local gateway credentials directly into root-owned `/srv/le-app-codex/nonproduction-secrets` with mode `0640`. - If the MariaDB read-only credential from the scrubbed environment is temporarily required to compare the old rootful source, transfer only the minimum required variable set through an administrator-reviewed, non-logging process; replace it once the rootless source clone is verified. - No production SMTP, OAuth, Ko-fi, Directus token, webhook, or server credential enters the new directory. @@ -387,7 +462,7 @@ Every deletion requires a separate explicit approval showing the resolved path/o Promotion path: ```text -/srv/le-payload-dev/lasereverything.net.db +/srv/le-app-codex/lasereverything.net.db -> reviewed Git commit + signed/recorded release manifest -> CI-built immutable image digests tested in fixture/snapshot/rehearsal/staging -> clean checkout or verified release artifact at /var/www/lasereverything.net.db @@ -405,7 +480,7 @@ Required workflow: The production checkout contains all application source/configuration needed to reproduce the deployment contract. Physical image layers, declared production PostgreSQL/media volumes, and shared host-proxy configuration may remain outside it, but their names/locations/interfaces are declared in the production overlay and release manifest rooted there. -Forbidden promotion inputs include `/srv/le-payload-dev/runtime`, `source`, `secrets`, `data`, `artifacts`, local CA material, Mailpit state, test traces, rootless Docker state, disposable databases/volumes, caches, and development `.env` files. No `rsync` or recursive copy from `/srv/le-payload-dev` to `/var/www` is permitted. +Forbidden promotion inputs include `/srv/le-app-codex/container-runtime`, `database-source-readonly`, `nonproduction-secrets`, `testing-data`, `build-artifacts`, local CA material, Mailpit state, test traces, rootless Docker state, disposable databases/volumes, caches, and development `.env` files. No `rsync` or recursive copy from `/srv/le-app-codex` to `/var/www` is permitted. ## Local HTTPS and CA trust @@ -416,9 +491,9 @@ The local gateway uses a repository-pinned proxy image and an ignored, generated - `mail.le.localhost`; - optional `legacy.le.localhost`. -All bind to loopback port 8443, avoiding privileged ports and host DNS changes (`*.localhost` resolves locally). Certificates and private keys live under `/srv/le-payload-dev/secrets/local-ca`, mode `0700/0600`, excluded from Git and build contexts. +All bind to loopback port 8443, avoiding privileged ports and host DNS changes (`*.localhost` resolves locally). Certificates and private keys live under `/srv/le-app-codex/nonproduction-secrets/local-ca`, mode `0700/0600`, excluded from Git and build contexts. -Automated Playwright tests may use a dedicated browser profile with its own trust database. Manual Chromium trust can be limited to `/home/le_payload_dev/.pki/nssdb` using the already installed `certutil`; system-wide CA trust is unnecessary. Importing the CA into even that user/browser profile is a host/user-state change and requires explicit approval. Removal deletes the certificate from that profile and the ignored CA directory. +Automated Playwright tests may use a dedicated browser profile with its own trust database. Manual Chromium trust can be limited to `/srv/le-app-codex/home/.pki/nssdb` using the already installed `certutil`; system-wide CA trust is unnecessary. Importing the CA into even that user/browser profile is a host/user-state change and requires explicit approval. Removal deletes the certificate from that profile and the ignored CA directory. ## Mailpit and outbound protection @@ -465,28 +540,28 @@ CPU and GPU images share server contract tests. Capability differences appear in Rollback must be run only after confirming the selected context/socket is the dedicated one: ```bash -# As le_payload_dev: stop project resources, then the user daemon. +# As le_app_codex: stop project resources, then the user daemon. DOCKER_HOST=unix:///run/user/1200/docker.sock docker compose \ - --project-name le_payload_dev down --remove-orphans + --project-name le-app-testing down --remove-orphans systemctl --user disable --now docker.socket docker.service # As administrator: disable optional lingering. -sudo loginctl disable-linger le_payload_dev +sudo loginctl disable-linger le_app_codex # Verify production daemon endpoint and data were never referenced by project metadata/logs. # Use the planning preflight/audit script; do not connect to or mutate production Docker. # Remove only the dedicated project subvolume after an explicit artifact review. -sudo btrfs subvolume delete /srv/le-payload-dev +sudo btrfs subvolume delete /srv/le-app-codex # Remove subordinate allocations and account after the subvolume/runtime is gone. -sudo usermod --del-subuids 165536-231071 --del-subgids 165536-231071 le_payload_dev -sudo userdel --remove le_payload_dev -sudo groupdel le_payload_dev 2>/dev/null || true +sudo usermod --del-subuids 165536-231071 --del-subgids 165536-231071 le_app_codex +sudo userdel --remove le_app_codex +sudo groupdel le_app_codex 2>/dev/null || true ``` The dedicated Docker context lives only in the dedicated user's home and disappears with that home. If another operator created a client context pointing to the user socket, remove that context explicitly from that operator's client configuration. Production secret permission hardening is not rolled back merely because development is removed; it is an independent security correction. Any temporary transitional ACLs, if an administrator added them contrary to the final copy-based design, must be inventoried and removed before account deletion. Removal does not touch `/var/run/docker.sock`, production contexts, `/var/lib/docker`, `/var/lib/mysql`, `/var/www` application content, production volumes/networks, transitional source during its rollback retention, or `/srv/directus-archive`. -Final proof compares before/after permission metadata and confirms: production socket remains denied, production service state is unchanged, no project mount referenced forbidden paths, the dedicated UID/subordinate mappings are gone, and only `/srv/le-payload-dev` resources were deleted. +Final proof compares before/after permission metadata and confirms: production socket remains denied, production service state is unchanged, no project mount referenced forbidden paths, the dedicated UID/subordinate mappings are gone, and only `/srv/le-app-codex` resources were deleted. diff --git a/docs/payload-migration/implementation-plan.md b/docs/le-app-database-migration/implementation-plan.md similarity index 94% rename from docs/payload-migration/implementation-plan.md rename to docs/le-app-database-migration/implementation-plan.md index 2170132..67c90af 100644 --- a/docs/payload-migration/implementation-plan.md +++ b/docs/le-app-database-migration/implementation-plan.md @@ -1,4 +1,4 @@ -# Payload transition implementation plan +# Laser Everything database transition implementation plan Status: revised pre-implementation plan. Work stops at the Milestone 0 host-runtime approval gate. @@ -9,7 +9,7 @@ Status: revised pre-implementation plan. Work stops at the Milestone 0 host-runt - Review `architecture-proposal.md`, `environment-parity.md`, and host prerequisites. 2. **Host runtime gate — requires human approval/provisioning** - - Provision the dedicated `le_payload_dev` account and a rootless Docker context owned only by it. + - Provision the dedicated `le_app_codex` account and a rootless Docker context owned only by it. - Restrict production environment files currently readable to `sol6_vi`; verify the development account cannot traverse production paths. - Use deterministic CPU BGBye initially; defer optional rootless GPU provisioning. - Approve browser-profile-scoped local CA trust for HTTPS testing. @@ -19,8 +19,8 @@ Status: revised pre-implementation plan. Work stops at the Milestone 0 host-runt 3. **Reversible canonical-path consolidation — separately approval-gated** - Inventory the transitional Git roots, symlinks, credential filenames, rootful disposable containers, and volumes without reading secret values. - - Create the sole checkout at `/srv/le-payload-dev/lasereverything.net.db` and verify `migration/payload` commit/remote state. - - Copy and checksum the scrubbed source into root-owned read-only `/srv/le-payload-dev/source`. + - Create the sole checkout at `/srv/le-app-codex/lasereverything.net.db` and verify `migration/le-app-database` commit/remote state. + - Copy and checksum the scrubbed source into root-owned read-only `/srv/le-app-codex/database-source-readonly`. - Create new nonproduction-only secrets and rootless MariaDB/PostgreSQL resources; do not reuse production credentials. - Preserve all old resources through the rollback period. Delete nothing without a later explicit approval. diff --git a/docs/payload-migration/metadata-disposition.md b/docs/le-app-database-migration/metadata-disposition.md similarity index 100% rename from docs/payload-migration/metadata-disposition.md rename to docs/le-app-database-migration/metadata-disposition.md