125 lines
6.0 KiB
Bash
Executable File
125 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Active installation manuals must drive the canonical two-service base+profile stack.
|
|
set -euo pipefail
|
|
|
|
root="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
tmp_parent="${TMPDIR:-/tmp}"
|
|
tmp="$(mktemp -d "${tmp_parent%/}/thoth-canonical-install.XXXXXX")"
|
|
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
|
|
|
for retired_example in \
|
|
"$root/docs/install/examples/local-compose.workspace-registry.yaml" \
|
|
"$root/docs/install/examples/server-compose.workspace-registry.yaml" \
|
|
"$root/docs/install/examples/git-ssh.workspace-registry.yaml" \
|
|
"$root/docs/install/examples/git-https.workspace-registry.yaml"; do
|
|
if [[ -e "$retired_example" ]]; then
|
|
echo "superseded one-service install example remains active: ${retired_example#"$root/"}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
printf '%s\n' '{}' >"$tmp/pi-auth.json"
|
|
printf '%s\n' 'THT_MODEL_API_KEY=fixture-model-api-key' >"$tmp/thothii.secrets"
|
|
printf '%s\n' 'schema_version: 1' >"$tmp/thothii-installation.yaml"
|
|
chmod 0600 "$tmp/pi-auth.json" "$tmp/thothii.secrets" "$tmp/thothii-installation.yaml"
|
|
mkdir -p "$tmp/data" "$tmp/pi-state" "$tmp/workspace-registry" "$tmp/auth"
|
|
chmod 0700 "$tmp/auth"
|
|
"$root/scripts/prepare-server-pi-state.sh" "$tmp/pi-state" "$(id -u)" "$(id -g)" >/dev/null
|
|
printf '%s\n' 'fixture-session-password' >"$tmp/session-runtime-password"
|
|
printf '%s\n' 'fixture-session-migrator-password' >"$tmp/session-migrator-password"
|
|
printf '%s\n' 'fixture-session-ca' >"$tmp/session-ca.pem"
|
|
cp "$root/deploy/workspaces/server-sessions.yaml.example" "$tmp/server-sessions.yaml"
|
|
chmod 0600 "$tmp/session-runtime-password" "$tmp/session-migrator-password" "$tmp/session-ca.pem"
|
|
|
|
for profile in local server; do
|
|
env_file="$tmp/$profile.env"
|
|
{
|
|
printf '%s\n' \
|
|
'THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/platform/thoth-workspaces.git' \
|
|
"PI_AUTH_FILE=$tmp/pi-auth.json" \
|
|
"THT_SECRETS_FILE=$tmp/thothii.secrets" \
|
|
"THT_INSTALLATION_CONFIG_SOURCE=$tmp/thothii-installation.yaml" \
|
|
"THT_AUTH_CONFIG_ROOT=$tmp/auth"
|
|
if [[ "$profile" == server ]]; then
|
|
printf '%s\n' \
|
|
"THT_DATA_ROOT=$tmp/data" \
|
|
"THT_PI_STATE_ROOT=$tmp/pi-state" \
|
|
"THT_WORKSPACE_REGISTRY_ROOT=$tmp/workspace-registry" \
|
|
"THT_SERVER_WORKSPACE_CONFIG=$tmp/server-sessions.yaml" \
|
|
'THT_SESSION_DB_HOST=sessions.example.invalid' \
|
|
'THT_SESSION_DB_NAME=thoth_sessions' \
|
|
'THT_SESSION_RUNTIME_USER=thoth_sessions_app' \
|
|
'THT_SESSION_MIGRATOR_USER=thoth_sessions_migrate' \
|
|
"THT_SESSION_RUNTIME_PASSWORD_SOURCE=$tmp/session-runtime-password" \
|
|
"THT_SESSION_MIGRATOR_PASSWORD_SOURCE=$tmp/session-migrator-password" \
|
|
"THT_SESSION_CA_SOURCE=$tmp/session-ca.pem"
|
|
fi
|
|
} >"$env_file"
|
|
|
|
compose_files=(-f "$root/compose.yaml" -f "$root/deploy/compose.$profile.yaml")
|
|
if [[ "$profile" == server ]]; then
|
|
compose_files+=(-f "$root/deploy/compose.session-server.yaml.example")
|
|
fi
|
|
docker compose --env-file "$env_file" "${compose_files[@]}" \
|
|
config --format json >"$tmp/$profile.json"
|
|
node - "$tmp/$profile.json" "$profile" <<'NODE'
|
|
const fs = require("fs");
|
|
const [path, profile] = process.argv.slice(2);
|
|
const config = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
const expected = "catalog-db,core,embedding,embedding-model-init,frontend,qdrant";
|
|
if (Object.keys(config.services).sort().join(",") !== expected) {
|
|
throw new Error(profile + ": install stack must be exactly " + expected);
|
|
}
|
|
if (!config.services.core.secrets?.some((secret) => secret.target === "thothii.secrets")) {
|
|
throw new Error(profile + ": install stack lacks the runtime secret bundle");
|
|
}
|
|
if (!config.services.core.volumes?.some(
|
|
(mount) => mount.target === "/home/thoth/.pi/agent/auth.json" && mount.read_only,
|
|
)) {
|
|
throw new Error(profile + ": install stack lacks the read-only Pi auth file");
|
|
}
|
|
const authConfig = config.services.core.volumes?.filter((mount) => mount.target === "/run/thothii-auth") || [];
|
|
if (authConfig.length !== 1 || authConfig[0].type !== "bind" || !authConfig[0].read_only) {
|
|
throw new Error(profile + ": core must receive one read-only authentication config bind");
|
|
}
|
|
if (Object.hasOwn(config.services.core.environment || {}, "THT_AUTH_RUNTIME_PROJECTION_ROOT")) {
|
|
throw new Error(profile + ": non-projected fixture unexpectedly selected runtime projection");
|
|
}
|
|
if (profile === "local") {
|
|
const authState = config.services.core.volumes?.filter((mount) => mount.target === "/data/auth") || [];
|
|
if (authState.length !== 1 || authState[0].type !== "volume" || authState[0].source !== "auth-state") {
|
|
throw new Error("local: core must receive the auth-state volume");
|
|
}
|
|
} else if (!config.services.core.volumes?.some((mount) => mount.target === "/data" && mount.type === "bind")) {
|
|
throw new Error("server: core must preserve the whole /data bind that contains auth state");
|
|
}
|
|
if ((config.services["workspace-maintenance"]?.volumes || []).some(
|
|
(mount) => mount.target === "/run/thothii-auth" || mount.target === "/data/auth",
|
|
)) {
|
|
throw new Error(profile + ": workspace-maintenance received authentication data");
|
|
}
|
|
if ((config.services.frontend.secrets || []).length !== 0) {
|
|
throw new Error(profile + ": frontend received runtime secrets");
|
|
}
|
|
if (profile === "server" && config.services.core.environment?.THT_SESSION_STORAGE !== "postgres") {
|
|
throw new Error("server: public startup must include the PostgreSQL session override");
|
|
}
|
|
if (JSON.stringify(config).includes("fixture-model-api-key")) {
|
|
throw new Error(profile + ": rendered Compose leaked a secret value");
|
|
}
|
|
NODE
|
|
done
|
|
|
|
for manual in "$root/docs/install/standalone-manual-it.md" "$root/docs/install/standalone-manual-en.md"; do
|
|
grep -Fq -- '--configure-only' "$manual" && grep -Fq -- 'catalog-migrate' "$manual" || {
|
|
echo "${manual#"$root/"} omits explicit configuration or migration" >&2
|
|
exit 1
|
|
}
|
|
if rg -q 'local-compose\.workspace-registry|server-compose\.workspace-registry' "$manual"; then
|
|
echo "${manual#"$root/"} references a retired Compose example" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "canonical install Compose contract passed."
|