#!/bin/bash set -u PACKAGE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) APP_ROOT="${TRIM_APPDEST:-$PACKAGE_ROOT/app}" DATA_ROOT="${TRIM_PKGVAR:-$PACKAGE_ROOT/var}" VOLUME_ROOT="${TRIM_APPDEST_VOL:-$DATA_ROOT/volume}" BACKUP_ROOT="${POSTGRES_SERVICE_BACKUP_ROOT:-$VOLUME_ROOT/@appshare/postgresql/backups}" RUNTIME_ROOT="$APP_ROOT/runtime" SERVER="$APP_ROOT/server/PostgresService.WebApi" PG_BIN="$RUNTIME_ROOT/usr/lib/postgresql/15/bin" PG_SHARE="$RUNTIME_ROOT/usr/share/postgresql/15" PG_LIB="$RUNTIME_ROOT/usr/lib/postgresql/15/lib" PG_DATA="$DATA_ROOT/postgres" RUN_ROOT="$DATA_ROOT/run" LOG_ROOT="$DATA_ROOT/log" APP_PID_FILE="$RUN_ROOT/postgres-service.pid" APP_LOG="$LOG_ROOT/postgres-service.log" PG_LOG="$LOG_ROOT/postgresql.log" PG_PORT="${POSTGRES_SERVICE_PORT:-15432}" SERVICE_PORT="${TRIM_SERVICE_PORT:-15433}" RUNTIME_PATH="$PG_BIN:$RUNTIME_ROOT/usr/bin:${PATH:-/usr/local/bin:/usr/bin:/bin}" RUNTIME_LIBRARY_PATH="$RUNTIME_ROOT/usr/lib/x86_64-linux-gnu:$RUNTIME_ROOT/lib/x86_64-linux-gnu:$PG_LIB" log_message() { mkdir -p "$LOG_ROOT" printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$APP_LOG" } run_pg() { env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$RUNTIME_PATH" "$@" } app_pid() { if [ -f "$APP_PID_FILE" ]; then pid=$(sed -n '1p' "$APP_PID_FILE" | tr -d '[:space:]') if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then printf '%s' "$pid" return 0 fi fi return 1 } postgres_running() { [ -x "$PG_BIN/pg_ctl" ] || return 1 [ -f "$PG_DATA/PG_VERSION" ] || return 1 run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" status >/dev/null 2>&1 } initialize_postgres() { [ ! -f "$PG_DATA/PG_VERSION" ] || return 0 mkdir -p "$PG_DATA" "$RUN_ROOT" "$LOG_ROOT" chmod 0700 "$PG_DATA" "$RUN_ROOT" run_pg "$PG_BIN/initdb" \ -D "$PG_DATA" \ -L "$PG_SHARE" \ --username=postgres_service \ --auth-local=trust \ --auth-host=scram-sha-256 \ --encoding=UTF8 \ --no-locale >>"$PG_LOG" 2>&1 || return 1 { printf "listen_addresses = '127.0.0.1'\n" printf "port = %s\n" "$PG_PORT" printf "unix_socket_directories = '%s'\n" "$RUN_ROOT" printf "password_encryption = 'scram-sha-256'\n" printf "max_connections = 100\n" printf "shared_buffers = '128MB'\n" printf "timezone = 'UTC'\n" printf "log_timezone = 'UTC'\n" printf "log_min_duration_statement = 5000\n" } >>"$PG_DATA/postgresql.conf" { printf 'local all postgres_service trust\n' printf 'local all all reject\n' printf 'host all all 127.0.0.1/32 scram-sha-256\n' printf 'host all all ::1/128 scram-sha-256\n' } >"$PG_DATA/pg_hba.conf" } start_postgres() { postgres_running && return 0 initialize_postgres || { log_message "PostgreSQL 初始化失败。"; return 1; } run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" -l "$PG_LOG" -w start || { log_message "PostgreSQL 启动失败。" return 1 } } stop_postgres() { if postgres_running; then run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" -m fast -t 180 -w stop >>"$PG_LOG" 2>&1 || { log_message "PostgreSQL 未能在 180 秒内安全停止。" return 1 } fi } start_app() { mkdir -p "$DATA_ROOT" "$RUN_ROOT" "$LOG_ROOT" "$BACKUP_ROOT" "$DATA_ROOT/tmp" "$DATA_ROOT/cache" chmod 0700 "$DATA_ROOT" "$RUN_ROOT" "$DATA_ROOT/tmp" "$DATA_ROOT/cache" 2>/dev/null || true [ -x "$SERVER" ] || { log_message "管理服务不存在或不可执行:$SERVER"; return 1; } [ -x "$PG_BIN/postgres" ] || { log_message "PostgreSQL 原生运行环境不完整。"; return 1; } if app_pid >/dev/null; then return 0; fi start_postgres || return 1 ( export ASPNETCORE_ENVIRONMENT=Production export ASPNETCORE_URLS="http://0.0.0.0:$SERVICE_PORT" export POSTGRES_SERVICE_DATA_ROOT="$DATA_ROOT" export POSTGRES_SERVICE_SOCKET_ROOT="$RUN_ROOT" export POSTGRES_SERVICE_PORT="$PG_PORT" export POSTGRES_SERVICE_ADMIN_USER=postgres_service export POSTGRES_SERVICE_PG_BIN="$PG_BIN" export POSTGRES_SERVICE_BACKUP_ROOT="$BACKUP_ROOT" export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 export DOTNET_BUNDLE_EXTRACT_BASE_DIR="$DATA_ROOT/dotnet-bundle" export XDG_CACHE_HOME="$DATA_ROOT/cache" export TMPDIR="$DATA_ROOT/tmp" export LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" export PATH="$RUNTIME_PATH" mkdir -p "$DOTNET_BUNDLE_EXTRACT_BASE_DIR" cd "$APP_ROOT/server" || exit 1 exec "$SERVER" ) >>"$APP_LOG" 2>&1 & pid=$! printf '%s\n' "$pid" >"$APP_PID_FILE" attempt=0 while [ "$attempt" -lt 90 ]; do if ! kill -0 "$pid" 2>/dev/null; then log_message "管理服务进程提前退出。" stop_postgres return 1 fi if (exec 3<>"/dev/tcp/127.0.0.1/$SERVICE_PORT") 2>/dev/null; then exec 3>&- log_message "PostgreSQL 共享服务启动成功,管理端口 $SERVICE_PORT,数据库端口 $PG_PORT。" return 0 fi attempt=$((attempt + 1)) sleep 1 done log_message "管理服务未能在 90 秒内就绪。" stop_app return 1 } stop_app() { if pid=$(app_pid); then kill "$pid" 2>/dev/null || true attempt=0 while kill -0 "$pid" 2>/dev/null && [ "$attempt" -lt 30 ]; do sleep 1 attempt=$((attempt + 1)) done kill -9 "$pid" 2>/dev/null || true fi rm -f "$APP_PID_FILE" stop_postgres } case "${1:-status}" in start) start_app ;; stop) stop_app ;; restart) stop_app && start_app ;; status) if app_pid >/dev/null && postgres_running; then exit 0; fi exit 3 ;; *) printf 'usage: %s {start|stop|restart|status}\n' "$0" >&2; exit 2 ;; esac