#!/usr/bin/env bash set -euo pipefail resolve_home() { local source="${BASH_SOURCE[0]}" while [ -L "$source" ]; do local dir dir="$(cd -P "$(dirname "$source")" >/dev/null 2>&1 && pwd)" source="$(readlink "$source")" [[ "$source" != /* ]] && source="$dir/$source" done cd -P "$(dirname "$source")/.." >/dev/null 2>&1 && pwd } CLOVER_HOME="$(resolve_home)" cd "$CLOVER_HOME" if [ "$(id -u)" -ne 0 ]; then echo "请使用 root 运行:sudo clover" >&2 exit 1 fi if [ ! -f .env ] || [ ! -f compose.yaml ]; then echo "未找到 Clover 安装配置:$CLOVER_HOME/.env" >&2 exit 1 fi compose_files=(-f compose.yaml) compose() { docker compose "${compose_files[@]}" "$@"; } pause() { read -r -p "按 Enter 返回菜单…" _ /dev/null || true printf '\n🍀 Clover 邮局管理\n安装目录:%s\n\n' "$CLOVER_HOME" } show_status() { echo "容器状态:" compose ps echo if docker inspect clover-panel >/dev/null 2>&1; then printf '面板健康:' if docker exec clover-panel node -e "fetch('http://127.0.0.1:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" >/dev/null 2>&1; then echo "正常" else echo "未就绪(可查看面板日志)" fi fi echo df -h "$CLOVER_HOME" | awk 'NR==1 || NR==2 {print "磁盘:" $0}' } show_logs() { local service="${1:-}" if [ -n "$service" ]; then compose logs --tail 100 -f "$service" else echo "选择日志服务:" select service in panel mail roundcube caddy certificate-helper 返回; do case "$service" in panel) compose logs --tail 100 -f panel; break ;; mail) compose logs --tail 100 -f mail; break ;; roundcube) compose logs --tail 100 -f roundcube; break ;; caddy) compose logs --tail 100 -f caddy; break ;; certificate-helper) compose logs --tail 100 -f certificate-helper; break ;; 返回) break ;; *) echo "请输入菜单编号。" ;; esac done fi } restart_services() { compose restart echo "Clover 服务已重启。" } repair_deploy() { echo "将重新拉取已发布镜像并部署 Clover 服务。配置、数据库和邮件卷会保留。" read -r -p "输入 REDEPLOY 继续:" confirm /dev/null 2>&1; then session_secret="$(openssl rand -hex 32)" else session_secret="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')" fi if grep -q '^SESSION_SECRET=' .env; then sed -i "s|^SESSION_SECRET=.*|SESSION_SECRET=${session_secret}|" .env else printf 'SESSION_SECRET=%s\n' "$session_secret" >> .env fi chmod 600 .env compose restart panel echo "管理员密码已重置,所有浏览器会话已注销;请用新密码重新登录。" fi unset password confirm } admin_create() { local existing confirm existing="$(docker exec clover-panel node -e 'const D=require("better-sqlite3");const d=new D(process.env.DATABASE_PATH||"/app/data/clover.db",{readonly:true});console.log(d.prepare("SELECT username FROM admins ORDER BY id").all().map(x=>x.username).join("、"));d.close();')" if [ -n "$existing" ]; then echo "当前管理员账户:$existing" echo "创建新管理员会覆盖旧管理员;旧密码、两步验证和 Passkey 不会转移,旧登录会话会失效。" read -r -p "输入 REPLACE 确认覆盖,其他内容取消:" confirm |reset <用户名>|rename <用户名>]" echo "提示:create 会替换原管理员;旧密码、两步验证和 Passkey 不会转移。" exit ;; esac while true; do header cat <<'EOF' 1) 查看状态 2) 重启服务 3) 更新 Clover 4) 修复 / 重新部署(保留配置与邮件数据) 5) 查看服务日志 6) 立即备份 7) 卸载服务(保留数据卷和配置) 8) 删除数据卷(保留安装文件和配置) 9) 管理员账户(仅一个;创建新管理员会覆盖旧账户) 0) 退出 EOF read -r -p "请选择:" choice