Copyright (C) 2026 Doan Luu Duc Tai This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . GUIX RECOVERY Goal: one .tar.gz, unpack/bootstrap, one command restores and starts services. Private state is GPG-encrypted inside the tarball. Bootstrap, binaries and public release/source are plaintext. Outer tarball is not encrypted or signed. Trust its origin; SHA256SUMS detects corruption, not malicious replacement. Keep decryption key/passphrase separately; never send them through chat. Guix supplies Podman/tools. Existing Caddy/cgit/SearXNG/chat OCI applications, static musl/OpenSSL builds, domains, HTTPS/WKD and crypto remain. No new Rust/npm dependency, Guix System install, or native service rewrite. One root .env and public .env.example; databases/TLS/owner/history: var/data. BUILD LOCAL sh infra/guix/build.sh cargo run --release --locked -- release Needs working Guix daemon, x86_64 Linux and rootless Podman prerequisites. Channel commits: bin/guix-channels.scm. time-machine builds the manifest's bin/guix-runtime.tar.gz. Release runs full checks and packages matching source, binaries, browser assets and saved images. No compilation on recovery host. Podman info smoke test is not clean-host/container lifecycle proof. Ordinary pack uses canonical /gnu/store. Relocatable -RR wrappers create a one-ID user namespace incompatible with required subordinate UID mappings. https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-pack.html https://guix.gnu.org/manual/devel/en/html_node/Replicating-Guix.html https://raw.githubusercontent.com/containers/podman/main/docs/tutorials/rootless_tutorial.md MAKE CARRY FILE Production: create consistent encrypted export; copy and verify it locally: ./bin/run backup --include-images Bundle a trusted freshly checked release with that export: sh infra/guix/bundle.sh /path/backup.tar.gz.gpg /path/release.tar.gz /path/recovery.tar.gz Bundler executes the trusted release's CLI to validate it with restore --release. Backup-kind archives, private .env/var/data, missing runtime/chat artifacts and missing saved images are refused before publication. CLI, scripts, config, runtime and provenance all come from the same release. Original release archive is unchanged and includes corresponding source and LICENSE. Original encrypted backup retains prior source/work and rollback. Runtime is duplicated outside release for bootstrap simplicity; extra disk space avoids another installer/format. One carry file remains sufficient. Old backups remain supported; release must implement this migration's --release validation and runtime export. Blindly reusing old management binary loses Guix files on the next backup. Restore now activates matching new source and binary, including canonical infra/guix/run.sh needed by later backups. BOOTSTRAP HOST; RESTORE Fresh x86_64 Linux: trusted shell, GNU coreutils/tar/gzip/findutils/diffutils, awk, util-linux flock, sudo/root access. Bootstrap refuses /var/guix and foreign /gnu trees. Installs only /gnu/store entries; never host /bin or /etc links. tar -xzf recovery.tar.gz sudo ./stack-recovery/bootstrap Root-owned non-writable closure entries are staged on /gnu's filesystem, published without overwriting, then recorded by pack digest. Retry reconciles unfinished installs; later packs retain old entries. Root install records and lock: /gnu/.stack-runtimes. Target needs no Guix daemon/downloads. Existing managed Guix hosts need separate planning; do not merge unmanaged store items. Administrator also supplies privileged newuidmap/newgidmap, >=65536 subordinate UIDs/GIDs, user namespaces, writable login XDG_RUNTIME_DIR, low-port sysctl net.ipv4.ip_unprivileged_port_start <=80. DNS/firewall/ports remain host duties. OPERATIONS.txt Debian setup installs distro Podman too: existing fallback, not evidence of a Guix-only target. Service user imports GPG key locally. ./stack-recovery/restore /home/ops/stack New destination required. Capsule checks hashes/bootstrap/prerequisites, decrypts through Rust manifest/path validation, requires saved backup images, then activates its paired release under the capsule's Guix runtime/config. Activation installs new public source/binaries/images, preserves .git, .env, var/data, and starts four services. Source worktree becomes new release; original source remains in encrypted backup/history. Only tested version pairs establish application/data compatibility; format-2 integrity cannot. Failed activation retains data plus private var/guix-recovery intent marker. Fix cause and rerun the same restore command with the same capsule/destination. Matching backup/release digests permit resume without decrypting/restoring twice; unrelated destinations are refused. Recovery lock excludes concurrent activation and is closed in child processes. Success removes pending marker. Do not replace an interrupted activation with plain up; it cannot finish copying a release. Completed recoveries use normal management commands: /home/ops/stack/bin/run up --production Launcher supplies --root and Guix-only CONTAINERS_CONF; do not pass --root. Runtime expands privately into content-addressed var/guix; old packs remain. Activation atomically replaces the archive/provenance files, including existing read-only packs; regression test covers replacement followed by export/restore. Direct bin/stack and releases without pack use host tools. Guix symlinks remain inside opaque runtime pack; Rust application extractor still rejects links. Boot setup remains explicit: systemd user service plus linger, OPERATIONS.txt. Unit uses bin/run at ~/stack; adapt paths for another destination. CHECKS; DEPLOYMENT GATE sh infra/guix/test.sh cargo stack check Shell tests need bubblewrap. Synthetic packs test actual bootstrap/filesystem behavior. Synthetic recovery tests actual orchestration, failed activation, resume, intent/lock refusal, private sentinels, and child FD closure. Fake tools do not prove real encryption, image import, services or kernel UID mapping. Rust tests prove archive-kind refusal before publication and runtime roundtrip. Actual static CLI also checked synthetic bundle validation/publication/privacy. No strict Rust/Clippy/Rustdoc, typed ESLint, LibreJS or crypto gate weakened. Existing documented lint/dependency exceptions remain unchanged. Image import supplies a temporary reject-by-default policy accepting local Docker/OCI archives only. Export separately accepts containers-storage. Manifest, architecture/layer/image-ID locks still gate startup; global host policy stays unchanged. Generated protocol JSON cannot contain legal metadata; its AGPL Rust generator carries the notice. POLICY.txt. Legacy backups may have a collapsed Caddy caddy/ tree. Verified backup restore moves credentials to data/caddy and autosave to config/caddy, preserving bytes and permissions. Conflicting layouts are refused before publication. Current exports copy /data/. and /config/. separately. Keep this compatibility path while retained backups depend on it. For each release, before production: 1. Full checked release with real Guix runtime and source/binary pair. 2. Fresh host without Guix/distro Podman/Git/GPG: offline bootstrap/restore; four services, exact images, detached UID/network helpers. 3. .env count, domains, HTTPS/WKD, original owner fingerprint; unchanged chat rows and history decryption with both original participant identities. 4. Backup recovered stack, second recovery, reboot startup. 5. First host-Podman activation and Guix runtime upgrades. Ordinary direct activate retains its process PATH; deploy through matching release runtime. 6. Fresh verified independent production backup; deploy only prebuilt release; verify public routes/state, retain prior release/runtime and encrypted export. Retain a verified independent export and complete original worktree, binary/launcher, installed unit, var/state.json, image-lock.json, images.tar and engine storage. Activation replaces public source and image metadata. Stop/cancel the new unit and stop Guix containers before rollback; confirm inactivity before moving engine trees. Retain the new tree, restore old engine path/metadata/worktree/launcher/unit and reload the user manager. For a Guix rollback use the restored prior bin/run, which selects its matching runtime. Only a host-tools release uses host PATH with Guix overrides unset. Preserve CURRENT .env/var/data; never extract retained copies over live data. Old releases do not remove newer Guix launcher/runtime files automatically. Never delete store entries still used by retained/running tools. DEPLOYED BASELINE — 2026-11-01 Signed commit 9349a2d deployed packed Podman 5.7.0 and four rootless services. Production .env, owner, every SQLite table and all 34 TLS key/certificate files matched. All 126 release source/binary files, public HTTPS/WKD, search and packed conmon paths verified. Only two ACME renewal retry timestamps changed. A fresh Debian 12 x86_64 KVM guest had no Guix/Podman/Git/GPG. Network was restricted before readonly-carry bootstrap/restore; GPG used a restricted forwarded agent, never a copied secret key. Restore, export/second recovery and final VM reboot passed; all 651 immutable snapshot files matched. Production-host reboot was not performed. The production database had zero conversations/messages; real historical-message decryption was untestable. Disposable browser tests recovered 54-message history with both identities; Firefox GNU LibreJS 7.21.1 rejected its unlicensed control. Tested runtime SHA256: c5c7b849da152ee7a98b01602a1e91285ce4e3a80021a9cecb5f1e594dfc7810 Its channel provenance is included in bin/guix-channels.scm. New source/packs require their own checks. Empty helper lists select Guix tools through PATH; ordinary host configuration stays unchanged. https://github.com/containers/podman/blob/v5.7.0/vendor/go.podman.io/common/pkg/config/config.go#L849-L875 https://github.com/containers/podman/blob/v5.7.0/cmd/podman/images/save.go#L91-L94 Private evidence: var/guix-*.log and .tools/guix-production/; these include private state hashes and stay ignored. Do not publish them with source.