feat: harden recording lifecycle and refresh fnOS UI

This commit is contained in:
2026-08-03 23:45:26 +08:00
parent e5b50ea85c
commit ecc737f0bd
90 changed files with 6995 additions and 1826 deletions
+9 -176
View File
@@ -8,29 +8,22 @@ VOLUME_ROOT="${TRIM_APPDEST_VOL:-$DATA_ROOT/volume}"
RECORD_ROOT="${LIVE_RECORDER_RECORD_ROOT:-$VOLUME_ROOT/@appshare/liverecorder/records}"
RUNTIME_ROOT="$APP_ROOT/runtime"
SERVER="$APP_ROOT/server/LiveRecorder.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"
NODE_BIN="$RUNTIME_ROOT/bin/node"
NODEJS_ROOT="${NODEJS_ROOT:-/var/apps/nodejs_v22/target}"
NODE_BIN="$NODEJS_ROOT/bin/node"
CURL_BIN="$RUNTIME_ROOT/bin/curl"
CA_BUNDLE="$RUNTIME_ROOT/etc/ssl/certs/ca-certificates.crt"
PG_DATA="$DATA_ROOT/postgres"
RUN_ROOT="$DATA_ROOT/run"
LOG_ROOT="$DATA_ROOT/log"
APP_PID_FILE="$RUN_ROOT/liverecorder.pid"
APP_LOG="$LOG_ROOT/liverecorder.log"
PG_LOG="$LOG_ROOT/postgresql.log"
ADMIN_PASSWORD_FILE="$DATA_ROOT/admin-password.seed"
POSTGRES_ENROLLMENT_TOKEN_FILE="$DATA_ROOT/postgres-enrollment-token.seed"
POSTGRES_CREDENTIALS_FILE="$DATA_ROOT/postgres-client.conf"
POSTGRES_MIGRATION_ROOT="$DATA_ROOT/postgres-migration"
POSTGRES_MIGRATION_MARKER="$POSTGRES_MIGRATION_ROOT/shared-database.active"
PG_PORT="${LIVE_RECORDER_POSTGRES_PORT:-54329}"
SERVICE_PORT="${TRIM_SERVICE_PORT:-18080}"
POSTGRES_SERVICE_API="${POSTGRES_SERVICE_API:-http://127.0.0.1:15433}"
SYSTEM_PATH="${PATH:-/usr/local/bin:/usr/bin:/bin}"
RUNTIME_PATH="$RUNTIME_ROOT/bin:$SYSTEM_PATH"
RUNTIME_LIBRARY_PATH="$RUNTIME_ROOT/lib:$PG_LIB"
RUNTIME_PATH="$RUNTIME_ROOT/bin:$NODEJS_ROOT/bin:$SYSTEM_PATH"
RUNTIME_LIBRARY_PATH="$RUNTIME_ROOT/lib"
DATABASE_CONNECTION_STRING=""
SHARED_DB_HOST=""
SHARED_DB_PORT=""
@@ -54,10 +47,6 @@ app_pid() {
return 1
}
run_pg() {
env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$PG_BIN:$RUNTIME_PATH" "$@"
}
run_native() {
env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$RUNTIME_PATH" \
SSL_CERT_FILE="$CA_BUNDLE" CURL_CA_BUNDLE="$CA_BUNDLE" "$@"
@@ -144,94 +133,11 @@ ensure_shared_credentials() {
enroll_shared_database
}
run_shared_pg() {
env LD_LIBRARY_PATH="$RUNTIME_LIBRARY_PATH" PATH="$PG_BIN:$RUNTIME_PATH" \
PGPASSWORD="$SHARED_DB_PASSWORD" "$@"
}
collect_database_counts() {
mode=$1
destination=$2
: >"$destination"
for table_name in AppSettings CleanupOperations LiveRooms RecordResults RecordSessions RecordTasks RecordUploadJobs SystemLogEntries UserAccounts UserSessions; do
if [ "$mode" = "private" ]; then
count=$(run_pg "$PG_BIN/psql" -h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder -d live_recorder -Atqc \
"SELECT count(*) FROM \"$table_name\"" 2>/dev/null) || return 1
else
count=$(run_shared_pg "$PG_BIN/psql" -h "$SHARED_DB_HOST" -p "$SHARED_DB_PORT" -U "$SHARED_DB_USER" -d "$SHARED_DB_NAME" -Atqc \
"SELECT count(*) FROM \"$table_name\"" 2>/dev/null) || return 1
fi
printf '%s=%s\n' "$table_name" "$count" >>"$destination"
done
}
migrate_private_postgres() {
mkdir -p "$POSTGRES_MIGRATION_ROOT"
chmod 0700 "$POSTGRES_MIGRATION_ROOT"
dump_file="$POSTGRES_MIGRATION_ROOT/private-postgres-15.dump"
source_counts="$POSTGRES_MIGRATION_ROOT/source-counts.txt"
target_counts="$POSTGRES_MIGRATION_ROOT/target-counts.txt"
start_postgres || return 1
log_message "开始导出原内置 PostgreSQL 数据库。"
run_pg "$PG_BIN/pg_dump" \
-h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder -d live_recorder \
--format=custom --no-owner --no-acl --file "$dump_file.tmp" >>"$PG_LOG" 2>&1 || return 1
mv "$dump_file.tmp" "$dump_file"
sha256sum "$dump_file" >"$dump_file.sha256"
collect_database_counts private "$source_counts" || return 1
log_message "开始恢复数据到 PostgreSQL 共享服务。"
run_shared_pg "$PG_BIN/psql" \
-h "$SHARED_DB_HOST" -p "$SHARED_DB_PORT" -U "$SHARED_DB_USER" -d "$SHARED_DB_NAME" \
-v ON_ERROR_STOP=1 -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public AUTHORIZATION \"$SHARED_DB_USER\"" >>"$PG_LOG" 2>&1 || return 1
run_shared_pg "$PG_BIN/pg_restore" \
-h "$SHARED_DB_HOST" -p "$SHARED_DB_PORT" -U "$SHARED_DB_USER" -d "$SHARED_DB_NAME" \
--no-owner --no-acl --exit-on-error "$dump_file" >>"$PG_LOG" 2>&1 || return 1
collect_database_counts shared "$target_counts" || return 1
if ! cmp -s "$source_counts" "$target_counts"; then
log_message "共享数据库行数校验失败,继续使用原数据库。"
return 1
fi
umask 077
{
printf 'migrated_at=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
printf 'database=%s\n' "$SHARED_DB_NAME"
printf 'dump=%s\n' "$dump_file"
} >"$POSTGRES_MIGRATION_MARKER"
stop_postgres || return 1
log_message "内置 PostgreSQL 已迁移到共享服务,旧数据和迁移转储已保留。"
}
select_database_connection() {
if ! ensure_shared_credentials; then
if [ -f "$POSTGRES_MIGRATION_MARKER" ]; then
log_message "已完成共享数据库迁移,但当前凭据不可用;为避免使用过期旧库,应用不会启动。"
return 1
fi
if [ -f "$PG_DATA/PG_VERSION" ]; then
log_message "共享服务暂不可用,本次启动继续使用原内置 PostgreSQL。"
start_postgres || return 1
DATABASE_CONNECTION_STRING="Host=$RUN_ROOT;Port=$PG_PORT;Database=live_recorder;Username=liverecorder;Timeout=15;Command Timeout=120;Keepalive=30"
return 0
fi
ensure_shared_credentials || {
log_message "无法连接 PostgreSQL 共享服务或取得数据库凭据,应用不会启动。"
return 1
fi
if [ -f "$PG_DATA/PG_VERSION" ] && [ ! -f "$POSTGRES_MIGRATION_MARKER" ]; then
if ! migrate_private_postgres; then
log_message "共享数据库迁移失败,本次启动继续使用原内置 PostgreSQL;下次启动会重试迁移。"
start_postgres || return 1
DATABASE_CONNECTION_STRING="Host=$RUN_ROOT;Port=$PG_PORT;Database=live_recorder;Username=liverecorder;Timeout=15;Command Timeout=120;Keepalive=30"
return 0
fi
elif [ ! -f "$PG_DATA/PG_VERSION" ] && [ ! -f "$POSTGRES_MIGRATION_MARKER" ]; then
mkdir -p "$POSTGRES_MIGRATION_ROOT"
umask 077
printf 'fresh_install_at=%s\ndatabase=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$SHARED_DB_NAME" >"$POSTGRES_MIGRATION_MARKER"
fi
return 0
}
}
system_media_tools_available() {
@@ -248,78 +154,6 @@ system_media_tools_available() {
return 0
}
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() {
if [ -f "$PG_DATA/PG_VERSION" ]; then
return 0
fi
mkdir -p "$PG_DATA" "$RUN_ROOT"
chmod 0700 "$PG_DATA" "$RUN_ROOT"
if ! run_pg "$PG_BIN/initdb" \
-D "$PG_DATA" \
-L "$PG_SHARE" \
--username=liverecorder \
--auth-local=trust \
--auth-host=reject \
--encoding=UTF8 \
--no-locale >>"$PG_LOG" 2>&1; then
log_message "PostgreSQL 初始化失败。"
return 1
fi
{
printf "listen_addresses = ''\n"
printf "port = %s\n" "$PG_PORT"
printf "unix_socket_directories = '%s'\n" "$RUN_ROOT"
printf "max_connections = 40\n"
printf "shared_buffers = '64MB'\n"
printf "timezone = 'UTC'\n"
printf "log_timezone = 'UTC'\n"
} >>"$PG_DATA/postgresql.conf"
return 0
}
start_postgres() {
if postgres_running; then
return 0
fi
initialize_postgres || return 1
if ! run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" -l "$PG_LOG" -w start; then
log_message "PostgreSQL 启动失败。"
return 1
fi
database_exists=$(run_pg "$PG_BIN/psql" \
-h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder -d postgres -Atqc \
"SELECT 1 FROM pg_database WHERE datname = 'live_recorder'" 2>/dev/null || true)
if [ "$database_exists" != "1" ]; then
if ! run_pg "$PG_BIN/createdb" \
-h "$RUN_ROOT" -p "$PG_PORT" -U liverecorder live_recorder >>"$PG_LOG" 2>&1; then
log_message "Live Recorder 数据库创建失败。"
return 1
fi
fi
return 0
}
stop_postgres() {
if postgres_running; then
# A first-run migration can dirty enough pages that a NAS needs more
# than pg_ctl's 60-second default to finish the shutdown checkpoint.
# Do not let restart race a database that is still shutting down.
if ! run_pg "$PG_BIN/pg_ctl" -D "$PG_DATA" -m fast -t 180 -w stop >>"$PG_LOG" 2>&1; then
log_message "PostgreSQL 未能在 180 秒内安全停止,已取消后续重启。"
return 1
fi
fi
}
launch_app_process() {
(
export ASPNETCORE_ENVIRONMENT=Production
@@ -354,7 +188,7 @@ start_app() {
log_message "应用程序不存在或不可执行:$SERVER"
return 1
fi
if [ ! -x "$PG_BIN/postgres" ] || [ ! -x "$NODE_BIN" ] || [ ! -x "$CURL_BIN" ] || [ ! -s "$CA_BUNDLE" ]; then
if [ ! -x "$NODE_BIN" ] || [ ! -x "$CURL_BIN" ] || [ ! -s "$CA_BUNDLE" ]; then
log_message "FPK 原生运行环境不完整。"
return 1
fi
@@ -400,7 +234,7 @@ 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
while kill -0 "$pid" 2>/dev/null && [ "$attempt" -lt 50 ]; do
sleep 1
attempt=$((attempt + 1))
done
@@ -409,7 +243,6 @@ stop_app() {
fi
fi
rm -f "$APP_PID_FILE"
stop_postgres
}
case "${1:-status}" in
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
# Persistent PostgreSQL data and recordings are preserved by default.
# Persistent application data and recordings are preserved by default.
exit 0