#!/usr/bin/env bash
# KingCMS-X 部署后权限设置（发布包内入口）：让 Web 用户（默认 www-data）能写代码目录，在线升级才不会因「不可写」失败。
#
# 用法（先 cd 到 x1 目录）：
#   sudo bash bin/setup-permissions [--dry-run]
#   sudo bash /path/to/x1/bin/setup-permissions [/path/to/x1] [--dry-run]
#
# 做四件事：
#   1. chown -R root:www-data <x1>
#   2. 排除 data/ 后给 <x1> 下所有文件/目录加组写权限（g+w）
#   3. 把 data/ 交回 www-data（数据库、上传、app.key 都要读写；第 1 步的 -R 会把它一起改掉）
#   4. data/app.key 设为 600（存在时）
# 最后以 www-data 身份逐项 test -w 验证 9 个代码路径 + 4 个根文件，有失败则非零退出
#
# 为什么需要：在线升级由 www-data 执行，要覆盖 x1/app、composer.json、INSTALL.md 等代码文件；
# 这些文件属主是 root 且只有 755/644 时，www-data 写不了，升级会报「不可写」并中止。
# 升级进程自己改不了 root 文件，所以这一步必须在服务器上以 root 执行。
# data/ 不参与 chmod g+w：数据库和上传目录按安装文档归 www-data，权限由安装步骤设置；
# 所以第 3 步把它从 root:www-data 交回 www-data，否则 SQLite 建不了 -wal/-shm、app.key 也读不了。
#
# 发布包只打 x1/，本脚本随包分发（x1/bin/setup-permissions），默认目标就是脚本所在 x1 目录，
# 逻辑与源码仓的 deploy/setup-permissions.sh 保持一致（同一套参数、--dry-run、校验与验证项）。
set -euo pipefail

script_dir=$(cd "$(dirname "$0")" && pwd)
x1_root=$(cd "$script_dir/.." && pwd)

web_user=${KINGCMS_WEB_USER:-www-data}
web_group=${KINGCMS_WEB_GROUP:-www-data}

dry_run=0
target=

usage() {
    cat <<EOF
用法：sudo bash bin/setup-permissions [/path/to/x1] [--dry-run]

给 KingCMS-X 的 x1 代码目录设置在线升级需要的权限：
  chown -R root:$web_group <x1>
  find <x1> -path <x1>/data -prune -o -exec chmod g+w {} +
  chown -R $web_user:$web_group <x1>/data   # 数据目录交回 Web 用户
  chmod 600 <x1>/data/app.key                # app.key 仍只给属主读写

参数：
  /path/to/x1   代码目录；省略时用本脚本所在的 x1/
  --dry-run     只打印将执行的命令和验证项，不改任何文件，也不需要 root
  -h, --help    显示本帮助

环境变量：KINGCMS_WEB_USER（默认 www-data）、KINGCMS_WEB_GROUP（默认 www-data）
退出码：0 = 权限已设置且逐项可写；非 0 = 参数/路径错误或仍有路径不可写
EOF
}

die() {
    printf '错误：%s\n' "$*" >&2
    exit 1
}

while [ "$#" -gt 0 ]; do
    case $1 in
        --dry-run) dry_run=1 ;;
        -h | --help)
            usage
            exit 0
            ;;
        -*)
            printf '错误：未知参数 %s\n' "$1" >&2
            usage >&2
            exit 2
            ;;
        *)
            [ -z "$target" ] || die "只能指定一个 x1 目录（多余参数：${1}）"
            target=$1
            ;;
    esac
    shift
done

if [ -z "$target" ]; then
    target=$x1_root
fi
[ -d "$target" ] || die "目录不存在：${target}（用法：sudo bash bin/setup-permissions /path/to/x1）"
target=$(cd "$target" && pwd -P)
[ -f "$target/VERSION" ] || die "不是 KingCMS 的 x1 目录（缺少 VERSION）：$target"
[ -f "$target/public/index.php" ] || die "不是 KingCMS 的 x1 目录（缺少 public/index.php）：$target"

if [ "$dry_run" -eq 0 ] && [ "$(id -u)" -ne 0 ]; then
    die "需要 root 执行：sudo bash bin/setup-permissions ${target}（只想看将执行的命令可加 --dry-run）"
fi
if [ "$dry_run" -eq 0 ]; then
    id -u "$web_user" >/dev/null 2>&1 \
        || die "系统里没有用户 ${web_user}；可用 KINGCMS_WEB_USER=<Web 用户> 指定"
fi

show() {
    printf '$ %s\n' "$*"
}
preview() {
    printf '[dry-run] %s\n' "$*"
}

printf '目标目录：%s\n' "$target"
printf 'Web 用户/属组：%s:%s\n\n' "$web_user" "$web_group"

printf '1/4 设置代码属主 root:%s\n' "$web_group"
if [ "$dry_run" -eq 1 ]; then
    preview "chown -R root:$web_group $target"
else
    show "chown -R root:$web_group $target"
    chown -R "root:$web_group" "$target" || die "chown 失败：$target"
fi

printf '2/4 排除 data/ 后加组写权限（g+w）\n'
if [ "$dry_run" -eq 1 ]; then
    preview "find $target -path $target/data -prune -o -exec chmod g+w {} +"
else
    show "find $target -path $target/data -prune -o -exec chmod g+w {} +"
    find "$target" -path "$target/data" -prune -o -exec chmod g+w {} + || die "chmod g+w 失败：$target"
fi

printf '3/4 把 data/ 交回 %s（数据库、上传、app.key 要读写）\n' "$web_user"
data_dir=$target/data
if [ ! -d "$data_dir" ]; then
    printf '  跳过：%s 不存在（首次访问会创建）\n' "$data_dir"
elif [ "$dry_run" -eq 1 ]; then
    preview "chown -R $web_user:$web_group $data_dir"
else
    show "chown -R $web_user:$web_group $data_dir"
    chown -R "$web_user:$web_group" "$data_dir" || die "chown 失败：$data_dir"
fi

app_key=$target/data/app.key
printf '4/4 收紧 data/app.key（%s）\n' "$app_key"
if [ "$dry_run" -eq 1 ]; then
    preview "chmod 600 $app_key"
    [ -f "$app_key" ] || printf '  提示：%s 不存在，首次访问时会自动创建为 0600\n' "$app_key"
elif [ -f "$app_key" ]; then
    show "chmod 600 $app_key"
    chmod 600 "$app_key" || die "chmod 600 失败：$app_key"
else
    printf '  跳过：%s 不存在（首次访问会自动创建为 0600）\n' "$app_key"
fi

printf '\n可写性验证（以 %s 身份 test -w）：\n' "$web_user"

# 9 个代码路径（x1 本身 + 8 个子目录）与 4 个根文件，对应 UpgradeRunner::writabilityProblems()
# 和升级时覆盖的根文件；不存在的路径跳过，不算失败。
verify_paths=(
    "$target"
    "$target/app"
    "$target/bin"
    "$target/data"
    "$target/modules"
    "$target/public"
    "$target/resources"
    "$target/themes"
    "$target/vendor"
)
verify_files=(
    "$target/composer.json"
    "$target/composer.lock"
    "$target/INSTALL.md"
    "$target/README.md"
)

if [ "$dry_run" -eq 0 ] && ! command -v sudo >/dev/null 2>&1; then
    die "找不到 sudo，无法以 $web_user 身份验证；请安装 sudo 后重跑，或手工用 test -w 检查"
fi

failures=0
checked=0
for path in "${verify_paths[@]}" "${verify_files[@]}"; do
    if [ ! -e "$path" ]; then
        printf '  跳过（不存在）：%s\n' "$path"
        continue
    fi
    if [ "$dry_run" -eq 1 ]; then
        preview "sudo -u $web_user test -w $path"
        continue
    fi
    if sudo -u "$web_user" test -w "$path"; then
        printf '  可写    %s\n' "$path"
        checked=$((checked + 1))
    else
        printf '  不可写  %s\n' "$path" >&2
        failures=$((failures + 1))
    fi
done

printf '\n'
if [ "$dry_run" -eq 1 ]; then
    printf '（--dry-run）未改动任何文件；去掉 --dry-run 后以 root 执行。\n'
    exit 0
fi
if [ "$failures" -gt 0 ]; then
    printf '权限设置完成，但仍有 %d 个路径对 %s 不可写。\n' "$failures" "$web_user" >&2
    exit 1
fi
printf '完成：%d 个检查项全部对 %s 可写。\n' "$checked" "$web_user"
