1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai>
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 <https://www.gnu.org/licenses/>.
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.
|