#!/bin/bash # ============================================ # 债务管理系统 - 管理脚本 v2.3 # ============================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' INSTALL_DIR="/opt/debt-manager" show_logo() { clear echo -e "${CYAN}" echo "╔══════════════════════════════════════════════╗" echo "║ 债务管理系统 Debt Manager ║" echo "║ 个人/小团队债务管理平台 ║" echo "║ 版本: v2.3 ║" echo "╚══════════════════════════════════════════════╝" echo -e "${NC}" } detect_arch() { ARCH=$(uname -m) case "$ARCH" in x86_64|amd64) ARCH_NAME="x86" ;; aarch64|arm64) ARCH_NAME="arm64" ;; *) echo -e "${RED}不支持的架构: $ARCH${NC}"; return 1 ;; esac echo -e "${CYAN}架构: $ARCH_NAME${NC}" } check_docker() { if ! command -v docker &> /dev/null; then echo -e "${RED}请先安装 Docker${NC}"; exit 1 fi if ! docker compose version &> /dev/null; then echo -e "${RED}请先安装 Docker Compose${NC}"; exit 1 fi } gen_password() { openssl rand -hex 16; } get_script_dir() { SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ]; do DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)" SOURCE="$(readlink "$SOURCE")" [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" done cd -P "$(dirname "$SOURCE")" && pwd } # 检查镜像是否存在 check_images_exist() { local backend_image="debt-manager-backend:${ARCH_NAME}" local frontend_image="debt-manager-frontend:${ARCH_NAME}" local postgres_image="postgres:15-alpine" local backend_exists=$(docker image inspect "$backend_image" 2>/dev/null && echo "yes" || echo "no") local frontend_exists=$(docker image inspect "$frontend_image" 2>/dev/null && echo "yes" || echo "no") local postgres_exists=$(docker image inspect "$postgres_image" 2>/dev/null && echo "yes" || echo "no") if [ "$backend_exists" = "yes" ] && [ "$frontend_exists" = "yes" ] && [ "$postgres_exists" = "yes" ]; then return 0 else return 1 fi } # 加载镜像(如果不存在) load_images_if_needed() { local script_dir="$1" if check_images_exist; then echo -e "${GREEN}镜像已存在,跳过加载${NC}" return 0 fi echo -e "${CYAN}加载镜像...${NC}" local loaded=0 for f in "$script_dir/deploy/"*-${ARCH_NAME}.tar.gz "$script_dir/deploy/postgres"*.tar.gz; do if [ -f "$f" ]; then local img_name=$(basename "$f" .tar.gz) if docker image inspect "$img_name" &>/dev/null; then echo -e " ${GREEN}✓${NC} $img_name (已存在)" else echo -e " 加载 $img_name ..." docker load < "$f" 2>/dev/null && loaded=$((loaded+1)) fi fi done if [ $loaded -gt 0 ]; then echo -e "${GREEN}加载了 $loaded 个镜像${NC}" fi } # 验证所有容器运行状态 verify_containers() { echo -e "${CYAN}验证容器状态...${NC}" local all_up=true for svc in db backend frontend; do local status=$(docker compose ps --format '{{.Status}}' --filter "name=$svc" 2>/dev/null) if echo "$status" | grep -q "Up"; then echo -e " ${GREEN}✓${NC} $svc: 运行中" else echo -e " ${RED}✗${NC} $svc: 未运行 ($status)" all_up=false fi done if $all_up; then echo -e "${GREEN}所有容器运行正常${NC}" return 0 else echo -e "${RED}部分容器未正常运行${NC}" return 1 fi } show_menu() { echo -e "${YELLOW}请选择操作:${NC}" echo "" echo " 1) 安装系统" echo " 2) 更新系统" echo " 3) 卸载系统" echo " 4) 重置系统" echo " 5) 查看状态" echo " 0) 退出" echo "" read -p "请输入选项 [0-5]: " choice } install_system() { echo -e "${CYAN}═══ 安装债务管理系统 ═══${NC}" if [ -d "$INSTALL_DIR" ]; then echo -e "${YELLOW}系统已安装,是否覆盖? (y/N)${NC}" read -p "> " confirm [ "$confirm" != "y" ] && return cd "$INSTALL_DIR" && docker compose down 2>/dev/null || true fi detect_arch || return SCRIPT_DIR="$(get_script_dir)" mkdir -p "$INSTALL_DIR" && cd "$INSTALL_DIR" # 复制 docker-compose 文件 if [ -f "$SCRIPT_DIR/deploy/docker-compose.${ARCH_NAME}.yml" ]; then cp "$SCRIPT_DIR/deploy/docker-compose.${ARCH_NAME}.yml" docker-compose.yml elif [ -f "$SCRIPT_DIR/docker-compose.yml" ]; then cp "$SCRIPT_DIR/docker-compose.yml" docker-compose.yml else echo -e "${RED}找不到 docker-compose 配置文件${NC}"; return 1 fi # 复制镜像文件 if [ -d "$SCRIPT_DIR/deploy" ]; then for f in "$SCRIPT_DIR/deploy/"*-${ARCH_NAME}.tar.gz "$SCRIPT_DIR/deploy/postgres"*.tar.gz; do [ -f "$f" ] && cp "$f" . done fi [ -f "$SCRIPT_DIR/restore.sh" ] && cp "$SCRIPT_DIR/restore.sh" . && chmod +x restore.sh # 生成密钥 DB_PASS=$(gen_password) SECRET=$(gen_password) cat > .env << EOF POSTGRES_DB=debt_manager POSTGRES_USER=postgres POSTGRES_PASSWORD=$DB_PASS DATABASE_URL=postgresql+asyncpg://postgres:$DB_PASS@db:5432/debt_manager SECRET_KEY=$SECRET EOF # 检查并加载镜像 load_images_if_needed "$SCRIPT_DIR" # 启动 echo -e "${CYAN}启动服务...${NC}" docker compose up -d echo -e "${CYAN}等待服务启动...${NC}" sleep 10 # 验证容器 if verify_containers; then IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost") echo "" echo -e "${GREEN}═══ 安装成功! ═══${NC}" echo -e " 前台: ${CYAN}http://$IP:806${NC}" echo -e " 后台: ${CYAN}http://$IP:806/admin.html${NC}" echo -e " 管理员: ${CYAN}admin / admin123${NC}" echo -e " ${YELLOW}请立即修改默认密码!${NC}" else echo -e "${RED}启动失败,查看日志:${NC}" docker compose logs --tail=30 fi } update_system() { echo -e "${CYAN}═══ 更新债务管理系统 ═══${NC}" [ ! -d "$INSTALL_DIR" ] && { echo -e "${YELLOW}系统未安装${NC}"; return; } cd "$INSTALL_DIR" detect_arch || return SCRIPT_DIR="$(get_script_dir)" # 备份 echo -e "${CYAN}备份数据库...${NC}" docker compose exec -T db pg_dump -U postgres debt_manager > "backup_$(date +%Y%m%d_%H%M%S).sql" 2>/dev/null || true echo -e "${GREEN}备份完成${NC}" # 更新文件 [ -f "$SCRIPT_DIR/deploy/docker-compose.${ARCH_NAME}.yml" ] && cp "$SCRIPT_DIR/deploy/docker-compose.${ARCH_NAME}.yml" docker-compose.yml if [ -d "$SCRIPT_DIR/deploy" ]; then for f in "$SCRIPT_DIR/deploy/"*-${ARCH_NAME}.tar.gz "$SCRIPT_DIR/deploy/postgres"*.tar.gz; do [ -f "$f" ] && cp "$f" . done fi # 检查并加载新镜像 load_images_if_needed "$SCRIPT_DIR" echo -e "${CYAN}重启服务...${NC}" docker compose down && docker compose up -d echo -e "${CYAN}等待服务启动...${NC}" sleep 8 if verify_containers; then echo -e "${GREEN}更新完成!${NC}" else echo -e "${RED}更新失败,查看日志:${NC}" docker compose logs --tail=30 fi } uninstall_system() { echo -e "${CYAN}═══ 卸载债务管理系统 ═══${NC}" [ ! -d "$INSTALL_DIR" ] && { echo -e "${YELLOW}系统未安装${NC}"; return; } echo -e "${RED}警告: 将删除所有数据,包括数据库!${NC}" read -p "输入 YES 确认: " confirm [ "$confirm" != "YES" ] && { echo -e "${RED}已取消${NC}"; return; } cd "$INSTALL_DIR" && docker compose down -v 2>/dev/null || true rm -rf "$INSTALL_DIR" echo -e "${GREEN}卸载完成${NC}" } reset_system() { echo -e "${CYAN}═══ 重置债务管理系统 ═══${NC}" [ ! -d "$INSTALL_DIR" ] && { echo -e "${RED}系统未安装${NC}"; return; } echo -e "${RED}警告: 将清空所有数据!${NC}" read -p "输入 YES 确认: " confirm [ "$confirm" != "YES" ] && { echo -e "${RED}已取消${NC}"; return; } cd "$INSTALL_DIR" echo -e "${CYAN}备份数据...${NC}" docker compose exec -T db pg_dump -U postgres debt_manager > "pre_reset_$(date +%Y%m%d_%H%M%S).sql" 2>/dev/null || true docker compose down -v 2>/dev/null || true docker volume rm debt-manager_pgdata 2>/dev/null || true DB_PASS=$(gen_password) SECRET=$(gen_password) cat > .env << EOF POSTGRES_DB=debt_manager POSTGRES_USER=postgres POSTGRES_PASSWORD=$DB_PASS DATABASE_URL=postgresql+asyncpg://postgres:$DB_PASS@db:5432/debt_manager SECRET_KEY=$SECRET EOF docker compose up -d echo -e "${CYAN}等待服务启动...${NC}" sleep 10 if verify_containers; then echo -e "${GREEN}重置完成!${NC}" echo -e " 管理员: ${CYAN}admin / admin123${NC}" else echo -e "${RED}重置失败,查看日志:${NC}" docker compose logs --tail=30 fi } show_status() { [ ! -d "$INSTALL_DIR" ] && { echo -e "${YELLOW}系统未安装${NC}"; return; } cd "$INSTALL_DIR" echo -e "${CYAN}═══ 服务状态 ═══${NC}" docker compose ps echo "" echo -e "${CYAN}磁盘使用:${NC}" du -sh "$INSTALL_DIR" 2>/dev/null || true } main() { show_logo check_docker while true; do show_menu case $choice in 1) install_system ;; 2) update_system ;; 3) uninstall_system ;; 4) reset_system ;; 5) show_status ;; 0) echo -e "${GREEN}再见!${NC}"; exit 0 ;; *) echo -e "${RED}无效选项${NC}" ;; esac echo "" read -p "按回车继续..." _ done } main