docs: add workspace registry installation manuals

This commit is contained in:
2026-08-04 07:57:09 +02:00
parent 802b564200
commit 72e16dd5ea
6 changed files with 584 additions and 0 deletions
+9
View File
@@ -60,6 +60,15 @@ The frontend depends on the core health check and proxies `/health` and `/api/*`
application health endpoint intentionally checks process readiness only; external dependency
diagnostics are exposed by `tht doctor` and do not prevent the UI from starting.
## Git-backed workspace registry
Workspace descriptors are shared through a validated Git repository while endpoint bindings and
secret files remain installation-local. Use the [local Mac/PC installation manual](docs/install/local-workspace-registry.md)
for Docker Desktop or a local engine, and the [server installation manual](docs/install/server-workspace-registry.md)
for the Gitea, reverse-proxy, backup, migration, and recovery workflow. The isolated deployment
exercise is `./scripts/workspace-registry-smoke.sh`; both manuals are checked with
`./scripts/verify-workspace-install-docs.sh --profile local` or `--profile server`.
`docker-compose.dev.yml` is deliberately local: both published ports bind to `127.0.0.1`,
`THT_SESSION_STORAGE=local`, and `THT_HOME=/data/local-home`. Do not set
`THOTH_PUBLIC_EXPOSURE=true` for that profile; the backend rejects that public/local combination
@@ -0,0 +1,46 @@
# Standalone local registry example. Copy beside the clone as compose.workspace-registry.yaml
# and put path-only bindings in .env; keep the referenced files outside Git.
name: thothii-workspace-registry-local
services:
core:
image: thothii-core:local
build:
context: ../../..
dockerfile: docker/core.Dockerfile
environment:
HOST: 0.0.0.0
PORT: "8787"
AUTH_MODE: none
THT_SESSION_STORAGE: local
THT_HOME: /data/local-home
SETTINGS_FILE: /data/settings/settings.json
THT_HARNESS_DIR: /app/harness
THT_BIN: /opt/venv/bin/tht
THT_WORKSPACE_REGISTRY_ROOT: /data/workspace-registry
THT_WORKSPACE_GIT_REMOTE: ${THT_WORKSPACE_GIT_REMOTE:-ssh://git@git.example.invalid/platform/thoth-workspaces.git}
THT_WORKSPACE_GIT_BRANCH: ${THT_WORKSPACE_GIT_BRANCH:-main}
THT_WORKSPACE_INSTALLATION_ID: ${THT_WORKSPACE_INSTALLATION_ID:-local-laptop}
THT_WORKSPACE_GIT_AUTHOR_NAME: ${THT_WORKSPACE_GIT_AUTHOR_NAME:-Thoth Workspace Registry}
THT_WORKSPACE_GIT_AUTHOR_EMAIL: ${THT_WORKSPACE_GIT_AUTHOR_EMAIL:-thoth-workspace-registry@localhost}
THT_WORKSPACE_SECRET_ROOTS: /run/secrets
GIT_CONFIG_COUNT: "2"
GIT_CONFIG_KEY_0: credential.helper
GIT_CONFIG_VALUE_0: store --file=/run/secrets/workspace-registry-git-credentials
GIT_CONFIG_KEY_1: http.sslCAInfo
GIT_CONFIG_VALUE_1: /run/secrets/workspace-registry-git-ca
GIT_SSH_COMMAND: ssh -i /run/secrets/workspace-registry-git-ssh-key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/run/secrets/workspace-registry-git-known-hosts
ports:
- "127.0.0.1:8787:8787"
volumes:
- thoth-local-data:/data
- workspace-registry:/data/workspace-registry
- ${THT_WORKSPACE_GIT_CREDENTIALS_FILE:-./installation-secrets/git-credentials}:/run/secrets/workspace-registry-git-credentials:ro
- ${THT_WORKSPACE_GIT_CA_FILE:-./installation-secrets/git-ca.pem}:/run/secrets/workspace-registry-git-ca:ro
- ${THT_WORKSPACE_GIT_SSH_KEY_FILE:-./installation-secrets/git-ssh-key}:/run/secrets/workspace-registry-git-ssh-key:ro
- ${THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE:-./installation-secrets/git-known-hosts}:/run/secrets/workspace-registry-git-known-hosts:ro
restart: "no"
volumes:
thoth-local-data: {}
workspace-registry: {}
@@ -0,0 +1,47 @@
# Server registry example. Copy to a reviewed, untracked operator directory and set host paths
# and Git values in its .env. The core remains non-root (UID 10001) and never receives secrets
# through the Git checkout.
name: thothii-workspace-registry-server
services:
core:
image: thothii-core:local
build:
context: ../../..
dockerfile: docker/core.Dockerfile
environment:
HOST: 0.0.0.0
PORT: "8787"
AUTH_MODE: upstream
THOTH_PUBLIC_EXPOSURE: "true"
THT_HARNESS_DIR: /app/harness
THT_BIN: /opt/venv/bin/tht
SETTINGS_FILE: /data/settings/settings.json
THT_WORKSPACE_REGISTRY_ROOT: /data/workspace-registry
THT_WORKSPACE_GIT_REMOTE: ${THT_WORKSPACE_GIT_REMOTE:-ssh://git@git.example.invalid/platform/thoth-workspaces.git}
THT_WORKSPACE_GIT_BRANCH: ${THT_WORKSPACE_GIT_BRANCH:-main}
THT_WORKSPACE_INSTALLATION_ID: ${THT_WORKSPACE_INSTALLATION_ID:-production-1}
THT_WORKSPACE_GIT_AUTHOR_NAME: ${THT_WORKSPACE_GIT_AUTHOR_NAME:-Thoth Workspace Registry}
THT_WORKSPACE_GIT_AUTHOR_EMAIL: ${THT_WORKSPACE_GIT_AUTHOR_EMAIL:-thoth-workspace-registry@localhost}
THT_WORKSPACE_SECRET_ROOTS: /run/secrets
GIT_CONFIG_COUNT: "2"
GIT_CONFIG_KEY_0: credential.helper
GIT_CONFIG_VALUE_0: store --file=/run/secrets/workspace-registry-git-credentials
GIT_CONFIG_KEY_1: http.sslCAInfo
GIT_CONFIG_VALUE_1: /run/secrets/workspace-registry-git-ca
GIT_SSH_COMMAND: ssh -i /run/secrets/workspace-registry-git-ssh-key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/run/secrets/workspace-registry-git-known-hosts
volumes:
- ${THT_HOST_DATA_ROOT:-/srv/thothii/data}:/data
- ${THT_WORKSPACE_REGISTRY_HOST_PATH:-/srv/thothii/workspace-registry}:/data/workspace-registry
- ${THT_WORKSPACE_GIT_CREDENTIALS_FILE:-/srv/thothii/secrets/git-credentials}:/run/secrets/workspace-registry-git-credentials:ro
- ${THT_WORKSPACE_GIT_CA_FILE:-/srv/thothii/secrets/git-ca.pem}:/run/secrets/workspace-registry-git-ca:ro
- ${THT_WORKSPACE_GIT_SSH_KEY_FILE:-/srv/thothii/secrets/git-ssh-key}:/run/secrets/workspace-registry-git-ssh-key:ro
- ${THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE:-/srv/thothii/secrets/git-known-hosts}:/run/secrets/workspace-registry-git-known-hosts:ro
networks:
- portal
restart: unless-stopped
networks:
portal:
external: true
name: ${THT_PORTAL_NETWORK:-omics_portal_omics_network}
+185
View File
@@ -0,0 +1,185 @@
# Local workspace-registry installation (Mac and PC)
This guide runs a single-user ThothII registry on Docker Desktop (macOS or Windows) or a local
Linux Docker Engine. It is intentionally loopback-only. Git is shared; the checkout, connector
bindings, credentials, and session data are local. Never put credentials in workspace YAML, Git,
browser drafts, diagnostics, or `.env.example`.
## Prerequisites
- macOS: Docker Desktop, Git, and sufficient volume disk space. Git Credential Manager is useful
for HTTPS sign-in.
- Windows: Docker Desktop with WSL2, Git for Windows, and the clone enabled in Docker file sharing.
Use absolute paths/WSL paths; PowerShell uses `;` rather than `:` in `COMPOSE_FILE`.
- Linux PC: Docker Engine, Compose plugin, Git, and a user permitted to run Docker.
- Outbound access to the Git remote. A local installation needs no inbound firewall rule.
Keep the operator `.env` and `installation-secrets/` outside the workspace-registry Git checkout.
On macOS/Linux use mode `0600` for individual secret files. On Windows apply an ACL that grants
read access only to the Docker Desktop user. Do not use an empty file to silently bypass a selected
authentication method.
## Git remote: SSH and HTTPS
Create one private repository such as `thoth-workspaces.git`. It contains canonical workspace
definitions and generated artifacts only:
```text
thoth-workspaces.yaml
workspaces/<workspace-id>.yaml
workspaces/<workspace-id>.env.example
workspaces/<workspace-id>.md
```
For SSH, use a scoped deploy key, a verified `known_hosts` file, and strict host-key checking. For
HTTPS, use Git Credential Manager or a secret-manager-created credentials file. Mount a private
HTTPS CA as its own file. Do not disable host or certificate verification.
```dotenv
THT_WORKSPACE_GIT_REMOTE=ssh://git@git.example.invalid/platform/thoth-workspaces.git
THT_WORKSPACE_GIT_BRANCH=main
THT_WORKSPACE_INSTALLATION_ID=local-laptop
THT_WORKSPACE_GIT_SSH_KEY_FILE=/absolute/path/installation-secrets/git-ssh-key
THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=/absolute/path/installation-secrets/git-known-hosts
THT_WORKSPACE_GIT_CA_FILE=/absolute/path/installation-secrets/git-ca.pem
```
For HTTPS set `THT_WORKSPACE_GIT_CREDENTIALS_FILE` instead of the SSH key/known-hosts pair. Remote
and branch are non-secret; every `*_FILE` is a local path whose content never enters Git or logs.
## Shared Git values, local bindings, and secret files
| Location | Contains | Never contains |
| --- | --- | --- |
| Git workspace repository | schema v2 YAML, generated binding names, LLM policy, model/index identity | installation hostnames, keys, passwords, certificates, SSH keys |
| local `.env` | remote, branch, installation ID, selected transport and endpoints | secret contents |
| local secret directory | Git credentials/key, known hosts, CA, connector secret files | a copied registry checkout |
| Docker volumes | registry checkout/snapshots/state/locks and local data | host-only secret source files |
The persistent volume is `/data/workspace-registry`:
```text
repo/ # persistent Git checkout
snapshots/ # immutable validated revisions used by sessions
state/ # active revision and registry state
locks/ # short-lived publish locks
```
Installation variables are deterministic: `psd-clinical` becomes `PSD_CLINICAL`, and every name
is `THT_WS_<NAMESPACE>_<ROLE>_<SUFFIX>`. Credentials and certificates use `*_FILE` path variables.
If declared, `THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE` is distinct from the vector reader
file; a reader credential is never repurposed for writing.
## Direct PostgreSQL, REST, and SSH tunnel bindings
Set only fields for the selected transport. Canonical YAML keeps database/schema/collection,
distance, embedding model, and dimensions shared in Git.
```dotenv
# Direct PostgreSQL and pgvector
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=postgres_direct
THT_WS_PSD_CLINICAL_DWH_HOST=dwh.example.invalid
THT_WS_PSD_CLINICAL_DWH_PORT=5432
THT_WS_PSD_CLINICAL_DWH_USER=thoth_reader
THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE=/run/secrets/psd-dwh-reader
THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT=pgvector_direct
THT_WS_PSD_CLINICAL_VECTOR_HOST=vector.example.invalid
THT_WS_PSD_CLINICAL_VECTOR_PORT=5432
THT_WS_PSD_CLINICAL_VECTOR_USER=thoth_vector_reader
THT_WS_PSD_CLINICAL_VECTOR_PASSWORD_FILE=/run/secrets/psd-vector-reader
THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL=https://embeddings.example.invalid
```
```dotenv
# REST; an API-key file is needed only for a declared bearer/x-api-key diagnostic.
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api
THT_WS_PSD_CLINICAL_DWH_BASE_URL=https://dwh.example.invalid
THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE=/run/secrets/psd-dwh-api-key
THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT=rest_api
THT_WS_PSD_CLINICAL_VECTOR_BASE_URL=https://vectors.example.invalid
THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE=/run/secrets/psd-vector-api-key
```
```dotenv
# SSH tunnel; host-key verification and TLS target name remain mandatory.
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=ssh_tunnel
THT_WS_PSD_CLINICAL_DWH_USER=thoth_reader
THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE=/run/secrets/psd-dwh-reader
THT_WS_PSD_CLINICAL_DWH_SSH_HOST=bastion.example.invalid
THT_WS_PSD_CLINICAL_DWH_SSH_PORT=22
THT_WS_PSD_CLINICAL_DWH_SSH_USER=thoth_tunnel
THT_WS_PSD_CLINICAL_DWH_SSH_PRIVATE_KEY_FILE=/run/secrets/psd-dwh-tunnel-key
THT_WS_PSD_CLINICAL_DWH_SSH_KNOWN_HOSTS_FILE=/run/secrets/psd-dwh-known-hosts
THT_WS_PSD_CLINICAL_DWH_SSH_TARGET_HOST=dwh.internal.example
THT_WS_PSD_CLINICAL_DWH_SSH_TARGET_PORT=5432
```
Repeat the SSH names for `VECTOR` where needed. REST diagnostics reject a private per-request CA
rather than weakening TLS; use runtime-trusted HTTPS or verified direct/SSH native TLS. See the
[diagnostic protocol](../workspace-diagnostic-protocol.md).
## Bootstrap, first pull, and diagnostics
Copy [the local Compose example](examples/local-compose.workspace-registry.yaml) into an untracked
operator directory, create its local `.env` and mounted secret files, then render it before start.
<!-- verify:command -->
```sh
docker compose -f docs/install/examples/local-compose.workspace-registry.yaml config --quiet
```
From the operator directory:
```sh
docker compose -f compose.workspace-registry.yaml up --build -d
curl --fail --silent http://127.0.0.1:8787/health
curl --fail --silent http://127.0.0.1:8787/workspace-registry/status
curl --fail --silent http://127.0.0.1:8787/workspaces
```
The first status request clones, validates all descriptors, and atomically activates a snapshot.
Use `POST /workspace-registry/pull` to fetch later revisions. Run workspace diagnostics only after
required bindings are mounted. The optional writer probe uses a distinct writer file and removes
its uniquely named temporary record; ordinary diagnostics are read-only.
To migrate an existing PSD descriptor, create/clone an empty private remote, transform with
absolute paths, review the schema-v1 result, explicitly add vector database/schema and the complete
schema-v2 contract, then commit/push. The transformer never imports `${ENV}` values or secrets.
```sh
npm --prefix backend run build
node backend/dist/workspaces/migrate-legacy.js --input /absolute/path/psd.yaml --output /absolute/path/thoth-workspaces
```
## Publish, update, backup, outage recovery, and rollback
Drafts live only in browser storage. Review the canonical field diff, validate/test locally, then
publish. If conflicted, pull first and create a new reviewed field-level draft; never hand-edit the
running `repo/` volume. Before upgrading, record registry status, stop Compose, and take a
timestamped ownership-preserving backup of both registry and local data volumes while excluding
`installation-secrets/`. Render Compose, rebuild, start, and check status before resuming work.
After a valid bootstrap, remote outage retains the last valid snapshot and reports `degraded: true`.
Pinned sessions continue. Repair network/authentication, pull, and confirm non-degraded status. To
undo a bad remote change, create a reviewed Git revert/release branch, advance the remote through
normal policy, pull, and confirm its new snapshot. Do not delete `snapshots/` as rollback.
## Troubleshooting
| Stable code | Meaning and safe action |
| --- | --- |
| `workspace_invalid` | Invalid descriptor/path/snapshot; restore a reviewed canonical revision. |
| `binding_missing` | A selected value or readable `*_FILE` is absent; fix the local binding/mount. |
| `workspace_not_activatable` | Diagnostics cannot activate the workspace; correct the selected transport. |
| `workspace_stale` | Checkout changed or is busy; stop competing pull/publish work. |
| `workspace_conflict` | Draft base differs from Git; pull, resolve the diff, validate, republish. |
| `git_unavailable` | Remote, path, network, or lock unavailable; preserve the degraded valid snapshot. |
| `git_auth_failed` | Mounted SSH/HTTPS material rejected/unreadable; rotate or fix permissions without logging it. |
| `git_non_fast_forward` | Checkout diverged; reconcile through the registry workflow. |
| `git_push_rejected` | Remote policy rejected the change; review branch protection/hooks. |
| `connector_unavailable` | DNS/TLS/auth/resource identity diagnostic failed; inspect local bindings and egress. |
| `semantic_index_incompatible` | Collection/model/dimensions/distance differs from Git; perform an explicit index migration. |
On macOS, restart Docker Desktop if a named volume disappears. On Windows, check WSL2 and Docker
file sharing. A failed first bootstrap has no snapshot fallback: repair remote trust and retry;
never create an unreviewed local registry repository.
+195
View File
@@ -0,0 +1,195 @@
# Server workspace-registry installation
This is the production operator guide. The application image is read-only, secrets are mounted
read-only, and sessions use immutable Git-validated snapshots. Expose the application only behind
an authenticated same-origin reverse proxy; never publish the core port directly.
## Service account, storage, and firewall
Create a dedicated host service account and an operator root such as `/srv/thothii`. The core
container is non-root UID/GID `10001` (`thoth`), so give that identity read/write ownership before
first startup. Keep storage separated:
```text
/srv/thothii/data/ # settings, session data, Pi state as applicable
/srv/thothii/workspace-registry/ # repo/, snapshots/, state/, locks/
/srv/thothii/secrets/ # Git and connector secret files, mode 0700
/srv/thothii/operator/ # untracked Compose/.env, mode 0700
```
Permit outbound TCP only to approved Git/Gitea, DWH, vector, embedding, and bastion endpoints.
Allow inbound traffic only from the reverse proxy/Docker network. Do not give the runtime service
account Gitea administration, database-superuser rights, or a shell in the Git host.
## Gitea and remote Git setup
Create a private Gitea (or compatible Git) repository such as `platform/thoth-workspaces`. Protect
`main` according to the release policy and grant the ThothII publisher only the intended repository
scope. Commit canonical schema-v2 descriptors and generated `.md`/`.env.example` artifacts only;
do not commit installation bindings or secret material.
For SSH, create a least-privilege deploy key, record Gitea's host key in managed known-hosts, and
use `ssh://git@git.example.invalid/platform/thoth-workspaces.git`. For HTTPS, create a scoped
machine credential in the secret manager and mount the Gitea/private CA separately. Never use a
Gitea admin credential in the application.
Bootstrap an empty remote from a temporary review clone: migrate legacy descriptors, review their
schema-v2 identity and generated artifacts, commit, and push `main`. The running server is not an
authoring environment for migration.
## Git credentials, CA, SSH key, and known-hosts mounts
Use the secret manager or a protected host-only procedure to create independent regular files under
`/srv/thothii/secrets`. Set individual mode `0600`, directory mode `0700`, and ownership readable
by the service account. These path-only variables are mounted read-only by Compose:
```dotenv
THT_WORKSPACE_GIT_CREDENTIALS_FILE=/srv/thothii/secrets/git-credentials
THT_WORKSPACE_GIT_CA_FILE=/srv/thothii/secrets/git-ca.pem
THT_WORKSPACE_GIT_SSH_KEY_FILE=/srv/thothii/secrets/git-ssh-key
THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=/srv/thothii/secrets/git-known-hosts
```
Use the credential file for HTTPS, or key and known-hosts for SSH. Strict host-key checking stays
enabled and Git stderr is not exposed by the API. Rotate by atomically replacing the secret file,
restarting `core`, and performing pull/status; never put the material in an environment variable or
`docker compose config` output.
## Shared Git values, local bindings, and secret files
Git describes workspace schema, immutable ID, DWH/vector identity, semantic-index dimensions and
distance, embedding contract, and LLM policy. The installation supplies remote/branch/installation
ID, transport, endpoints, users, ports, and `THT_WS_*` bindings. Secret contents are only in files,
never the values stored in Git or browser-local drafts.
The runtime registry layout is persistent and must be backed up together:
```text
/data/workspace-registry/repo/
/data/workspace-registry/snapshots/
/data/workspace-registry/state/
/data/workspace-registry/locks/
```
Variable names derive from the immutable ID: `psd-clinical` becomes `PSD_CLINICAL`, producing
`THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE`. A declared vector writer uses the distinct
`THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE`; a reader file is never a writer substitute.
## Direct PostgreSQL, REST, and SSH tunnel bindings
Select only a transport allowed by canonical YAML; preserve database/schema/collection, model,
dimensions, and distance as Git-shared identity.
```dotenv
# Direct PostgreSQL/pgvector with verified native TLS if a CA path is supplied.
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=postgres_direct
THT_WS_PSD_CLINICAL_DWH_HOST=dwh.internal.example
THT_WS_PSD_CLINICAL_DWH_PORT=5432
THT_WS_PSD_CLINICAL_DWH_USER=thoth_reader
THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE=/run/secrets/psd-dwh-reader
THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT=pgvector_direct
THT_WS_PSD_CLINICAL_VECTOR_HOST=vector.internal.example
THT_WS_PSD_CLINICAL_VECTOR_PORT=5432
THT_WS_PSD_CLINICAL_VECTOR_USER=thoth_vector_reader
THT_WS_PSD_CLINICAL_VECTOR_PASSWORD_FILE=/run/secrets/psd-vector-reader
```
```dotenv
# REST needs API-key file paths only when the descriptor declares authenticated diagnostics.
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api
THT_WS_PSD_CLINICAL_DWH_BASE_URL=https://dwh.internal.example
THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE=/run/secrets/psd-dwh-api-key
THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT=rest_api
THT_WS_PSD_CLINICAL_VECTOR_BASE_URL=https://vectors.internal.example
THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE=/run/secrets/psd-vector-api-key
THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL=https://embeddings.internal.example
```
```dotenv
# SSH tunnel requires explicit host-key verification and TLS target identity.
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=ssh_tunnel
THT_WS_PSD_CLINICAL_DWH_USER=thoth_reader
THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE=/run/secrets/psd-dwh-reader
THT_WS_PSD_CLINICAL_DWH_SSH_HOST=bastion.internal.example
THT_WS_PSD_CLINICAL_DWH_SSH_PORT=22
THT_WS_PSD_CLINICAL_DWH_SSH_USER=thoth_tunnel
THT_WS_PSD_CLINICAL_DWH_SSH_PRIVATE_KEY_FILE=/run/secrets/psd-dwh-tunnel-key
THT_WS_PSD_CLINICAL_DWH_SSH_KNOWN_HOSTS_FILE=/run/secrets/psd-dwh-known-hosts
THT_WS_PSD_CLINICAL_DWH_SSH_TARGET_HOST=dwh.internal.example
THT_WS_PSD_CLINICAL_DWH_SSH_TARGET_PORT=5432
```
Repeat SSH variables for `VECTOR` when selected. REST diagnostics refuse private per-request CAs
rather than disable verification; use runtime-trusted HTTPS or verified direct/SSH native TLS. See
the [diagnostic protocol](../workspace-diagnostic-protocol.md) for its read-only checks and optional
reversible writer probe.
## Same-origin reverse proxy, bootstrap, and health
Copy [the server Compose example](examples/server-compose.workspace-registry.yaml) to the protected
operator directory, set host paths/remote/branch/installation ID/portal network in local `.env`,
then render it before deployment.
<!-- verify:command -->
```sh
docker compose -f docs/install/examples/server-compose.workspace-registry.yaml config --quiet
```
Configure the portal proxy so the frontend and `/api` share one origin. It authenticates first and
forwards the trusted identity expected by `AUTH_MODE=upstream`; it is the only public listener.
From a trusted maintenance shell:
```sh
docker compose -f compose.workspace-registry.yaml up --build -d
docker compose -f compose.workspace-registry.yaml exec -T core curl --fail --silent http://127.0.0.1:8787/health
docker compose -f compose.workspace-registry.yaml exec -T core curl --fail --silent http://127.0.0.1:8787/workspace-registry/status
```
`/health` is liveness. Registry status verifies branch/head/degraded state and the active validated
snapshot; authenticated `/workspaces` verifies application access. A server with no active snapshot
is not ready for workspace sessions even if liveness succeeds.
## Pull, publish, upgrade, backup, and recovery
Use the authenticated Workspace Management UI or `POST /workspace-registry/pull`. Drafts are
browser-local. Publish takes a canonical diff, validates before commit, and pushes under a registry
lock. On `workspace_conflict`, pull, resolve the reviewed field-level draft, validate/test, and
publish; never edit `repo/` inside a running volume.
For upgrades, record active status/head, drain active Pi work, stop `core`, and take a
filesystem-consistent backup of `/srv/thothii/workspace-registry` plus `/srv/thothii/data`. Exclude
`/srv/thothii/secrets`. Render Compose, deploy the compatible image, verify health/status, then
resume proxy traffic.
For PSD migration, use a temporary review clone and the legacy transformer with absolute paths.
Its schema-v1 output is `migration_required`; explicitly supply vector database/schema, collection
identity, diagnostics, and the reviewed v2 contract before commit. Never import `${ENV}` values or
copy secret files.
After valid bootstrap, Git outage retains the active snapshot with `degraded: true`. Repair
egress/DNS/CA/credentials, pull, and confirm healthy status. Roll back a bad descriptor through a
reviewed Git revert/release branch, advance the remote through normal policy, pull it, and confirm
the replacement snapshot. Restore a registry backup only while stopped and with a compatible image;
do not delete snapshots as a rollback shortcut.
## Troubleshooting and snapshot rollback
| Stable code | Meaning and safe response |
| --- | --- |
| `workspace_invalid` | Invalid descriptor/path/snapshot; restore a reviewed canonical Git revision. |
| `binding_missing` | Missing/invalid local value or readable `*_FILE`; correct mount and permissions. |
| `workspace_not_activatable` | Bindings/diagnostics cannot activate; use sanitized fields to fix selected transport. |
| `workspace_stale` | Checkout changed/locked; stop concurrent registry work, never force Git in the volume. |
| `workspace_conflict` | Draft base stale; pull, resolve, validate, republish. |
| `git_unavailable` | Storage/remote/DNS/firewall/lock failed; preserve degraded active state while repairing it. |
| `git_auth_failed` | SSH/HTTPS material rejected or unreadable; rotate/fix file without printing it. |
| `git_non_fast_forward` | Checkout diverged; reconcile through registry workflow and branch policy. |
| `git_push_rejected` | Gitea policy rejected publish; review hooks/branch protection. |
| `connector_unavailable` | DNS/TLS/auth/resource identity failed; check egress and local bindings. |
| `semantic_index_incompatible` | Collection/model/dimensions/distance differs; perform explicit index migration. |
If the current snapshot is valid but Git remains down, continue only work safe on that pinned
revision and monitor status. If snapshots are missing or corrupt, stop the service, restore the
newest verified registry backup, start it privately, verify status, and then reopen proxy traffic.
A first-bootstrap failure has no fallback: repair remote trust rather than creating an unreviewed
runtime checkout.
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Validate the installation manuals without reading an operator environment or production remote.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd -P)"
profile="${1:-}"
case "$profile" in
--profile)
profile="${2:-}"
[[ $# -eq 2 ]] || { echo "usage: $0 --profile {local|server}" >&2; exit 2; }
;;
*)
echo "usage: $0 --profile {local|server}" >&2
exit 2
;;
esac
case "$profile" in
local)
manual="$root/docs/install/local-workspace-registry.md"
example="$root/docs/install/examples/local-compose.workspace-registry.yaml"
headings=(
"Prerequisites"
"Git remote: SSH and HTTPS"
"Shared Git values, local bindings, and secret files"
"Direct PostgreSQL, REST, and SSH tunnel bindings"
"Bootstrap, first pull, and diagnostics"
"Publish, update, backup, outage recovery, and rollback"
"Troubleshooting"
)
;;
server)
manual="$root/docs/install/server-workspace-registry.md"
example="$root/docs/install/examples/server-compose.workspace-registry.yaml"
headings=(
"Service account, storage, and firewall"
"Gitea and remote Git setup"
"Git credentials, CA, SSH key, and known-hosts mounts"
"Shared Git values, local bindings, and secret files"
"Direct PostgreSQL, REST, and SSH tunnel bindings"
"Same-origin reverse proxy, bootstrap, and health"
"Pull, publish, upgrade, backup, and recovery"
"Troubleshooting and snapshot rollback"
)
;;
*)
echo "unknown documentation profile: $profile" >&2
exit 2
;;
esac
[[ -f "$manual" ]] || { echo "missing $profile installation manual: $manual" >&2; exit 1; }
[[ -f "$example" ]] || { echo "missing $profile Compose example: $example" >&2; exit 1; }
for heading in "${headings[@]}"; do
grep -Fqx "## $heading" "$manual" >/dev/null || {
echo "missing required heading in $profile manual: $heading" >&2
exit 1
}
done
grep -Fq "$(basename "$example")" "$manual" || {
echo "the $profile manual does not reference its Compose example" >&2
exit 1
}
# Values for secret-bearing variables must be paths. These patterns catch common accidental
# credentials while allowing declarative *_FILE bindings and explicitly empty assignments.
if grep -Ein '(^|[[:space:]])(password|api[_-]?key|token|secret)[[:space:]]*[:=][[:space:]]*[^[:space:]#]' \
"$manual" "$example" >/dev/null; then
echo "installation documentation contains a secret literal" >&2
exit 1
fi
commands="$(mktemp "${TMPDIR:-/tmp}/thoth-install-docs.XXXXXX")"
trap 'rm -f "$commands"' EXIT HUP INT TERM
# A runnable documentation command is a sh fence immediately following this marker. Commands
# outside the marker are explanatory/operator commands and are deliberately never executed here.
awk '
/^<!--[[:space:]]*verify:command[[:space:]]*-->[[:space:]]*$/ { marked=1; next }
marked && /^```(sh|bash|shell)[[:space:]]*$/ { in_fence=1; marked=0; seen=1; next }
in_fence && /^```[[:space:]]*$/ { in_fence=0; next }
in_fence { print }
' "$manual" >"$commands"
[[ -s "$commands" ]] || { echo "no marked runnable commands in $profile manual" >&2; exit 1; }
echo "== Validate $profile documented Compose example =="
(
cd "$root"
bash "$commands"
)
echo "== Run isolated workspace-registry bootstrap and recovery smoke =="
(
cd "$root"
env -u WORKSPACE_GIT_REMOTE ./scripts/workspace-registry-smoke.sh
)
echo "$profile installation documentation verification passed"