Files
UmbraAudio/infra/bootstrap_enterprise_mint.sh
B.D. Murphy 0c9bea72e0 Add enterprise/infra tooling and repoint Qwen3-TTS submodule to internal fork
Two groups of changes, committed together as the follow-up to the initial push:

1. .gitmodules — submodule Qwen3-TTS now points at the internal Gitea fork
   (ssh://git@mini-eq:2222/umbragit/Qwen3-TTS.git) instead of upstream GitHub,
   so a clone actually gets the UmbraAudio branding/integration commit
   012e452 rather than vanilla upstream.

2. New infra/ tooling for the cross-machine work in spec 21: agent_bridge,
   remote_credential_broker (Keychain-backed, secrets read at runtime and
   generated via read_secret/random_secret — no hardcoded credentials), the
   enterprise bootstrap scripts, and the oldbox SSH/auth prompts. Plus
   backup/ restore scripts, two specs (18 UmbraTrans, 19 enterprise control
   plane), umbraaudio/voices/lmstudio_gate.py, and in-flight edits to the
   discovery/attribution/profiling modules, config, chapter_builder, emotion,
   pronunciation, qwen_tts and routes.

Also adds *.db to .gitignore: SQLite project databases are runtime state and no
.db file was tracked before.

Verified before staging: .env's only change is UMBRA_TTS__QWEN_TTS_HELPER_URL and
the committed copy has zero secret-looking values; the broker ships only
config.example.json.
2026-10-06 06:57:41 -07:00

512 lines
20 KiB
Bash
Executable File

#!/usr/bin/env bash
# Phase-one enterprise control-plane bootstrap for Linux Mint 22.x.
# This provisions PostgreSQL, database roles, core tenancy/identity tables,
# and least-privilege service accounts. It does not install application code,
# change Qwen, manage Docker, or change firewall rules.
set -Eeuo pipefail
umask 077
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
CONFIG_FILE="${UMBRA_BOOTSTRAP_CONFIG:-${SCRIPT_DIR}/enterprise-bootstrap.env}"
MODE="check"
DRY_RUN=0
LOCK_DIR="/run/lock/umbra-enterprise-bootstrap.lock"
LOG_FILE="/var/log/umbra-enterprise-bootstrap.log"
UMBRA_HOSTNAME="oldbox.bdmurphy.me"
UMBRA_PUBLIC_URL="https://oldbox.bdmurphy.me"
UMBRA_DB_NAME="umbracontrol"
UMBRA_DB_HOST="127.0.0.1"
UMBRA_DB_PORT="5432"
UMBRA_PG_VERSION="16"
UMBRA_APP_ROOT="/opt/umbracontrol"
UMBRA_DATA_ROOT="/var/lib/umbracontrol"
UMBRA_BACKUP_ROOT="/var/backups/umbracontrol"
UMBRA_BOOTSTRAP_ADMIN_EMAIL="umbratools@gmail.com"
UMBRA_ORGANIZATION_NAME="Umbra"
UMBRA_ORGANIZATION_SLUG="umbra"
UMBRA_QWEN_URL="http://192.168.123.14:8881"
UMBRA_INSTALL_PACKAGES=1
UMBRA_INSTALL_UV=1
UMBRA_CREATE_BOOTSTRAP_ADMIN=1
UMBRA_CONFIG_DIR="/etc/umbracontrol"
UMBRA_CREDENTIALS_FILE="/etc/umbracontrol/db-credentials.env"
UMBRA_BOOTSTRAP_TOKEN_FILE="/root/umbra-enterprise-bootstrap.token"
UMBRA_PG_SUPERUSER="postgres"
UMBRA_SERVICE_GROUP="umbracontrol"
UMBRA_API_USER="umbracontrol_api"
UMBRA_WORKER_USER="umbracontrol_worker"
UMBRA_READONLY_USER="umbracontrol_readonly"
UMBRA_BACKUP_USER="umbracontrol_backup"
UMBRA_MIGRATOR_USER="umbracontrol_migrator"
UMBRA_OWNER_ROLE="umbracontrol_owner"
usage() {
cat <<'EOF'
Usage: bootstrap_enterprise_mint.sh [options]
Options:
--check Read-only host and dependency inspection (default)
--dry-run Show apply actions without changing the host
--apply Apply the phase-one infrastructure bootstrap; requires root
--status Show installation/service/database status
--verify Verify installed services and database connectivity
--config FILE Read non-secret KEY=VALUE configuration
-h, --help Show this help
This phase intentionally does not install application code, modify Qwen,
manage Docker containers, or change firewall rules. Run it first on Mint,
then deploy the application and systemd units in a separate reviewed step.
EOF
}
while (($#)); do
case "$1" in
--check) MODE="check"; shift ;;
--dry-run) MODE="apply"; DRY_RUN=1; shift ;;
--apply) MODE="apply"; DRY_RUN=0; shift ;;
--status) MODE="status"; shift ;;
--verify) MODE="verify"; shift ;;
--config) CONFIG_FILE="${2:?--config requires a file}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) printf 'Unknown option: %s\n' "$1" >&2; usage >&2; exit 2 ;;
esac
done
log() {
local line
line="[$(date -Is)] $*"
printf '%s\n' "$line"
if [[ -f "$LOG_FILE" && "$(id -u)" -eq 0 ]]; then
printf '%s\n' "$line" >> "$LOG_FILE"
fi
}
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || die "Required command is missing: $1"
}
require_root() {
[[ "$(id -u)" -eq 0 ]] || die "This operation requires root. Use --check for a read-only inspection."
}
load_config() {
[[ -f "$CONFIG_FILE" ]] || {
[[ "$MODE" == "check" || "$MODE" == "status" || "$MODE" == "verify" ]] && return 0
die "Configuration file not found: $CONFIG_FILE"
}
local line key value
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line#"${line%%[![:space:]]*}"}"
[[ -z "$line" || "${line:0:1}" == "#" ]] && continue
[[ "$line" == *=* ]] || die "Invalid configuration line (expected KEY=VALUE): $line"
key="${line%%=*}"
value="${line#*=}"
key="${key//[[:space:]]/}"
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
case "$key" in
UMBRA_HOSTNAME|UMBRA_PUBLIC_URL|UMBRA_DB_NAME|UMBRA_DB_HOST|UMBRA_DB_PORT|UMBRA_PG_VERSION|UMBRA_APP_ROOT|UMBRA_DATA_ROOT|UMBRA_BACKUP_ROOT|UMBRA_BOOTSTRAP_ADMIN_EMAIL|UMBRA_ORGANIZATION_NAME|UMBRA_ORGANIZATION_SLUG|UMBRA_QWEN_URL|UMBRA_INSTALL_PACKAGES|UMBRA_INSTALL_UV|UMBRA_CREATE_BOOTSTRAP_ADMIN)
printf -v "$key" '%s' "$value"
;;
*) die "Unsupported configuration key: $key" ;;
esac
done < "$CONFIG_FILE"
}
validate_config() {
[[ "$UMBRA_HOSTNAME" =~ ^[A-Za-z0-9.-]+$ ]] || die "Invalid UMBRA_HOSTNAME"
[[ "$UMBRA_PUBLIC_URL" =~ ^https?://[A-Za-z0-9._:-]+(/.*)?$ ]] || die "Invalid UMBRA_PUBLIC_URL"
[[ "$UMBRA_DB_NAME" =~ ^[a-z_][a-z0-9_]*$ ]] || die "Invalid UMBRA_DB_NAME"
[[ "$UMBRA_DB_HOST" =~ ^[A-Za-z0-9._:-]+$ ]] || die "Invalid UMBRA_DB_HOST"
[[ "$UMBRA_DB_PORT" =~ ^[0-9]+$ && "$UMBRA_DB_PORT" -ge 1 && "$UMBRA_DB_PORT" -le 65535 ]] || die "Invalid UMBRA_DB_PORT"
[[ "$UMBRA_PG_VERSION" =~ ^[0-9]+$ ]] || die "Invalid UMBRA_PG_VERSION"
[[ "$UMBRA_APP_ROOT" == /* && "$UMBRA_DATA_ROOT" == /* && "$UMBRA_BACKUP_ROOT" == /* ]] || die "Application/data paths must be absolute"
[[ "$UMBRA_BOOTSTRAP_ADMIN_EMAIL" == *@*.* ]] || die "Invalid administrator email"
[[ "$UMBRA_ORGANIZATION_SLUG" =~ ^[a-z0-9][a-z0-9-]*$ ]] || die "Invalid organization slug"
[[ "$UMBRA_QWEN_URL" =~ ^https?:// ]] || die "Invalid UMBRA_QWEN_URL"
for value in "$UMBRA_INSTALL_PACKAGES" "$UMBRA_INSTALL_UV" "$UMBRA_CREATE_BOOTSTRAP_ADMIN"; do
[[ "$value" == 0 || "$value" == 1 ]] || die "Boolean configuration values must be 0 or 1"
done
}
run() {
if ((DRY_RUN)); then
printf '[dry-run]'
printf ' %q' "$@"
printf '\n'
else
"$@"
fi
}
run_root() {
require_root
run "$@"
}
read_secret() {
local key="$1"
[[ -f "$UMBRA_CREDENTIALS_FILE" ]] || return 1
awk -F= -v key="$key" '$1 == key {print substr($0, index($0, "=") + 1); exit}' "$UMBRA_CREDENTIALS_FILE"
}
write_secret_line() {
local key="$1" value="$2" temporary
if ((DRY_RUN)); then
printf '[dry-run] store generated secret %s in %s\n' "$key" "$UMBRA_CREDENTIALS_FILE"
return
fi
install -d -m 0750 -o root -g "$UMBRA_SERVICE_GROUP" "$UMBRA_CONFIG_DIR"
touch "$UMBRA_CREDENTIALS_FILE"
chown root:"$UMBRA_SERVICE_GROUP" "$UMBRA_CREDENTIALS_FILE"
chmod 0640 "$UMBRA_CREDENTIALS_FILE"
temporary="$(mktemp "${UMBRA_CONFIG_DIR}/.credentials.XXXXXX")"
awk -F= -v key="$key" -v value="$value" '
$1 == key { print key "=" value; found = 1; next }
{ print }
END { if (!found) print key "=" value }
' "$UMBRA_CREDENTIALS_FILE" > "$temporary"
chown root:"$UMBRA_SERVICE_GROUP" "$temporary"
chmod 0640 "$temporary"
mv -f "$temporary" "$UMBRA_CREDENTIALS_FILE"
}
random_secret() {
openssl rand -hex 32
}
check_host() {
log "Read-only host inspection"
printf 'hostname: '; hostname -f 2>/dev/null || hostname
printf 'os: '; . /etc/os-release; printf '%s\n' "${PRETTY_NAME:-unknown}"
printf 'kernel: '; uname -r
printf 'user: '; id
printf 'cpu: '; nproc
free -h | sed -n '1,3p'
if command -v nvidia-smi >/dev/null 2>&1; then
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader 2>/dev/null || true
fi
printf '%s\n' 'storage:'
df -hT / /home /var/lib 2>/dev/null || true
printf '%s\n' 'listening ports:'
ss -lntup 2>/dev/null | sed -n '1,100p' || true
printf '%s\n' 'relevant services:'
systemctl list-units --type=service --all --no-pager 2>/dev/null | grep -Ei 'docker|ollama|lmstudio|qwen|postgres|umbra' || true
printf '%s\n' 'DNS:'
getent ahosts "$UMBRA_HOSTNAME" 2>/dev/null || true
log "No changes made"
}
check_tools() {
require_command openssl
require_command systemctl
if [[ "$MODE" == "apply" && "$UMBRA_INSTALL_PACKAGES" -eq 1 ]]; then
require_command apt-get
fi
}
ensure_group_and_users() {
log "Ensuring service group and non-login service accounts"
if ! getent group "$UMBRA_SERVICE_GROUP" >/dev/null; then
run groupadd --system "$UMBRA_SERVICE_GROUP"
else
log "Group already exists: $UMBRA_SERVICE_GROUP"
fi
ensure_service_user "$UMBRA_API_USER"
ensure_service_user "$UMBRA_WORKER_USER"
ensure_service_user "$UMBRA_READONLY_USER"
ensure_service_user "$UMBRA_BACKUP_USER"
}
ensure_service_user() {
local user="$1"
if id "$user" >/dev/null 2>&1; then
log "Service account already exists: $user"
return
fi
run useradd --system --gid "$UMBRA_SERVICE_GROUP" --home-dir /nonexistent --no-create-home --shell /usr/sbin/nologin "$user"
}
ensure_directories() {
log "Creating application, data, backup, and configuration directories"
run install -d -m 0755 -o root -g root "$UMBRA_APP_ROOT"
run install -d -m 0750 -o root -g "$UMBRA_SERVICE_GROUP" "$UMBRA_DATA_ROOT"
run install -d -m 0700 -o root -g root "$UMBRA_BACKUP_ROOT"
run install -d -m 0750 -o root -g "$UMBRA_SERVICE_GROUP" "$UMBRA_CONFIG_DIR"
run install -d -m 0750 -o root -g "$UMBRA_SERVICE_GROUP" "$UMBRA_APP_ROOT/app" "$UMBRA_APP_ROOT/venv" "$UMBRA_APP_ROOT/bin"
}
install_packages() {
[[ "$UMBRA_INSTALL_PACKAGES" -eq 1 ]] || { log "Package installation disabled by configuration"; return; }
log "Installing Mint/Ubuntu packages"
run env DEBIAN_FRONTEND=noninteractive apt-get update
run env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl openssl postgresql postgresql-client postgresql-contrib \
python3 python3-venv python3-pip rsync
}
ensure_uv() {
[[ "$UMBRA_INSTALL_UV" -eq 1 ]] || { log "uv installation disabled by configuration"; return; }
if command -v uv >/dev/null 2>&1; then
log "uv already available: $(command -v uv)"
return
fi
log "Installing pinned uv bootstrap environment"
run python3 -m venv "$UMBRA_APP_ROOT/bootstrap-venv"
run "$UMBRA_APP_ROOT/bootstrap-venv/bin/python" -m pip install --upgrade pip
run "$UMBRA_APP_ROOT/bootstrap-venv/bin/python" -m pip install 'uv==0.11.7'
run ln -sfn "$UMBRA_APP_ROOT/bootstrap-venv/bin/uv" "$UMBRA_APP_ROOT/bin/uv"
}
psql_as_postgres() {
runuser -u "$UMBRA_PG_SUPERUSER" -- env PGOPTIONS='-c log_statement=none' \
psql -v ON_ERROR_STOP=1 "$@"
}
ensure_postgres_cluster() {
log "Ensuring PostgreSQL cluster and service"
local version cluster
if command -v pg_lsclusters >/dev/null 2>&1; then
version="$(pg_lsclusters --no-header 2>/dev/null | awk 'NR == 1 {print $1}')"
cluster="$(pg_lsclusters --no-header 2>/dev/null | awk 'NR == 1 {print $2}')"
else
version=""
cluster=""
fi
if [[ -z "$version" || -z "$cluster" ]]; then
version="$UMBRA_PG_VERSION"
cluster="main"
run pg_createcluster "$version" "$cluster" --start
fi
if command -v pg_lsclusters >/dev/null 2>&1; then
if ! pg_lsclusters --no-header | awk -v v="$version" -v c="$cluster" '$1 == v && $2 == c && $3 == "online" {found=1} END {exit !found}'; then
run pg_ctlcluster "$version" "$cluster" start
fi
else
log "pg_lsclusters is not installed; cluster start will be performed after package installation"
fi
run systemctl enable postgresql
}
ensure_database_roles() {
log "Creating PostgreSQL roles"
local role password login
for role in \
"$UMBRA_OWNER_ROLE" \
"$UMBRA_MIGRATOR_USER" \
"$UMBRA_API_USER" \
"$UMBRA_WORKER_USER" \
"$UMBRA_READONLY_USER" \
"$UMBRA_BACKUP_USER"; do
password=""
login="LOGIN"
[[ "$role" == "$UMBRA_OWNER_ROLE" ]] && login="NOLOGIN"
if [[ "$role" != "$UMBRA_OWNER_ROLE" ]]; then
if ((DRY_RUN)); then
password="$(random_secret)"
else
password="$(read_secret "${role^^}_PASSWORD" 2>/dev/null || true)"
[[ -n "$password" ]] || password="$(random_secret)"
fi
write_secret_line "${role^^}_PASSWORD" "$password"
fi
if ((DRY_RUN)); then
printf '[dry-run] ensure PostgreSQL role %s (%s)\n' "$role" "$login"
continue
fi
if [[ -n "$password" ]]; then
printf 'DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %s) THEN CREATE ROLE %s %s PASSWORD %s; ELSE ALTER ROLE %s %s PASSWORD %s; END IF; END $$;\n' \
"'$role'" "$role" "$login" "'$password'" "$role" "$login" "'$password'" \
| psql_as_postgres postgres >/dev/null
else
printf 'DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %s) THEN CREATE ROLE %s %s; END IF; END $$;\n' \
"'$role'" "$role" "$login" \
| psql_as_postgres postgres >/dev/null
fi
done
}
ensure_database() {
log "Creating enterprise database if absent"
if ((DRY_RUN)); then
printf '[dry-run] ensure database %s owned by %s\n' "$UMBRA_DB_NAME" "$UMBRA_OWNER_ROLE"
return
fi
if ! psql_as_postgres postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '$UMBRA_DB_NAME'" | grep -q 1; then
runuser -u "$UMBRA_PG_SUPERUSER" -- createdb --owner="$UMBRA_OWNER_ROLE" "$UMBRA_DB_NAME"
fi
psql_as_postgres postgres -c "REVOKE ALL ON DATABASE \"$UMBRA_DB_NAME\" FROM PUBLIC" >/dev/null
psql_as_postgres postgres -c "GRANT CONNECT ON DATABASE \"$UMBRA_DB_NAME\" TO \"$UMBRA_MIGRATOR_USER\", \"$UMBRA_API_USER\", \"$UMBRA_WORKER_USER\", \"$UMBRA_READONLY_USER\"" >/dev/null
psql_as_postgres postgres -c "GRANT CONNECT ON DATABASE \"$UMBRA_DB_NAME\" TO \"$UMBRA_BACKUP_USER\"" >/dev/null
}
apply_schema() {
local schema_file="${SCRIPT_DIR}/postgres/001_control_plane.sql"
[[ -f "$schema_file" ]] || die "Schema file is missing: $schema_file"
log "Applying core identity/tenancy schema"
if ((DRY_RUN)); then
printf '[dry-run] psql -d %s -f %s\n' "$UMBRA_DB_NAME" "$schema_file"
return
fi
/bin/cat -- "$schema_file" | psql_as_postgres "$UMBRA_DB_NAME" >/dev/null
log "Core schema applied"
}
write_nonsecret_runtime_config() {
local config_file="$UMBRA_CONFIG_DIR/control-plane.env"
run install -d -m 0750 -o root -g "$UMBRA_SERVICE_GROUP" "$UMBRA_CONFIG_DIR"
if ((DRY_RUN)); then
printf '[dry-run] write non-secret runtime configuration to %s\n' "$config_file"
return
fi
cat > "${config_file}.tmp" <<EOF
UMBRA_PUBLIC_URL=$UMBRA_PUBLIC_URL
UMBRA_HOSTNAME=$UMBRA_HOSTNAME
UMBRA_DB_NAME=$UMBRA_DB_NAME
UMBRA_DB_HOST=$UMBRA_DB_HOST
UMBRA_DB_PORT=$UMBRA_DB_PORT
UMBRA_QWEN_URL=$UMBRA_QWEN_URL
UMBRA_APP_ROOT=$UMBRA_APP_ROOT
UMBRA_DATA_ROOT=$UMBRA_DATA_ROOT
EOF
chown root:"$UMBRA_SERVICE_GROUP" "${config_file}.tmp"
chmod 0640 "${config_file}.tmp"
mv -f "${config_file}.tmp" "$config_file"
}
create_bootstrap_admin() {
[[ "$UMBRA_CREATE_BOOTSTRAP_ADMIN" -eq 1 ]] || { log "Bootstrap admin creation disabled"; return; }
local token token_hash
if [[ -f "$UMBRA_BOOTSTRAP_TOKEN_FILE" ]]; then
token="$(cat "$UMBRA_BOOTSTRAP_TOKEN_FILE")"
else
token="$(random_secret)"
if ((DRY_RUN)); then
printf '[dry-run] create one-time admin token at %s\n' "$UMBRA_BOOTSTRAP_TOKEN_FILE"
return
fi
printf '%s\n' "$token" > "$UMBRA_BOOTSTRAP_TOKEN_FILE"
chown root:root "$UMBRA_BOOTSTRAP_TOKEN_FILE"
chmod 0600 "$UMBRA_BOOTSTRAP_TOKEN_FILE"
fi
[[ "$token" =~ ^[a-f0-9]{64}$ ]] || die "Bootstrap token file has an unexpected format"
token_hash="$(printf '%s' "$token" | sha256sum | awk '{print $1}')"
log "Creating/confirming bootstrap platform administrator"
if ((DRY_RUN)); then
printf '[dry-run] create administrator %s and organization %s\n' "$UMBRA_BOOTSTRAP_ADMIN_EMAIL" "$UMBRA_ORGANIZATION_NAME"
return
fi
psql_as_postgres "$UMBRA_DB_NAME" \
-v admin_email="$UMBRA_BOOTSTRAP_ADMIN_EMAIL" \
-v token_hash="$token_hash" \
-v organization_name="$UMBRA_ORGANIZATION_NAME" \
-v organization_slug="$UMBRA_ORGANIZATION_SLUG" <<'SQL' >/dev/null
INSERT INTO users (email, display_name, auth_provider, auth_subject, status, mfa_required)
VALUES (:'admin_email', :'admin_email', 'bootstrap', 'bootstrap:' || gen_random_uuid()::text, 'invited', true)
ON CONFLICT (email) DO UPDATE
SET display_name = EXCLUDED.display_name,
auth_provider = 'bootstrap',
mfa_required = true,
updated_at = now();
INSERT INTO organizations (name, slug)
VALUES (:'organization_name', :'organization_slug')
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name;
INSERT INTO user_roles (user_id, role_id, scope, organization_id, project_id, created_by)
SELECT u.id, r.id, 'platform', NULL, NULL, u.id
FROM users u
JOIN roles r ON r.scope = 'platform' AND r.name = 'platform_admin'
WHERE u.email = :'admin_email'
ON CONFLICT DO NOTHING;
INSERT INTO user_roles (user_id, role_id, scope, organization_id, project_id, created_by)
SELECT u.id, r.id, 'organization', o.id, NULL, u.id
FROM users u
JOIN organizations o ON o.slug = :'organization_slug'
JOIN roles r ON r.scope = 'organization' AND r.name = 'organization_admin'
WHERE u.email = :'admin_email'
ON CONFLICT DO NOTHING;
INSERT INTO bootstrap_tokens (user_id, token_hash, expires_at)
SELECT id, :'token_hash', now() + interval '24 hours'
FROM users
WHERE email = :'admin_email'
ON CONFLICT (token_hash) DO NOTHING;
SQL
log "Bootstrap token stored root-only at $UMBRA_BOOTSTRAP_TOKEN_FILE"
}
apply_mode() {
if ((DRY_RUN == 0)); then
require_root
log "Beginning phase-one enterprise bootstrap"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
die "Another bootstrap process holds $LOCK_DIR"
fi
trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT
touch "$LOG_FILE"
chmod 0640 "$LOG_FILE"
else
log "Beginning phase-one enterprise bootstrap dry-run"
fi
install_packages
ensure_group_and_users
ensure_directories
ensure_uv
ensure_postgres_cluster
ensure_database_roles
ensure_database
apply_schema
write_nonsecret_runtime_config
create_bootstrap_admin
log "Phase-one bootstrap completed"
log "Application code, systemd units, Qwen, Docker, and firewall were not modified"
}
status_mode() {
log "Umbra enterprise installation status"
printf 'host: '; hostname -f 2>/dev/null || hostname
printf 'dns: '; getent ahosts "$UMBRA_HOSTNAME" 2>/dev/null || true
printf 'postgres: '; command -v pg_lsclusters >/dev/null 2>&1 && pg_lsclusters || echo 'not installed'
printf 'api user: '; id "$UMBRA_API_USER" 2>/dev/null || echo 'not installed'
printf 'worker user: '; id "$UMBRA_WORKER_USER" 2>/dev/null || echo 'not installed'
systemctl is-active postgresql 2>/dev/null || true
printf 'qwen port: '; (curl -sS --max-time 3 "$UMBRA_QWEN_URL/health" 2>&1 | head -c 300) || true; printf '\n'
}
verify_mode() {
log "Verifying phase-one installation"
require_command psql
require_root
pg_isready -h "$UMBRA_DB_HOST" -p "$UMBRA_DB_PORT" -d "$UMBRA_DB_NAME"
psql_as_postgres "$UMBRA_DB_NAME" -v ON_ERROR_STOP=1 -c 'SELECT version, applied_at FROM schema_migrations ORDER BY version' -c 'SELECT count(*) AS organizations FROM organizations' -c 'SELECT count(*) AS users FROM users'
systemctl is-active postgresql
log "Verification completed"
}
main() {
load_config
validate_config
check_tools
case "$MODE" in
check) check_host ;;
apply) apply_mode ;;
status) status_mode ;;
verify) verify_mode ;;
*) die "Unknown mode: $MODE" ;;
esac
}
main "$@"