aboutsummaryrefslogtreecommitdiff
path: root/docs/OPERATIONS.txt
blob: 6cd9a6b05a342227c13fb085ad538f14d91542d9 (plain)
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
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:

        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.