bdc47dde7f
GSD-Unit: M001/S06/T01
196 lines
5.7 KiB
Bash
Executable File
196 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
API_BASE="${API_BASE:-http://localhost:5202/api}"
|
|
AUTH_TOKEN="${AUTH_TOKEN:-}"
|
|
CURL_TIMEOUT="${CURL_TIMEOUT:-15}"
|
|
CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-3}"
|
|
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
note() {
|
|
printf '%s\n' "$*"
|
|
}
|
|
|
|
fail() {
|
|
printf 'ERROR: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
json_get() {
|
|
local file="$1"
|
|
local path="$2"
|
|
python3 - "$file" "$path" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
file_path, path = sys.argv[1], sys.argv[2]
|
|
with open(file_path, 'r', encoding='utf-8') as fh:
|
|
data = json.load(fh)
|
|
|
|
value = data
|
|
for part in path.split('.'):
|
|
if isinstance(value, dict) and part in value:
|
|
value = value[part]
|
|
else:
|
|
raise KeyError(path)
|
|
|
|
if value is None:
|
|
print('null')
|
|
elif isinstance(value, bool):
|
|
print('true' if value else 'false')
|
|
elif isinstance(value, (dict, list)):
|
|
print(json.dumps(value, separators=(',', ':')))
|
|
else:
|
|
print(value)
|
|
PY
|
|
}
|
|
|
|
validate_json() {
|
|
local file="$1"
|
|
if ! python3 - "$file" <<'PY'
|
|
import json
|
|
import sys
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as fh:
|
|
json.load(fh)
|
|
PY
|
|
then
|
|
printf 'Raw response body:\n' >&2
|
|
cat "$file" >&2
|
|
fail "Malformed JSON returned by API."
|
|
fi
|
|
}
|
|
|
|
request_json() {
|
|
local name="$1"
|
|
local url="$2"
|
|
local body_file="$3"
|
|
shift 3
|
|
local -a headers=("$@")
|
|
local err_file="$TMP_DIR/${name}.curl.err"
|
|
local status_file="$TMP_DIR/${name}.status"
|
|
: >"$err_file"
|
|
|
|
set +e
|
|
curl -fsS \
|
|
--connect-timeout "$CONNECT_TIMEOUT" \
|
|
--max-time "$CURL_TIMEOUT" \
|
|
-H 'Accept: application/json' \
|
|
"${headers[@]}" \
|
|
-o "$body_file" \
|
|
-w '%{http_code}' \
|
|
"$url" >"$status_file" 2>"$err_file"
|
|
local curl_exit=$?
|
|
set -e
|
|
|
|
if [[ $curl_exit -eq 0 ]]; then
|
|
validate_json "$body_file"
|
|
return 0
|
|
fi
|
|
local status='000'
|
|
if [[ -s "$status_file" ]]; then
|
|
status="$(tr -d '\r\n' <"$status_file")"
|
|
fi
|
|
|
|
case "$curl_exit:$status" in
|
|
7:*|28:*|52:*|56:*|6:*)
|
|
printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2
|
|
fail "Cannot reach ${url}. Start the API from JobTrackerApi/ with 'dotnet run' and verify it is listening on ${API_BASE%/api}."
|
|
;;
|
|
22:401|22:403)
|
|
note "${name}: auth required."
|
|
note "Hint: export AUTH_TOKEN with an admin bearer token from POST ${API_BASE}/auth/login before rerunning this preflight."
|
|
return 22
|
|
;;
|
|
22:*)
|
|
local diag_body="$TMP_DIR/${name}.diag.body"
|
|
local diag_status="$TMP_DIR/${name}.diag.status"
|
|
local diag_err="$TMP_DIR/${name}.diag.err"
|
|
curl -sS \
|
|
--connect-timeout "$CONNECT_TIMEOUT" \
|
|
--max-time "$CURL_TIMEOUT" \
|
|
-H 'Accept: application/json' \
|
|
"${headers[@]}" \
|
|
-o "$diag_body" \
|
|
-w '%{http_code}' \
|
|
"$url" >"$diag_status" 2>"$diag_err" || true
|
|
local http_status="$(tr -d '\r\n' <"$diag_status")"
|
|
printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2
|
|
if [[ -s "$diag_body" ]]; then
|
|
printf 'Response body:\n' >&2
|
|
cat "$diag_body" >&2
|
|
printf '\n' >&2
|
|
fi
|
|
fail "${name} probe failed with HTTP ${http_status:-unknown}."
|
|
;;
|
|
*)
|
|
printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2
|
|
fail "${name} probe failed with curl exit ${curl_exit}."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
auth_config_body="$TMP_DIR/auth-config.json"
|
|
request_json "auth config" "$API_BASE/auth/config" "$auth_config_body"
|
|
|
|
require_auth="$(json_get "$auth_config_body" 'requireAuth')"
|
|
google_enabled="$(json_get "$auth_config_body" 'googleEnabled')"
|
|
local_enabled="$(json_get "$auth_config_body" 'localEnabled')"
|
|
allow_registration="$(json_get "$auth_config_body" 'allowRegistration')"
|
|
|
|
note "Preflight target: ${API_BASE}"
|
|
note "cors.requiredOriginPair=UI http://localhost:3000 -> API http://localhost:5202/api"
|
|
note "auth.requireAuth=${require_auth}"
|
|
note "auth.localEnabled=${local_enabled}"
|
|
note "auth.googleEnabled=${google_enabled}"
|
|
note "auth.allowRegistration=${allow_registration}"
|
|
|
|
system_body="$TMP_DIR/admin-system.json"
|
|
declare -a auth_headers=()
|
|
if [[ -n "$AUTH_TOKEN" ]]; then
|
|
auth_headers+=(-H "Authorization: Bearer ${AUTH_TOKEN}")
|
|
fi
|
|
|
|
if request_json "admin system" "$API_BASE/admin/system" "$system_body" "${auth_headers[@]}"; then
|
|
db_provider="$(json_get "$system_body" 'database.provider')"
|
|
db_configured="$(json_get "$system_body" 'database.looksConfigured')"
|
|
db_connect="$(json_get "$system_body" 'database.canConnect')"
|
|
db_target="$(json_get "$system_body" 'database.target')"
|
|
db_warning="$(json_get "$system_body" 'database.warning')"
|
|
auth_required_runtime="$(json_get "$system_body" 'auth.required')"
|
|
gmail_configured="$(json_get "$system_body" 'auth.gmailConfigured')"
|
|
ai_healthy="$(json_get "$system_body" 'ai.healthy')"
|
|
ai_model="$(json_get "$system_body" 'ai.model')"
|
|
ai_last_error="$(json_get "$system_body" 'ai.lastError')"
|
|
|
|
note "db.provider=${db_provider}"
|
|
note "db.looksConfigured=${db_configured}"
|
|
note "db.canConnect=${db_connect}"
|
|
note "db.target=${db_target}"
|
|
note "db.warning=${db_warning}"
|
|
note "auth.runtimeRequired=${auth_required_runtime}"
|
|
note "gmailConfigured=${gmail_configured}"
|
|
note "ai.healthy=${ai_healthy}"
|
|
note "ai.model=${ai_model}"
|
|
note "ai.lastError=${ai_last_error}"
|
|
|
|
if [[ "$db_connect" != "true" ]]; then
|
|
fail "Database is not connectable according to /admin/system."
|
|
fi
|
|
|
|
note "Preflight passed."
|
|
exit 0
|
|
fi
|
|
|
|
note "db.provider=unknown"
|
|
note "db.looksConfigured=unknown"
|
|
note "db.canConnect=unknown"
|
|
note "db.target=unknown"
|
|
note "db.warning=admin token required to inspect database status"
|
|
note "gmailConfigured=unknown"
|
|
note "ai.healthy=unknown"
|
|
note "ai.model=unknown"
|
|
note "ai.lastError=unknown"
|
|
note "Preflight partially passed: anonymous auth config is reachable, but admin/system requires an admin bearer token for DB/Gmail/AI details."
|