#!/usr/bin/env bash # FLNXI customer first-install guide for NAS/Linux hosts. set -euo pipefail PROJECT_DIR="." COMPOSE_FILE="docker-compose.prod.yml" ENV_FILE=".env" EXPECTED_VERSION="" START=0 PREPARE_DIRECTORIES=0 PLAN_ONLY=0 SKIP_MIGRATION_STATUS=0 SKIP_OBJECT_STORAGE_CHECK=0 HEALTH_TIMEOUT_SECONDS=120 usage() { cat <<'EOF' FLNXI NAS/Linux 客户首装向导 用法: bash scripts/customer-install.sh [选项] 常用选项: --project-dir 部署目录,默认当前目录 --compose-file Compose 文件名,默认 docker-compose.prod.yml --env-file 环境文件名,默认 .env --expect-version 期望镜像内 VERSION.txt 版本 --start 执行 docker compose up -d --prepare-directories 缺少 uploads/data 时创建并检查权限 --plan-only 只打印步骤,不启动容器 --skip-migration-status 跳过 Prisma 迁移状态检查 --skip-object-storage-check 跳过 MinIO bucket 检查 --health-timeout-seconds 健康检查等待秒数,默认 120 -h, --help 显示帮助 EOF } while [[ $# -gt 0 ]]; do case "$1" in --project-dir) PROJECT_DIR="${2:?--project-dir requires a value}" shift 2 ;; --compose-file) COMPOSE_FILE="${2:?--compose-file requires a value}" shift 2 ;; --env-file) ENV_FILE="${2:?--env-file requires a value}" shift 2 ;; --expect-version) EXPECTED_VERSION="${2:?--expect-version requires a value}" shift 2 ;; --start) START=1 shift ;; --prepare-directories) PREPARE_DIRECTORIES=1 shift ;; --plan-only) PLAN_ONLY=1 shift ;; --skip-migration-status) SKIP_MIGRATION_STATUS=1 shift ;; --skip-object-storage-check) SKIP_OBJECT_STORAGE_CHECK=1 shift ;; --health-timeout-seconds) HEALTH_TIMEOUT_SECONDS="${2:?--health-timeout-seconds requires a value}" shift 2 ;; -h|--help) usage exit 0 ;; *) printf '[ERROR] 未知参数:%s\n' "$1" >&2 exit 1 ;; esac done step() { printf '\n[%s/4] %s\n' "$1" "$2" } printf '========================================\n' printf ' FLNXI NAS/Linux 客户首装向导\n' printf '========================================\n' printf '部署目录:%s\n' "$PROJECT_DIR" printf 'Compose:%s\n' "$COMPOSE_FILE" printf '环境文件:%s\n' "$ENV_FILE" step 1 "检查部署文件和客户现场配置" printf '会检查 .env、APP_IMAGE、许可证参数、数据目录和常见占位值。\n' if [[ "$PREPARE_DIRECTORIES" -eq 1 ]]; then printf '缺少 uploads/data 目录时会自动创建,并在 root/sudo 运行时调整为 UID 1001 可写。\n' fi if [[ "$PLAN_ONLY" -eq 1 ]]; then step 2 "准备启动方式" printf '如果希望脚本启动容器,请重新执行并加 --start。\n' printf '如果已经通过 NAS 图形界面启动,则不加 --start,脚本只做验收。\n' step 3 "运行首装验收" printf '验收内容包括 Compose 配置、容器状态、运行镜像、VERSION.txt、/api/health、Prisma 迁移状态和 MinIO bucket。\n' if [[ -n "$EXPECTED_VERSION" ]]; then printf '会额外确认当前运行版本等于:%s。\n' "$EXPECTED_VERSION" fi step 4 "查看结果" printf '所有步骤通过后,浏览器打开 APP_PUBLIC_URL / NEXTAUTH_URL。\n' printf '如失败,根据脚本输出先修 .env、镜像、目录权限或容器日志。\n' exit 0 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SMOKE_SCRIPT="$SCRIPT_DIR/smoke-install.sh" [[ -f "$SMOKE_SCRIPT" ]] || { printf '[ERROR] 找不到 smoke-install.sh:%s\n' "$SMOKE_SCRIPT" >&2 exit 1 } COMMON_ARGS=( --project-dir "$PROJECT_DIR" --compose-file "$COMPOSE_FILE" --env-file "$ENV_FILE" --health-timeout-seconds "$HEALTH_TIMEOUT_SECONDS" ) if [[ -n "$EXPECTED_VERSION" ]]; then COMMON_ARGS+=(--expect-version "$EXPECTED_VERSION") fi if [[ "$SKIP_MIGRATION_STATUS" -eq 1 ]]; then COMMON_ARGS+=(--skip-migration-status) fi if [[ "$SKIP_OBJECT_STORAGE_CHECK" -eq 1 ]]; then COMMON_ARGS+=(--skip-object-storage-check) fi PREFLIGHT_ARGS=(--preflight-only) if [[ "$PREPARE_DIRECTORIES" -eq 1 ]]; then PREFLIGHT_ARGS+=(--prepare-directories) fi bash "$SMOKE_SCRIPT" "${COMMON_ARGS[@]}" "${PREFLIGHT_ARGS[@]}" step 2 "确认容器启动方式" if [[ "$START" -eq 1 ]]; then printf '将由脚本执行 docker compose up -d。\n' else printf '未传 --start,默认认为容器已由 NAS 图形界面或其它编排入口启动。\n' fi step 3 "执行首装启动和验收" VERIFY_ARGS=() if [[ "$START" -eq 1 ]]; then VERIFY_ARGS+=(--start) fi if [[ "$PREPARE_DIRECTORIES" -eq 1 ]]; then VERIFY_ARGS+=(--prepare-directories) fi bash "$SMOKE_SCRIPT" "${COMMON_ARGS[@]}" "${VERIFY_ARGS[@]}" step 4 "完成" printf '首装向导完成。下一步请用浏览器打开 APP_PUBLIC_URL / NEXTAUTH_URL,并完成管理员初始化、登录、上传和下载验收。\n'