summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/CHAT-APPLICATION.txt403
-rw-r--r--docs/CHAT-OPENSSL-MIGRATION.txt234
-rw-r--r--docs/CHAT-PRODUCTION.txt222
-rw-r--r--docs/CONFIGURATION.txt143
-rw-r--r--docs/LINTING.txt167
-rw-r--r--docs/MAP.txt175
-rw-r--r--docs/OPERATIONS.txt347
-rw-r--r--docs/POLICY.txt121
-rw-r--r--docs/TYPESCRIPT.txt125
9 files changed, 1680 insertions, 257 deletions
diff --git a/docs/CHAT-APPLICATION.txt b/docs/CHAT-APPLICATION.txt
new file mode 100644
index 0000000..388c677
--- /dev/null
+++ b/docs/CHAT-APPLICATION.txt
@@ -0,0 +1,403 @@
+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/>.
+
+
+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.
+
+WHAT EXISTS
+
+ Rust HTTP server and Rust/WASI cryptography. Strict TypeScript compiles the
+ interface and capability-limited WASI host to small, readable, free classic
+ JavaScript. See TYPESCRIPT.txt for build/licensing. Text uses textContent.
+ Existing services, including SearXNG, are unchanged.
+
+ Crypto download shows an ASCII bar, percentage, and received/total MiB;
+ unknown totals show received MiB. A 45-second stall aborts with reload
+ instructions; slow transfers continue while bytes arrive. Chat connection has
+ a separate status. No browser cache or storage was added.
+
+ CREATE NEW CHAT generates a purpose-specific identity locally and downloads
+ its protected file. Reopen that exact file with its password before first
+ authentication/message. OPEN CHAT FILE restores access on later visits.
+ No username/password account; messages never require updating the stable file.
+ The owner has a separate identity/inbox. The canonical RSA OpenPGP key does
+ not encrypt chat.
+
+ History defaults to zero messages. Authenticated counters show total, sent,
+ and received messages. Choose 0 to N recent messages, where N is the current
+ conversation total, or page backwards for older messages. Counts refresh when
+ the history input is focused, history is requested, or a send is acknowledged.
+ Large selections are fetched and verified in batches of at most 50; that is
+ a per-request resource bound, not a user-visible 50-message selection limit.
+ Reload, lock, or leaving clears the application's in-memory references and
+ visible messages. A separate authenticated session expires after 30 minutes.
+ There is no automatic background polling, notification, or remote logout.
+ Refresh the owner inbox and load history to fetch newly arrived messages.
+
+LOCAL PREREQUISITES AND COMMANDS
+
+ Start at the repository root on main. Native development needs Rust, libclang,
+ OpenSSL >=3.5 development libraries, C compiler, pkg-config, make, Perl, curl,
+ and tar. Checked OpenSSL: 3.6.5. WASI builds target x86_64 GNU/Linux and download
+ hash-verified WASI SDK 34.0/OpenSSL sources. Toolchains stay off the VPS.
+
+ Build and initialize once:
+
+ sh infra/chat/build-openssl-wasi.sh
+ cargo stack licenses
+ sh infra/chat/build-source.sh
+ cargo stack chat --init
+
+ Initialization privately creates/appends CHAT_SETUP_TOKEN and CHAT_ORIGIN
+ in the single root .env, preserving an existing token and SearXNG settings.
+ Root .env.example lists both; never commit .env or create crate-specific files.
+ CHAT_BIND and public navigation use the shared loader; see CONFIGURATION.txt.
+
+ For Rust-only edits, sh infra/chat/build-browser.sh reuses verified OpenSSL/SDK
+ inputs. Source/version/configuration/flag changes require an OpenSSL rebuild.
+ WASI linear memory is capped at 256 MiB.
+
+ Start:
+
+ cargo stack chat
+
+ Open http://127.0.0.1:9080/ and choose owner-only OWNER SETUP,
+ or use http://127.0.0.1:9080/#setup.
+
+ Read CHAT_SETUP_TOKEN from root .env locally; paste into setup. Choose a
+ display name and strong unique password; save, reopen, and unlock the file.
+ This registers the owner public identity and disables token-based setup.
+ The token grants neither login nor key recovery. OWNER SETUP remains visible
+ with existing-file/reset instructions; it never silently replaces a key.
+ Never publish private file/password. Keep both in KeePassXC with a separately
+ protected database backup.
+
+ Create a visitor chat in another window at http://127.0.0.1:9080/.
+ Identity stays in each page's memory; browser login is not shared. Control-C
+ stops development. Restart invalidates sessions, preserving identities,
+ conversations, counts, and ciphertext.
+
+ Direct development requires loopback and an exactly matching bind/origin.
+ Production uses a private listener behind Caddy HTTPS, with explicit
+ CHAT_HOST/HTTPS_PORT origin; forwarded headers cannot choose it. Never expose
+ direct development through a public proxy.
+
+OWNER FILE LOSS AND BACKEND RESET
+
+ Setup tokens grant registration, not private-key recovery. A new identity
+ cannot decrypt old messages if the owner file/backups are lost. Reset clears
+ all messages, encrypted profiles, conversations, counters, and owner registration.
+ Root .env and exports survive. This does not forensically erase backups,
+ snapshots, or OS copies.
+
+ 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
+ before destructive reset.
+
+ Read the current public fingerprint, even if you have lost the private file:
+
+ cargo stack chat --owner-fingerprint
+
+ Stop the chat server with Control-C in its terminal, then explicitly pass
+ that full fingerprint to the destructive reset command:
+
+ cargo stack chat --reset-owner PASTE_CURRENT_OWNER_FINGERPRINT_HERE
+
+ Mismatched fingerprints refuse without deletion. Server/reset share a lock;
+ direct development also requires a free loopback service port, protecting
+ older running processes without the lock. Online snapshots and fingerprint
+ reads remain available during operation.
+
+ Restart:
+
+ cargo stack chat
+
+ Use OWNER SETUP and existing root .env CHAT_SETUP_TOKEN; generate, save, and
+ reopen a fresh file. Back up file/password in KeePassXC. Visitors also need
+ fresh files: old owner-key pins correctly reject replacement. Reset cannot
+ prove continuity with the lost key.
+
+ An owner file found later may recover old conversations from an encrypted
+ backup in isolation. Reset retains exports and never substitutes new keys
+ into old ciphertext or visitor files.
+
+FILE AND MESSAGE CRYPTOGRAPHY
+
+ OpenPGP v6, ML-KEM-1024 + X448 encryption, ML-DSA-87 + Ed448 signatures,
+ and AES-256 content encryption. There is no RSA, classical-only, or alternative
+ post-quantum fallback. Each certificate has exactly the expected two keys and
+ a fixed purpose-only user ID. Names are not placed in public certificate IDs.
+ The server rejects uploaded secret-key material and unsupported certificates.
+
+ New identity JSON uses indentation and 80-character Base64 ciphertext chunks;
+ the last may be shorter. JSON punctuation/indentation adds width. Readers join
+ chunks before decoding. Private fields stay encrypted. This application envelope
+ is not OpenPGP ASCII armor. Its identifier is authenticated as associated data:
+
+ stack-chat-file/v2;argon2id-m65536-t3-p1;aes256-gcm
+
+ Existing compact v1 files with one ciphertext string remain supported:
+
+ stack-chat-file/v1;argon2id-m65536-t3-p1;aes256-gcm
+
+ v1 retains its original associated data. The tag binds versions: relabeling
+ or reshaping fields cannot convert files. Existing files need no rewrite;
+ old readers reject v2. Algorithms/KDF parameters stay fixed.
+
+ Argon2id version 0x13 uses 65536 KiB, three passes, one lane, and a 32-byte
+ derived key. A fresh CSPRNG salt of 16 bytes and AES-256-GCM nonce of 12 bytes
+ are generated for each file; the authentication tag is 16 bytes. These KDF
+ parameters are fixed by the format, not accepted from an untrusted file.
+ The outer JSON contains only format, salt, nonce, ciphertext, and tag, encoded
+ in base64 where applicable. The encrypted payload contains the origin, name,
+ pinned owner public certificate, and the secret OpenPGP certificate.
+ Secret key material inside that already encrypted envelope is unprotected
+ only in process memory. Never extract or save the decrypted payload.
+
+ Passwords must be 12 to 1024 UTF-8 bytes. This is a length floor, not a claim
+ of sufficient entropy: choose a strong unique passphrase. File parsing and
+ parameter checks precede the expensive KDF. Import is limited to 256 KiB.
+ Wrong passwords, malformed files, authentication failures, and unsupported
+ formats return a generic error. Mutable KDF keys, plaintext envelopes, and
+ private serialization buffers are zeroized where practical.
+
+ Unlock checks exact origin and owner pin. Owner creation pins its own public
+ certificate; visitors pin the certificate advertised over the trusted origin
+ connection. First visits still trust served application/TLS, without an
+ out-of-band identity guarantee.
+
+ Each text message is signed, then encrypted for both participants. A signed
+ plaintext JSON object binds v=1, kind=message, exact origin, conversation ID,
+ sender fingerprint, CSPRNG message ID, and UTF-8 text. Receivers compare the verified signer fingerprint to the conversation
+ record and expected sender, then verify every payload binding before displaying text. Encrypted profile
+ objects bind kind=profile, origin, conversation, sender, and display name.
+ The Rust encryptor also enforces the pinned recipient, sender, origin, and
+ conversation before signing; the server cannot select an arbitrary recipient
+ by changing a public-key field. Display names remain labels, not proof of a
+ unique person. The interface shows full conversation fingerprints.
+ Message and profile formats are distinct to prevent cross-purpose replay.
+ Compression, unsigned messages, an unexpected content cipher, and malformed
+ recipient sets are rejected. Private certificates are never sent to the API.
+
+AUTHENTICATION AND STORAGE
+
+ One JSON API endpoint /api accepts same-origin POST requests only; no CORS,
+ cookie authentication, URL credentials, or URL conversation identifiers.
+ A fresh random challenge binds protocol, exact origin, full fingerprint,
+ random nonce, an expiry, and the pinned owner fingerprint. It expires after 60 seconds and is consumed on
+ the first verification attempt, including a failed signature attempt.
+ A detached purpose-specific signature proves possession of the identity.
+ A random bearer session token then authorizes API requests for 30 minutes.
+ Session tokens and pending challenges exist only in server and page memory.
+ The owner can access the inbox and any conversation; a visitor can access
+ only the conversation indexed by their own verified fingerprint.
+
+ Data lives under var/data/chat/chat.sqlite, in a private 0700 directory with
+ a 0600 database. SQLite uses foreign keys, FULL synchronous transactions, and
+ DELETE journaling. Durable data consists of the owner public certificate,
+ visitor public certificates, an encrypted name/profile, encrypted messages,
+ sender fingerprints, random message IDs, sequence numbers, and counts derived
+ from rows. Names and message text are not stored as plaintext. There are no
+ application access logs or deliberately retained client IP addresses.
+ SQLite journals contain the same public metadata and ciphertext, not private
+ keys or message plaintext. OS diagnostics and a future reverse proxy need
+ their own logging review before deployment.
+
+ Transactional sends deduplicate exact ciphertext/message-ID/sender/conversation
+ retries, returning the original sequence/counts. Reused IDs with changed data
+ are rejected. This handles lost responses after storage. Keep the page unlocked
+ to retry; reload loses pending ciphertext/draft, so inspect history before
+ resending. Retries are deliberate, never automatic.
+
+ Limits: 8192 UTF-8 bytes per text, 16384 bytes per signed payload, 65536 base64
+ characters per stored ciphertext, 50 history rows per API request, 1000 visitor
+ identities, 10000 messages per conversation, and a one-billion-character
+ total message-ciphertext budget. At most 60 authentication challenges per
+ global minute window, 128 pending challenges, 256 active sessions, and four
+ queued cryptographic/database tasks are admitted. API bodies are at most
+ 256 KiB. Database access is serialized. These are basic bounds, not robust
+ protection against determined traffic, account creation, CPU, or disk attacks.
+ Public-key and profile storage, indexes, journals, and backups add overhead to
+ the ciphertext budget. Monitor actual disk use. No public abuse controls,
+ deletion UI, in-place key rotation UI, blocklist, or password-change UI are
+ implemented. Lost-owner recovery uses the explicit backend reset above.
+
+BROWSER STATE AND LIMITATIONS
+
+ No application cookies, localStorage, sessionStorage, IndexedDB, service
+ workers, analytics, external scripts, fonts, or third-party requests. Assets
+ and API responses use no-store. Browser fetches omit credentials. Private
+ file/password bytes travel only to a local worker and Rust/WASM memory.
+ Each crypto worker terminates after its operation or a timeout. Lock and
+ pagehide terminate remaining workers, clear drafts/messages/credentials,
+ clear input controls, and revoke download URLs. Restored pages clear state.
+
+ This means no intentional persistent application state, not literally zero
+ traces. Browser password managers, downloads, history, caches outside the
+ application's control, memory copies, swap, extensions, OS crash dumps,
+ screenshots, and a restored process can retain data. JavaScript strings,
+ garbage collection, compiler/library copies, and WASM memory cannot be
+ guaranteed erased. A 90-second worker timeout and 256 MiB WASM linear-memory maximum bound
+ each cryptographic instance. Browser/host copies add memory overhead. A malformed file or message can still expose defects in
+ the Rust, C, WASM, or browser stack. OpenSSL uses C/FFI; Rust's unsafe-code
+ prohibition does not make dependencies memory-safe. Native server and WASI
+ OpenSSL updates must both be reviewed and rebuilt when advisories warrant.
+
+ A stolen file enables offline password guessing. A stolen file and password,
+ or an unlocked identity, enable impersonation and decryption of accessible
+ historical ciphertext. There is no username recovery, forward secrecy, or
+ automatic key ratchet. Losing the owner file/password loses owner access to
+ every conversation encrypted to it. A server rollback can omit messages;
+ signatures do not prove a complete transcript. Encrypted messages reveal
+ metadata, lengths, ordering, and interaction timing. The server sees client
+ addresses during connections. Bearer-token theft permits API use until
+ expiry or lock, though decrypting or signing still requires the private key.
+
+ Malicious servers can replace JavaScript/WASM and steal passwords/plaintext
+ on the next visit; key pins and LibreJS cannot prevent it. Shared-host/OS
+ compromise, extensions, browser bugs, dependency attacks, malicious friends,
+ and denial of service remain risks. Purpose labels cannot prevent key export
+ or reuse. This is not a SimpleX equivalent.
+
+BACKUP, EXPORT, AND RESTORE
+
+ cargo stack backup uses SQLite online backups, not ordinary live database/
+ journal copies. chat and chat-local get separate consistent snapshots while
+ running; chat stays unpaused. Other Podman services retain pause/copy/resume.
+ Chat snapshots are atomic, without a cross-service transaction. Exports
+ include root .env and must stay private/encrypted.
+
+ For a chat-only consistent database export:
+
+ cargo run --locked -p stack-chat-server -- --root . --snapshot var/exports/chat-snapshot.sqlite
+
+ Create var/exports privately first; destination must not exist. Standalone
+ snapshots contain ciphertext/public metadata, without full-stack GPG encryption.
+ Encrypt before off-machine storage. Browser private identities are absent;
+ server backups cannot recover lost identity files/passwords.
+
+ Restore a full stack using the existing README.txt backup/restore instructions.
+ For a chat-only restore, stop the chat server, put the verified snapshot at
+ var/data/chat/chat.sqlite under the destination root, set directory mode 0700
+ and database mode 0600, restore the single root .env privately, and rebuild
+ the corresponding source/WASM on the development machine. Start locally and
+ open the unchanged owner or visitor file with its password. Changing the
+ origin deliberately makes old files fail their origin binding; there is no
+ origin migration UI yet. Do not edit an encrypted file by hand.
+
+ Keep an encrypted backup off-VPS. After recovery, test owner/visitor identities,
+ counts, and decryption. Keep private files/passwords in KeePassXC with a protected
+ database backup, never source control/public archives. Local deletion or file
+ loss does not delete stored ciphertext, exports, or participants' copies.
+
+INTERFACE AND SITE STYLE
+
+ web/styles.css supplies red ASCII logo, 80-column desktop width, black/white
+ light/dark themes, blue links, green prompts/actions, ASCII frames, and bracketed
+ controls. License links retain amber/muted green. Yellow LibreJS badge has a
+ keyboard-accessible warning; JavaScript licenses sit below it. Small screens
+ retain readable controls and fit ASCII rules without shrinking all text.
+ Root viewport scrolls at the screen edge; no inner logo scrolling.
+ No remote fonts, images, or additional scripts.
+
+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.
+
+ 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
+ previous local license-page URL redirects there. cgit keeps ordinary source
+ and raw-file views without a custom HTML renderer.
+
+ First-party software is GNU AGPL version 3 or later. Legal license headers
+ are the exception to the no-comments convention. Documentation is plaintext.
+ JavaScript has GNU LibreJS @license/@license-end markers and a linked
+ jslicense-labels1 table, following GNU's JavaScript license web-label format:
+
+ https://www.gnu.org/licenses/javascript-labels.html
+ https://www.gnu.org/software/librejs/free-your-javascript.html
+
+ The yellow GNU LibreJS-compliant badge links to the GNU project and exposes
+ a hover/focus explanation distinguishing software freedom from security.
+ A source archive is built by infra/chat/build-source.sh from an explicit
+ source allowlist, TypeScript sources/configurations/npm lock, Cargo-vendored
+ dependencies, and the original verified
+ OpenSSL archive. It excludes .env, .git, keys, target files, runtime data,
+ downloaded browser tools, and exports. .env.example is public. The original
+ dependency license texts are preserved. GNU-friendly human-readable names
+ such as Expat License are used in the generated third-party license page;
+ machine-readable SPDX identifiers remain unchanged.
+
+ Rebuild the source archive after any source, lockfile, or notice changes.
+ Do not publish a binary without its exact corresponding sources. Inspect
+ tar -tzf var/build/chat-source.tar.gz and the generated license page.
+ The SQLite bundled C source is provided by libsqlite3-sys in the vendor tree.
+ The complete unmodified OpenSSL release is included separately.
+
+ Run the normal strict workspace check, then browser tests:
+
+ cargo stack check
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:crypto
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:chat
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:progress
+
+ Automation needs Node/root locked Playwright, with browsers in .tools/browsers;
+ see TYPESCRIPT.txt for installation/strict builds. Releases exclude these tools.
+ Fixtures isolate roots with one temporary .env each, deleting private data on
+ completion. Default loopback 9081 (CHAT_TEST_PORT overrides) leaves preview 9080
+ free. Packaged HTTPS tests retain disposable roots; see CHAT-PRODUCTION.txt.
+ Tests neither initialize the real owner nor touch the VPS.
+
+ LibreJS verification uses the Mozilla-distributed GNU LibreJS 7.21.1 in
+ Playwright Firefox 157.0, without whitelisting the application. The full chat
+ workflow passes while an unlicensed external control script is blocked. Set
+ CHAT_LIBREJS=1 when running the application browser test to reproduce this
+ variant; it needs that signed extension at .tools/librejs.xpi and Firefox
+ downloaded into .tools/browsers. The extension download used was:
+
+ https://addons.mozilla.org/firefox/downloads/file/4169076/librejs-7.21.1.xpi
+
+ LibreJS rejected a combined GNU prefix/or-later suffix. The table uses recognized
+ GNU-AGPL-3.0; prose/legal headers retain version-3-or-later. Readable classic
+ scripts/workers avoid module-script interception requirements. This tests
+ license recognition/operation, not delivered-code safety.
+
+ The crypto browser probe fails closed without browser CSPRNG entropy and on
+ unknown WASI imports. The application browser test uses real WASI/OpenSSL
+ cryptography, protected file downloads and reopen, incorrect passwords,
+ signed sends/replies, safe text display, counts, zero-history, loading all
+ 54 fixture messages through 50+4 batches, pagination,
+ lost-response retry deduplication, reload, explicit lock, origin rejection,
+ live database snapshot restoration with both original identity files, owner
+ setup/access controls, refusing reset while running or with a mismatched
+ fingerprint, successful backend reset and fresh registration, rejecting old
+ visitor key pins, in-flight lock, back/forward restoration, and checks for
+ plaintext persistence in the database/journals or private credential uploads.
+ The packaged variant additionally verifies certificate trust/hostname through
+ curl, restrictive export permissions, and full encrypted recovery with saved
+ 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.
diff --git a/docs/CHAT-OPENSSL-MIGRATION.txt b/docs/CHAT-OPENSSL-MIGRATION.txt
new file mode 100644
index 0000000..4bb87fb
--- /dev/null
+++ b/docs/CHAT-OPENSSL-MIGRATION.txt
@@ -0,0 +1,234 @@
+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
new file mode 100644
index 0000000..fdb48a6
--- /dev/null
+++ b/docs/CHAT-PRODUCTION.txt
@@ -0,0 +1,222 @@
+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/>.
+
+
+PRODUCTION CHAT INTEGRATION
+
+ Implemented on main. Builds/tests do not activate the VPS. Experimental;
+ no independent cryptographic or application security audit. Read
+ CHAT-APPLICATION.txt limitations before private use.
+
+ Rootless Podman runs Caddy, cgit, SearXNG, and chat. Releases contain static
+ x86_64 GNU/Linux management/chat binaries, browser WASM, exact corresponding
+ sources, and native images. Chat starts FROM scratch: no compiler, shell,
+ package manager, or first-party Python. Root filesystem is read-only;
+ capabilities are dropped; only private data is writable.
+
+ Caddy serves HTTPS at CHAT_HOST and redirects www.CHAT_HOST, preserving paths.
+ CHAT_UPSTREAM defaults to private chat:8080. Chat publishes no host ports;
+ only gateway HTTP and TCP/UDP HTTPS are published. Host SSH policy is separate.
+
+ Root .env selects the explicit external origin. Chat accepts exactly that
+ HTTPS origin or the dedicated local gateway origin. Forwarded client headers
+ cannot select it. Origin checks supplement cryptographic authentication.
+
+BUILD ON THE WORKSTATION
+
+ Build host and target must be x86_64 GNU/Linux. Requires pinned Rust, Podman,
+ Git, GnuPG, GCC-compatible C compiler, Linux UAPI headers, libclang, pkg-config,
+ make, Perl, curl, tar, Node >=22.18.0, npm, and OpenSSL 3.5+ development libraries.
+ Browser tests also need pinned Playwright, Chromium, and Firefox. Workstation
+ tools stay local; production needs neither Cargo nor compiler.
+
+ cargo stack release
+
+ Runs TypeScript build/checks, formatting, strict Clippy, workspace tests,
+ rustdoc, cargo-deny, and cargo-about. Builds static binaries, WASI, exact
+ sources, chat/cgit images, and complete image export. Regenerates workspace
+ THIRD-PARTY-LICENSES.html. Verify archive checksum and recovery before deployment.
+
+ infra/chat/build-native.sh verifies OpenSSL 3.6.5's original source archive:
+ a2157c2830efdec3788939b00c9b0638306d3f0bbb76dc4832ee503bb397df98
+ It verifies Alpine musl-dev 1.2.6-r3 headers:
+ 310b51cff12e1d5ce5f135c558a82e2550427da5465bbd16c9ff9b05e63ee3d9
+ Sources: official OpenSSL GitHub release and Alpine CDN. Compilation uses
+ 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 cache under .tools/. Version/source/flag changes require an OpenSSL
+ rebuild. 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. cargo-deny excludes C/OpenSSL.
+
+ Source downloads include first-party code, Cargo-vendored dependencies, and
+ unmodified OpenSSL, excluding development toolchain/sysroot inputs. Saved
+ images preserve tested runtime. ARM requires a separately supported native release.
+
+LOCAL MODES AND ORIGIN BINDING
+
+ Direct Rust development:
+ cargo stack chat
+ http://127.0.0.1:9080/
+ var/data/chat
+
+ Container preview:
+ cargo stack dev
+ http://chat.localhost:8080/
+ var/data/chat-local
+
+ Production:
+ ./bin/stack up --production
+ https://CHAT_HOST/ with the configured HTTPS_PORT
+ var/data/chat
+
+ Distinct origins require separate identity files. Direct development and
+ production share a relative database path on separate machines; do not switch
+ valuable installations between them. Integration tests need a fresh restored
+ release. Rebuild after edits to refresh browser, server, sources, notices, and image.
+
+ One root .env holds hostnames, ports, upstreams, backups, SEARXNG_SECRET, and
+ CHAT_SETUP_TOKEN. Only the example is public; releases/source downloads exclude
+ actual .env. Caddy keys/chat databases stay under var/data. Initialization
+ preserves configured values and generates missing secrets.
+
+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.
+ 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.
+ Public certificate validation requires standard reachable ACME ports.
+
+ The systemd user service runs ./bin/stack up --production at boot and
+ ./bin/stack down on stop. Podman restarts
+ failed containers. Complete matching-architecture releases need no compilation
+ or registry pull. Check all four services after activation:
+
+ ./bin/stack 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.
+ The displayed limitations are CHAT-APPLICATION.txt. Chat has its own origin;
+ the main website remains JavaScript-free. Its Message Me links lead to chat.
+
+ At the production HTTPS origin, choose OWNER SETUP. Read the root .env token
+ locally; never publish or send it through chat. Choose a unique password;
+ save, reopen, and unlock the downloaded owner file. Keep file/password in
+ KeePassXC with a protected database backup. The token cannot replace the file.
+ Never silently change a development file's pinned origin.
+
+BACKUP, RESTORE, AND KEY LOSS
+
+ A complete encrypted application export:
+ ./bin/stack 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
+ for chat/chat-local, Caddy certificates, repositories, static binaries, WASM,
+ sources, and exact images. Chat stays unpaused so pending writers can complete.
+ Database contents are ciphertext/public keys and CHAT-APPLICATION.txt metadata.
+ 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
+ cd /path/stack-restored
+ ./bin/stack up --production
+
+ Restore defaults to local mode with fresh project names, avoiding replacement
+ of another installation. up --production reselects production database/origin.
+ Keep hostname/origin, repoint DNS, and use unchanged identity files/passwords.
+ IP changes preserve origin; hostname/HTTPS-port changes do not. Never expose
+ two restored copies under one origin. Restart invalidates sessions; reopening
+ files authenticates again. Historical ciphertext/counters persist.
+
+ CHAT_SETUP_TOKEN cannot recover a lost private file. If backups are also lost,
+ 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
+
+ Reset requires a stopped server and matching full fingerprint. Root .env and
+ exports survive. Create a new production owner file; old visitor files reject
+ its changed key and need fresh identities. Exports/external snapshots are not erased.
+
+VERIFICATION
+
+ The browser harness supports packaged HTTPS installations without Rust or
+ target/ inside them. It creates disposable identities and a separate temporary
+ GnuPG key, takes a complete encrypted export with images during live chat,
+ restores into a new directory, starts the saved images, and reopens the same
+ files to decrypt history. It also exercises wrong passwords, safe text,
+ counters, 0-to-N history, 50-row batches, retry deduplication, origin rejection,
+ browser storage checks, and backend reset guards. It never resets real data.
+
+ Example after restoring a release to a disposable staging directory and
+ 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
+ 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.
+ Add CHAT_LIBREJS=1 for Firefox with the actual GNU LibreJS add-on. That test
+ verifies the add-on is active and blocks an unlicensed control script.
+ Disposable staging needs a fresh database for each complete run.
+
+ Browser automation permits the fixture's private CA only for functional tests.
+ Separately verify its chain and hostname with curl --cacert and --resolve;
+ test the canonical route and www redirect without disabling 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
new file mode 100644
index 0000000..f0f8457
--- /dev/null
+++ b/docs/CONFIGURATION.txt
@@ -0,0 +1,143 @@
+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/>.
+
+CENTRAL CONFIGURATION
+
+ One root .env holds each installation's deployment settings and secrets.
+ Public .env.example documents every supported key, public default, private
+ placeholder, and comment. Hostnames remain public in generated pages, DNS,
+ certificates, and URLs. Centralization is not secrecy or access control.
+
+ cargo stack config, service initialization, and chat initialization preserve
+ existing settings/secrets and append missing public defaults to the same .env.
+ No second environment file. The shared Rust configuration library also reads
+ defaults from .env.example, keeping older exports compatible.
+
+PUBLIC SETTINGS
+
+ BASE_DOMAIN
+ Root web/WKD domain. Derive www aliases from each configured host.
+
+ SITE_HOST, GIT_HOST, SEARCH_HOST, WKD_HOST, CHAT_HOST
+ Fully qualified DNS names; no scheme, path, or port. Set production
+ HTTPS routes and canonical www redirects. CHAT_HOST also selects the
+ exact trusted external chat origin.
+
+ CHAT_LOCAL_HOST
+ Development gateway hostname ending in .localhost. Default:
+ chat.localhost. Browser identity files bind to this origin and local port.
+
+ WEB_BIND
+ Production publish address. Default :: serves IPv4/IPv6. Use 127.0.0.1
+ for isolated staging. Development always binds loopback.
+
+ HTTP_PORT, HTTPS_PORT
+ Caddy listeners and Podman published ports; defaults 80/443. HTTPS also
+ publishes UDP for HTTP/3. Generated links include nonstandard HTTPS ports.
+ Public certificate validation still needs standard external CA validation
+ ports or alternative certificate/routing arrangements. Port settings do
+ not configure DNS, firewall rules, or external forwarding.
+
+ LOCAL_HTTP_PORT
+ Local HTTP on 127.0.0.1; host HTTPS port is this value plus one. Caddy
+ still listens on container ports 80/443. --port overrides one invocation.
+ var/state.json retains installation/project names and image state.
+ An explicit .env local port overrides the saved port.
+
+ CGIT_UPSTREAM, SEARXNG_UPSTREAM, CHAT_UPSTREAM
+ Proxy targets, e.g. cgit:8080, searxng:8080, chat:8080. Not bind settings.
+ Bundled internal 8080 listeners and health checks remain image contracts.
+ A changed target needs an actual service at that host/port.
+
+ CHAT_BIND, CHAT_ORIGIN
+ Direct development socket and exact browser origin. Defaults:
+ 127.0.0.1:9080 and http://127.0.0.1:9080. Require loopback and an exact
+ HTTP origin match. --bind overrides CHAT_BIND. Containers bind privately
+ to 0.0.0.0:8080; trusted origin comes from CHAT_HOST/HTTPS_PORT or
+ CHAT_LOCAL_HOST/LOCAL_HTTP_PORT. Identity files pin their origin.
+ Editing these settings neither migrates encrypted identities nor grants
+ access on another origin.
+
+ BACKUP_RECIPIENT, BACKUP_PUBLIC_KEY
+ Public OpenPGP fingerprint and project-relative public key path for
+ encrypted exports. --recipient/--key override them. Keep the secret
+ GnuPG key and revocation certificate outside the project.
+
+PRIVATE SETTINGS
+
+ SEARXNG_SECRET
+ Generated at first stack initialization; existing values preserved.
+
+ CHAT_SETUP_TOKEN
+ Generated by chat initialization or first startup. Authorizes only
+ first owner registration, including after an explicit backend reset.
+ Cannot recover a lost owner private key.
+
+RENDER AND APPLY
+
+ Edit .env locally; never commit or publish it. See .env.example for exact
+ keys. Validate hostnames, ports, sockets, fingerprints, and paths before use.
+ Diagnostics never dump the config map or echo invalid .env contents.
+
+ Render public deployment files without starting services:
+
+ cargo stack config
+ cargo stack config --production
+
+ Output under var/local/ or var/production/:
+
+ Caddyfile
+ cgitrc
+ web/
+
+ Templates use {{PUBLIC_KEY}} placeholders: infra/Caddyfile,
+ infra/Caddyfile.local, infra/containers/cgit/cgitrc, first-party website HTML.
+ Never run Caddy on unrendered templates. Podman mounts generated files.
+ Public host/origin changes reach routes, www redirects, website links, cgit
+ clone prefixes, and chat navigation. Local rendering uses /git, /search,
+ and http://CHAT_LOCAL_HOST:LOCAL_HTTP_PORT for chat.
+
+ Apply to existing local services:
+
+ cargo stack up --local
+
+ Production uses prebuilt management command/images:
+
+ ./bin/stack up --production
+
+ Running file servers can read rendered website updates. Listener, container
+ environment, and loaded Caddy changes need the service update above.
+ Restart direct local chat with cargo stack chat for new bind/origin/navigation
+ settings. Reopen your identity file afterward, as after any chat restart.
+
+PUBLICATION AND LIMITS
+
+ stack-config substitutes only explicitly allowlisted public values.
+ {{CHAT_SETUP_TOKEN}} and other private/unknown placeholders fail instead of
+ exposing values. Public pages/proxy config never receive a full environment
+ dump. Chat receives the public allowlist plus CHAT_SETUP_TOKEN; other
+ containers receive only declared secrets. Caddy needs rendered public config,
+ not .env or private tokens.
+
+ Git ignores .env, runtime files, local renders, exports, and tool caches.
+ Source templates, commented example, validated defaults, and configuration
+ implementation remain public. Full encrypted backups include the single
+ .env; source-only releases exclude it.
+
+ Personal content stays in source: copyright, author names, canonical contact,
+ public keys/WKD hashes, repository metadata, GNU license links. Changing a
+ hostname creates no new email identity and regenerates no WKD keys.
+ Image digests, source hashes, fixed crypto/protocol/resource bounds remain
+ reviewed reproducible code/config, not private deployment knobs.
diff --git a/docs/LINTING.txt b/docs/LINTING.txt
new file mode 100644
index 0000000..bd1cdac
--- /dev/null
+++ b/docs/LINTING.txt
@@ -0,0 +1,167 @@
+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/>.
+
+STRICT LINT POLICY
+
+ Cargo.toml defines workspace policy; all four packages inherit it.
+ forbid rejects source-level lowering; deny permits narrower source overrides.
+ Neither prevents build-config edits. Pin compiler/tools; use checked builds.
+
+RUST AND CLIPPY
+
+ Every formerly denied individual Clippy rule and enabled group uses forbid.
+ No blanket allows. Groups include all, correctness, suspicious, cargo, style,
+ complexity, perf, pedantic, nursery; multiple_crate_versions and
+ redundant_pub_crate also remain forbidden. Forbid Rustdoc's supported all group.
+
+ Forbid Rust safety/docs/visibility/cast/edition/future-compatibility checks,
+ underscore bindings, and warnings. Independently forbid unused-group members
+ compatible with generated code. Also restrict panic/unimplemented/unreachable
+ paths, panic in Result, unsafe allowance attributes, numeric/pointer casts,
+ native-endian data, string slices, infinite loops, and test placement.
+ Exact rules: Cargo.toml. Do not enable contradictory restriction groups wholesale.
+
+ Workarounds preserve strictness:
+
+ Explicit checked Clap builders replace derives. Clap still parses all
+ arguments; no application shell parser. Both command schemas test modes,
+ required paths, listeners, global --root, and maintenance conflicts.
+ Help/rejected-option exit codes were compared with previous binaries.
+ Removed unused derive feature and its lockfile dependency.
+
+ Explicit checked Tokio runtime builder replaces runtime attribute.
+ Construction failures use existing error path; no broad generated
+ expect/style/complexity allowances remain.
+
+ Management/server implementations use private app namespaces and
+ pub(super), narrower than crate visibility. Re-export only documented
+ entry point at binary root. Enforce redundant_pub_crate/unreachable_pub
+ without exposing helpers or allowing either lint.
+
+ Serde containers set crate = "serde", avoiding generated extern-crate
+ branch and unused_extern_crates/clippy::useless_attribute allowances.
+ Preserve file/wire field names, ordering, tagging, unknown-field checks.
+
+ Result-returning tests use anyhow::ensure instead of panicking, permitting
+ panic_in_result_fn without suppressions.
+
+THREE REMAINING RUST DENY SETTINGS
+
+ unused, nonstandard_style, unused_qualifications stay deny for generated-code
+ compatibility, not permission for source allowances. First-party
+ clippy::allow_attributes and allow_attributes_without_reason are forbidden.
+
+ Pinned serde_derive/src/dummy.rs unconditionally allows non_upper_case_globals,
+ unused_attributes, unused_qualifications, clippy::absolute_paths. Generated
+ field-identifier visitor also allows non_camel_case_types. Correct first-party
+ names/qualifications cannot remove these generated attributes.
+
+ Applied supported crate-path workaround; inspected all macros. Clippy with
+ -F unused, -F nonstandard_style, or -F unused_qualifications still rejects
+ pinned Serde expansion. No supported derive setting removes its unconditional
+ wrapper. Further changes require a generator fork or serialization replacement,
+ including security-sensitive identity/envelope/request formats: explicit
+ upstream/format migrations, not configuration fixes.
+
+ Independently forbid every other unused member and non_snake_case. warnings
+ remains forbidden; explicit deny groups supply narrow effective levels needed
+ by generated attributes. Added no first-party allow/expect attributes.
+
+DUPLICATE DEPENDENCIES
+
+ clippy.toml allowed-duplicate-crates contains only diagnosed names:
+ generic-array, getrandom, syn. Lint/cargo group forbid all other duplicates.
+ Pinned crypto/parser/build graph needs incompatible semver lines:
+
+ generic-array 0.14.7 and 1.4.5
+ getrandom 0.2.17 and 0.4.3
+ syn 2.0.119 and 3.0.9
+
+ These are not duplicate patch releases. Unification needs upstream API changes
+ or older dependencies. No crypto/backend changes or downgrades for lint.
+ cargo-deny enforces exact reviewed deny.toml exceptions, including build/dev
+ dependencies. Clippy's name-only allowance cannot replace that stronger audit.
+
+TYPESCRIPT
+
+ Check strict mode, indexed access, optional properties, unused, returns,
+ switches, overrides, imports. Explicitly disable noCheck/allowJs and both
+ declaration-library skip flags. Enable isolatedModules, erasableSyntaxOnly,
+ verbatimModuleSyntax. Browser/worker: Bundler resolution, Preserve output,
+ no runtime imports. Tests/tools: separate NodeNext configurations.
+
+ eslint.config.ts extends typed strict/stylistic presets. All diagnostics are
+ errors; warnings fail. Disable inline config; prohibit suppression directives
+ and unsafe assertions. Reject ambiguous boolean coercion, unhandled/misused
+ promises, explicit/leaked any, non-null assertions, unsafe calls/returns/
+ arguments, eval, unsafe casts, unchecked discriminated-union switches.
+ Functions expose checked return types; callbacks retain declared contracts.
+
+ DOM async tasks and test HTTP handlers catch rejections; Node receives no
+ unobserved promises. WASI validates start export before calling it. Shared
+ host is a frozen, non-writable global. JSON/worker boundaries validate unknown
+ values at runtime.
+
+ TypeScript 7.0.2 emits through explicit typescript-native npm alias.
+ typescript-eslint 8.72.1 supports >=4.8.4 <6.1.0 and requires JavaScript
+ compiler API; TypeScript 6.0.3 supplies it. Both compilers check sources.
+ No unsupported-peer/ignore-peer-dependency install flags. Name intended
+ compiler path in every invocation to avoid npm bin collisions.
+
+ Isolated declaration emission constrains library distribution, not application
+ safety. This app publishes no TypeScript library; classic scripts preserve
+ tested LibreJS loading. moduleDetection=force applies to tests/tools. Do not
+ change browser scripts to ESM solely for that setting. TypeScript 7 removed
+ allowSyntheticDefaultImports=false; omit obsolete switch. Typed lint checks
+ unsafe interop at use sites.
+
+DEVELOPMENT DEPENDENCY ADVISORY
+
+ ESLint/typescript-eslint request minimatch 10. GHSA-g3hv-75wg-m249 affects
+ minimatch <11.0.1. Root npm override pins patched 11.0.3; no advisory ignore
+ or npm audit fix --force. Reviewed exports/Node engine; typed lint and rule-
+ selection/negative-control checks exercise compatibility. Remove override
+ when upstream ranges naturally admit a patched version.
+
+ npm audit --audit-level=info runs in npm run check and Cargo check/release.
+ Development findings fail too. Tools retain original notices/machine SPDX.
+ Human docs call MIT the Expat License. Visitors receive no third-party JS.
+
+CHECKS AND EXPORTS
+
+ npm run build
+ cargo stack check
+ cargo stack release
+
+ npm build checks both compilers, typed ESLint, npm advisories; emits browser/
+ worker/test artifacts. Cargo adds formatting, strict Clippy, all workspace
+ tests, strict Rustdoc, cargo-deny, cargo-about. Browser/LibreJS controls:
+ TYPESCRIPT.txt.
+
+ Corresponding sources/releases/backups include clippy.toml, eslint.config.ts,
+ every tsconfig, both lockfiles. Public exports exclude installed node_modules,
+ runtime state, private .env. No extra deployment env file or production Node.
+
+PRIMARY REFERENCES
+
+ https://doc.rust-lang.org/stable/rustc/lints/levels.html
+ https://doc.rust-lang.org/clippy/lints.html
+ https://doc.rust-lang.org/clippy/lint_configuration.html
+ https://serde.rs/container-attrs.html
+ https://github.com/serde-rs/serde/blob/v1.0.229/serde_derive/src/dummy.rs
+ https://www.typescriptlang.org/tsconfig/
+ https://typescript-eslint.io/users/configs/
+ https://typescript-eslint.io/users/dependency-versions/
+ https://github.com/advisories/GHSA-g3hv-75wg-m249
diff --git a/docs/MAP.txt b/docs/MAP.txt
new file mode 100644
index 0000000..6f5e13d
--- /dev/null
+++ b/docs/MAP.txt
@@ -0,0 +1,175 @@
+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/>.
+
+FILE MAP
+
+ Public tree: every tracked file, including this map.
+ Static inventory; update when public paths change. Root: stack/.
+ Private/generated trees below list roles only; contents stay unlisted.
+
+stack/ Personal service source, build recipes and recovery tooling.
+|-- .cargo/ Cargo command aliases.
+| `-- config.toml Cargo stack alias.
+|-- crates/ Workspace application/configuration crates.
+| |-- chat-crypto/ Fixed-suite Rust and browser cryptography.
+| | |-- browser/ Browser crypto types and WASI adapter.
+| | | |-- protocol.d.ts Shared typed API, crypto, worker, WASI message contracts.
+| | | `-- wasi.ts Browser WASI host: memory, I/O, entropy, command execution.
+| | |-- src/ Rust crypto library and executable.
+| | | |-- application.rs Protected identities, authentication proofs, message encryption/decryption.
+| | | |-- lib.rs Public crypto API and fixed-suite compatibility round trip.
+| | | `-- main.rs Native/WASI executable: request input and crypto result output.
+| | |-- tests/ Real-browser crypto compatibility tests.
+| | | `-- browser.ts Real-browser crypto probe, entropy-failure and network-isolation checks.
+| | |-- Cargo.toml Crypto library/probe targets, fixed backend features, dependencies.
+| | `-- README.txt Crypto suite, native/WASI build, compatibility checks.
+| |-- chat-server/ Private-chat backend and browser client.
+| | |-- src/ Rust HTTP server and maintenance entry.
+| | | |-- app/ Server argument, HTTP, service and storage modules.
+| | | | |-- cli.rs Server flags, listener defaults, maintenance argument conflicts.
+| | | | |-- http.rs HTTP routes, packaged assets, response security headers.
+| | | | |-- maintenance.rs Server file lock, owner fingerprint, destructive owner reset.
+| | | | |-- service.rs Authentication challenges/sessions, authorization, chat operations.
+| | | | `-- store.rs SQLite owner metadata, conversations, ciphertext history persistence.
+| | | |-- app.rs Server initialization, origin/listener checks, command dispatch.
+| | | `-- main.rs Chat server executable entry point.
+| | |-- tests/ Chat browser integration and loading-state tests.
+| | | |-- browser.ts Chat browser flows, owner/visitor access, packaged backup/restore.
+| | | `-- progress.ts Crypto-download progress, unknown length, failure and LibreJS checks.
+| | |-- web/ Chat markup, styling and TypeScript client.
+| | | |-- app.ts Chat browser UI, credentials, API calls, encrypted conversations.
+| | | |-- index.html Chat page markup and public-origin template.
+| | | |-- javascript.html Browser JavaScript license/source listing.
+| | | |-- style.css Chat page layout, controls, status and conversation styling.
+| | | `-- worker.ts One-operation crypto worker; returns result/error, then closes.
+| | |-- Cargo.toml Chat HTTP server dependencies and workspace lint inheritance.
+| | `-- README.txt Server purpose, local startup, browser-test entry.
+| |-- config/ Shared environment/configuration crate.
+| | |-- src/ Validated configuration and public-rendering implementation.
+| | | `-- lib.rs Root .env parsing/validation, defaults, public-only template values.
+| | |-- Cargo.toml Shared configuration crate dependencies and lint inheritance.
+| | `-- README.txt Configuration crate responsibilities and public rendering boundary.
+| `-- 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-PRODUCTION.txt Chat release packaging, HTTPS activation, owner setup, production recovery.
+| |-- CONFIGURATION.txt Supported root settings, validation, public/private state boundaries.
+| |-- 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.
+| |-- POLICY.txt License/header, source-comment, configuration, strictness and portability rules.
+| `-- TYPESCRIPT.txt Typed browser/worker/test builds, ESLint policy, compiler/test commands.
+|-- infra/ Service definitions, host integration and build recipes.
+| |-- chat/ Native/WASI/TypeScript/source build scripts.
+| | |-- build-browser.sh Link Rust/OpenSSL browser crypto command into WASI WebAssembly.
+| | |-- build-native.sh Build static musl management/server commands and native crypto probe.
+| | |-- build-openssl-wasi.sh Fetch verified SDK/OpenSSL sources; build browser crypto library.
+| | |-- build-source.sh Archive corresponding source, vendored Rust crates and OpenSSL source.
+| | |-- build-typescript.sh Install locked dev dependencies; run typed checks and emit browser JS.
+| | `-- openssl-wasi.conf OpenSSL build target settings for WASI.
+| |-- containers/ Container build contexts.
+| | |-- cgit/ cgit image and its nginx/FastCGI process configuration.
+| | | |-- .containerignore Restrict cgit build context to required container files.
+| | | |-- Containerfile Build pinned Debian cgit/nginx/FastCGI/supervisor image.
+| | | |-- cgitrc cgit repository listing, paths and presentation template.
+| | | |-- entrypoint.sh Prepare cgit container runtime and start its supervisor.
+| | | |-- nginx.conf Serve cgit HTTP/static assets; route CGI through fcgiwrap.
+| | | `-- 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.
+| |-- searxng/ Search service configuration.
+| | `-- settings.yml SearXNG defaults: private instance, rate limiting, HTML search.
+| |-- Caddyfile Production domain routing, HTTPS, WKD and service reverse proxies.
+| |-- Caddyfile.local Loopback development routing without public certificate requests.
+| |-- host-setup.sh Debian ops-user Podman packages, UID/GID checks, low ports and linger.
+| |-- podman.conf Legacy Podman override selecting slirp4netns rootless networking.
+| |-- services.toml Service images, mounts, environment allowlists, security and health probes.
+| `-- stack.service Systemd user boot unit for ~/stack start/stop.
+|-- src/ Management executable source.
+| |-- app/ Management command implementation modules.
+| | |-- archive.rs Consistent snapshots, encrypted exports and validated directory restores.
+| | |-- chat.rs Chat build/package artifacts, container settings and owner administration.
+| | |-- cli.rs Management subcommands, flags and argument validation.
+| | |-- files.rs Safe filesystem/archive operations, manifests and hashes.
+| | |-- images.rs Export/import exact container images; enforce platform and content locks.
+| | |-- process.rs Run child processes; capture output and report failures/status.
+| | |-- project.rs Discover/lock project; settings, Podman commands, service lifecycle.
+| | |-- release.rs Build checked release archives; validate/install production artifacts.
+| | `-- render.rs Render per-mode Caddy, website and cgit configuration.
+| |-- app.rs Management command dispatch, check pipeline and license generation.
+| `-- main.rs Management executable entry point.
+|-- tests/ Shared first-party browser-test helpers.
+| `-- browser-support.ts Shared browser-test response validators, server and browser cleanup helpers.
+|-- web/ Main-site template, styles, licenses and public key discovery.
+| |-- .well-known/ Standard public discovery endpoints.
+| | `-- openpgpkey/ WKD public-key discovery tree.
+| | |-- hu/ Direct WKD hashed-user key files.
+| | | `-- t76q8xqkeassgy1kr38re1ppj6cpc1dp Owner public key for direct WKD lookup.
+| | |-- luuduct.ai/ Advanced WKD namespace for luuduct.ai.
+| | | |-- hu/ Advanced WKD hashed-user key files.
+| | | | `-- t76q8xqkeassgy1kr38re1ppj6cpc1dp Same owner public key for advanced luuduct.ai WKD lookup.
+| | | `-- policy Advanced WKD policy marker for luuduct.ai.
+| | `-- policy Direct WKD policy marker.
+| |-- THIRD-PARTY-LICENSES.html Generated Rust dependency attribution and full license texts.
+| |-- index.html Main-site public links, identity and service-origin template.
+| `-- styles.css Main-site visual layout, links and tooltips.
+|-- .env.example Public settings template; private-value placeholders.
+|-- .gitattributes Whitespace handling for generated third-party license HTML.
+|-- .gitignore Exclude secrets, runtime state, dependencies, build output.
+|-- Cargo.lock Exact Rust dependency versions and source checksums.
+|-- Cargo.toml Management binary, workspace members, dependencies, strict lints.
+|-- LICENSE Unmodified GNU AGPLv3 license text; sources allow later versions.
+|-- README.txt Project entry: setup, commands, checks, documentation links.
+|-- about.hbs HTML template for generated Rust dependency licenses.
+|-- about.toml License-page acceptance list and private-package handling.
+|-- clippy.toml Reviewed duplicate-crate exceptions for Clippy.
+|-- deny.toml Dependency advisory, license, source and duplicate-version policy.
+|-- eslint.config.ts Typed ESLint rules and per-environment file scopes.
+|-- package-lock.json Exact npm dev dependency versions, integrity and dependency graph.
+|-- package.json Browser tooling dependencies and npm check/build/test commands.
+|-- rust-toolchain.toml Pinned Rust version, formatting/lint components and target triples.
+|-- tsconfig.browser.json Browser main-thread and WASI-host compiler scope.
+|-- tsconfig.json Shared strict TypeScript compiler options.
+|-- tsconfig.tests.json Node browser-automation compiler scope.
+|-- tsconfig.tools.json Node ESLint configuration compiler scope.
+`-- tsconfig.worker.json Web-worker compiler scope, separate from DOM globals.
+
+PRIVATE AND GENERATED PATHS — NOT PUBLIC SOURCE
+
+ .env Sole private root configuration; never commit.
+ .git/ Local Git objects, refs, hooks and repository settings.
+ .tools/ Downloaded toolchains, build tools, sources and caches.
+ bin/ Prebuilt management/chat commands and packaged chat assets.
+ dist/ Locally built release tarballs and checksum files.
+ node_modules/ Locked npm development dependencies; recreated by npm ci.
+ target/ Cargo build output, WASM targets and generated rustdoc.
+ var/ Private/generated project state; excluded from Git.
+ backups/ Imported backup archives and recovery test destinations.
+ build/ Generated TypeScript, source/vendor bundles and build work.
+ data/ Persistent application databases, TLS keys and Git repos.
+ caddy/ ACME account/certificate keys, Caddy data/configuration.
+ chat/ Production chat database: owner identity and ciphertext.
+ chat-local/ Isolated local chat database and server state.
+ repos/ Served bare Git repositories; separate from root .git/.
+ searxng/ Runtime search configuration and persistent service files.
+ exports/ Encrypted application backups and checksum files.
+ incoming/ Copied release archives and temporary deployment bootstrap.
+ podman/ Recreated container storage/run state; never transfer as backup.
+ graphify/ Public-source graph index, visual map and extraction artifacts.
+
+ Other var/ paths may hold generated fixtures or temporary work. This map does
+ not enumerate private files. Backups must keep .env and var/data; public Git
+ alone cannot recover services. See OPERATIONS.txt.
diff --git a/docs/OPERATIONS.txt b/docs/OPERATIONS.txt
index 826c4cf..55dc81c 100644
--- a/docs/OPERATIONS.txt
+++ b/docs/OPERATIONS.txt
@@ -13,110 +13,85 @@ 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
+STACK OPERATIONS
- 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
+ Root: ~/stack. Develop on 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.
-WHAT YOU NEED TO KEEP
+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.
+ 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.
- 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.
+ 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.
- The public Git repository alone is not a backup: it excludes .env, live
- data, private certificates, and application repositories under var/.
+DEVELOP LOCAL
- 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:
+ From ~/stack on 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:
+ Open http://localhost:8080, http://localhost:8080/git/,
+ http://localhost:8080/search/. Alternate port:
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.
+ 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.
- 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:
+ Project engine storage: var/podman/. Bare 'podman ps' uses another store.
+ Inspect project containers 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.
+ Keep both global options for 'logs CONTAINER', 'exec CONTAINER', 'images'.
+ cargo stack status gives names. Never migrate var/podman/; export images.
-STRICT LOCAL CHECKS
+CHECK LOCAL
- Install the two development-only tools once:
+ Install development audit 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.
+ Checks formatting, all workspace Clippy targets/features, 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.
-EXPORT AND BACK UP PRODUCTION
+BACK UP PRODUCTION
- On the VPS:
+ On 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:
+ 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:
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:
+ 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:
umask 077
mkdir -p ~/stack/var/backups/production
@@ -125,43 +100,31 @@ EXPORT AND BACK UP 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.
+ 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.
- 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 LINUX
-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:
+ 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 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:
+ 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
- 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.
+ 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.
- 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:
+ No CLI/toolchain yet: bootstrap trusted export with GnuPG/tar. Bash/fish:
umask 077
mkdir -p ~/stack-bootstrap
@@ -169,164 +132,134 @@ RESTORE ON ANOTHER LINUX MACHINE
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:
+ 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
- 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.
+ 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.
- 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.
+NEW PRODUCTION HOST
-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:
+ Restore into target user's ~/stack. Debian: review infra/host-setup.sh;
+ replace 'ops' if needed; 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:
+ Installs Podman/network tools, checks existing subordinate ranges, enables
+ user linger, 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.
+ 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.
- 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:
+ 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.
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:
+ 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:
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.
+ Unit assumes ~/stack; edit paths for other location. Uses bin/stack.
+ Linger keeps user service alive
+ without login. Containers use unless-stopped restart policy.
- If offering SSH Git access at the former VPS path, create this optional
- host symlink as the administrator:
+ Optional former VPS SSH Git path; administrator only:
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.
+ Never overwrite existing repo. Current VPS already has this layout link.
+ Public HTTPS Git needs no host symlink.
-MAKE AND DEPLOY A RELEASE
+BUILD AND DEPLOY
- Develop and run the checks on your workstation, then:
+ Develop/check on workstation:
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:
+ 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 -m 'Describe the change'
+ git commit -S3346AA3150F8F16828BB0D14F8C66DF77AFECEA6 -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:
+ 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:
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:
+ On 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
+ 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
-
- 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.
+ 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.
+
+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.
diff --git a/docs/POLICY.txt b/docs/POLICY.txt
index 592269b..dea595b 100644
--- a/docs/POLICY.txt
+++ b/docs/POLICY.txt
@@ -17,59 +17,80 @@ PROJECT CONVENTIONS
License: GNU Affero General Public License, version 3 or later.
SPDX identifier: AGPL-3.0-or-later.
- Copyright holder: Doan Luu Duc Tai <doan@luuduct.ai>, 2026.
-
- Every nontrivial first-party source, configuration, template, and prose
- file carries the AGPL copyright and permission header. LICENSE is the
- unmodified license text. Binary public keys and empty WKD policy files
- retain their protocol format. Generated Cargo.lock retains Cargo's own
- metadata as well as the project notice.
-
- Do not add explanatory source comments. Express intent through structure
- and names. Legal headers are the required exception. Use .txt for prose
- documentation. The generated HTML license page is an explicit exception.
- Rust documentation attributes provide crate metadata without comments.
-
- Keep configured secrets only in the root .env. Never commit .env or var/.
- Only the root .env.example is published; environment variants and nested
- examples are ignored. .env.example lists required keys with placeholders. Public hostnames,
- public GPG keys, paths, configuration, and source belong in Git.
- TLS account/certificate private keys, databases, and other generated
- private state are persistent data in var/data, not .env configuration.
- The GPG secret key and revocation certificate remain outside this project.
+ Copyright holder/commit author: Doan Luu Duc Tai <doan@luuduct.ai>, 2026.
+ Sign commits with RSA-2048 key:
+ 3346AA3150F8F16828BB0D14F8C66DF77AFECEA6
+
+ Every nontrivial first-party source/config/template/prose file needs the AGPL
+ copyright and permission header. LICENSE stays unmodified. Binary public keys
+ and empty WKD policy files retain protocol format. Generated Cargo.lock keeps
+ Cargo metadata plus project notice. JSON npm manifests use top-level // legal
+ metadata; compiler JSONC uses its usual header syntax. Preserve generated
+ JavaScript legal markers.
+
+ No explanatory source comments. Express intent through names/structure.
+ Required legal headers are the exception. Prose uses .txt; generated HTML
+ license page is an explicit exception. Rust doc attributes supply crate
+ metadata without comments.
+
+ One root .env holds deployment overrides/configured secrets. Never commit
+ .env or var/. Publish only root .env.example; ignore variants/nested examples.
+ Its comments document public defaults/private placeholders. Public GPG keys,
+ paths, templates, shared config logic, and source belong in Git. Hostnames
+ stay visible in rendered pages; .env centralization is no access boundary.
+ TLS account/certificate private keys, databases, and generated private state
+ live under var/data, not .env. Keep GPG secret key/revocation certificate
+ outside the project.
STRICTNESS
- Preserve the requested Rust and Clippy deny groups, including pedantic
- and nursery. The one additional exception is redundant_pub_crate: its
- advice to make internal binary items public conflicts with unreachable_pub,
- which remains denied. Internal interfaces remain explicitly crate-private.
- No unsafe code, unwrap, expect, todo, debug output, arithmetic shortcuts,
- or broad warning suppression is introduced.
-
- cargo-deny rejects duplicate crate versions, wildcard requirements, unknown
- registries, unknown Git sources, and yanked versions. There are no duplicate
- version skip exceptions. Its AGPL allowance is scoped to this package.
- The dependency allowlist retains the requested permissive licenses and
- Zlib; an unused allowance may produce an informational audit warning.
-
- Human license names follow https://www.gnu.org/licenses/license-list.html.
- In particular, display Expat License for SPDX MIT. Do not alter original
- copyright notices, legal texts, crate metadata, or machine SPDX identifiers.
- cargo-about covers Rust dependencies. Supporting container applications
- and their operating-system packages carry additional upstream licenses.
+ Forbid every enabled Clippy group/individual rule; no blanket allows.
+ Rust/Rustdoc use forbid where generated code supports it. Three Rust settings
+ stay deny for pinned Serde derives; unaffected members remain independently
+ forbidden. No first-party lint allowances. LINTING.txt records tested
+ workarounds, generated-attribute evidence, and narrow duplicate exceptions.
+ Private app namespaces keep narrow visibility while redundant_pub_crate and
+ unreachable_pub both remain enforced.
+
+ 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.
+ Scope first-party AGPL/required dependency license allowances per package.
+ Allowlist retains requested permissive licenses and Zlib; unused allowances
+ may produce informational audit warnings.
+
+ Strict TypeScript separates browser/worker/Node environments and validates
+ untrusted responses. npm dependencies stay exact, development-only,
+ integrity-locked; disable installation lifecycle scripts. Typed ESLint rejects
+ unsafe any/assertions, unchecked promises, and suppression directives. Check
+ both supported compiler APIs and npm advisories. See TYPESCRIPT.txt/LINTING.txt.
+
+ Human license names follow https://www.gnu.org/licenses/license-list.html:
+ display Expat License for SPDX MIT. Preserve original copyright, legal text,
+ crate metadata, and machine SPDX identifiers. cargo-about covers Rust.
+ Container applications/OS packages retain their additional upstream licenses.
PORTABILITY
- All first-party project files and state live below stack/. Standard host
- integration consists of packages, subordinate UID/GID allocations, one
- low-port sysctl setting, user linger, and a systemd user-service link.
- Host SSH keys, firewall policy, accounts, and disks are host administration,
- not application export contents.
-
- Releases currently contain a static x86_64 Linux command and native
- linux/amd64 container images. They are portable across compatible x86_64
- Linux machines. An ARM machine needs a native release build; the command
- rejects mismatched production images. Development mode may fetch/build
- native images on an architecture mismatch. Container source and Rust
- source are public so new targets can be added without restoring an OS.
+ All first-party files/state live below stack/. Standard host integration:
+ 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.
+
+ Releases include static x86_64 Linux management/chat commands, WASM browser
+ crypto, exact corresponding sources, native linux/amd64 container images.
+ Compatible x86_64 Linux hosts can run them. ARM needs a native release build;
+ production rejects mismatched images. Development may fetch/build native
+ images on architecture mismatch. Public container/Rust sources permit new
+ targets without restoring an OS.
+
+ Preserve upstream whitespace/line endings in generated third-party legal
+ texts. .gitattributes exempts only THIRD-PARTY-LICENSES.html from whitespace
+ errors. First-party checks stay strict; workspace audits cover every member.
+
+STATIC ASSET CACHING
+
+ Main-site stylesheet URLs include web/styles.css SHA-256 as query version.
+ Update both website templates after CSS changes. Render test checks the
+ published hash, preventing stale CSS/new markup pairs. Tooltip text uses
+ native hidden plus CSS visibility, so stylesheet failure cannot expose it.
diff --git a/docs/TYPESCRIPT.txt b/docs/TYPESCRIPT.txt
new file mode 100644
index 0000000..7fa5125
--- /dev/null
+++ b/docs/TYPESCRIPT.txt
@@ -0,0 +1,125 @@
+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/>.
+
+TYPESCRIPT BROWSER TOOLCHAIN
+
+ First-party browser logic/automation use TypeScript. Rust handles crypto,
+ API, backups, deployment. Browsers run generated JavaScript; compile-time
+ types never replace validation of untrusted input.
+
+SOURCE LAYOUT
+
+ crates/chat-server/web/app.ts
+ Interface, session, history, downloads, API validation.
+ crates/chat-server/web/worker.ts
+ One-shot crypto worker; ends after each request.
+ crates/chat-crypto/browser/wasi.ts
+ Restricted WASI host for unchanged Rust/OpenSSL module.
+ crates/chat-crypto/browser/protocol.d.ts
+ Shared API/crypto request/response contracts. No emitted JavaScript;
+ checked in both browser environments.
+ crates/chat-crypto/tests/browser.ts
+ Real WebAssembly compatibility, entropy, capability tests.
+ crates/chat-server/tests/browser.ts
+ Complete browser chat/recovery tests, including GNU LibreJS.
+ crates/chat-server/tests/progress.ts
+ Download progress/failure tests, including GNU LibreJS.
+ tests/browser-support.ts
+ Checked fixture parsing, typed browser-automation helpers.
+
+ eslint.config.ts/tsconfig.tools.json provide typed lint-tool checks.
+ Private Rust CLIs: src/app and crates/chat-server/src/app. Parser/runtime
+ changes: LINTING.txt.
+
+ tsconfig.json supplies shared strict checks. Separate browser/worker/test
+ configs use DOM/WebWorker/Node environments, preventing accidental production
+ Node API use. Check strict, unchecked indexed access, exact optional properties,
+ unused declarations/parameters, implicit returns, switch fallthrough, implicit
+ overrides, indexed-property access. Type errors prevent emission.
+ No suppression directives or untyped escape hatches.
+
+DEVELOPMENT
+
+ Development needs Node >=22.18.0/npm. Root package.json/package-lock.json pin
+ TypeScript 7.0.2, Playwright 1.64.1, Node declarations, platform compilers,
+ integrity hashes. ESLint 10.14.0/typescript-eslint 8.72.1 supply typed strict
+ linting. TypeScript 6.0.3 supplies their supported JavaScript API; native
+ TypeScript 7.0.2 alias emits. Both check sources. LINTING.txt explains the
+ advisory-driven minimatch override. No npm application/runtime dependencies.
+ TypeScript/Playwright: Apache License, Version 2.0. Node declarations/
+ undici-types: Expat License. Installed packages retain original notices;
+ machine SPDX spelling stays unchanged.
+
+ Build/check all TypeScript, including browser tests:
+
+ npm run build
+
+ infra/chat/build-typescript.sh installs locked dev packages without lifecycle
+ scripts, checks browser/worker/test/tool configs, typed ESLint, both compilers,
+ npm advisories; emits JavaScript under var/build/typescript. npm cache:
+ .tools/npm-cache. Git ignores node_modules/generated output. Scripts stay
+ readable/unminified; no bundler needed.
+
+ cargo stack check runs this build, strict Rust checks, cargo-about generation.
+ cargo stack release builds TypeScript before packaging production artifacts.
+
+ Recheck after npm run build without reinstalling:
+
+ npm run check
+
+ Fetch pinned Playwright browsers once:
+
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" node_modules/.bin/playwright install chromium firefox
+
+ Build native chat server/WASI module, then run:
+
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:crypto
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:chat
+ PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:progress
+ CHAT_LIBREJS=1 PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:chat
+ CHAT_LIBREJS=1 PLAYWRIGHT_BROWSERS_PATH="$PWD/.tools/browsers" npm run test:progress
+
+ LibreJS variants need signed Mozilla extension .tools/librejs.xpi; see
+ CHAT-APPLICATION.txt. Complete chat test proves LibreJS active and rejects an
+ unlicensed control. Run chat variants sequentially; they share default port.
+ npm audit checks dev dependencies in npm run check/Cargo check/release;
+ it does not audit Rust, delivered browser code, or containers. cargo-deny
+ remains the separate strict Rust dependency audit.
+
+RELEASES, SOURCE, AND LIBREJS
+
+ Generated app.js/worker.js/wasi.mjs retain public URLs, AGPL headers,
+ recognized @license/@license-end markers. JavaScript license table links
+ each file to complete corresponding TypeScript/Rust source archive.
+ Classic scripts/importScripts preserve LibreJS compatibility. No runtime
+ module loader or third-party browser scripts added.
+
+ Local native-server tests serve var/build/typescript. Release packages files
+ into bin/chat/web and bin/chat/wasi.mjs; containers serve packaged files.
+ Copy HTML/CSS explicitly; never substitute TypeScript for executable JS.
+ /SOURCE.tar.gz contains TypeScript, shared declarations, every compiler config,
+ npm lock, tests/build scripts, Cargo-vendored dependencies, verified OpenSSL
+ archive. Node/npm/TypeScript are general-purpose development tools pinned and
+ documented here. Exclude installed copies/downloaded browsers.
+
+ Source exports/releases/backups carry root npm manifests/TypeScript configs.
+ Activation/restored prebuilt images need no destination Node/npm/TypeScript/
+ Rust. Preserve single private root .env and persistent data. Source archive
+ excludes .env, credentials, runtime DBs, exports, .git, node_modules, build/cache
+ directories. Root .env.example remains public; no extra environment file.
+
+ Test generated JavaScript after every compiler upgrade. Static types cannot
+ guarantee privacy, security, or LibreJS recognition. Existing runtime/Rust
+ still enforce browser storage restrictions, protocol validation, algorithms.