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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
|
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/>.
STACK OPERATIONS AND RECOVERY
Application root: ~/stack
Development machine: local Linux workstation
Production host: ops@23.155.44.67
Public source: https://git.doan.luuduct.ai/stack
License: GNU Affero General Public License, version 3 or later
WHAT YOU NEED TO KEEP
A full encrypted export made with --include-images contains public source,
the compiled management command, .env, persistent application data,
certificate state, bare repositories, working Git history if present, and
the exact service images. It includes a file checksum manifest.
It excludes the container engine's storage, build caches, old exports,
host packages, host SSH keys, firewall rules, disks, and OS configuration.
These are recreated on a new host. No old server filesystem is required.
The public Git repository alone is not a backup: it excludes .env, live
data, private certificates, and application repositories under var/.
Your GPG private key and its passphrase are needed to decrypt backups.
Keep the private-key export and revocation certificate in KeePassXC and
keep an independent copy of the database. The server has only the public
key. This guide never needs your private key or password in chat.
Default backup recipient fingerprint:
3346AA3150F8F16828BB0D14F8C66DF77AFECEA6
DAILY DEVELOPMENT
Run these from ~/stack on your workstation:
cargo stack dev
cargo stack status
cargo stack down
Visit http://localhost:8080, http://localhost:8080/git/, and
http://localhost:8080/search/. To use different ports:
cargo stack dev --port 9080
Local mode binds loopback only, rewrites public service links locally,
keeps public DNS untouched, and does not request public TLS certificates.
It automatically creates .env with a random SearXNG secret if absent.
Edit .env to change configured secrets. Do not commit it.
Public service settings belong in infra/, and website content in web/.
New Rust backends belong in crates/, explicitly added to Cargo.toml.
Services are declared in infra/services.toml. A service receives only the
.env keys listed in its environment array, not every project secret.
Rust commands use a project-specific Podman store under var/podman/.
A bare 'podman ps' lists your normal store, which may be empty. To inspect
this project's store manually, run from ~/stack:
podman --root "$PWD/var/podman/storage" --runroot "$PWD/var/podman/run" ps -a
Reuse those two global options before 'logs CONTAINER', 'exec CONTAINER',
or 'images'. Container names are shown by cargo stack status.
Never copy var/podman/ to another machine; export images instead.
STRICT LOCAL CHECKS
Install the two development-only tools once:
cargo install --locked --version 0.20.2 cargo-deny --root .tools
cargo install --locked --version 0.9.3 --features cli cargo-about --root .tools
Then run:
cargo stack check
This checks formatting, all workspace Clippy targets/features, tests,
private rustdoc, dependency advisories, duplicate versions, sources, and
licenses. It regenerates THIRD-PARTY-LICENSES.html through cargo-about.
Use cargo fmt --all when formatting needs correction.
After changing dependency requirements, update Cargo.lock deliberately,
run the complete check, and commit the lockfile and generated license page.
Never disable audit failures merely to get a release out.
EXPORT AND BACK UP PRODUCTION
On the VPS:
cd ~/stack
./bin/stack backup --include-images
The command briefly pauses the three services while copying consistent
application files, then resumes them before compression and encryption.
It uses the published public key, verifies the recipient fingerprint,
checks bare Git repositories, saves exact images, and produces:
var/exports/stack-TIMESTAMP.tar.gz.gpg
var/exports/stack-TIMESTAMP.tar.gz.gpg.sha256
Both files have restrictive permissions. Temporary plaintext staging is
removed on normal completion and handled errors. No scheduled backup job
is installed. You decide when and where to retain exports.
On your workstation, create a destination under stack and copy the two
actual filenames printed by the command:
umask 077
mkdir -p ~/stack/var/backups/production
scp ops@23.155.44.67:stack/var/exports/stack-TIMESTAMP.tar.gz.gpg ~/stack/var/backups/production/
scp ops@23.155.44.67:stack/var/exports/stack-TIMESTAMP.tar.gz.gpg.sha256 ~/stack/var/backups/production/
cd ~/stack/var/backups/production
sha256sum -c stack-TIMESTAMP.tar.gz.gpg.sha256
Replace TIMESTAMP with the actual timestamp. A checksum detects accidental
corruption; keep the trusted encrypted file and checksum together.
Retain at least one copy on another device or location.
Once copied and verified, delete only the corresponding VPS export files
if desired. Do not delete var/data or .env.
A smaller export without images is available through ./bin/stack backup.
It needs registry/build access for local startup and is not a standalone
production deployment. Prefer --include-images for disaster recovery.
RESTORE ON ANOTHER LINUX MACHINE
Install Podman, Git, GnuPG, and normal rootless networking prerequisites.
Rootless Podman needs at least 65536 subordinate UIDs and GIDs for the
target user. Use that distribution's supported Podman installation.
The current ready-made binary/images target x86_64 Linux (linux/amd64).
Copy the encrypted export and its checksum to the new machine. Verify
the checksum first. Import your secret-key backup if not already present:
gpg --import private-key-backup.asc
GnuPG asks for the key passphrase through its normal local interface.
Never pass it on the command line or put it in .env.
If you already have a compatible stack command, restore with it:
./bin/stack restore /path/to/stack-TIMESTAMP.tar.gz.gpg ~/stack-restored
The destination must not exist. Restore decrypts privately, rejects unsafe
archive paths and links, checks every manifest entry, assigns a unique
project identity, and defaults to local mode. It does not start services.
It refuses to overwrite an existing installation.
For a new machine with no stack command or Rust toolchain, bootstrap with
standard GnuPG and tar. The shell commands below work in bash and fish:
umask 077
mkdir -p ~/stack-bootstrap
gpg --output ~/stack-bootstrap/export.tar.gz --decrypt /path/to/stack-TIMESTAMP.tar.gz.gpg
tar -xzf ~/stack-bootstrap/export.tar.gz -C ~/stack-bootstrap stack/bin/stack
~/stack-bootstrap/stack/bin/stack restore /path/to/stack-TIMESTAMP.tar.gz.gpg ~/stack
Only use this bootstrap with your own trusted export; the extracted binary
is executable code. The restore command validates the complete archive.
After successful restoration, delete ~/stack-bootstrap, which contains
the temporary decrypted archive. Keep only the encrypted export as backup.
Start the recovered stack locally first:
cd ~/stack
./bin/stack up --local --port 8080
./bin/stack status
Saved images load automatically and their identities are checked against
the image lock. No registry pull or compilation is needed on a matching
architecture. Check the homepage, Git repositories, search, and WKD.
Then stop it with ./bin/stack down before configuring production.
A release archive can be restored by the same command. Releases contain
source, a binary, and images, but no .env or application data. Starting a
restored release creates fresh state; it does not recover lost data.
SET UP A NEW PRODUCTION HOST
Restore to the target user's ~/stack, using the steps above. On Debian,
adapt the username 'ops' in infra/host-setup.sh if necessary, review it,
and run:
sudo sh ~/stack/infra/host-setup.sh
It installs Podman and networking tools, checks existing subordinate
UID/GID ranges, enables user linger for boot, and sets:
net.ipv4.ip_unprivileged_port_start=80
This allows rootless listeners on ports 80 and above system-wide; ports
below 80 remain privileged. It does not change SSH or your firewall.
If the user lacks subordinate ranges, allocate unused ranges through
the distribution's account administration before rerunning the script.
On other Linux distributions, install equivalent prerequisites and set
that sysctl and user linger using the host's supported tools. Production
requires a systemd user manager for this project's supplied boot service.
Allow inbound 22/tcp, 80/tcp, 443/tcp, and 443/udp through the host/provider
firewall, for IPv4 and IPv6 as applicable. Backend container ports must not
be published. Preserve your SSH access and verify the address before DNS
changes. There is no need to restore Docker's old generated firewall rules.
Point public A records to the new IPv4 address, and AAAA records only to a
working new IPv6 address. Remove stale AAAA records. Public hostnames are
listed in infra/Caddyfile. Update canonical hosts there if changing domains.
Keep the www aliases and openpgpkey names pointing to the canonical hosts.
Start production:
cd ~/stack
./bin/stack up --production
Production binds 80/tcp, 443/tcp, and 443/udp. Caddy manages certificates
and renewals automatically, redirects HTTP to HTTPS, and redirects www
aliases to the canonical URLs. Certificate state is in var/data/caddy/.
Public certificate issuance needs working public DNS and connectivity.
Enable startup at boot:
systemctl --user link ~/stack/infra/stack.service
systemctl --user daemon-reload
systemctl --user enable --now stack.service
systemctl --user status stack.service
This unit assumes ~/stack. Edit its paths if deliberately using another
location. User linger keeps it running without an interactive login.
Application containers use an unless-stopped restart policy.
If offering SSH Git access at the former VPS path, create this optional
host symlink as the administrator:
sudo install -d -m 755 /srv/git
sudo ln -s /home/ops/stack/var/data/repos/stack /srv/git/stack
Do not overwrite an existing repository there. The current VPS already
has this link, updated for the new layout. Public HTTPS Git works without
the optional host link.
MAKE AND DEPLOY A RELEASE
Develop and run the checks on your workstation, then:
cd ~/stack
cargo stack release
Release reruns the full check, builds a static Rust binary locally,
builds the cgit container locally, and packages the configured image set.
Outputs are dist/stack-release-TIMESTAMP.tar.gz and its .sha256 file.
No .env, live data, .git history, or previous backup is included.
Test the release in a fresh local directory before deploying, using the
restore command followed by up --local on an unused port. Inspect the
same four services and then stop the test installation.
Commit and push reviewed public changes with normal Git:
git add -A
git diff --cached --stat
git commit -m 'Describe the change'
git push origin main
.gitignore excludes configured secrets and all runtime/artifact trees.
Review the staged files. Deployment uses the release archive, not git pull.
Copy the actual archive and checksum to production:
ssh ops@23.155.44.67 'mkdir -p ~/stack/var/incoming'
scp dist/stack-release-TIMESTAMP.tar.gz ops@23.155.44.67:stack/var/incoming/
scp dist/stack-release-TIMESTAMP.tar.gz.sha256 ops@23.155.44.67:stack/var/incoming/
On the VPS:
cd ~/stack/var/incoming
sha256sum -c stack-release-TIMESTAMP.tar.gz.sha256
cd ~/stack
./bin/stack activate var/incoming/stack-release-TIMESTAMP.tar.gz
./bin/stack status
Activation verifies the release and platform, replaces public source and
the binary, loads exact images, preserves .env and var/data, and replaces
the running containers. It does not compile Rust or build container images.
Expect a brief interruption while containers are replaced. Take a verified
export first if a future change could require recovery.
After verifying production, delete the corresponding incoming archive and
checksum if you do not want release copies left on the VPS.
RECOVERY LIMITS AND SECRET ROTATION
Exports capture this application, not an entire bootable VPS image.
Host account, SSH, firewall, and package setup remain host responsibilities.
Changing CPU architecture requires a native release build. Registry access
is needed only when images are omitted or rebuilding for another target.
Current persistent state is files, Git repositories, and Caddy state.
When adding a database, add its supported flush/dump procedure to the
export workflow and test recovery before relying on that database's backup.
If changing the OpenPGP backup recipient, publish its public key, update
the command's default fingerprint, and test encryption and restoration.
For one export, use --recipient FULL_FINGERPRINT --key /path/public-key.asc.
Keep old secret keys while any retained export still depends on them.
Legacy format-1 exports made before the Rust migration use their embedded
historical Python recovery helper. Keep that helper with those exports.
The current command intentionally accepts only its verified format-2
archives. Fresh format-2 production backups replace the normal workflow.
LICENSE PAGE IN CGIT
The stack repository's About tab renders only
web/THIRD-PARTY-LICENSES.html. Source and Raw links retain the normal cgit
views and preserve revision queries. Other files are not rendered as HTML.
The rendered document is isolated in a sandboxed frame, with scripts and
external resources blocked. The standalone website license page remains
available at /THIRD-PARTY-LICENSES.html.
|