29 KiB
Install ThothII on a Linux server
Server authentication uses generic OIDC with the reverse proxy preserving the configured public
origin and callback path. Follow the OIDC guide, Authentik guide
when applicable, and the authentication acceptance matrix.
The host authentication CLI is tht. Use tht --installation <descriptor> workspace inspect --workspace <id> --json for the active workspace snapshot, tht --installation <descriptor> auth check for live non-interactive authentication diagnosis, auth check --interactive for
device-flow identity validation, and tht ... doctor --json for the aggregate installation gate.
This guide is for an installer with basic Linux administration and very basic Docker knowledge.
It deploys the same Compose distribution used on a local PC: the mandatory application is exactly
frontend plus core, and pinned Pi is inside core. The server does not need host Pi, Node.js,
Python, Go, a browser shell, or a Docker socket inside either container.
Examples use /srv/thothii as an example operator root, thoth.example.com as a replaceable
DNS name, and systemd-based command names. Adapt them to local policy. Complete the server session
storage overlay and migration procedure before exposing a production installation.
Deployment contract
- The generic Linux host and Docker Compose v2 are the deployment platform. No other application's Compose project, network, path, or runtime is required.
frontendis the only published service and defaults to127.0.0.1:8080;corehas no host port. A host Nginx or Caddy listener terminates TLS and sends all application traffic tofrontend, never directly tocore.- DWH, vector database, embedding service, and LLM are external configurable endpoints. This remains true when they happen to run on the same physical server.
- Application, Git, connector, and session credentials are protected host files mounted read-only
under
/run/secrets. Pi's protected auth JSON uses its dedicated read-only Pi mount. No secret value belongs in Git, images, browser storage, environment values, rendered Compose, or logs. - The Git-backed workspace registry is the source of truth. Installation-local bindings identify endpoints and secret-file paths; they do not replace the reviewed Git workspace descriptors.
thtis the operator CLI for start, stop, status, health, logs, Pi lifecycle, drain, and rollback. Raw Compose lifecycle commands bypass installation state and are unsupported.
Read server workspace-registry installation,
Pi management, and the session-server comments in
deploy/compose.session-server.yaml.example before the first public start.
Service account and directories
The container runtime identity is fixed at UID/GID 10001. It does not require or permit creation of a matching host account or group. Keep the number unmapped and use numeric ownership only for the dedicated bind paths that the non-root container must read or write. If either lookup below finds a host identity, stop and design an explicit remapping before installation.
if getent passwd 10001 >/dev/null || getent group 10001 >/dev/null; then
printf '%s\n' 'UID/GID 10001 is already mapped; stop' >&2
exit 1
fi
operator_uid="$(id -u)"
operator_gid="$(id -g)"
test "$operator_uid" -ne 0
id -nG | tr ' ' '\n' | grep -Fx docker >/dev/null
The invoking, pre-existing administrator owns source and operator files. It must already have the
site-approved Docker access required to run tht; this guide never changes group membership.
Docker-group membership is effectively host-root access and must remain limited to reviewed
administrators. Do not grant the operator direct write access to container runtime trees.
Create explicit directories. source contains the clone; operator contains untracked path-only
configuration; the three writable trees are bind-mounted into core; secrets contains regular
files only. The parent is owned by the operator with numeric group 10001 so both the operator and
container can traverse it. Numeric ownership does not add entries to /etc/passwd or /etc/group.
operator_uid="$(id -u)"
operator_gid="$(id -g)"
sudo install -d -o "$operator_uid" -g 10001 -m 0750 /srv/thothii
sudo install -d -o "$operator_uid" -g "$operator_gid" -m 0750 /srv/thothii/source
sudo install -d -o "$operator_uid" -g "$operator_gid" -m 0750 /srv/thothii/operator
sudo install -d -o 10001 -g "$operator_gid" -m 0750 /srv/thothii/secrets
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/data
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/pi-state
sudo install -d -o 10001 -g 10001 -m 0750 /srv/thothii/workspace-registry
sudo install -d -o root -g root -m 0700 /srv/thothii-backups
stat -c '%u:%g %a %n' \
/srv/thothii /srv/thothii/source /srv/thothii/operator /srv/thothii/secrets \
/srv/thothii/data /srv/thothii/pi-state /srv/thothii/workspace-registry \
/srv/thothii-backups
Expected: the parent is operator_uid:10001 750; source/operator are
operator_uid:operator_gid 750; secrets are 10001:operator_gid 750; the three runtime trees are
10001:10001 750; backups are 0:0 700. Re-run the empty getent checks after creation. Do not
make /srv/thothii a shared application directory.
Projected server authentication: canonical root and runtime projection
For a server descriptor that declares authentication.runtimeProjection, authentication has two
different roots. The canonical authentication root (authentication.configDirectory) is the
root-operated source of truth. It and its regular files are root:root 0700/0600. The runtime
projection is a separate Linux-only tree for the container reader: its root, generations, and
generation directories are 10001:10001 0700; CURRENT, manifest.json, auth.yaml, and (for
local mode) users.yaml are 10001:10001 0600. The publisher assigns the numeric IDs directly;
it does not create a host user or group for 10001.
The runtime projection has only CURRENT and generations/<64-lowercase-hex>/. CURRENT selects
one complete immutable generation. A successful configure, user mutation, restore, or explicit
publish first blocks CURRENT, then verifies a new immutable generation, then makes it ready.
The selected generation and up to two predecessor generations are retained; no operator edits a
generation or CURRENT directly. A ready projection is usable only when its canonical revision is
equal to the current canonical authentication root. If the runtime projection is blocked, missing,
tampered, or unequal, start, update --check-only, auth check, and doctor fail closed before
admission or Compose lifecycle work.
The runtime directory must be an absolute canonical path, distinct from the canonical root, and
must exactly equal THT_AUTH_RUNTIME_ROOT in the protected installation environment. The server
profile and numeric UID/GID values are validated before any projected mutation or publication.
The descriptor loader adds compose.auth-runtime-projection.yaml automatically when
runtimeProjection is present; do not list that file under overrides. The automatic override
mounts the runtime projection read-only and core-only at /run/thothii-auth; the canonical
authentication root is never mounted. No other service receives that mount or
THT_AUTH_RUNTIME_PROJECTION_ROOT. The example descriptor uses
/srv/example/thothii/auth-runtime only as a replaceable path and contains no credential value.
This source change is prepared and tested only: Project A has not been started. It does not
authorize a raw Compose lifecycle launch, an Nginx change, legacy-stack change, or mutation of
/srv. A later manual gate needs separate explicit authorization before applying any descriptor
or runtime root to a server.
Status, repair, and safe evidence
Use the root-operated installation command; retain only its small redacted JSON result:
sudo tht --installation "$INSTALLATION" auth status --json
state: "ready" and equal: true are required before a projected server can start. state: "blocked", equal: false, or a command refusal means that the runtime projection is blocked or
cannot be validated. Do not start the stack, inspect YAML, print an environment, or edit CURRENT
or a generation. Confirm the protected canonical root is available, then republish it with:
sudo tht --installation "$INSTALLATION" auth publish
sudo tht --installation "$INSTALLATION" auth status --json
auth publish reconstructs the selected immutable generation from the canonical root; it never
uses an older runtime generation as authority. If publish fails, leave the projection blocked and
escalate using the sanitized command result plus descriptor path and timestamp only. Do not attach
passwords, hashes, YAML, raw environment output, nginx -T, or a secret-bearing diff to evidence.
Authentication restore
An authentication-bearing restore first publishes a blocked selector, restores canonical authentication, and publishes a verified candidate generation before any restart. If candidate or recovery verification fails, the verified recovery checkpoint is republished when possible; an unverified result remains blocked and prevents start. A restore without authentication entries does not touch the runtime projection. This is in addition to the normal restore requirement that browser sessions and pending OIDC state are cleared.
Firewall and network boundaries
Set THOTH_SERVER_BIND=127.0.0.1. Permit inbound TCP 80/443 only to the TLS proxy; port 80 should
redirect to HTTPS. Do not open 8080 externally, and do not add a core port. If a separate proxy
host is used, replace loopback with a private, firewalled address and allow only that proxy source.
Allow outbound DNS and HTTPS to the source/Git registries, plus only the configured ports for the
Git remote, DWH, vector database, embedding service, LLM, session PostgreSQL, and any approved
bastion. Docker's private thothii network carries only frontend↔core traffic. Do not attach
the mandatory stack to another application's network.
After start, confirm the host listens as intended:
sudo ss -lntp
Expected public listeners are the proxy on 80/443 and the frontend on loopback 8080. There must be no host listener for core port 8787.
Address co-resident external services
Endpoint values are resolved inside core. Therefore container 127.0.0.1 means the container
itself, not the Linux host. Prefer real DNS names with TLS, authentication, and firewall policy,
even for services on this physical server.
When DNS is unavailable for a host-published service, create an untracked override such as
/srv/thothii/operator/host-gateway.yaml and add it to the installation descriptor:
services:
core:
extra_hosts:
- "host.docker.internal:host-gateway"
Use host.docker.internal in the endpoint binding. The host-gateway mapping supplies routing;
it does not bundle or trust the target service. A host service listening only on host
127.0.0.1 is not reachable through this mapping. Bind that service to the ThothII Docker
bridge gateway address or to a dedicated private host interface—never to 0.0.0.0 merely to make
the check pass. A stable internal DNS record routed through an authenticated private listener is
the preferred alternative.
After the first bounded start attempt, copy the exact core container name from tht status
into CORE_NAME, then derive—not guess—the network ID, Linux bridge interface, gateway, and
subnet. Compose networks normally use br-<first-12-network-id>; an explicit
com.docker.network.bridge.name option takes precedence:
CORE_NAME=replace-with-exact-core-container-name
NETWORK_ID=$(docker inspect --format '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' "$CORE_NAME")
NETWORK_NAME=$(docker network inspect --format '{{.Name}}' "$NETWORK_ID")
BRIDGE=$(docker network inspect --format '{{index .Options "com.docker.network.bridge.name"}}' "$NETWORK_ID")
test -n "$BRIDGE" || BRIDGE="br-${NETWORK_ID%${NETWORK_ID#????????????}}"
GATEWAY=$(docker network inspect --format '{{(index .IPAM.Config 0).Gateway}}' "$NETWORK_ID")
SUBNET=$(docker network inspect --format '{{(index .IPAM.Config 0).Subnet}}' "$NETWORK_ID")
printf 'network=%s bridge=%s gateway=%s subnet=%s\n' "$NETWORK_NAME" "$BRIDGE" "$GATEWAY" "$SUBNET"
ip address show dev "$BRIDGE"
Bind the co-resident service to $GATEWAY. In the host firewall INPUT chain, allow its exact
TCP port only when source is $SUBNET, input interface is $BRIDGE, and destination is
$GATEWAY; reject other sources to that listener and persist the rules using the distribution's
firewall manager. Docker's DOCKER-USER chain governs forwarded/published traffic and does not
replace this host-input rule. Ask the firewall administrator to implement the equivalent policy
with nftables when iptables is not the site's source of truth.
For an iptables-managed host, replace the port before applying these reviewed rules; the second rule prevents any other interface/source from reaching that gateway listener:
EXTERNAL_PORT=replace-with-exact-service-port
sudo iptables -I INPUT 1 -i "$BRIDGE" -s "$SUBNET" -d "$GATEWAY" -p tcp --dport "$EXTERNAL_PORT" -j ACCEPT
sudo iptables -I INPUT 2 -d "$GATEWAY" -p tcp --dport "$EXTERNAL_PORT" -j REJECT
Confirm reachability with tht pi test for the configured LLM/Pi path and with the
authenticated Workspace Diagnostics action for DWH, vector collection/embedding pairing, and
embedding endpoints. A timeout paired with ss -lntp, ip address show dev "$BRIDGE", and the
firewall counters distinguishes a loopback bind from a subnet/interface rule failure. Do not add
a shell to the browser or mount the Docker socket into core for this diagnostic.
Configure each boundary independently:
- DWH: read-only runtime identity, database/schema, verified TLS, and direct or REST endpoint.
- Vector database: endpoint plus exact database/schema, collection, distance metric, and writer policy declared by the reviewed workspace.
- Embedding service: endpoint and the collection/embedding pairing—model and dimensions must match the existing collection. Co-residence does not permit silently changing that pairing.
- LLM: authenticated endpoint selected through deployment and Pi configuration.
Never add those services to ThothII's mandatory Compose files. Follow the diagnostic protocol before enabling a workspace.
Prepare operator files and secrets
Clone with LF line endings, then verify before every build:
git -c core.autocrlf=false clone \
https://github.example.invalid/your-org/ThothII.git /srv/thothii/source/ThothII
cd /srv/thothii/source/ThothII
git config --local core.autocrlf false
bash scripts/verify-line-endings.sh
sudo /srv/thothii/source/ThothII/scripts/prepare-server-pi-state.sh \
/srv/thothii/pi-state 10001 10001
The last command is a mandatory clean-install and restore preflight. The server profile bind-mounts
the writable Pi-state root and then overlays protected auth.json plus tracked models.json and
settings.json read-only below it. Docker requires those three hidden target files to exist under
the host parent bind before startup. The initializer creates them atomically with UID/GID 10001,
mode 0600, rejects symlink roots or targets, and never overwrites existing contents. It is safe to rerun
after restoring pi-state; run it before any tht start, Compose render/start, or Pi update.
Copy the path-only server environment and installation descriptor:
cp deploy/env/server.env.example /srv/thothii/operator/server.env
cp docs/install/examples/thothii-installation.server.yaml \
/srv/thothii/operator/thothii-installation.yaml
chmod 0600 /srv/thothii/operator/server.env \
/srv/thothii/operator/thothii-installation.yaml
The invoking operator owns both placeholder files; use an editor that preserves ownership and mode,
or create replacements under umask 0077 in the operator directory.
Replace every placeholder with an absolute path. Use exactly one Git transport override. For
HTTPS, replace deploy/compose.git-ssh.yaml with deploy/compose.git-https.yaml. Keep the required
session-server overlay. Optional host-gateway or pinned
image overrides go after them.
Create each installation credential (Pi/application, Git, and session storage) as an independent
regular file in /srv/thothii/secrets, owned by
UID 10001, the invoking operator's numeric primary GID, and mode 0640. Owner access lets the UID
10001 container read a file mounted under /run/secrets; group access lets the operator run tht. The
operator environment records only absolute *_FILE or *_SOURCE paths for those installation
credentials. DWH and Evidence values are entered later through Workspace management and persist
as ciphertext under /data/workspace-secrets; the frontend receives no secret values. Do not
print file contents while testing permissions.
operator_gid="$(id -g)"
sudo find /srv/thothii/secrets -type f -exec chown "10001:$operator_gid" {} +
sudo find /srv/thothii/secrets -type f -exec chmod 0640 {} +
sudo find /srv/thothii/secrets -type f \( ! -uid 10001 -o ! -gid "$operator_gid" -o ! -perm 0640 \) -print
Configure the remote repository and exactly one read-only Git transport as described in
server workspace repository installation. After startup, complete
the selected workspace's DWH and Evidence credentials through Workspace management. Secret values
must never be pasted into server.env, the installation YAML, a URL, or a shell argument.
Build locally or select pinned images
Choose one image source. For a source build, the repository's reproducible launcher builds the
same core and frontend images used by the local profile. It requires only Git, Docker, and
Compose; copy the reviewed path-only server environment to the launcher's untracked input first:
cd /srv/thothii/source/ThothII
cp /srv/thothii/operator/server.env deploy/env/local.env
bash scripts/build-local.sh
The printed local-profile start command is not the server start command; use tht below.
Alternatively, create a reviewed untracked override with release images pinned by immutable digest. Mutable tags are not a production pin:
services:
core:
build: !reset null
image: registry.example.com/thothii/core@sha256:<64-lowercase-hex-digits>
session-migrate:
build: !reset null
image: registry.example.com/thothii/core@sha256:<64-lowercase-hex-digits>
frontend:
build: !reset null
image: registry.example.com/thothii/frontend@sha256:<64-lowercase-hex-digits>
Add that absolute file last in overrides. core and session-migrate must use the exact same
core digest; neither may retain a local build or :local image. Frontend uses its own exact digest.
Both images must come from one compatible release; the core image must retain the declared Pi
version labels checked by tht pi doctor. Pull access
belongs in the host Docker credential store, not in Compose or the installation descriptor.
Install tht
Build the operator binaries with Docker. No Go installation or Go knowledge is required:
cd /srv/thothii/source/ThothII
THT_THT_OUTPUT_DIRECTORY=/srv/thothii/operator/build-output \
bash scripts/build-tht.sh
operator_gid="$(id -g)"
sudo install -o root -g "$operator_gid" -m 0750 \
/srv/thothii/operator/build-output/tht-linux-amd64 \
/srv/thothii/operator/tht
The source checkout remains controlled by the invoking operator. The explicit output directory is the only build
write boundary; the build script rejects relative or non-canonical output paths. After installation,
remove or retain build-output according to the site's reviewed artifact policy.
Use tht-linux-arm64 on an ARM64 server. Set these variables in the maintenance shell; do
not source server.env as shell code:
THT_BIN=/srv/thothii/operator/tht
INSTALLATION=/srv/thothii/operator/thothii-installation.yaml
"$THT_BIN" --help
"$THT_BIN" --installation "$INSTALLATION" update --check-only
Every operator command includes the descriptor explicitly. This preserves the installation's
profile, overrides, project identity, and durable current-image selector. The general form is
tht --installation /absolute/path/thothii-installation.yaml <command>.
Start and verify readiness
Keep the TLS proxy stopped or firewalled during bootstrap. First stop the app, run the
installation-aware session migration, and inspect its pristine JSON. The command activates only
the session-migrate profile/service with --no-deps --no-TTY; it derives the migrator image from the
selected core image after all installation overrides, so this procedure is identical for source
and pinned modes. It exits nonzero unless both arrays are empty:
"$THT_BIN" --installation "$INSTALLATION" stop
"$THT_BIN" --installation "$INSTALLATION" sessions migrate --yes
Successful output has this shape (the applied list may contain versions on first use):
{"applied":[],"drifted":[],"pending":[]}
Only after seeing "pending":[] and "drifted":[], start and verify:
"$THT_BIN" --installation "$INSTALLATION" start
"$THT_BIN" --installation "$INSTALLATION" status
"$THT_BIN" --installation "$INSTALLATION" doctor
curl --fail http://127.0.0.1:8080/health
"$THT_BIN" --installation "$INSTALLATION" pi doctor
"$THT_BIN" --installation "$INSTALLATION" pi test
/health proves process liveness. Readiness additionally requires both healthy services, a valid
Pi provider/model smoke, a successful Git registry pull with an active validated snapshot, valid
workspace diagnostics, and ready session PostgreSQL. Use the authenticated Workspace Management
page to pull and diagnose the reviewed workspace. A liveness response alone is not release
approval.
After configuring the proxy, open https://thoth.example.com in a browser. Verify an unauthenticated
request is denied or redirected by the real identity provider, an authorized user can load the
same-origin UI and /api, an unauthorized user is denied, and an administrator alone can open Pi
Management. Keep port 8080 inaccessible from other hosts.
Configure TLS and upstream authentication
Choose Nginx or Caddy. Both examples terminate
TLS and proxy only to loopback frontend. They preserve SSE and clear client-supplied identity
headers before authentication.
The authentication gateway must validate a real login/session and return normalized issuer,
subject, display-name, and admin claims only after success. Merely forwarding those headers does
not authenticate anyone. Do not enable AUTH_MODE=upstream on a listener reachable around the
trusted proxy, and never expose core.
Operate Pi, drain, and roll back
Configure only closed provider/model/reasoning choices. Credentials remain protected files:
"$THT_BIN" --installation "$INSTALLATION" pi status
"$THT_BIN" --installation "$INSTALLATION" pi configure
"$THT_BIN" --installation "$INSTALLATION" pi doctor
"$THT_BIN" --installation "$INSTALLATION" pi logs
Before an update, announce maintenance and ask users to finish active work. --drain closes new
admission and waits until no active sessions remain; it does not discard sessions. Build-source
and registry-source examples are:
"$THT_BIN" --installation "$INSTALLATION" pi update \
--version 0.81.0 --source build --yes --drain
"$THT_BIN" --installation "$INSTALLATION" pi update \
--version 0.81.0 --source pull \
--image registry.example.com/thothii/core@sha256:<64-lowercase-hex-digits> \
--yes --drain
The transaction recreates only core, preserves volumes, verifies health/configuration/Pi, and
automatically attempts rollback after a post-mutation failure. For interrupted or ambiguous state:
"$THT_BIN" --installation "$INSTALLATION" pi maintenance status
"$THT_BIN" --installation "$INSTALLATION" pi rollback --yes
"$THT_BIN" --installation "$INSTALLATION" pi maintenance recover --yes
Leave maintenance active if rollback cannot be verified. Preserve .tht/<installation-id>/
recovery state, repair the reported host/configuration issue, and rerun rollback or maintenance
recovery. Never delete or edit current-image.yaml or update-state.json to force progress.
Back up and restore
Back up before source, workspace, session-schema, or Pi changes. Drain work, stop the installation,
record git rev-parse HEAD, image digests, and tht status, then archive the three bind trees
with numeric ownership. Do not include live secrets in this ordinary archive.
"$THT_BIN" --installation "$INSTALLATION" stop
BACKUP=/srv/thothii-backups/2026-08-05
sudo install -d -o root -g root -m 0700 "$BACKUP"
sudo tar --numeric-owner --xattrs --acls -C /srv/thothii -czf "$BACKUP/runtime-data.tgz" \
data pi-state workspace-registry
sudo sh -ceu 'cd "$1"; sha256sum runtime-data.tgz > SHA256SUMS; sha256sum --check SHA256SUMS' sh "$BACKUP"
Back up the installation descriptor, path-only environment, generated overrides, source revision, and secret files to separate encrypted access-controlled storage. Database-backed production sessions require their own PostgreSQL-native consistent backup; the local bind tree is not a substitute. Test both restore paths periodically.
Restore only while stopped. Verify the checksum, extract first into a new empty root, inspect ownership and expected registry layout, then retain the old trees by renaming them before placing the restored set. This keeps the previous state recoverable:
RESTORE=/srv/thothii-restore-2026-08-05
sudo install -d -o root -g root -m 0700 "$RESTORE"
sudo sh -ceu 'cd "$1"; sha256sum --check SHA256SUMS' sh /srv/thothii-backups/2026-08-05
sudo tar --numeric-owner --xattrs --acls -C "$RESTORE" \
-xzf /srv/thothii-backups/2026-08-05/runtime-data.tgz
sudo test -d "$RESTORE/workspace-registry/repo"
sudo test -d "$RESTORE/workspace-registry/snapshots"
After placing the restored pi-state tree and before the first start, rerun
sudo /srv/thothii/source/ThothII/scripts/prepare-server-pi-state.sh /srv/thothii/pi-state 10001 10001.
It validates or recreates only the hidden regular mount targets; it does not alter restored Pi
state or any protected configuration source.
During the reviewed restore window, move each old tree to a timestamped sibling, move the matching
restored tree into /srv/thothii, restore the PostgreSQL session backup from the same recovery
point, and keep the proxy closed. Run update --check-only, start, doctor, pi test, registry
status, workspace diagnostics, and a known historical session before reopening traffic. Never
merge an archive into a non-empty tree.
Diagnostics
Begin with bounded, sanitized installation-aware commands:
"$THT_BIN" --installation "$INSTALLATION" status
"$THT_BIN" --installation "$INSTALLATION" doctor
"$THT_BIN" --installation "$INSTALLATION" logs
"$THT_BIN" --installation "$INSTALLATION" pi status
"$THT_BIN" --installation "$INSTALLATION" pi doctor
"$THT_BIN" --installation "$INSTALLATION" pi test
"$THT_BIN" --installation "$INSTALLATION" pi logs
"$THT_BIN" --installation "$INSTALLATION" pi maintenance status
Use the authenticated Workspace Management status and diagnostic actions for Git revision, degraded snapshot, bindings, DWH, vector, and embedding checks. Review proxy logs separately, but configure both proxy and log shipping to exclude cookies, authorization data, identity payloads, query strings, and secret values. Do not render Compose or print an environment as a diagnostic.
Typical boundaries are: doctor for Docker/Compose/LF/volume/service health; pi doctor for image
and provider/model integrity; registry status for Git/snapshot health; workspace diagnostics for
external service identity; and the proxy/identity provider for login failures.
Data-preserving uninstall
Drain and stop through tht, take and verify one final backup, and disable the TLS proxy
route. Set THT_BACKUP_ROOT=/srv/thothii-backups in server.env; the removal command verifies the
filesystem identity of that backup root, all three bind trees, and every declared secret before
and after removing anything.
First run without confirmation. It displays the exact installation project, service, container name, container ID, and stopped state, then exits without mutation. Check every target:
"$THT_BIN" --installation "$INSTALLATION" stop
"$THT_BIN" --installation "$INSTALLATION" remove
If and only if both targets are the expected stopped frontend and core containers, confirm:
"$THT_BIN" --installation "$INSTALLATION" remove --yes exact-core-id exact-frontend-id
Replace both example IDs with the values from the immediately preceding dry-run. The command
refuses confirmation if the current target set differs. The confirmed operation passes only those
previously displayed immutable container IDs to Docker,
uses no force or volume option, rejects running/replaced containers, and proves the preservation
paths still identify the same filesystem objects. Keep /srv/thothii/data, pi-state,
workspace-registry, operator, protected secrets, database backups, and the installation
descriptor if reinstallation is possible. Do not prune global Docker data.
Do not run docker compose down --volumes; it deletes persistent application data. Reusing the
same protected descriptor path preserves the tht installation identity and allows a later
compatible source checkout to reconnect the retained state.