XuqmGroup-PrivateDeploy/scripts/lib.sh

137 行
3.3 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
AUDIT_LOG="$ROOT_DIR/logs/audit.log"
PROGRESS_FILE="$ROOT_DIR/.deploy-state/progress.md"
now() {
date +"%Y-%m-%dT%H:%M:%S%z"
}
audit() {
local action="$1"
local status="$2"
local message="${3:-}"
mkdir -p "$(dirname "$AUDIT_LOG")"
printf '{"time":"%s","action":"%s","status":"%s","message":"%s"}\n' \
"$(now)" "$action" "$status" "$message" >> "$AUDIT_LOG"
}
progress() {
local step="$1"
local status="$2"
local notes="${3:-}"
mkdir -p "$(dirname "$PROGRESS_FILE")"
if [ ! -f "$PROGRESS_FILE" ]; then
printf '# Deployment Progress\n\n| Time | Step | Status | Notes |\n|------|------|--------|-------|\n' > "$PROGRESS_FILE"
fi
printf '| %s | %s | %s | %s |\n' "$(now)" "$step" "$status" "$notes" >> "$PROGRESS_FILE"
}
fail_json() {
local code="$1"
local message="$2"
local step="${3:-unknown}"
printf '{"code":"%s","message":"%s","step":"%s","recoverable":true}\n' "$code" "$message" "$step" >&2
audit "$step" "FAILED" "$message"
progress "$step" "FAILED" "$message"
exit 1
}
load_env() {
set -a
[ -f "$ROOT_DIR/.env" ] && . "$ROOT_DIR/.env"
[ -f "$ROOT_DIR/config/infra.env" ] && . "$ROOT_DIR/config/infra.env"
[ -f "$ROOT_DIR/config/xuqm.env" ] && . "$ROOT_DIR/config/xuqm.env"
[ -f "$ROOT_DIR/config/secrets.env" ] && . "$ROOT_DIR/config/secrets.env"
set +a
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail_json "XUQM_PRIVATE_4001" "missing required command: $1" "preflight"
}
random_secret() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -base64 32 | tr -d '\n'
else
printf '%s_%s' "$(date +%s)" "$RANDOM"
fi
}
ensure_secret_file() {
if [ ! -f "$ROOT_DIR/config/secrets.env" ]; then
cp "$ROOT_DIR/config/secrets.env.example" "$ROOT_DIR/config/secrets.env"
chmod 600 "$ROOT_DIR/config/secrets.env" || true
fi
}
set_env_value() {
local file="$1"
local key="$2"
local value="$3"
mkdir -p "$(dirname "$file")"
touch "$file"
if grep -q "^${key}=" "$file"; then
sed -i.bak "s|^${key}=.*|${key}=${value}|" "$file"
rm -f "${file}.bak"
else
printf '%s=%s\n' "$key" "$value" >> "$file"
fi
}
ensure_env_value() {
local file="$1"
local key="$2"
local current="${3:-}"
local generated="$4"
if [ -z "$current" ] || [ "$current" = "change-me" ]; then
set_env_value "$file" "$key" "$generated"
printf '%s' "$generated"
else
printf '%s' "$current"
fi
}
profile_contains() {
local profiles="$1"
local target="$2"
case ",${profiles}," in
*,"${target}",*) return 0 ;;
*) return 1 ;;
esac
}
add_profile() {
local profiles="${1:-base}"
local target="$2"
if profile_contains "$profiles" "$target"; then
printf '%s' "$profiles"
else
printf '%s,%s' "$profiles" "$target"
fi
}
remove_profile() {
local profiles="$1"
local target="$2"
local result=""
local item
IFS=',' read -r -a items <<< "$profiles"
for item in "${items[@]}"; do
[ "$item" = "$target" ] && continue
[ -z "$item" ] && continue
if [ -z "$result" ]; then
result="$item"
else
result="$result,$item"
fi
done
printf '%s' "${result:-base}"
}
compose() {
docker compose --env-file "$ROOT_DIR/.env" -f "$ROOT_DIR/docker-compose.yml" -f "$ROOT_DIR/docker-compose.infra.yml" "$@"
}