#!/usr/bin/env bash # 部门关键任务追踪看板 · Docker 部署脚本 # 用法: ./deploy.sh up 构建镜像并后台启动(默认命令) # ./deploy.sh update 改完代码后重建镜像并替换容器 # ./deploy.sh restart 重启容器(不换镜像) # ./deploy.sh down 停止并删除容器,数据留在 ./data # ./deploy.sh logs 跟踪日志(Ctrl+C 退出) # ./deploy.sh backup 导出数据库快照(部署到新机器时带走数据) # ./deploy.sh status 容器状态 + 健康检查 + 访问地址 set -euo pipefail cd "$(dirname "$0")" CONTAINER=unis-board # ---------- 0. 前置检查 ---------- command -v docker >/dev/null 2>&1 || { echo "!! 没有 docker 命令,请先安装 Docker / Docker Desktop" >&2; exit 1; } docker info >/dev/null 2>&1 || { echo "!! docker 守护进程没起来(Docker Desktop 是否没运行?)" >&2; exit 1; } if docker compose version >/dev/null 2>&1; then COMPOSE=(docker compose) elif command -v docker-compose >/dev/null 2>&1; then COMPOSE=(docker-compose) else echo "!! 没找到 docker compose,也没找到 docker-compose" >&2 exit 1 fi # 对外端口:命令行环境变量 > .env 里的 HOST_PORT > 8770 if [ -z "${HOST_PORT:-}" ] && [ -f .env ]; then HOST_PORT="$(sed -n 's/^HOST_PORT=//p' .env | tail -1)" fi HOST_PORT="${HOST_PORT:-8770}" URL="http://127.0.0.1:${HOST_PORT}" # ---------- 1. 准备 .env 与 data 目录 ---------- prepare() { if [ ! -f .env ]; then cp .env.example .env echo "==> 已从 .env.example 生成 .env(AI Key、管理员账号密码请按需修改,改完 restart 生效)" fi # 先建好目录,免得 Docker 用 root 建出来盖掉宿主机权限 mkdir -p data/uploads data/exports } # ---------- 2. 等服务就绪 ---------- wait_ready() { echo -n "==> 等待服务就绪 " for _ in $(seq 1 30); do state="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$CONTAINER" 2>/dev/null || echo missing)" case "$state" in healthy) echo; echo "==> 已就绪:$URL"; return 0 ;; unhealthy) echo; echo "!! 容器不健康,执行 ./deploy.sh logs 看原因" >&2; return 1 ;; esac echo -n "." sleep 2 done echo; echo "!! 等待超时(当前状态:$state),执行 ./deploy.sh logs 看原因" >&2 return 1 } # ---------- 3. 分发命令 ---------- case "${1:-up}" in up|start) first_run=0 [ -f data/board.db ] || first_run=1 prepare "${COMPOSE[@]}" up -d --build wait_ready echo " API 文档:$URL/api/docs" if [ "$first_run" = "1" ]; then echo " 首次启动:管理员账号取自 .env 的 ADMIN_USERNAME/ADMIN_PASSWORD(默认 admin / admin123,请登录后立刻改密码)" fi ;; update) prepare "${COMPOSE[@]}" up -d --build --force-recreate wait_ready ;; restart) "${COMPOSE[@]}" restart wait_ready ;; down|stop) "${COMPOSE[@]}" down echo "==> 已停止(数据仍在 ./data,要备份请复制 data/board.db)" ;; logs) exec "${COMPOSE[@]}" logs -f --tail=100 ;; backup) # 用 SQLite 备份 API 取一致性快照:服务正在写也不会拷到半截页 [ -f data/board.db ] || { echo "!! 没找到 data/board.db(若已换 MySQL,请改用 mysqldump)" >&2; exit 1; } PYBIN="$(command -v python3 || command -v python || true)" [ -n "$PYBIN" ] || { echo "!! 备份需要 python3(只拷库,不用装项目依赖)" >&2; exit 1; } mkdir -p data/backups OUT="data/backups/board-$(date +%Y%m%d_%H%M%S).db" "$PYBIN" - "$OUT" <<'PYEOF' import os, sqlite3, sys src = sqlite3.connect("data/board.db") dst = sqlite3.connect(sys.argv[1]) with dst: src.backup(dst) print(f"[ok] 已导出 {sys.argv[1]}({os.path.getsize(sys.argv[1]) // 1024} KB)") PYEOF echo "==> 部署到新机器:把这个文件放到目标机的 data/board.db,再执行 ./deploy.sh up" ;; status) "${COMPOSE[@]}" ps echo -n "==> /health:" curl -s --max-time 3 "$URL/health" 2>/dev/null || echo " 无响应(容器未启动?./deploy.sh up)" echo echo "==> 访问地址:$URL" ;; *) sed -n '3,9p' "$0" exit 1 ;; esac