#!/bin/bash
# main：应用的启动 / 停止 / 状态查询。
#
# 飞牛在应用中心点「启动」「停止」以及查看运行状态时调用本脚本。
# 退出码：0 = 成功或运行中；1 = 失败；3 = status 时表示未运行。
set -uo pipefail

SM_HOOK=main
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
. "$SELF_DIR/lib.sh"

PID_FILE="${TRIM_PKGVAR:-/tmp}/app.pid"

# —— 进程存活判定 ——
is_running() {
    local pid
    [ -f "$PID_FILE" ] || return 1
    pid="$(head -n 1 "$PID_FILE" 2>/dev/null | tr -d '[:space:]')"
    [ -n "$pid" ] || return 1
    if kill -0 "$pid" 2>/dev/null; then
        return 0
    fi
    # 进程已不在但 pid 文件还在：清理残留，避免下次误判为运行中。
    rm -f "$PID_FILE" 2>/dev/null
    return 1
}

# —— 启动 ——
start_process() {
    if is_running; then
        sm_log main "进程已在运行，跳过启动"
        return 0
    fi

    local bin data app_dest
    # 注意：sm_fail 在命令替换的子 shell 里 exit，因此必须检查赋值状态。
    bin="$(sm_select_binary)" || return 1
    data="$(sm_data_dir)" || return 1
    app_dest="$(sm_app_dest)"

    # 日志轮转（设计 §11.5）：每次启动一份独立的控制台日志，旧的只留最近 5 份。
    # 服务器 stdout/stderr（含公开面访问日志）都进这份文件；lifecycle.log 只记
    # 生命周期事件，保持小而干净。Go 侧不另写日志文件——同一行落两处没有意义。
    local console_log="$data/logs/console-$(date +%Y%m%d-%H%M%S).log"
    mkdir -p "$data/logs" 2>/dev/null || true
    # 先落一份空的：清理时把「本次这份」也算进 5 份里，稳定后目录中恰好 5 份，
    # 且正在写入的当前文件永远是最新的那份、不在删除名单里。
    : >> "$console_log"
    ls -1t "$data/logs"/console-*.log 2>/dev/null | tail -n +6 | while read -r old; do
        rm -f "$old"
    done

    # 数据目录结构由 install_callback 建好；这里兜底一次，
    # 保证手工拷贝文件（跳过安装流程）时也能起来。失败不阻断。
    mkdir -p "$data/sites" "$data/logs" "$data/backup" 2>/dev/null || true

    local args=(
        --data "$data"
        --socket "$app_dest/sitemanager.sock"
    )
    if [ -n "${SM_DEV_ADDR:-}" ]; then
        args=(--data "$data" --dev "$SM_DEV_ADDR")
    fi

    # 给随包 nginx 授予绑 443 的文件能力（以 root 运行时并非必需；保留以防
    # 将来改回包用户运行）。
    sm_grant_nginx_cap

    sm_log main "启动：$bin ${args[*]}"
    # 后台启动。管理进程以 root 运行（privilege run-as=root）：统一出口的 nginx
    # 需要绑定 443 并读取用户在「应用设置 → 目录授权」里授权的证书与静态目录。
    # $! 就是管理进程本身，直接 kill 它即收走其全部子进程。
    "$bin" "${args[@]}" >> "$console_log" 2>&1 &
    local pid=$!
    printf '%s' "$pid" > "$PID_FILE"

    # 启动存活校验：给最多 5 秒，看进程是否还在。
    local waited=0
    while [ "$waited" -lt 5 ]; do
        sleep 1
        waited=$((waited + 1))
        if ! kill -0 "$pid" 2>/dev/null; then
            sm_log main "进程在 ${waited}s 内退出，启动失败"
            rm -f "$PID_FILE" 2>/dev/null
            sm_user_error "站点管理启动失败，详情见 $console_log"
            return 1
        fi
    done

    rm -f "$PID_FILE" 2>/dev/null
    printf '%s' "$pid" > "$PID_FILE"
    sm_log main "启动成功，pid=$pid"
    return 0
}

# —— 停止 ——
stop_process() {
    if ! is_running; then
        sm_log main "进程未运行，无需停止"
        rm -f "$PID_FILE" 2>/dev/null
        return 0
    fi
    local pid
    pid="$(head -n 1 "$PID_FILE" | tr -d '[:space:]')"

    sm_log main "发送 TERM 给 pid=$pid"
    kill -TERM "$pid" 2>/dev/null

    # 主程序收到 TERM 会先关统一出口、再关公开面与管理面。
    # 给 20 秒：公开端口上可能有正在进行的下载。
    local waited=0
    while kill -0 "$pid" 2>/dev/null && [ "$waited" -lt 20 ]; do
        sleep 1
        waited=$((waited + 1))
        [ $((waited % 5)) -eq 0 ] && sm_log main "等待退出…(${waited}s/20s)"
    done

    if kill -0 "$pid" 2>/dev/null; then
        sm_log main "超时未退出，发送 KILL"
        kill -KILL "$pid" 2>/dev/null
        sleep 1
    else
        sm_log main "进程已退出"
    fi
    rm -f "$PID_FILE" 2>/dev/null
    return 0
}

case "${1:-}" in
start)
    start_process
    exit $?
    ;;
stop)
    stop_process
    exit $?
    ;;
status)
    if is_running; then
        exit 0
    fi
    exit 3
    ;;
*)
    sm_log main "未知子命令：${1:-（空）}"
    sm_user_error "内部错误：未知的运行控制命令"
    exit 1
    ;;
esac
