summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoan Luu Duc Tai <doan@luuduct.ai>2026-11-01 08:25:58 -0600
committerDoan Luu Duc Tai <doan@luuduct.ai>2026-11-01 08:25:58 -0600
commit607be5ee239a9b7969eab96145c8af4ee57053d1 (patch)
tree582ada863d690ed8f113f266f2afc8252d421c39
parent9349a2de648e241da8e00ba7ae25cb2a877ff3e9 (diff)
chore: remove obsolete migration code and docsHEADmain
-rw-r--r--README.txt9
-rw-r--r--crates/chat-crypto/README.txt3
-rw-r--r--crates/chat-server/web/javascript.html2
-rw-r--r--docs/CHAT-APPLICATION.txt20
-rw-r--r--docs/CHAT-CRYPTO.txt170
-rw-r--r--docs/CHAT-OPENSSL-MIGRATION.txt234
-rw-r--r--docs/CHAT-PRODUCTION.txt61
-rw-r--r--docs/CONFIGURATION.txt2
-rw-r--r--docs/GUIX.txt112
-rw-r--r--docs/MAP.txt10
-rw-r--r--docs/OPERATIONS.txt312
-rw-r--r--docs/POLICY.txt4
-rw-r--r--src/app/files.rs1
-rw-r--r--src/app/project.rs6
14 files changed, 392 insertions, 554 deletions
diff --git a/README.txt b/README.txt
index ab5a3bd..3b6488e 100644
--- a/README.txt
+++ b/README.txt
@@ -61,8 +61,8 @@ CHECK AND RELEASE
Cargo.lock + about.hbs generate THIRD-PARTY-LICENSES.html.
Display GNU license names; preserve exact SPDX identifiers and upstream legal text.
- Build/test locally. Deploy prebuilt archives. VPS needs Podman, Git, GnuPG;
- no Rust/Cargo/Node/npm/TypeScript. Backup, restore, host setup, deployment:
+ Build/test locally. Deploy prebuilt archives; Guix supplies runtime tools.
+ VPS needs no Rust/Cargo/Node/npm/TypeScript. Backup, restore, host setup, deployment:
docs/OPERATIONS.txt. Building a release does not activate production.
CHAT — EXPERIMENTAL
@@ -76,8 +76,9 @@ CHAT — EXPERIMENTAL
SQLite snapshots use bundled SQLite C library. Static releases use OpenSSL,
hash-verified musl headers, Rust musl target. VPS needs no compiler.
-GUIX — EXPERIMENTAL
+GUIX RECOVERY
Single .tar.gz capsule with encrypted private state: docs/GUIX.txt.
Canonical Guix store needs privileged bootstrap; existing managed stores refused.
- Real Guix lifecycle/clean-host restore remain unproven. Do not deploy yet.
+ Production uses the packed rootless runtime. Offline clean-host restore,
+ second recovery and VM reboot passed. Host prerequisites/limits: docs/GUIX.txt.
diff --git a/crates/chat-crypto/README.txt b/crates/chat-crypto/README.txt
index 0fb26e7..cb8d9b2 100644
--- a/crates/chat-crypto/README.txt
+++ b/crates/chat-crypto/README.txt
@@ -18,7 +18,6 @@ PRIVATE CHAT CRYPTOGRAPHY
Fixed encryption: RFC 9980 ML-KEM-1024 + X448, OpenPGP v6.
Signing: ML-DSA-87 + Ed448, OpenPGP v6.
Sequoia 2.4.2 + OpenSSL 3.6.5. No algorithm substitution or advisory exception.
- Superseded RustCrypto backend is not selected.
Provides purpose-specific identities, Argon2id/AES-256-GCM protected files,
detached authentication proofs, signed two-recipient messages, bounded
@@ -29,7 +28,7 @@ PRIVATE CHAT CRYPTOGRAPHY
../../docs/CHAT-APPLICATION.txt: formats, setup, tests, backups, limits.
../../docs/CHAT-PRODUCTION.txt: packaged HTTPS.
- ../../docs/CHAT-OPENSSL-MIGRATION.txt: backend decision, source hashes,
+ ../../docs/CHAT-CRYPTO.txt: backend decision, source hashes,
build flags, FFI/capability limits, reviewed dependency exceptions.
Experimental. Compatibility tests are not an application security audit.
diff --git a/crates/chat-server/web/javascript.html b/crates/chat-server/web/javascript.html
index b39504c..7c8d1d7 100644
--- a/crates/chat-server/web/javascript.html
+++ b/crates/chat-server/web/javascript.html
@@ -24,6 +24,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<tr><td><a href="/wasi.mjs">wasi.mjs</a></td><td><a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU-AGPL-3.0</a></td><td><a href="/SOURCE.tar.gz">Complete corresponding source</a></td></tr>
</tbody></table></div>
<p>The readable JavaScript is compiled from the included TypeScript sources. The source archive includes shared protocol declarations, compiler configurations, the npm lock, and build/test scripts. No third-party JavaScript runs in the browser. Node, npm, and the pinned TypeScript compiler are development tools; the production server serves prebuilt scripts.</p>
-<p>The WebAssembly module is compiled from the Rust workspace and unmodified OpenSSL 3.6.5, under their respective free software licenses. The source archive includes first-party sources, Cargo dependencies, the OpenSSL source archive, build scripts, and notices. General-purpose Rust and WASI SDK tools are documented with verified download locations in CHAT-OPENSSL-MIGRATION.txt.</p>
+<p>The WebAssembly module is compiled from the Rust workspace and unmodified OpenSSL 3.6.5, under their respective free software licenses. The source archive includes first-party sources, Cargo dependencies, the OpenSSL source archive, build scripts, and notices. General-purpose Rust and WASI SDK tools are documented with verified download locations in CHAT-CRYPTO.txt.</p>
<p><a href="{{SITE_ORIGIN}}/THIRD-PARTY-LICENSES.html">Third-party licenses</a> / <a href="/SOURCE.tar.gz">Source archive</a> / <a href="/">Return</a></p>
</main></body></html>
diff --git a/docs/CHAT-APPLICATION.txt b/docs/CHAT-APPLICATION.txt
index 388c677..6a90477 100644
--- a/docs/CHAT-APPLICATION.txt
+++ b/docs/CHAT-APPLICATION.txt
@@ -16,9 +16,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
EXPERIMENTAL PRIVATE CHAT
- Available on main: direct development and prebuilt Podman/HTTPS deployment.
- See CHAT-PRODUCTION.txt for release, setup, and recovery. Feature work does
- not merge main or activate the VPS. Experimental; no independent messenger audit.
+ Direct development and prebuilt Podman/HTTPS deployment are supported.
+ See CHAT-PRODUCTION.txt for release, setup and recovery. Experimental;
+ no independent messenger security audit.
WHAT EXISTS
@@ -108,9 +108,9 @@ OWNER FILE LOSS AND BACKEND RESET
Run development commands at the stack root. Reset has no HTTP API; private
chat-directory access is the administrative boundary. Production runs
- ./bin/stack chat --owner-fingerprint and ./bin/stack chat --reset-owner in an
- isolated maintenance container. First stop with ./bin/stack down; restart
- with ./bin/stack up --production. No VPS Rust required. Read CHAT-PRODUCTION.txt
+ ./bin/run chat --owner-fingerprint and ./bin/run chat --reset-owner in an
+ isolated maintenance container. First stop with ./bin/run down; restart
+ with ./bin/run up --production. No VPS Rust required. Read CHAT-PRODUCTION.txt
before destructive reset.
Read the current public fingerprint, even if you have lost the private file:
@@ -321,10 +321,8 @@ INTERFACE AND SITE STYLE
LICENSES, SOURCE, AND VERIFICATION
- Bundled SQLite snapshots require a musl C compiler for portable CLI builds.
- The root CLI ran as x86_64 static PIE; VPS SQLite packages are unnecessary.
- Local checks compiled C with Alpine musl 1.2.6 headers/host compiler and
- linked with Rust's self-contained musl runtime.
+ Portable CLI builds statically link bundled SQLite with musl. Production
+ needs no SQLite package or compiler. Build inputs: CHAT-PRODUCTION.txt.
The rendered dependency-license page is centralized on the main website at
https://doan.luuduct.ai/THIRD-PARTY-LICENSES.html. Chat links point there; its
@@ -400,4 +398,4 @@ LICENSES, SOURCE, AND VERIFICATION
images and no build directories inside the recovered installation.
Native tests cover authorization, challenge reuse, expiry, locked sessions,
quotas, idempotency, and SQLite snapshot recovery. The implemented design and
- limits are documented here. CHAT-OPENSSL-MIGRATION.txt records the backend review.
+ limits are documented here. CHAT-CRYPTO.txt records the backend review.
diff --git a/docs/CHAT-CRYPTO.txt b/docs/CHAT-CRYPTO.txt
new file mode 100644
index 0000000..fa9fa1a
--- /dev/null
+++ b/docs/CHAT-CRYPTO.txt
@@ -0,0 +1,170 @@
+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/>.
+
+CHAT CRYPTOGRAPHY AND BUILDS
+
+ Application formats, authentication, recovery and security limits:
+ CHAT-APPLICATION.txt. Static native packaging and deployment:
+ CHAT-PRODUCTION.txt. Functional compatibility is not a cryptographic,
+ side-channel or application security audit.
+
+FIXED ALGORITHMS AND BACKEND
+
+ Sequoia OpenPGP 2.4.2, RFC 9980 ML-KEM-1024 + X448 encryption,
+ ML-DSA-87 + Ed448 signatures, OpenPGP v6 keys, AES-256 content encryption.
+ Only crypto-openssl is enabled; no algorithm substitution, experimental/
+ variable-time backend opt-in, or ignored RustSec advisory. Native and browser
+ probes require the OpenSSL backend and expected key algorithms.
+
+ The superseded Rust backend's required curve arithmetic depended on an
+ incompatible crypto-bigint generation affected by RUSTSEC-2026-0387.
+ OpenSSL avoids that dependency; the resolved graph has no cx448,
+ crypto-bigint or rsa crate. This does not prove absence of C defects or
+ browser side channels. See https://rustsec.org/advisories/RUSTSEC-2026-0387.html.
+
+SOURCE PROVENANCE
+
+ Rust: rust-toolchain.toml. Sequoia: exact Cargo.toml requirement/Cargo.lock.
+ OpenSSL: 3.6.5. WASI SDK: 34.0 x86_64 Linux, bundled Clang/libclang
+ 23.1.0-wasi-sdk. Browser tooling: package.json/package-lock.json.
+
+ WASI SDK archive:
+ https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-34/wasi-sdk-34.0-x86_64-linux.tar.gz
+ SHA-256 b761e3a0721dbae9c09a0059e5fdb2bf917d1b4a8a7b430fb3b5aafb0984b2c4
+
+ OpenSSL archive:
+ https://github.com/openssl/openssl/releases/download/openssl-3.6.5/openssl-3.6.5.tar.gz
+ SHA-256 a2157c2830efdec3788939b00c9b0638306d3f0bbb76dc4832ee503bb397df98
+
+ Build scripts verify these digests before extraction. Pinned hashes do not
+ independently verify upstream signatures or promise cross-host identical
+ binaries. Sources, SDK and static libraries stay under ignored .tools/;
+ Cargo output stays under target/. Native development uses the host library;
+ production static native builds use the verified source, CHAT-PRODUCTION.txt.
+
+WASI BUILD AND ENTROPY
+
+ infra/chat/openssl-wasi.conf adds a BASE_unix target with little-endian,
+ 32-bit bignums. C uses -O2; no cryptographic routine or RNG patch is applied.
+ OpenSSL's --with-rand-seed=getrandom reaches WASI random_get through libc.
+ The browser bridge uses only crypto.getRandomValues, splitting requests
+ at 65536 bytes. Entropy failures reject; no fixed seed or success fallback.
+ The browser probe includes a failing-entropy negative control.
+
+ Static WASI builds disable assembly, async operations, automatic config
+ loading, DSO/modules, POSIX I/O, secure-memory arenas, shared libraries,
+ sockets, C stdio, thread pools, threads and interactive console UI.
+ Ordinary WASM memory provides no locked-page, secure-erasure or delivered-
+ JavaScript protection. OpenSSL uses C/FFI; first-party Rust unsafe-code
+ prohibition does not make dependencies memory-safe.
+
+ _WASI_EMULATED_GETPID and libwasi-emulated-getpid supply process-ID emulation.
+ getuid/geteuid/getgid/getegid map to getpagesize. These values provide neither
+ entropy nor identity credentials; there is no real OS user/privilege boundary.
+ This portability mapping does not emulate general POSIX security.
+
+ Bindgen uses SDK libclang/sysroot, wasm32-wasip1 and -fvisibility=default
+ so declarations remain available. Rust's bundled WASI libc precedes SDK
+ libraries to resolve __wasi_init_tp; SDK libraries supply emulated-getpid.
+ Generated cryptographic bindings are not edited. The linker caps WASM linear
+ memory at 256 MiB; browser/host copies add overhead.
+
+BROWSER CAPABILITIES
+
+ crates/chat-crypto/browser/wasi.ts supplies clocks, browser CSPRNG, empty
+ arguments/environment, in-memory request stdin and captured stdout/stderr.
+ Empty stdin runs the compatibility probe; application commands use JSON.
+ Requests exceeding 8388608 JavaScript string units are rejected; combined
+ stdout/stderr exceeding 1048576 string units abort execution.
+
+ No real files, preopened directories, sockets or process launch are supplied.
+ Filesystem/network access is refused; unknown imports fail before
+ instantiation. The application runs crypto in one-shot
+ workers with a timeout; CHAT-APPLICATION.txt documents memory/state limits.
+
+ Capability denials prevent accidental host storage/network access, not
+ malicious JavaScript from the same server. Memory, clocks, randomness and
+ bridge imports remain trusted. Browsers/OSes can retain memory, caches,
+ crash dumps, swap and restored state; no complete erasure is guaranteed.
+
+DEPENDENCY EXCEPTIONS
+
+ deny.toml allows only exact older duplicate versions; advisory/license
+ checks still apply. No RustSec ignore. Wildcards, unknown Git/registry sources
+ and unreviewed duplicates remain rejected. Cargo.lock pins both API lines:
+
+ block-buffer 0.10.4 vs 0.12.1
+ crypto-common 0.1.7 vs 0.2.2
+ digest 0.10.7 vs 0.11.3
+ Sequoia's Argon2 0.5.3 -> BLAKE2 0.10 needs the older digest API;
+ root SHA-256 and parser-generator SHA3 use the newer API.
+ cpufeatures 0.2.17 vs 0.3.1
+ Argon2 uses 0.2; root SHA-256/parser-generator SHA3 use 0.3.
+ generic-array 0.14.7 vs 1.4.5
+ Argon2/BLAKE2 digest uses 0.14; SHA1 collision detection uses 1.x.
+ getrandom 0.2.17 vs 0.4.3
+ Sequoia's unknown-unknown WASM dependencies declare 0.2; root
+ tempfile uses 0.4. Browser WASI uses OpenSSL/bridge entropy.
+ Audit the all-target graph rather than excluding this edge.
+ hashbrown 0.15.5 vs 0.17.1
+ Parser-generator petgraph uses 0.15; its indexmap 2.14 uses 0.17.
+ itertools 0.13.0 vs 0.15.0
+ shlex 1.3.0 vs 2.0.1
+ Bindgen uses older APIs; lalrpop and cc require newer ones.
+ syn 2.0.119 vs 3.0.9
+ Bindgen/prettyplease and Windows support use syn 2; root derives
+ and other dependencies use syn 3. Unification needs upstream API
+ migration or unrelated downgrades, not a lockfile update.
+
+ Build dependencies remain audited. Clippy's name-only exceptions for
+ generic-array/getrandom/syn do not replace exact cargo-deny checks.
+ LINTING.txt documents the lint policy.
+
+EXTERNAL LIBRARIES AND LICENSES
+
+ cargo-deny does not audit external OpenSSL C or the SDK. Review separately:
+ https://openssl-library.org/news/timeline/
+ https://openssl-library.org/news/vulnerabilities-3.6/
+ A Rust audit does not guarantee the pinned C version's future security.
+ Updates require upstream advisory review, every affected URL/hash,
+ native/WASI rebuilds, both browsers, cargo-deny, regenerated notices/sources
+ and release recovery. Never waive advisories to pass.
+
+ libloading used by bindgen/clang has a package-scoped ISC allowance.
+ cargo-about covers workspace Rust licenses, not external C/SDK obligations.
+ Distributed OpenSSL/SDK artifacts retain their required licenses/sources;
+ OpenSSL LICENSE.txt is Apache-2.0. Rust notices alone are insufficient.
+ Corresponding sources include the unmodified OpenSSL archive; toolchain
+ declarations/download locations remain public.
+
+BUILD AND CHECK FROM THE STACK ROOT
+
+ Native development: OpenSSL >=3.5 headers/library, libclang, pkg-config,
+ C compiler and pinned Rust. WASI cross-build: x86_64 GNU/Linux, Perl, make,
+ curl, tar, sha256sum. Browser tooling/install instructions: TYPESCRIPT.txt.
+
+ cargo test --locked -p stack-chat-crypto
+ cargo run --locked -p stack-chat-crypto
+ sh infra/chat/build-openssl-wasi.sh
+ npm run build
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:crypto
+ cargo stack check
+
+ The WASI script builds verified OpenSSL into .tools/openssl-wasi, links the
+ Rust WASI command and prints its SHA-256. Rust-only edits may reuse inputs
+ with sh infra/chat/build-browser.sh; source/version/configuration/flag changes
+ require an OpenSSL rebuild. Extracted sources can be reused; independent
+ verification needs a fresh ignored tools/build tree. Native and browser
+ probe results must explicitly identify the OpenSSL backend.
diff --git a/docs/CHAT-OPENSSL-MIGRATION.txt b/docs/CHAT-OPENSSL-MIGRATION.txt
deleted file mode 100644
index 4bb87fb..0000000
--- a/docs/CHAT-OPENSSL-MIGRATION.txt
+++ /dev/null
@@ -1,234 +0,0 @@
-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/>.
-
-
-OPENSSL BACKEND MIGRATION RECORD
-
- Date: 2026-10-30. Historical compatibility/dependency-security experiment
- using disposable identities. For the later messenger and packaged deployment,
- see CHAT-APPLICATION.txt and CHAT-PRODUCTION.txt.
-
-DECISION AND FIXED ALGORITHMS
-
- Retain Sequoia OpenPGP 2.4.2, RFC 9980 ML-KEM-1024 + X448 encryption,
- ML-DSA-87 + Ed448 signatures, OpenPGP v6 keys, and AES-256 content encryption.
- Only the backend changes; algorithms and wire protocol stay fixed.
- Probe assertions reject unexpected backends or key algorithms.
-
- Previous Rust curve arithmetic reached cx448 0.1.1 -> crypto-bigint 0.5.5;
- the backend also included rsa 0.9.10. Neither RustSec issue was ignored;
- no mathematical implementation was patched locally.
- RUSTSEC-2026-0387 affected required X448/Ed448 curve arithmetic, not unused RSA.
- Its fix requires crypto-bigint >=0.7.6; cx448 0.1.1 requires incompatible 0.5.
- A lockfile update alone could not fix it. No practical browser key-extraction
- exploit was demonstrated; functional tests do not prove constant-time behavior.
- The closed probe used no RSA private operations, but that did not establish
- unreachability through hostile imports or future application interfaces.
- https://rustsec.org/advisories/RUSTSEC-2026-0387.html
- https://rustsec.org/advisories/RUSTSEC-2023-0071.html
-
- Only crypto-openssl is enabled. allow-experimental-crypto and
- allow-variable-time-crypto are removed. The resolved graph has no cx448,
- crypto-bigint, or rsa crate. Removing those dependencies does not prove
- absence of browser side channels or external C defects.
-
-VERSIONS AND SOURCE PROVENANCE
-
- Rust toolchain: 1.99.1, pinned in rust-toolchain.toml.
- Sequoia OpenPGP: 2.4.2, exact requirement and Cargo.lock.
- OpenSSL: 3.6.5, native host library and source-built static WASI libraries.
- WASI SDK: 34.0 x86_64 Linux, bundled Clang/libclang 23.1.0-wasi-sdk.
- Browser test runner: Playwright 1.64.1, Chromium 156.0.8078.4.
-
- SDK archive:
- https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-34/wasi-sdk-34.0-x86_64-linux.tar.gz
- SHA-256 b761e3a0721dbae9c09a0059e5fdb2bf917d1b4a8a7b430fb3b5aafb0984b2c4
-
- OpenSSL archive:
- https://github.com/openssl/openssl/releases/download/openssl-3.6.5/openssl-3.6.5.tar.gz
- SHA-256 a2157c2830efdec3788939b00c9b0638306d3f0bbb76dc4832ee503bb397df98
-
- Digests match upstream GitHub release asset metadata; scripts verify them
- before extraction. This pins inputs, without independently verifying release
- signatures or proving cross-host bit-for-bit builds. Archives, SDK, libraries,
- and build products stay under ignored .tools/ and target/. The OS supplies
- the native development library.
-
-WHY WASI
-
- The Rust proof used wasm32-unknown-unknown and wasm-bindgen glue. OpenSSL uses
- a wasm32-wasip1 command and explicit browser WASI bridge, calling the native
- tests' compatibility_probe. The browser loads this binary directly and
- requires output identifying OpenSSL 3.6.5; stale Rust-backend artifacts cannot
- count as OpenSSL evidence. Do not reuse historical browser-build commands.
-
-BUILD CONFIGURATION AND BOUNDARIES
-
- infra/chat/openssl-wasi.conf adds a BASE_unix target with little-endian,
- 32-bit bignums; cryptographic routines are unchanged. C uses -O2. No rand.patch,
- unconditional entropy-success patch, or custom RNG is applied. SHA-256 checks
- of all 5889 original regular files against the downloaded archive found none
- changed or missing after build; only the target configuration was added.
- OpenSSL 3.6.5 supports WASI getentropy in
- providers/implementations/rands/seeding/rand_unix.c.
-
- Configure selects --with-rand-seed=getrandom. WASI libc's entropy request
- reaches wasi_snapshot_preview1.random_get. The browser bridge services it
- exclusively through crypto.getRandomValues, splitting requests at 65536
- bytes. There is no Math.random, fixed seed, or success-on-failure fallback.
- The negative test replaces that API with a failure and requires rejection.
-
- Static builds disable assembly, async operations, automatic config loading,
- DSO/modules, POSIX I/O, secure-memory arenas, shared libraries, sockets,
- C stdio, thread pools, threads, and interactive console UI to avoid unsupported
- capabilities. Ordinary WASM linear memory provides no secure-erasure,
- locked-page, or delivered-JS protection. Rust output remains captured.
-
- _WASI_EMULATED_GETPID and libwasi-emulated-getpid supply SDK process-ID emulation.
- getuid/geteuid/getgid/getegid map to getpagesize, following common WASI port
- configuration. The browser module has no real OS user IDs or privilege boundary.
- These values supply neither randomness nor identity credentials. This portability
- assumption does not emulate general POSIX security.
-
- Bindings use SDK libclang/WASI sysroot, target wasm32-wasip1, and
- -fvisibility=default; without explicit visibility, bindgen omitted declarations.
- Rust's bundled WASI libc precedes SDK libraries; reversed order failed to link
- __wasi_init_tp. SDK libraries remain available for emulated-getpid. These were
- toolchain integration failures; generated cryptographic bindings were not edited.
-
-BROWSER CAPABILITIES
-
- The WASI bridge supplies clocks, browser CSPRNG, empty arguments/environment,
- EOF on stdin, and bounded in-memory stdout/stderr for this closed test.
- It supplies no real files, preopened directories, sockets, or process launch.
- Listed filesystem/network operations return NOTCAPABLE or BADF. Unknown
- imports fail before instantiation. Standard output is capped at 65536 chars.
-
- Denials prevent accidental host storage/network access, not malicious JS from
- the same server. Memory, clocks, randomness, and bridge imports remain trusted.
- The test closes Chromium after 120 seconds if execution stalls.
-
-VERIFICATION
-
- Native: the same probe passes using OpenSSL 3.6.5. It checks v6 generation,
- exact algorithm selection, protected-secret-key serialization/import,
- signed UTF-8 encryption to both participants, decryption by each, wrong
- password rejection, unexpected signer rejection, and ciphertext tampering.
-
- Browser: actual Chromium executes the OpenSSL WASI binary and passes the
- same checks, then repeats with a fresh module instance. One observed normal
- execution requested 1792 bytes across 72 browser CSPRNG calls. These counts
- are observations, not cryptographic requirements or entropy estimates.
- Removing secure randomness causes failure rather than a successful result.
- A modified module requesting an unknown host import is also rejected before
- instantiation. These negative tests require the expected failure reason.
-
- The harness observed no external requests, cookies, localStorage,
- sessionStorage, IndexedDB databases, or service-worker registrations; responses
- carry Cache-Control: no-store. This concerns only the probe. Browsers/OSes can
- retain memory, caches, crash dumps, swap, and restored state. No complete chat
- session existed at this checkpoint. Native tests, WASI build, and browser
- execution are separate gates; functional tests and scans are not cryptographic
- or side-channel audits.
-
-DEPENDENCY POLICY REVIEW
-
- RustSec reports no advisories for this resolved graph; no ignore was added.
- Bans on wildcards, unknown Git/registry sources, and unreviewed duplicates remain.
- cargo update cannot unify these upstream API generations. Exceptions match only
- exact older versions; advisory/license checks still apply. Cargo.lock pins both sides:
-
- block-buffer 0.10.4 vs 0.12.1
- crypto-common 0.1.7 vs 0.2.2
- digest 0.10.7 vs 0.11.3
- Sequoia's Argon2 0.5.3 -> BLAKE2 0.10 uses the older digest API.
- Root SHA-256 and the parser generator's SHA3 use the newer API.
-
- cpufeatures 0.2.17 vs 0.3.1
- Argon2 uses 0.2; root SHA-256 and parser-generator SHA3 use 0.3.
-
- generic-array 0.14.7 vs 1.4.5
- Argon2/BLAKE2 digest uses 0.14; SHA1 collision detection uses 1.x.
-
- getrandom 0.2.17 vs 0.4.3
- Sequoia's unknown-unknown WASM target dependencies declare 0.2;
- the root CLI's tempfile uses 0.4. The current browser target is WASI,
- whose OpenSSL entropy source is the bridge described above. The
- all-target graph is still audited rather than excluding that edge.
-
- hashbrown 0.15.5 vs 0.17.1
- petgraph uses 0.15 directly and indexmap 2.14 uses 0.17. Both are
- brought in by the parser generator at build time.
-
- itertools 0.13.0 vs 0.15.0
- shlex 1.3.0 vs 2.0.1
- bindgen uses older APIs; lalrpop and cc require the newer ones.
- These are build dependencies, not a justification to skip auditing.
-
- syn 2.0.119 vs 3.0.9
- bindgen/prettyplease and Windows support use syn 2. Root derive
- macros and other dependencies use syn 3. Changing that would require
- upstream migration or unrelated dependency downgrades.
-
- The only new license allowance is ISC for bindgen/clang's libloading.
- GNU-compatible licenses retain attribution; cargo-about covers the workspace.
- OpenSSL C and SDK are outside Cargo. Distributed browser artifacts must carry
- their original licenses/sources as required. OpenSSL LICENSE.txt is Apache-2.0.
- Rust notices alone do not cover external C/toolchain distribution obligations.
-
-EXTERNAL LIBRARY MAINTENANCE
-
- cargo-deny excludes external OpenSSL C and SDK. Review separately; rebuild as needed:
- https://openssl-library.org/news/timeline/
- https://openssl-library.org/news/vulnerabilities-3.6/
- Tested OpenSSL 3.6.5 is the 2026-09-29 security-patch release. A Rust audit
- does not guarantee this pinned C version's future security. Document a supported
- update policy before production deployment.
-
-REPRODUCE FROM THE STACK ROOT
-
- Native prerequisites: OpenSSL >=3.5 development headers/library, libclang,
- pkg-config, and the pinned Rust toolchain. For the cross build also provide
- a Linux x86_64 build host, Perl, make, curl, tar, and sha256sum. No root-owned
- packages or system settings are changed by the cross-build script.
-
- cargo test --locked -p stack-chat-crypto
- cargo run --locked -p stack-chat-crypto
- sh infra/chat/build-openssl-wasi.sh
- npm run build
- PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" node_modules/.bin/playwright install chromium
- PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:crypto
- cargo stack check
-
- The script pins versions/hashes, builds static OpenSSL under .tools/openssl-wasi,
- builds the Rust WASI command, and prints its SHA-256. It uses no unverified
- precompiled OpenSSL. Extracted sources may be reused; independent verification
- needs a fresh ignored tools/build tree. Downloads/test artifacts are not secrets.
-
- Build logs and complete dependency trees are local ignored diagnostics in
- .tools/chat-openssl-*.log and .tools/chat-openssl-duplicates.txt. The native
- result and the browser result must explicitly identify the OpenSSL backend.
-
-REMAINING WORK
-
- Observed WASI artifact SHA-256 on this development machine:
- 7fba6b96377619ef41f056d4027c4409107372e9300b2dd69be865e72892df79
- This is a diagnostic identifier, not a cross-host reproducibility promise.
-
- This checkpoint removed the Rust-backend blocker and demonstrated fixed
- algorithms in-browser. It was not a complete messenger, OpenSSL/WASI/JIT timing
- audit, LibreJS certification, or proof against malicious delivery. File format,
- authentication, server/database, quotas, hostile-input handling, pagination/counts,
- recovery, session lifecycle, and browser-state tests remained to implement.
diff --git a/docs/CHAT-PRODUCTION.txt b/docs/CHAT-PRODUCTION.txt
index 2507e07..6c901dc 100644
--- a/docs/CHAT-PRODUCTION.txt
+++ b/docs/CHAT-PRODUCTION.txt
@@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
PRODUCTION CHAT INTEGRATION
- Implemented on main. Builds/tests do not activate the VPS. Experimental;
+ Builds/tests do not activate the VPS. Experimental;
no independent cryptographic or application security audit. Read
CHAT-APPLICATION.txt limitations before private use.
@@ -57,7 +57,7 @@ BUILD ON THE WORKSTATION
isolated musl/GCC built-in and host Linux UAPI headers, excluding glibc.
Static OpenSSL has no dynamic providers and seeds through getrandom. The
native compatibility probe must pass. WASI uses separately verified SDK/OpenSSL
- inputs in CHAT-OPENSSL-MIGRATION.txt. Cryptographic algorithms stay fixed.
+ inputs in CHAT-CRYPTO.txt. Cryptographic algorithms stay fixed.
Inputs cache under .tools/. Version/source/flag changes require an OpenSSL
rebuild. Updates require upstream advisory review, every affected URL/hash,
@@ -81,7 +81,7 @@ LOCAL MODES AND ORIGIN BINDING
var/data/chat-local
Production:
- ./bin/stack up --production
+ ./bin/run up --production
https://CHAT_HOST/ with the configured HTTPS_PORT
var/data/chat
@@ -97,10 +97,8 @@ LOCAL MODES AND ORIGIN BINDING
DEPLOY AND CREATE THE REAL OWNER FILE
- Follow OPERATIONS.txt. Extract the verified release's new management binary
- into var/incoming/bootstrap; invoke with --root set to the current installation.
- The new binary understands its release schema, verifies the archive, and
- installs chat artifacts before startup.
+ Follow OPERATIONS.txt. Activate through the verified new management binary
+ with its matching runtime; activation installs chat artifacts before startup.
The host needs rootless Podman prerequisites, user linger, low-port permission,
DNS A/AAAA records for CHAT_HOST and www.CHAT_HOST, and the normal 80/443
firewall rules. Caddy obtains and renews public certificates automatically.
@@ -111,7 +109,7 @@ DEPLOY AND CREATE THE REAL OWNER FILE
failed containers. Complete matching-architecture releases need no compilation
or registry pull. Check all four services after activation:
- ./bin/stack status
+ ./bin/run status
Check HTTPS, HTTP-to-HTTPS and www redirects, the homepage Services entry,
/LIMITATIONS.txt, /javascript.html, and /SOURCE.tar.gz on the chat origin.
@@ -127,7 +125,7 @@ DEPLOY AND CREATE THE REAL OWNER FILE
BACKUP, RESTORE, AND KEY LOSS
A complete encrypted application export:
- ./bin/stack backup --include-images
+ ./bin/run backup --include-images
Copy .gpg and .sha256 off the VPS; local backups cannot survive VPS loss.
Configured recipients apply. Export includes one .env, online SQLite snapshots
@@ -137,9 +135,9 @@ BACKUP, RESTORE, AND KEY LOSS
Owner private file and GnuPG secret key need separate backups.
Restore to a new directory, verify, then explicitly start production mode:
- ./bin/stack restore /path/export.tar.gz.gpg /path/stack-restored
+ ./bin/run restore /path/export.tar.gz.gpg /path/stack-restored
cd /path/stack-restored
- ./bin/stack up --production
+ ./bin/run up --production
Restore defaults to local mode with fresh project names, avoiding replacement
of another installation. up --production reselects production database/origin.
@@ -152,10 +150,10 @@ BACKUP, RESTORE, AND KEY LOSS
reset deletes every conversation/message; it decrypts and recovers nothing.
Reset has no HTTP endpoint and requires filesystem/Podman access:
- ./bin/stack chat --owner-fingerprint
- ./bin/stack down
- ./bin/stack chat --reset-owner PASTE_CURRENT_OWNER_FINGERPRINT_HERE
- ./bin/stack up --production
+ ./bin/run chat --owner-fingerprint
+ ./bin/run down
+ ./bin/run chat --reset-owner PASTE_CURRENT_OWNER_FINGERPRINT_HERE
+ ./bin/run up --production
Reset requires a stopped server and matching full fingerprint. Root .env and
exports survive. Create a new production owner file; old visitor files reject
@@ -175,7 +173,7 @@ VERIFICATION
configuring fixture .localhost hostnames, WEB_BIND=127.0.0.1, HTTP_PORT=8181,
HTTPS_PORT=8443, and LOCAL_HTTP_PORT=8180:
- ./bin/stack up --production
+ ./bin/run up --production
CHAT_TEST_ROOT=/absolute/disposable/stack CHAT_TEST_ORIGIN=https://chat.fixture.localhost:8443 PLAYWRIGHT_BROWSERS_PATH=.tools/browsers npm run test:chat
Run from the source checkout for the harness and Playwright installation.
@@ -189,34 +187,3 @@ VERIFICATION
Production uses ordinary trusted public certificates with no TLS bypass.
Passing these checks demonstrates recovery and functional compatibility; it
does not establish absence of attacks or constitute an independent audit.
-
-RECORDED RESULTS, 2026-10-31
-
- The complete native workspace check passed: formatting, strict Clippy,
- 14 tests, private rustdoc, cargo-deny with no ignored advisories, and
- cargo-about generation. Static musl and WASI OpenSSL 3.6.5 probes passed
- without changing the required RFC 9980 algorithms.
-
- Direct development passed in Chromium. Packaged HTTPS passed independently
- in Chromium and Firefox with GNU LibreJS 7.21.1 active; its unlicensed-script
- control was rejected. Both packaged tests recovered a complete encrypted
- application export, including saved images, into a new directory, then
- reopened the unchanged owner/visitor files and decrypted the history.
- Export and checksum permissions were verified as 0600.
-
- Runtime inspection verified read-only root filesystem, zero effective
- capabilities, no chat host ports, and unless-stopped restart policy. Killing
- the disposable chat process with SIGKILL caused an automatic restart, and
- its owner fingerprint persisted. The supplied systemd user unit passed
- systemd-analyze verification; this did not reboot the real VPS.
-
- curl independently verified the fixture CA chain and hostname for canonical
- chat, www redirect, and homepage. The homepage Services limitations link
- resolved correctly. The source download matched the packaged source archive
- byte-for-byte and contained only .env.example, without actual .env.
-
- Upgrade testing started the 2026-10-30 three-service release, then activated
- the chat-enabled release through its new prebuilt management binary. All
- four services started, existing data and SearXNG secret persisted, and the
- new owner setup token was generated in the same root .env. These tests used
- disposable local data; they did not register a real production owner.
diff --git a/docs/CONFIGURATION.txt b/docs/CONFIGURATION.txt
index f0f8457..edd3d30 100644
--- a/docs/CONFIGURATION.txt
+++ b/docs/CONFIGURATION.txt
@@ -115,7 +115,7 @@ RENDER AND APPLY
Production uses prebuilt management command/images:
- ./bin/stack up --production
+ ./bin/run up --production
Running file servers can read rendered website updates. Listener, container
environment, and loaded Caddy changes need the service update above.
diff --git a/docs/GUIX.txt b/docs/GUIX.txt
index 05aa572..89f4c9a 100644
--- a/docs/GUIX.txt
+++ b/docs/GUIX.txt
@@ -37,9 +37,8 @@ BUILD LOCAL
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. Former -RR wrapper creates one-ID
- user namespace; privileged helpers cannot map host subordinate IDs there.
- Builder's canonical store could bypass relocation and hide this defect.
+ 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
@@ -48,7 +47,7 @@ MAKE CARRY FILE
Production: create consistent encrypted export; copy and verify it locally:
- ./bin/stack backup --include-images
+ ./bin/run backup --include-images
Bundle a trusted freshly checked release with that export:
@@ -132,23 +131,19 @@ CHECKS; DEPLOYMENT GATE
No strict Rust/Clippy/Rustdoc, typed ESLint, LibreJS or crypto gate weakened.
Existing documented lint/dependency exceptions remain unchanged.
- Clean-host test exposed missing distro container signature policy. Image load
- now supplies a temporary policy: reject by default; accept local Docker/OCI
- archives only. Export supplies a separate temporary policy accepting only
- containers-storage; registry transport remains rejected. Verified archive
- manifests and architecture/layer/image-ID locks
- still gate startup. Registry transport is not accepted by this load policy;
- global host policy stays unchanged. Policies are generated by AGPL Rust code;
- the foreign schema rejects comment/legal metadata, so no headerless source file
- is added. The initial failed VM retained decrypted data and pending intent.
-
- A second clean-host check found legacy backups had collapsed Caddy's /data
- and /config into one caddy/ tree. After manifest verification, backup restore
- moves credentials into data/caddy and autosave into config/caddy; bytes and
- permissions are retained. Conflicting old/new layouts are refused before
- publishing a destination. New exports copy /data/. and /config/. separately.
-
- Before production, in order:
+ 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.
@@ -160,48 +155,41 @@ CHECKS; DEPLOYMENT GATE
6. Fresh verified independent production backup; deploy only prebuilt release;
verify public routes/state, retain prior release/runtime and encrypted export.
- Rollback: stop new containers before old runtime binds production ports.
- Keep .env/var/data; never delete closure entries used by running processes.
-
-VERIFIED — 2026-11-01
-
- Offline KVM guest: official Debian 12 x86_64 image, no initial /gnu, Guix,
- Podman, Git or GPG. Host supplies only documented kernel/account prerequisites.
- Network restricted before bootstrap; readonly carry file plus separately
- forwarded GPG agent; no private key copied. Bootstrap and one restore command
- loaded four saved images and started all services without source builds.
-
- Strict HTTPS verified all four domains using original certificates; both WKD
- routes matched the original key. All 651 immutable snapshot files matched
- after legacy Caddy path normalization: 18 active TLS keys, 16 certificates.
- All 126 release source/binary files matched. Original .env, owner fingerprint
- and every canonical SQLite table matched. Production snapshot contained zero
- conversations/messages; original real-message decryption therefore untestable.
- Disposable Chromium/Firefox tests verified both participant identities,
- 54-message history, encrypted snapshots/recovery and GNU LibreJS 7.21.1.
-
- Full local release gates passed live npm/Rust advisory checks, strict fmt,
- Clippy, Rustdoc, typed ESLint and both TypeScript compilers; no suppressions.
- Required OpenSSL 3.6.5 hybrid suites, protected OpenPGP v6 identity, wrong-pass
- and tamper controls passed. Synthetic shell tests cover bootstrap collisions,
- interrupted recovery/retry, lock exclusion and child descriptor closure.
-
- Runtime: 500687877 bytes, SHA256:
+ 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
- Guix channel 230aa373f315f247852ee07dff34146e9b480aec; 113 store entries.
- Git 2.52.0, GnuPG 2.4.7, Podman 5.7.0, conmon 2.1.13, crun 1.25.1.
- Empty helper lists force PATH lookup, selecting packed conmon/crun. Ordinary
- host config stays unchanged. Rootless cleanup handles immutable user-owned
- temporary trees without changing symlink targets or the published pack.
+ 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
- Deployment gate above still applies to each carried artifact. Retain complete
- original worktree, binary/launcher, installed user unit and engine storage
- independently before activation. Reload the installed unit after switching to
- bin/run. For rollback stop Guix containers first, retain their engine tree,
- restore prior engine path, binary/launcher and unit; invoke prior activation
- with host PATH and Guix CONTAINERS_CONF overrides unset. Keep current .env and
- var/data. An old release alone does not remove a newer Guix launcher/runtime.
- Private evidence: var/guix-*.log and .tools/guix-production/. These are ignored
- and include private state hashes; do not publish them with public source.
+ Private evidence: var/guix-*.log and .tools/guix-production/; these include
+ private state hashes and stay ignored. Do not publish them with source.
diff --git a/docs/MAP.txt b/docs/MAP.txt
index de546b8..dc4da33 100644
--- a/docs/MAP.txt
+++ b/docs/MAP.txt
@@ -15,7 +15,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
FILE MAP
- Public tree: every tracked file plus pending Guix source/docs and this map.
+ Public tree: every tracked file and its use.
Static inventory; update when public paths change. Root: stack/.
Private/generated trees below list roles only; contents stay unlisted.
@@ -64,10 +64,10 @@ stack/ Personal service source, build recipes and recovery tooling.
| `-- README.txt Workspace crate roles and shared policy.
|-- docs/ Plaintext setup, policy, security and recovery guides.
| |-- CHAT-APPLICATION.txt Chat setup, protected files, user flows, recovery, security limits.
-| |-- CHAT-OPENSSL-MIGRATION.txt OpenSSL crypto migration evidence, dependencies, reviewed exceptions.
+| |-- CHAT-CRYPTO.txt Current crypto/backend builds, provenance, boundaries and dependency exceptions.
| |-- CHAT-PRODUCTION.txt Chat release packaging, HTTPS activation, owner setup, production recovery.
| |-- CONFIGURATION.txt Supported root settings, validation, public/private state boundaries.
-| |-- GUIX.txt Experimental Guix capsule, canonical-store bootstrap, gates and blockers.
+| |-- GUIX.txt Guix capsule, canonical-store bootstrap, recovery gates and rollback.
| |-- LINTING.txt Strict Rust/browser checks, generated-code limits, justified exceptions.
| |-- MAP.txt This public-file tree and private/runtime directory roles.
| |-- OPERATIONS.txt Stack development, encrypted backup/restore, host setup, deployment.
@@ -91,7 +91,7 @@ stack/ Personal service source, build recipes and recovery tooling.
| | | `-- supervisord.conf Run nginx and fcgiwrap inside the cgit container.
| | `-- chat/ Prebuilt chat image recipe.
| | `-- Containerfile Package prebuilt static chat server and web/crypto assets.
-| |-- guix/ Experimental Guix runtime and recovery capsule tooling.
+| |-- guix/ Guix runtime and recovery capsule tooling.
| | |-- bootstrap.sh Root-owned canonical closure installation; refuse managed/foreign stores.
| | |-- build.sh Capture Guix channels; build canonical runtime pack; probe Podman info.
| | |-- bundle.sh Validate paired release; wrap encrypted backup, release and bootstrap.
@@ -187,6 +187,6 @@ PRIVATE AND GENERATED PATHS — NOT PUBLIC SOURCE
not enumerate private files. Backups must keep .env and var/data; public Git
alone cannot recover services. See OPERATIONS.txt and GUIX.txt.
- Experimental host integration: /gnu/store holds canonical Guix packages;
+ Host integration: /gnu/store holds canonical Guix packages;
/gnu/.stack-runtimes holds root-owned package install records and lock.
Neither holds application configuration, databases or TLS keys.
diff --git a/docs/OPERATIONS.txt b/docs/OPERATIONS.txt
index 91ab10b..bf675be 100644
--- a/docs/OPERATIONS.txt
+++ b/docs/OPERATIONS.txt
@@ -15,84 +15,82 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
STACK OPERATIONS
- Root: ~/stack. Develop on local Linux; production: ops@23.155.44.67.
+ Root: ~/stack. Workstation: local Linux. Production: ops@23.155.44.67.
Public source: https://git.doan.luuduct.ai/stack.
- GNU Affero General Public License, version 3 or later.
- Experimental Guix capsule: GUIX.txt. Commands below use host Podman.
+ Production commands use bin/run, which selects packed Guix tools/config.
+ Direct bin/stack uses host tools; never mix engines against the same storage.
KEEP
- Full --include-images encrypted export: public source, compiled CLI, .env,
- persistent data, certificates, bare repos, working Git history if present,
- exact service images, checksum manifest. No old server filesystem needed.
- Excludes engine storage, caches, old exports, host packages/SSH keys/firewall,
- disks, OS configuration. Recreate host setup separately.
- Public Git alone cannot recover .env, live data, TLS keys, or var/ repos.
-
- Decryption needs GPG private key + passphrase. Keep key export/revocation
- certificate in KeePassXC; keep independent database copy. Server holds public
- key only. Never send private key/password through chat.
- Default recipient: 3346AA3150F8F16828BB0D14F8C66DF77AFECEA6.
+ For new-host recovery keep the carry file described in GUIX.txt: encrypted
+ application export plus matching checked release, Guix runtime and bootstrap.
+ Private state is encrypted; public tools/source are plaintext. Keep its trusted
+ checksum and an independent copy on another device/location.
+ It preserves one .env, databases, TLS keys, bare repos, working Git history,
+ binaries, browser assets, sources and exact service images. Engine storage,
+ caches, host accounts/SSH/firewall, disks and OS configuration are excluded.
+ Public Git alone cannot recover private state.
+
+ Keep GPG secret key/passphrase separately; default backup recipient:
+ 3346AA3150F8F16828BB0D14F8C66DF77AFECEA6
+ Keep an independent protected key export/revocation certificate. Chat owner
+ and visitor private identity files/passwords also need separate backups.
+ Never send private keys or passwords through chat.
DEVELOP LOCAL
- From ~/stack on workstation:
-
cargo stack dev
cargo stack status
cargo stack down
- Open http://localhost:8080, http://localhost:8080/git/,
- http://localhost:8080/search/. Alternate port:
-
- cargo stack dev --port 9080
-
- Local mode: loopback only, local links, unchanged public DNS, no public TLS.
- Missing .env: create with random SearXNG secret. Edit secrets there; never commit.
- infra/: service settings. web/: content. crates/: new backends; add workspace members.
- infra/services.toml declares services. Each gets only listed environment keys.
+ Open http://localhost:8080, /git/, /search/, http://chat.localhost:8080/.
+ Alternate port: cargo stack dev --port 9080. Local mode binds loopback only;
+ no public TLS or DNS changes. Missing .env is initialized with random secrets.
+ infra/services.toml lists each service's environment, mounts and health check.
- Project engine storage: var/podman/. Bare 'podman ps' uses another store.
- Inspect project containers from ~/stack:
+ Local development uses host Podman. Project storage is var/podman; bare
+ podman ps uses another store. From the local checkout:
podman --root "$PWD/var/podman/storage" --runroot "$PWD/var/podman/run" ps -a
- Keep both global options for 'logs CONTAINER', 'exec CONTAINER', 'images'.
- cargo stack status gives names. Never migrate var/podman/; export images.
+ Keep both global options for logs/exec/images. Use bin/run status on production.
+ Never transfer var/podman as backup; export saved images instead.
-CHECK LOCAL
-
- Install development audit tools once:
+CHECK AND BUILD LOCAL
cargo install --locked --version 0.20.2 cargo-deny --root .tools
cargo install --locked --version 0.9.3 --features cli cargo-about --root .tools
-
cargo stack check
+ cargo run --release --locked -- release
- Checks Guix shell/bootstrap fixtures (bubblewrap), formatting, workspace Clippy, tests, private
- rustdoc, advisories, duplicate versions, sources, licenses. cargo-about
- regenerates THIRD-PARTY-LICENSES.html. Format with cargo fmt --all.
- Dependency changes: update Cargo.lock deliberately; run full check; commit
- lockfile and license page. Never disable audit failures to ship.
+ Check covers TypeScript compilers/typed ESLint/npm audit, Guix shell fixtures,
+ formatting, strict workspace Clippy/tests/private Rustdoc, advisories, exact
+ duplicate exceptions and licenses. cargo-about regenerates the license page.
+ Dependency edits require deliberate lockfile review, full checks and notices.
+ Never waive failures. Format with cargo fmt --all.
-BACK UP PRODUCTION
+ Build the Guix pack first when adding/changing runtime tools: GUIX.txt.
+ Release builds static Rust/chat WASM, corresponding source, cgit/chat images
+ and complete image export. Output: dist/stack-release-TIMESTAMP.tar.gz and
+ .sha256. Actual .env, live data, .git and prior backups are excluded.
+ Build/test locally; no compiler or registry pull is needed on the matching
+ x86_64 production host. Restore/inspect a disposable release before deployment.
- On VPS:
+BACK UP PRODUCTION
cd ~/stack
- ./bin/stack backup --include-images
+ ./bin/run backup --include-images
Briefly pauses Caddy/cgit/SearXNG during file copy; resumes before compression
- and encryption. Chat stays live for SQLite online backup. Snapshots include
- chat and chat-local; exclude raw journals and server locks.
- Verifies public recipient fingerprint, checks bare repos, saves exact images:
+ and encryption. Chat stays live for SQLite online backup. Export includes
+ chat/chat-local snapshots; excludes raw journals and server locks.
+ Verifies recipient fingerprint, bare repos and exact image identities.
+ Output has restrictive permissions:
var/exports/stack-TIMESTAMP.tar.gz.gpg
var/exports/stack-TIMESTAMP.tar.gz.gpg.sha256
- Restrictive permissions. Removes temporary plaintext on success/handled errors.
- No scheduled backup. Choose retention and off-host copies yourself.
- On workstation, copy actual filenames printed above:
+ Copy both files off the VPS; substitute actual filenames:
umask 077
mkdir -p ~/stack/var/backups/production
@@ -101,166 +99,118 @@ BACK UP PRODUCTION
cd ~/stack/var/backups/production
sha256sum -c stack-TIMESTAMP.tar.gz.gpg.sha256
- Replace TIMESTAMP. Checksum detects corruption; keep trusted export + checksum.
- Keep another copy on separate device/location. After verification, optionally
- delete matching VPS export files only. Never delete var/data or .env.
- ./bin/stack backup without images needs registry/build access for local startup;
- not standalone production recovery. Prefer --include-images.
-
-RESTORE ON LINUX
-
- Host tools: Podman, Git, GnuPG, rootless networking prerequisites.
- Target user needs >=65536 subordinate UIDs/GIDs. Use distro-supported Podman.
- Prebuilt binary/images: x86_64 Linux (linux/amd64).
- Copy export + checksum; verify checksum first. If needed, import key:
-
- gpg --import private-key-backup.asc
-
- GnuPG prompts locally. Never pass passphrase through arguments or .env.
- Existing compatible CLI:
-
- ./bin/stack restore /path/to/stack-TIMESTAMP.tar.gz.gpg ~/stack-restored
-
- Destination must not exist. Restore decrypts privately; rejects unsafe paths
- and links; checks every manifest entry; assigns unique project identity;
- defaults local; starts nothing; never overwrites existing installation.
+ No scheduled backup. Choose retention/off-host copies; remove matching VPS
+ exports only after verification. Never delete .env or var/data.
+ Export without --include-images is not standalone production recovery.
+ Pair the verified export with a checked release using infra/guix/bundle.sh;
+ the resulting carry file restores with the matching manager/source/runtime.
- No CLI/toolchain yet: bootstrap trusted export with GnuPG/tar. Bash/fish:
+RECOVER ON LINUX
- 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
-
- Bootstrap executes extracted code: use only your trusted export.
- Rust restore validates full archive. After success, delete ~/stack-bootstrap:
- it holds decrypted archive. Retain encrypted backup.
- Test restored stack locally first:
-
- cd ~/stack
- ./bin/stack up --local --port 8080
- ./bin/stack status
+ Follow GUIX.txt for host prerequisites and GPG key access. On a fresh x86_64
+ Linux host, as the service user, with a new destination:
- Saved images load automatically; image lock verifies identities. Matching
- architecture needs no registry pull/build. Check homepage, Git, search, WKD.
- ./bin/stack down before production setup.
- Same restore accepts release archives: source/binary/images, no .env/live data.
- Starting restored release creates fresh state; cannot recover lost data.
+ tar -xzf recovery.tar.gz
+ sudo ./stack-recovery/bootstrap
+ ./stack-recovery/restore /home/ops/stack
-NEW PRODUCTION HOST
+ Capsule verifies/decrypts the export, activates its paired release and starts
+ four services. A failed activation can resume with the same restore command.
+ Keep hostname/origin and identity files unchanged; repoint DNS when IP changes.
+ Recovery does not configure host SSH, firewall, account or boot startup.
- Restore into target user's ~/stack. Debian: review infra/host-setup.sh;
- replace 'ops' if needed; run:
+ A raw format-2 export can be restored for inspection on a host that already
+ has compatible tools and any required Guix closure:
- sudo sh ~/stack/infra/host-setup.sh
+ ./bin/run restore /path/export.tar.gz.gpg /path/stack-restored
- Installs Podman/network tools, checks existing subordinate ranges, enables
- user linger, sets:
+ Raw restore validates all manifest paths/hashes, defaults local mode with a
+ fresh project identity, starts nothing and refuses an existing destination.
+ Public release archives restore similarly but contain no private data.
+ An old raw export may lack the launcher/runtime; use a paired capsule for
+ complete recovery. Test owner/visitor identities and history after recovery.
- net.ipv4.ip_unprivileged_port_start=80
+HOST AND BOOT SETUP
- System-wide rootless ports >=80; ports <80 stay privileged. SSH/firewall unchanged.
- Missing ranges: allocate unused ranges through distro account tools; rerun.
- Other distros: equivalent packages, sysctl, linger. Supplied boot unit needs
- systemd user manager.
-
- Host/provider firewall: allow 22/tcp, 80/tcp, 443/tcp, 443/udp for applicable
- IPv4/IPv6. Never publish backend container ports. Preserve SSH; verify address
- before changing DNS. A: new IPv4. AAAA: working new IPv6 only; remove stale AAAA.
- Hosts live in root .env; edit and reapply rendered settings. infra/Caddyfile
- is public template. Keys/render-only commands: CONFIGURATION.txt.
- Preserve www aliases and openpgpkey names pointing to canonical hosts.
+ Host/provider firewall: retain SSH and allow HTTP 80/tcp, HTTPS 443/tcp+udp
+ for applicable IPv4/IPv6. Never publish backend container ports. A points to
+ the new IPv4; publish AAAA only with working IPv6. Keep www/WKD aliases.
+ Rootless IDs, mapping helpers, runtime directory and low-port sysctl: GUIX.txt.
+ Debian host-tools fallback: infra/host-setup.sh installs distro Podman and
+ prerequisites. Guix supplies its own Podman/network tools.
cd ~/stack
- ./bin/stack up --production
-
- Production binds 80/tcp, 443/tcp, 443/udp. Caddy issues/renews certificates,
- redirects HTTP to HTTPS and www to canonical URLs. State: var/data/caddy/.
- Public issuance needs working public DNS/connectivity.
- Enable boot startup:
-
+ ./bin/run up --production
systemctl --user link ~/stack/infra/stack.service
systemctl --user daemon-reload
systemctl --user enable --now stack.service
systemctl --user status stack.service
- Unit assumes ~/stack; edit paths for other location. Current unit uses bin/run:
- confirm launcher exists before enabling. Linger keeps user service alive
- without login. Containers use unless-stopped restart policy.
+ Administrator enables linger for the service user. Unit assumes ~/stack;
+ adapt paths for another location. Caddy stores credentials in var/data/caddy,
+ issues/renews public certificates and redirects HTTP/www. ACME needs public
+ DNS/connectivity. Containers use unless-stopped restart policy.
- Optional former VPS SSH Git path; administrator only:
+ Current VPS SSH Git compatibility path /srv/git/stack links to
+ /home/ops/stack/var/data/repos/stack. Preserve it for existing clone URLs;
+ public HTTPS Git needs no host symlink.
- sudo install -d -m 755 /srv/git
- sudo ln -s /home/ops/stack/var/data/repos/stack /srv/git/stack
+DEPLOY A CHECKED RELEASE
- Never overwrite existing repo. Current VPS already has this layout link.
- Public HTTPS Git needs no host symlink.
+ Review/stage public changes, sign and push the current branch:
-BUILD AND DEPLOY
-
- Develop/check on workstation:
-
- cd ~/stack
- cargo stack release
-
- Release reruns full check; builds static Rust CLI/chat + browser WASM;
- packages matching source; builds cgit/chat containers; saves configured images.
- Output: dist/stack-release-TIMESTAMP.tar.gz + .sha256.
- Excludes .env, live data, .git history, previous backups.
- Restore into fresh local test directory; up --local on free port; inspect
- four services; stop test. Commit/push reviewed public changes:
-
- git add -A
git diff --cached --stat
git commit -S3346AA3150F8F16828BB0D14F8C66DF77AFECEA6 -m 'Describe the change'
- git push origin main
-
- Review staged files. .gitignore excludes secrets/runtime/artifacts.
- Use RSA-2048 signed commits; author/key: POLICY.txt.
- Deploy archive, never git pull. Copy actual filenames:
+ git push origin HEAD
- 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/
+ Do not deploy with git pull. Take a verified independent export first; retain
+ complete old worktree/binary/unit, var/state.json, image-lock.json, images.tar
+ and prior engine storage for rollback. Guix upgrade/rollback rules: GUIX.txt.
+ Copy the checked public release and checksum to ~/stack/var/incoming.
+ Before activation install any new closure through its verified capsule
+ bootstrap; an existing managed Guix store needs separate planning.
- On VPS:
+ Bash on VPS, after that bootstrap; substitute actual TIMESTAMP:
- cd ~/stack/var/incoming
- sha256sum -c stack-release-TIMESTAMP.tar.gz.sha256
+ (
+ set -eu
cd ~/stack
- mkdir -p var/incoming/bootstrap
- tar -xzf var/incoming/stack-release-TIMESTAMP.tar.gz -C var/incoming/bootstrap stack/bin/stack
- var/incoming/bootstrap/stack/bin/stack --root "$PWD" activate var/incoming/stack-release-TIMESTAMP.tar.gz
- ./bin/stack status
- rm -rf -- var/incoming/bootstrap
-
- Use verified NEW release binary: old binary may reject new service schema.
- Activation verifies archive/platform, replaces public source/binary, loads
- exact images, preserves .env/var/data, replaces running containers. No compiler,
- Rust build, image build, or private-config overwrite. Expect brief interruption.
- Take verified export before changes needing recovery. Verify production;
- optionally delete corresponding incoming archive/checksum.
-
-RECOVERY LIMITS; ROTATE KEYS
-
- Application export is not bootable VPS image. Accounts, SSH, firewall,
- packages remain host duties. Other CPU architecture needs native release.
- Registry needed only without saved images or when rebuilding another target.
- State: files, Git repos, Caddy, encrypted SQLite chat. SQLite uses online backup.
- Recovery proof: reopen BOTH participants' unchanged identities; decrypt history.
- See CHAT-PRODUCTION.txt. Future databases need supported snapshot/recovery.
-
- New backup recipient: publish public key, update default fingerprint, test
- encryption/restore. Single export: --recipient FULL_FINGERPRINT --key /path/public-key.asc.
- Retain old secret keys while any retained export needs them.
- Restore accepts verified format-2 application archives.
+ STACK_ROOT=$PWD
+ STACK_RELEASE=$PWD/var/incoming/stack-release-TIMESTAMP.tar.gz
+ (cd var/incoming && sha256sum -c stack-release-TIMESTAMP.tar.gz.sha256)
+ STACK_STAGE=$(mktemp -d "$PWD/var/incoming/.activate.XXXXXX")
+ tar -xzf "$STACK_RELEASE" -C "$STACK_STAGE" stack/bin/stack stack/bin/guix-runtime.tar.gz stack/infra/guix/containers.conf
+ mkdir "$STACK_STAGE/runtime"
+ tar -xzf "$STACK_STAGE/stack/bin/guix-runtime.tar.gz" --no-same-owner -C "$STACK_STAGE/runtime"
+ env PATH="$STACK_STAGE/runtime/bin:$PATH" CONTAINERS_CONF="$STACK_STAGE/stack/infra/guix/containers.conf" "$STACK_STAGE/stack/bin/stack" --root "$STACK_ROOT" activate "$STACK_RELEASE"
+ systemctl --user daemon-reload
+ systemctl --user restart stack.service
+ ./bin/run status
+ chmod -R u+w -- "$STACK_STAGE"
+ rm -rf -- "$STACK_STAGE"
+ )
+
+ Use the NEW manager and matching runtime. Activation verifies archive/platform,
+ replaces public source/binaries/images, preserves .env/var/data/.git and
+ replaces containers. Expect interruption. The installed unit may already link
+ to infra/stack.service; reload its definition rather than copying over itself.
+ If activation fails, retain staging/data and diagnose before retry or rollback.
+ Verify public routes, TLS/WKD, owner and database before removing incoming files.
+
+RECOVERY LIMITS; KEY ROTATION
+
+ Other CPU architectures need native release builds. Host DNS/PTR, firewall,
+ accounts and SSH remain outside the carry file. New databases need consistent
+ snapshot/recovery support before inclusion. Preserve both participants' chat
+ files/passwords; server backups cannot replace them. CHAT-PRODUCTION.txt.
+
+ For a new backup recipient, publish its public key, update root .env and test
+ encryption/recovery. One export can select --recipient FULL_FINGERPRINT and
+ --key /path/public-key.asc. Keep old secret keys while retained exports need them.
LICENSE PAGE; REDIRECTS
- cargo-about generates web/THIRD-PARTY-LICENSES.html. Caddy serves canonical
- https://doan.luuduct.ai/THIRD-PARTY-LICENSES.html; other deployments use SITE_HOST.
- Website/chat links share page; local preview uses local main-site origin.
- cgit Source/Raw expose generated file; chat /THIRD-PARTY-LICENSES.html redirects
- to canonical page. Source exports retain offline license texts.
- Homepage /message and /message/ redirect to current chat origin.
+ cargo-about generates web/THIRD-PARTY-LICENSES.html. Caddy serves it on the
+ main-site origin; chat redirects its former local license URL there. Source
+ exports retain offline license texts. cgit Source/Raw expose the generated file.
+ Homepage /message and /message/ redirect to the configured chat origin.
diff --git a/docs/POLICY.txt b/docs/POLICY.txt
index 747b774..c3dffae 100644
--- a/docs/POLICY.txt
+++ b/docs/POLICY.txt
@@ -56,7 +56,7 @@ STRICTNESS
cargo-deny rejects duplicate versions, wildcard requirements, unknown
registries/Git sources, and yanked versions. Chat has exact-version duplicate
- exceptions reviewed in CHAT-OPENSSL-MIGRATION.txt. Ignore no RustSec advisory.
+ exceptions reviewed in CHAT-CRYPTO.txt. Ignore no RustSec advisory.
Scope first-party AGPL/required dependency license allowances per package.
Allowlist retains requested permissive licenses and Zlib; unused allowances
may produce informational audit warnings.
@@ -78,7 +78,7 @@ PORTABILITY
packages, subordinate UID/GID allocations, one low-port sysctl, user linger,
systemd user-service link. Host SSH keys, firewall policy, accounts, and disks
are host administration, excluded from application exports.
- Experimental Guix capsule also installs canonical /gnu/store packages and
+ Guix capsule bootstrap installs canonical /gnu/store packages and
/gnu/.stack-runtimes install records. Private application state stays in stack/.
Releases include static x86_64 Linux management/chat commands, WASM browser
diff --git a/src/app/files.rs b/src/app/files.rs
index e96e5a4..8b4f70b 100644
--- a/src/app/files.rs
+++ b/src/app/files.rs
@@ -43,7 +43,6 @@ pub(super) const SOURCE: &[&str] = &[
".cargo",
".gitignore",
".gitattributes",
- ".forgejo",
"src",
"tests",
"crates",
diff --git a/src/app/project.rs b/src/app/project.rs
index ac55ff3..bb7fa5a 100644
--- a/src/app/project.rs
+++ b/src/app/project.rs
@@ -47,14 +47,14 @@ pub(super) struct Settings {
pub(super) online: bool,
}
-#[derive(Debug, Deserialize, Serialize)]
+#[derive(Debug, Deserialize)]
#[serde(crate = "serde", deny_unknown_fields)]
pub(super) struct Configuration {
pub(super) format: u32,
pub(super) services: BTreeMap<String, Service>,
}
-#[derive(Debug, Deserialize, Serialize)]
+#[derive(Debug, Deserialize)]
#[serde(crate = "serde", deny_unknown_fields)]
pub(super) struct Service {
pub(super) image: String,
@@ -73,7 +73,7 @@ pub(super) struct Service {
tmpfs: Vec<String>,
}
-#[derive(Debug, Deserialize, Serialize)]
+#[derive(Debug, Deserialize)]
#[serde(crate = "serde", deny_unknown_fields)]
struct Mount {
source: String,