fix: close deployment decoupling review
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Validate the installation manuals without reading an operator environment or production remote.
|
||||
# Verify canonical local/server installation manuals and their base+override Compose paths.
|
||||
set -euo pipefail
|
||||
|
||||
root="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
profile="${1:-}"
|
||||
mode="${1:-}"
|
||||
|
||||
trim() {
|
||||
local value="$1"
|
||||
@@ -41,266 +41,13 @@ verify_path_variable_values() {
|
||||
fi
|
||||
fi
|
||||
done <"$source"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_server_public_contract() {
|
||||
local server_example="$root/docs/install/examples/server-compose.workspace-registry.yaml"
|
||||
for expected in \
|
||||
'THT_SESSION_STORAGE: postgres' \
|
||||
'THT_SESSION_RUNTIME_PASSWORD_FILE: /run/secrets/session_runtime_password' \
|
||||
'THT_SESSION_DB_SSLMODE: ${THT_SESSION_DB_SSLMODE:-verify-full}' \
|
||||
'THT_SESSION_DB_SSLROOTCERT: /run/secrets/session_ca.pem' \
|
||||
'session_runtime_password:' \
|
||||
'session_ca:'; do
|
||||
grep -Fq "$expected" "$server_example" || {
|
||||
echo "server Compose example lacks required public PostgreSQL/TLS contract: $expected" >&2
|
||||
return 1
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
verify_manual_supported_path() {
|
||||
local profile="$1" manual="$2"
|
||||
local source_root_export='export THT_SOURCE_ROOT=/absolute/path/to/ThothII'
|
||||
local bindings_export='export THT_WORKSPACE_BINDINGS_ENV_FILE="$(pwd -P)/workspace-bindings.env"'
|
||||
local generator='"$THT_SOURCE_ROOT/scripts/generate-connector-secrets-override.sh" --bindings-env "$THT_WORKSPACE_BINDINGS_ENV_FILE" --operator-env .env --output connector-secrets.local.yaml'
|
||||
local wrapper='"$THT_SOURCE_ROOT/scripts/compose-with-preflight.sh" --env-file .env'
|
||||
|
||||
grep -Fq "$source_root_export" "$manual" || {
|
||||
echo "$profile manual does not export THT_SOURCE_ROOT for its shell commands" >&2
|
||||
return 1
|
||||
}
|
||||
grep -Fq "$bindings_export" "$manual" || {
|
||||
echo "$profile manual does not export THT_WORKSPACE_BINDINGS_ENV_FILE for its shell commands" >&2
|
||||
return 1
|
||||
}
|
||||
grep -Fq "$generator" "$manual" || {
|
||||
echo "$profile manual does not document the connector override generator" >&2
|
||||
return 1
|
||||
}
|
||||
grep -Fq "$wrapper" "$manual" || {
|
||||
echo "$profile manual does not document the Compose preflight wrapper" >&2
|
||||
return 1
|
||||
}
|
||||
if grep -Eq 'connector-secrets\.workspace-registry|docker compose' "$manual"; then
|
||||
echo "$profile manual documents a bypassed Compose or copied connector override path" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "$profile manual requires generated connector override and Compose preflight passed"
|
||||
}
|
||||
|
||||
compose_fixture() {
|
||||
local name="$1" directory="$2"; shift 2
|
||||
(
|
||||
cd "$directory"
|
||||
"$root/scripts/compose-with-preflight.sh" --env-file .env "$@" config --quiet
|
||||
)
|
||||
echo "$name passed"
|
||||
}
|
||||
|
||||
prepare_binding_fixture() {
|
||||
local directory="$1"
|
||||
printf '%s\n' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT=postgres_direct' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE=/run/secrets/north-star-research-dwh-password' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_VECTOR_TRANSPORT=rest_api' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_FILE=/run/secrets/north-star-research-vector-api-key' \
|
||||
>"$directory/workspace-bindings.env"
|
||||
printf 'THT_WORKSPACE_BINDINGS_ENV_FILE=%s\n' "$directory/workspace-bindings.env" >>"$directory/.env"
|
||||
}
|
||||
|
||||
verify_connector_fixture() {
|
||||
local directory="$1" rendered project connector_override
|
||||
project="thoth-install-connector-fixture-$$"
|
||||
connector_override="$directory/connector-secrets.local.yaml"
|
||||
"$root/scripts/generate-connector-secrets-override.sh" \
|
||||
--bindings-env "$directory/workspace-bindings.env" --operator-env "$directory/.env" \
|
||||
--output "$connector_override" >/dev/null
|
||||
rendered="$(
|
||||
cd "$directory"
|
||||
"$root/scripts/compose-with-preflight.sh" --env-file .env \
|
||||
-f compose.workspace-registry.yaml -f connector-secrets.local.yaml config
|
||||
)"
|
||||
for expected in \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT: postgres_direct' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE: /run/secrets/north-star-research-dwh-password' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_FILE: /run/secrets/north-star-research-vector-api-key' \
|
||||
'target: north-star-research-dwh-password' \
|
||||
'target: north-star-research-vector-api-key'; do
|
||||
grep -Fq "$expected" <<<"$rendered" || {
|
||||
echo "connector fixture does not give core required binding or secret target: $expected" >&2
|
||||
return 1
|
||||
}
|
||||
done
|
||||
echo "copied connector binding/secret fixture passed"
|
||||
if ! (
|
||||
cd "$directory"
|
||||
"$root/scripts/compose-with-preflight.sh" --project-name "$project" --env-file .env \
|
||||
-f compose.workspace-registry.yaml -f connector-secrets.local.yaml run --rm --no-deps --build --entrypoint sh core -c '
|
||||
test "$THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT" = postgres_direct
|
||||
test "$THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE" = /run/secrets/north-star-research-dwh-password
|
||||
test "$THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_FILE" = /run/secrets/north-star-research-vector-api-key
|
||||
test -f "$THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE"
|
||||
test -f "$THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_FILE"
|
||||
'
|
||||
); then
|
||||
(
|
||||
cd "$directory"
|
||||
"$root/scripts/compose-with-preflight.sh" --project-name "$project" --env-file .env \
|
||||
-f compose.workspace-registry.yaml -f connector-secrets.local.yaml down --volumes --remove-orphans
|
||||
) || true
|
||||
return 1
|
||||
fi
|
||||
(
|
||||
cd "$directory"
|
||||
"$root/scripts/compose-with-preflight.sh" --project-name "$project" --env-file .env \
|
||||
-f compose.workspace-registry.yaml -f connector-secrets.local.yaml down --volumes --remove-orphans
|
||||
)
|
||||
echo "core process sees connector bindings and secret files passed"
|
||||
}
|
||||
|
||||
verify_documented_operator_path() {
|
||||
local profile="$1" directory="$2" documented_source_root="$3" connector_override
|
||||
connector_override="$directory/connector-secrets.local.yaml"
|
||||
(
|
||||
cd "$directory"
|
||||
unset THT_SOURCE_ROOT THT_WORKSPACE_BINDINGS_ENV_FILE
|
||||
export THT_SOURCE_ROOT="$documented_source_root"
|
||||
export THT_WORKSPACE_BINDINGS_ENV_FILE="$(pwd -P)/workspace-bindings.env"
|
||||
"$THT_SOURCE_ROOT/scripts/generate-connector-secrets-override.sh" \
|
||||
--bindings-env "$THT_WORKSPACE_BINDINGS_ENV_FILE" --operator-env .env \
|
||||
--output connector-secrets.local.yaml >/dev/null
|
||||
"$THT_SOURCE_ROOT/scripts/compose-with-preflight.sh" --env-file .env \
|
||||
-f compose.workspace-registry.yaml -f git-ssh.workspace-registry.yaml \
|
||||
-f connector-secrets.local.yaml config --quiet
|
||||
)
|
||||
echo "$profile documented shell environment fixture passed"
|
||||
}
|
||||
|
||||
verify_copied_operator_fixtures() {
|
||||
local fixture_root local_dir server_dir https_dir ssh_dir connector_dir
|
||||
fixture_root="$(mktemp -d "${TMPDIR%/}/thoth-install-fixtures.XXXXXX")"
|
||||
trap 'rm -rf "$fixture_root"' RETURN
|
||||
local_dir="$fixture_root/local"; server_dir="$fixture_root/server"
|
||||
https_dir="$fixture_root/https"; ssh_dir="$fixture_root/ssh"; connector_dir="$fixture_root/connector"
|
||||
mkdir -p "$local_dir" "$server_dir" "$https_dir" "$ssh_dir" "$connector_dir"
|
||||
|
||||
cp "$root/docs/install/examples/local-compose.workspace-registry.yaml" "$local_dir/compose.workspace-registry.yaml"
|
||||
printf 'THT_SOURCE_ROOT=%s\n' "$root" >"$local_dir/.env"
|
||||
prepare_binding_fixture "$local_dir"
|
||||
compose_fixture "copied local base fixture" "$local_dir" -f compose.workspace-registry.yaml
|
||||
|
||||
cp "$root/docs/install/examples/server-compose.workspace-registry.yaml" "$server_dir/compose.workspace-registry.yaml"
|
||||
cp "$root/deploy/workspaces/server-sessions.yaml.example" "$server_dir/server-sessions.yaml"
|
||||
: >"$server_dir/session-runtime-password"; : >"$server_dir/session-ca.pem"
|
||||
printf '%s\n' \
|
||||
"THT_SOURCE_ROOT=$root" \
|
||||
"THT_SERVER_WORKSPACE_CONFIG=$server_dir/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_RUNTIME_PASSWORD_SOURCE=$server_dir/session-runtime-password" \
|
||||
"THT_SESSION_CA_SOURCE=$server_dir/session-ca.pem" >"$server_dir/.env"
|
||||
prepare_binding_fixture "$server_dir"
|
||||
compose_fixture "copied server PostgreSQL/TLS fixture" "$server_dir" -f compose.workspace-registry.yaml
|
||||
|
||||
cp "$root/docs/install/examples/local-compose.workspace-registry.yaml" "$https_dir/compose.workspace-registry.yaml"
|
||||
cp "$root/docs/install/examples/git-https.workspace-registry.yaml" "$https_dir/git-https.yaml"
|
||||
: >"$https_dir/git-credentials"; : >"$https_dir/git-ca.pem"
|
||||
printf '%s\n' \
|
||||
"THT_SOURCE_ROOT=$root" \
|
||||
"THT_WORKSPACE_GIT_CREDENTIALS_FILE=$https_dir/git-credentials" \
|
||||
"THT_WORKSPACE_GIT_CA_FILE=$https_dir/git-ca.pem" >"$https_dir/.env"
|
||||
prepare_binding_fixture "$https_dir"
|
||||
compose_fixture "copied HTTPS Git override fixture" "$https_dir" -f compose.workspace-registry.yaml -f git-https.yaml
|
||||
|
||||
cp "$root/docs/install/examples/local-compose.workspace-registry.yaml" "$ssh_dir/compose.workspace-registry.yaml"
|
||||
cp "$root/docs/install/examples/git-ssh.workspace-registry.yaml" "$ssh_dir/git-ssh.yaml"
|
||||
: >"$ssh_dir/git-ssh-key"; : >"$ssh_dir/git-known-hosts"
|
||||
printf '%s\n' \
|
||||
"THT_SOURCE_ROOT=$root" \
|
||||
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$ssh_dir/git-ssh-key" \
|
||||
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$ssh_dir/git-known-hosts" >"$ssh_dir/.env"
|
||||
prepare_binding_fixture "$ssh_dir"
|
||||
compose_fixture "copied SSH Git override fixture" "$ssh_dir" -f compose.workspace-registry.yaml -f git-ssh.yaml
|
||||
|
||||
: >"$server_dir/git-ssh-key"; : >"$server_dir/git-known-hosts"
|
||||
: >"$server_dir/dwh-password"; : >"$server_dir/vector-api-key"
|
||||
printf '%s\n' \
|
||||
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$server_dir/git-ssh-key" \
|
||||
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$server_dir/git-known-hosts" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$server_dir/dwh-password" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_SOURCE=$server_dir/vector-api-key" >>"$server_dir/.env"
|
||||
cp "$root/docs/install/examples/git-ssh.workspace-registry.yaml" "$server_dir/git-ssh.workspace-registry.yaml"
|
||||
: >"$ssh_dir/dwh-password"; : >"$ssh_dir/vector-api-key"
|
||||
printf '%s\n' \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$ssh_dir/dwh-password" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_SOURCE=$ssh_dir/vector-api-key" >>"$ssh_dir/.env"
|
||||
cp "$root/docs/install/examples/git-ssh.workspace-registry.yaml" "$ssh_dir/git-ssh.workspace-registry.yaml"
|
||||
verify_documented_operator_path local "$ssh_dir" "$root"
|
||||
verify_documented_operator_path server "$server_dir" "$root"
|
||||
|
||||
cp "$root/docs/install/examples/local-compose.workspace-registry.yaml" "$connector_dir/compose.workspace-registry.yaml"
|
||||
: >"$connector_dir/dwh-password"; : >"$connector_dir/vector-password"
|
||||
printf '%s\n' \
|
||||
"THT_SOURCE_ROOT=$root" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$connector_dir/dwh-password" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_SOURCE=$connector_dir/vector-password" >"$connector_dir/.env"
|
||||
prepare_binding_fixture "$connector_dir"
|
||||
verify_connector_fixture "$connector_dir"
|
||||
|
||||
printf 'THT_WS_EXAMPLE_DWH_PASSWORD_FILE=not-a-path\n' >"$fixture_root/non-path-secret.env"
|
||||
if verify_path_variable_values "$fixture_root/non-path-secret.env" >/dev/null 2>&1; then
|
||||
echo "non-path secret-file fixture was accepted" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "non-path secret-file fixture rejected passed"
|
||||
|
||||
printf 'THT_WS_EXAMPLE_DWH_PASSWORD_SOURCE=literal-value\n' >"$fixture_root/literal-source.env"
|
||||
if verify_path_variable_values "$fixture_root/literal-source.env" >/dev/null 2>&1; then
|
||||
echo "literal secret-source fixture was accepted" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "literal secret-source fixture rejected passed"
|
||||
|
||||
printf 'THT_WS_EXAMPLE_DWH_PASSWORD_SOURCE=installation-secrets/password\n' >"$fixture_root/relative-source.env"
|
||||
if verify_path_variable_values "$fixture_root/relative-source.env" >/dev/null 2>&1; then
|
||||
echo "relative secret-source fixture was accepted" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "relative secret-source fixture rejected passed"
|
||||
|
||||
printf 'THT_WS_EXAMPLE_DWH_PASSWORD_SOURCE=/srv/thothii/secrets/../password\n' >"$fixture_root/non-normalized-source.env"
|
||||
if verify_path_variable_values "$fixture_root/non-normalized-source.env" >/dev/null 2>&1; then
|
||||
echo "non-normalized secret-source fixture was accepted" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "non-normalized secret-source fixture rejected passed"
|
||||
}
|
||||
|
||||
case "$profile" in
|
||||
--fixtures-only)
|
||||
[[ $# -eq 1 ]] || { echo "usage: $0 --fixtures-only" >&2; exit 2; }
|
||||
verify_manual_supported_path local "$root/docs/install/local-workspace-registry.md"
|
||||
verify_manual_supported_path server "$root/docs/install/server-workspace-registry.md"
|
||||
verify_copied_operator_fixtures
|
||||
exit 0
|
||||
;;
|
||||
--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"
|
||||
verify_manual() {
|
||||
local profile="$1" manual
|
||||
manual="$root/docs/install/$profile-workspace-registry.md"
|
||||
local -a headings
|
||||
if [[ "$profile" == local ]]; then
|
||||
headings=(
|
||||
"Prerequisites"
|
||||
"Git remote: SSH and HTTPS"
|
||||
@@ -310,10 +57,7 @@ case "$profile" in
|
||||
"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"
|
||||
else
|
||||
headings=(
|
||||
"Service account, storage, and firewall"
|
||||
"Gitea and remote Git setup"
|
||||
@@ -324,50 +68,186 @@ case "$profile" in
|
||||
"Pull, publish, upgrade, backup, and recovery"
|
||||
"Troubleshooting and snapshot rollback"
|
||||
)
|
||||
fi
|
||||
for heading in "${headings[@]}"; do
|
||||
grep -Fqx "## $heading" "$manual" || {
|
||||
echo "missing required heading in $profile manual: $heading" >&2
|
||||
return 1
|
||||
}
|
||||
done
|
||||
for expected in \
|
||||
'export THT_SOURCE_ROOT=/absolute/path/to/ThothII' \
|
||||
'--env-file "$THT_OPERATOR_ENV"' \
|
||||
"-f \"\$THT_SOURCE_ROOT/compose.yaml\" -f \"\$THT_SOURCE_ROOT/deploy/compose.$profile.yaml\"" \
|
||||
'"$THT_SOURCE_ROOT/scripts/generate-connector-secrets-override.sh"'; do
|
||||
grep -Fq -- "$expected" "$manual" || {
|
||||
echo "$profile manual lacks canonical operator step: $expected" >&2
|
||||
return 1
|
||||
}
|
||||
done
|
||||
if rg -n 'local-compose\.workspace-registry|server-compose\.workspace-registry|connector-secrets\.workspace-registry|docker compose' "$manual"; then
|
||||
echo "$profile manual documents a superseded or bypassed Compose path" >&2
|
||||
return 1
|
||||
fi
|
||||
verify_path_variable_values "$manual"
|
||||
echo "$profile manual canonical base+override references passed"
|
||||
}
|
||||
|
||||
write_private() {
|
||||
local path="$1" value="$2"
|
||||
printf '%s\n' "$value" >"$path"
|
||||
chmod 0600 "$path"
|
||||
}
|
||||
|
||||
verify_compose_fixtures() {
|
||||
local fixture connector_override profile rendered
|
||||
fixture="$(mktemp -d "${TMPDIR%/}/thoth-install-fixtures.XXXXXX")"
|
||||
trap 'rm -rf "$fixture"' RETURN
|
||||
mkdir -p "$fixture/data" "$fixture/pi-state" "$fixture/workspace-registry"
|
||||
|
||||
write_private "$fixture/pi-auth.json" '{"zai":{"type":"api_key","key":"fixture-native-auth-key"}}'
|
||||
write_private "$fixture/thothii.secrets" 'THT_MODEL_API_KEY=fixture-model-api-key'
|
||||
write_private "$fixture/git-ssh-key" 'fixture-git-ssh-key'
|
||||
write_private "$fixture/git-known-hosts" 'fixture-git-known-hosts'
|
||||
write_private "$fixture/dwh-password" 'fixture-dwh-password'
|
||||
write_private "$fixture/vector-api-key" 'fixture-vector-api-key'
|
||||
write_private "$fixture/session-runtime-password" 'fixture-session-runtime-password'
|
||||
write_private "$fixture/session-migrator-password" 'fixture-session-migrator-password'
|
||||
write_private "$fixture/session-ca.pem" 'fixture-session-ca'
|
||||
cp "$root/deploy/workspaces/server-sessions.yaml.example" "$fixture/server-sessions.yaml"
|
||||
|
||||
printf '%s\n' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT=postgres_direct' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE=/run/secrets/north-star-research-dwh-password' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_VECTOR_TRANSPORT=rest_api' \
|
||||
'THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_FILE=/run/secrets/north-star-research-vector-api-key' \
|
||||
>"$fixture/workspace-bindings.env"
|
||||
|
||||
printf '%s\n' \
|
||||
'THT_WORKSPACE_GIT_REMOTE=ssh://git@git.example.invalid/platform/thoth-workspaces.git' \
|
||||
"PI_AUTH_FILE=$fixture/pi-auth.json" \
|
||||
"THT_SECRETS_FILE=$fixture/thothii.secrets" \
|
||||
"THT_WORKSPACE_BINDINGS_ENV_FILE=$fixture/workspace-bindings.env" \
|
||||
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$fixture/git-ssh-key" \
|
||||
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$fixture/git-known-hosts" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$fixture/dwh-password" \
|
||||
"THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_SOURCE=$fixture/vector-api-key" \
|
||||
"THT_DATA_ROOT=$fixture/data" \
|
||||
"THT_PI_STATE_ROOT=$fixture/pi-state" \
|
||||
"THT_WORKSPACE_REGISTRY_ROOT=$fixture/workspace-registry" \
|
||||
"THT_SERVER_WORKSPACE_CONFIG=$fixture/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=$fixture/session-runtime-password" \
|
||||
"THT_SESSION_MIGRATOR_PASSWORD_SOURCE=$fixture/session-migrator-password" \
|
||||
"THT_SESSION_CA_SOURCE=$fixture/session-ca.pem" \
|
||||
>"$fixture/operator.env"
|
||||
|
||||
connector_override="$fixture/connector-secrets.local.yaml"
|
||||
"$root/scripts/generate-connector-secrets-override.sh" \
|
||||
--bindings-env "$fixture/workspace-bindings.env" \
|
||||
--operator-env "$fixture/operator.env" \
|
||||
--output "$connector_override" >/dev/null
|
||||
|
||||
for profile in local server; do
|
||||
rendered="$fixture/$profile.json"
|
||||
files=(
|
||||
-f "$root/compose.yaml"
|
||||
-f "$root/deploy/compose.$profile.yaml"
|
||||
)
|
||||
if [[ "$profile" == server ]]; then
|
||||
files+=(-f "$root/deploy/compose.session-server.yaml.example")
|
||||
fi
|
||||
files+=(
|
||||
-f "$root/deploy/compose.git-ssh.yaml"
|
||||
-f "$connector_override"
|
||||
)
|
||||
"$root/scripts/compose-with-preflight.sh" --env-file "$fixture/operator.env" \
|
||||
"${files[@]}" config --format json >"$rendered"
|
||||
|
||||
node - "$rendered" "$profile" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const [path, profile] = process.argv.slice(2);
|
||||
const config = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||
if (Object.keys(config.services).sort().join(",") !== "core,frontend") {
|
||||
throw new Error(profile + ": mandatory stack must be exactly core,frontend");
|
||||
}
|
||||
const core = config.services.core;
|
||||
for (const target of [
|
||||
"/home/thoth/.pi/agent/auth.json",
|
||||
"/home/thoth/.pi/agent/models.json",
|
||||
"/home/thoth/.pi/agent/settings.json",
|
||||
]) {
|
||||
if (!(core.volumes || []).some((mount) => mount.target === target && mount.read_only)) {
|
||||
throw new Error(profile + ": missing read-only Pi mount " + target);
|
||||
}
|
||||
}
|
||||
for (const [name, value] of Object.entries({
|
||||
THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE: "/run/secrets/north-star-research-dwh-password",
|
||||
THT_WS_NORTH_STAR_RESEARCH_VECTOR_API_KEY_FILE: "/run/secrets/north-star-research-vector-api-key",
|
||||
})) {
|
||||
if (core.environment?.[name] !== value) throw new Error(profile + ": missing binding " + name);
|
||||
}
|
||||
const secretTargets = new Set((core.secrets || []).map((secret) => secret.target));
|
||||
for (const target of [
|
||||
"thothii.secrets",
|
||||
"north-star-research-dwh-password",
|
||||
"north-star-research-vector-api-key",
|
||||
]) {
|
||||
if (!secretTargets.has(target)) throw new Error(profile + ": missing secret target " + target);
|
||||
}
|
||||
if (profile === "server") {
|
||||
for (const target of ["session_runtime_password", "session_ca.pem"]) {
|
||||
if (!secretTargets.has(target)) throw new Error("server: missing session secret " + target);
|
||||
}
|
||||
}
|
||||
if ((config.services.frontend.secrets || []).length !== 0) {
|
||||
throw new Error(profile + ": frontend received a runtime secret");
|
||||
}
|
||||
const rendered = JSON.stringify(config);
|
||||
for (const value of [
|
||||
"fixture-native-auth-key", "fixture-model-api-key", "fixture-git-ssh-key",
|
||||
"fixture-git-known-hosts", "fixture-dwh-password", "fixture-vector-api-key",
|
||||
"fixture-session-runtime-password", "fixture-session-migrator-password", "fixture-session-ca",
|
||||
]) {
|
||||
if (rendered.includes(value)) throw new Error(profile + ": rendered Compose leaked " + value);
|
||||
}
|
||||
NODE
|
||||
echo "canonical $profile base+override fixture passed"
|
||||
done
|
||||
|
||||
printf 'THT_WS_EXAMPLE_DWH_PASSWORD_SOURCE=relative/secret\n' >"$fixture/unsafe.env"
|
||||
if verify_path_variable_values "$fixture/unsafe.env" >/dev/null 2>&1; then
|
||||
echo "relative secret-source fixture was accepted" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "relative secret-source fixture rejected passed"
|
||||
}
|
||||
|
||||
case "$mode" in
|
||||
--fixtures-only)
|
||||
[[ $# -eq 1 ]] || { echo "usage: $0 --fixtures-only" >&2; exit 2; }
|
||||
verify_manual local
|
||||
verify_manual server
|
||||
verify_compose_fixtures
|
||||
;;
|
||||
--profile)
|
||||
profile="${2:-}"
|
||||
[[ $# -eq 2 && "$profile" =~ ^(local|server)$ ]] \
|
||||
|| { echo "usage: $0 --profile {local|server}" >&2; exit 2; }
|
||||
verify_manual "$profile"
|
||||
verify_compose_fixtures
|
||||
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"
|
||||
;;
|
||||
*)
|
||||
echo "unknown documentation profile: $profile" >&2
|
||||
echo "usage: $0 --fixtures-only | --profile {local|server}" >&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
|
||||
verify_path_variable_values "$manual"
|
||||
verify_path_variable_values "$example"
|
||||
verify_path_variable_values "$root/docs/install/examples/git-https.workspace-registry.yaml"
|
||||
verify_path_variable_values "$root/docs/install/examples/git-ssh.workspace-registry.yaml"
|
||||
verify_path_variable_values "$root/docs/install/examples/workspace-bindings.env.example"
|
||||
verify_server_public_contract
|
||||
verify_manual_supported_path "$profile" "$manual"
|
||||
|
||||
echo "== Validate copied operator fixtures and documented optional Git transports =="
|
||||
verify_copied_operator_fixtures
|
||||
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user