103 lines
3.4 KiB
Bash
Executable File
103 lines
3.4 KiB
Bash
Executable File
#!/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"
|