Some checks are pending
CI — Docker Build & Push / Build & Push Image (push) Waiting to run
scripts/deploy.sh — 一键部署:
- 自动检测发行版(Ubuntu/Debian/CentOS/RHEL/Rocky/AlmaLinux/
Fedora/Alpine/Arch/Manjaro/openSUSE)并安装 Docker + Compose
- 兼容 Docker Compose v1(docker-compose)和 v2(docker compose)
- 支持两种部署模式:新建 MySQL / 现有 MySQL
- 支持镜像来源:Gitea 仓库拉取 / 本地构建
- 交互式配置:端口、密钥、管理员账号、MySQL 密码等
- 自动生成加密随机 SECRET_KEY
- 可选启用 Nginx 反向代理(--profile nginx)
- 启动后执行健康检查,访问 /auth/login 验证
scripts/update.sh — 一键更新:
- 读取 .deploy-state 恢复上次部署配置,无需重新输入参数
- 更新前自动备份当前镜像标签(rollback-<时间戳>)
- 拉取新镜像时对比摘要,无变化时提示可跳过
- 健康检查失败时自动回滚至备份标签并重启服务
- 自动清理旧备份镜像(仅保留最近 3 个)
- 支持 --yes 免交互、--tag 指定目标标签
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
498 lines
20 KiB
Bash
498 lines
20 KiB
Bash
#!/usr/bin/env bash
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 个人资料库 — 一键部署脚本
|
||
# 支持发行版:Ubuntu / Debian / CentOS / RHEL / Rocky / AlmaLinux /
|
||
# Fedora / Alpine / Arch / Manjaro
|
||
#
|
||
# 用法:
|
||
# curl -fsSL https://your-gitea/raw/.../scripts/deploy.sh | bash
|
||
# 或:bash scripts/deploy.sh
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
set -euo pipefail
|
||
IFS=$'\n\t'
|
||
|
||
# ── 脚本自身位置 & 项目根目录 ─────────────────────────────────────
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||
STATE_FILE="$PROJECT_DIR/.deploy-state"
|
||
LOG_FILE="/tmp/resource-library-deploy-$(date +%Y%m%d%H%M%S).log"
|
||
|
||
# ── 颜色 & 输出 ──────────────────────────────────────────────────
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
||
|
||
log() { echo -e "$(date '+%H:%M:%S') $*" | tee -a "$LOG_FILE"; }
|
||
info() { echo -e "${CYAN}[INFO]${NC} $*" | tee -a "$LOG_FILE"; }
|
||
ok() { echo -e "${GREEN}[ OK ]${NC} $*" | tee -a "$LOG_FILE"; }
|
||
warn() { echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "$LOG_FILE"; }
|
||
err() { echo -e "${RED}[ERR ]${NC} $*" | tee -a "$LOG_FILE" >&2; }
|
||
die() { err "$*"; exit 1; }
|
||
section() { echo -e "\n${BOLD}${BLUE}── $* ─────────────────────────────────────${NC}" | tee -a "$LOG_FILE"; }
|
||
ask() { echo -e "${YELLOW}[?]${NC} $*"; }
|
||
|
||
banner() {
|
||
cat << 'EOF'
|
||
|
||
╔═══════════════════════════════════════════╗
|
||
║ 个人资料库 一键部署脚本 ║
|
||
║ Personal Resource Library Deployer ║
|
||
╚═══════════════════════════════════════════╝
|
||
|
||
EOF
|
||
}
|
||
|
||
# ── 工具函数 ──────────────────────────────────────────────────────
|
||
command_exists() { command -v "$1" &>/dev/null; }
|
||
|
||
# 生成安全随机字符串
|
||
gen_secret() {
|
||
python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null ||
|
||
openssl rand -hex 32 2>/dev/null ||
|
||
tr -dc 'A-Za-z0-9@#$%' < /dev/urandom | head -c 64
|
||
}
|
||
|
||
# 读取用户输入,支持默认值和可选的不回显(密码)
|
||
prompt() {
|
||
local var_name="$1" prompt_text="$2" default="${3:-}" secret="${4:-no}"
|
||
local value=""
|
||
if [ -n "$default" ]; then
|
||
ask "$prompt_text [默认: ${secret:+***}${default:-}]: "
|
||
else
|
||
ask "$prompt_text: "
|
||
fi
|
||
if [ "$secret" = "yes" ]; then
|
||
read -r -s value
|
||
echo
|
||
else
|
||
read -r value
|
||
fi
|
||
value="${value:-$default}"
|
||
eval "$var_name='$value'"
|
||
}
|
||
|
||
# ── 环境检测 ──────────────────────────────────────────────────────
|
||
check_bash_version() {
|
||
local ver="${BASH_VERSINFO[0]:-0}"
|
||
[ "$ver" -ge 4 ] || die "需要 Bash 4.0+,当前版本: $BASH_VERSION"
|
||
}
|
||
|
||
check_root() {
|
||
if [ "$(id -u)" -ne 0 ]; then
|
||
if command_exists sudo; then
|
||
warn "建议以 root 或 sudo 运行;继续尝试…"
|
||
SUDO="sudo"
|
||
else
|
||
die "需要 root 权限(或安装 sudo)"
|
||
fi
|
||
else
|
||
SUDO=""
|
||
fi
|
||
}
|
||
|
||
check_internet() {
|
||
info "检测网络连通性…"
|
||
if ! curl -fsSL --max-time 8 https://get.docker.com > /dev/null 2>&1 && \
|
||
! wget -q --timeout=8 -O /dev/null https://get.docker.com 2>/dev/null; then
|
||
warn "无法访问 https://get.docker.com,Docker 自动安装可能失败"
|
||
warn "请手动安装 Docker 后重新运行此脚本"
|
||
else
|
||
ok "网络正常"
|
||
fi
|
||
}
|
||
|
||
# ── 发行版检测 ────────────────────────────────────────────────────
|
||
detect_os() {
|
||
OS_ID="unknown"; OS_PRETTY="Unknown"; PKG_MGR="unknown"
|
||
|
||
if [ -f /etc/os-release ]; then
|
||
# shellcheck disable=SC1091
|
||
. /etc/os-release
|
||
OS_ID="${ID:-unknown}"
|
||
OS_PRETTY="${PRETTY_NAME:-$OS_ID}"
|
||
OS_ID_LIKE="${ID_LIKE:-}"
|
||
fi
|
||
|
||
# 根据 ID 或 ID_LIKE 映射包管理器
|
||
case "$OS_ID" in
|
||
ubuntu|debian|linuxmint|pop|raspbian|kali)
|
||
PKG_MGR="apt" ;;
|
||
centos|rhel|ol|scientific)
|
||
PKG_MGR="yum_or_dnf" ;;
|
||
rocky|almalinux|eurolinux|springdale)
|
||
PKG_MGR="dnf" ;;
|
||
fedora)
|
||
PKG_MGR="dnf" ;;
|
||
alpine)
|
||
PKG_MGR="apk" ;;
|
||
arch|manjaro|endeavouros|garuda|artix)
|
||
PKG_MGR="pacman" ;;
|
||
opensuse*|sles)
|
||
PKG_MGR="zypper" ;;
|
||
*)
|
||
if echo "${OS_ID_LIKE:-}" | grep -qiE "debian|ubuntu"; then
|
||
PKG_MGR="apt"
|
||
elif echo "${OS_ID_LIKE:-}" | grep -qiE "rhel|centos|fedora"; then
|
||
PKG_MGR="yum_or_dnf"
|
||
elif echo "${OS_ID_LIKE:-}" | grep -qiE "suse"; then
|
||
PKG_MGR="zypper"
|
||
fi
|
||
;;
|
||
esac
|
||
|
||
info "操作系统: $OS_PRETTY (ID=$OS_ID, PKG=$PKG_MGR)"
|
||
}
|
||
|
||
# ── Docker 安装 ───────────────────────────────────────────────────
|
||
# 使用 Docker 官方安装脚本(支持大多数发行版),Alpine/Arch 走包管理器
|
||
install_docker() {
|
||
section "安装 Docker"
|
||
|
||
if command_exists docker; then
|
||
local ver
|
||
ver=$(docker --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1)
|
||
ok "Docker 已安装: $ver"
|
||
return 0
|
||
fi
|
||
|
||
info "开始安装 Docker…"
|
||
|
||
case "$PKG_MGR" in
|
||
apk)
|
||
$SUDO apk add --no-cache docker docker-cli-compose
|
||
$SUDO rc-update add docker boot 2>/dev/null || true
|
||
$SUDO service docker start 2>/dev/null || true
|
||
;;
|
||
pacman)
|
||
$SUDO pacman -Sy --noconfirm docker docker-compose
|
||
$SUDO systemctl enable --now docker
|
||
;;
|
||
zypper)
|
||
$SUDO zypper install -y docker docker-compose
|
||
$SUDO systemctl enable --now docker
|
||
;;
|
||
*)
|
||
# 官方安装脚本(Ubuntu/Debian/CentOS/RHEL/Rocky/Fedora 等)
|
||
local tmp
|
||
tmp=$(mktemp)
|
||
curl -fsSL https://get.docker.com -o "$tmp" || \
|
||
wget -qO "$tmp" https://get.docker.com || \
|
||
die "下载 Docker 安装脚本失败"
|
||
$SUDO sh "$tmp"
|
||
rm -f "$tmp"
|
||
;;
|
||
esac
|
||
|
||
# 启动并设置开机自启(systemd 系统)
|
||
if command_exists systemctl && [ "$PKG_MGR" != "apk" ]; then
|
||
$SUDO systemctl enable docker 2>/dev/null || true
|
||
$SUDO systemctl start docker 2>/dev/null || true
|
||
fi
|
||
|
||
# 将当前用户加入 docker 组(后续免 sudo)
|
||
if [ -n "${SUDO_USER:-}" ] && getent group docker &>/dev/null; then
|
||
$SUDO usermod -aG docker "$SUDO_USER"
|
||
warn "已将 $SUDO_USER 加入 docker 组,需重新登录后生效"
|
||
fi
|
||
|
||
ok "Docker 安装完成: $(docker --version 2>/dev/null || echo '(请重新登录后验证)')"
|
||
}
|
||
|
||
# ── Docker Compose 检测 ───────────────────────────────────────────
|
||
detect_compose() {
|
||
if docker compose version &>/dev/null 2>&1; then
|
||
COMPOSE_CMD="docker compose"
|
||
ok "Docker Compose v2 (plugin): $(docker compose version --short 2>/dev/null)"
|
||
elif command_exists docker-compose; then
|
||
COMPOSE_CMD="docker-compose"
|
||
ok "Docker Compose v1 (standalone): $(docker-compose --version 2>/dev/null)"
|
||
else
|
||
info "未找到 Docker Compose,尝试安装 Compose 插件…"
|
||
install_compose_plugin
|
||
COMPOSE_CMD="docker compose"
|
||
fi
|
||
}
|
||
|
||
install_compose_plugin() {
|
||
local COMPOSE_VER="v2.27.1"
|
||
local ARCH; ARCH=$(uname -m)
|
||
case "$ARCH" in
|
||
x86_64) ARCH="x86_64" ;;
|
||
aarch64|arm64) ARCH="aarch64" ;;
|
||
armv7l) ARCH="armv7" ;;
|
||
*) die "不支持的 CPU 架构: $ARCH" ;;
|
||
esac
|
||
|
||
local URL="https://github.com/docker/compose/releases/download/${COMPOSE_VER}/docker-compose-linux-${ARCH}"
|
||
local DEST="/usr/local/lib/docker/cli-plugins/docker-compose"
|
||
$SUDO mkdir -p "$(dirname "$DEST")"
|
||
$SUDO curl -fsSL "$URL" -o "$DEST" || $SUDO wget -qO "$DEST" "$URL" || \
|
||
die "下载 Docker Compose 插件失败"
|
||
$SUDO chmod +x "$DEST"
|
||
ok "Docker Compose 插件安装完成"
|
||
}
|
||
|
||
# ── 交互配置 ──────────────────────────────────────────────────────
|
||
collect_config() {
|
||
section "部署配置"
|
||
echo
|
||
echo " 请选择部署模式:"
|
||
echo " 1) 新建 MySQL(全栈,适合新服务器)"
|
||
echo " 2) 使用现有 MySQL(适合已有数据库)"
|
||
echo
|
||
ask "请输入选项 [1/2](默认: 1): "
|
||
read -r DEPLOY_MODE_INPUT
|
||
DEPLOY_MODE_INPUT="${DEPLOY_MODE_INPUT:-1}"
|
||
|
||
case "$DEPLOY_MODE_INPUT" in
|
||
1) DEPLOY_MODE="new-mysql" ; COMPOSE_FILE="docker-compose.yml" ; ENV_FILE=".env.docker" ;;
|
||
2) DEPLOY_MODE="external-db" ; COMPOSE_FILE="docker-compose.external-db.yml" ; ENV_FILE=".env.external-db" ;;
|
||
*) die "无效选项: $DEPLOY_MODE_INPUT" ;;
|
||
esac
|
||
ok "部署模式: $DEPLOY_MODE"
|
||
|
||
echo
|
||
echo " 请选择镜像来源:"
|
||
echo " 1) 从 Gitea 镜像仓库拉取(需已完成 CI 构建)"
|
||
echo " 2) 本地构建(无需 CI,首次部署推荐)"
|
||
echo
|
||
ask "请输入选项 [1/2](默认: 2): "
|
||
read -r IMG_SRC_INPUT
|
||
IMG_SRC_INPUT="${IMG_SRC_INPUT:-2}"
|
||
|
||
case "$IMG_SRC_INPUT" in
|
||
1) IMAGE_SOURCE="registry" ;;
|
||
2) IMAGE_SOURCE="build" ;;
|
||
*) die "无效选项: $IMG_SRC_INPUT" ;;
|
||
esac
|
||
|
||
if [ "$IMAGE_SOURCE" = "registry" ]; then
|
||
prompt REGISTRY_HOST "Gitea 镜像仓库地址" "git.hty1024.com"
|
||
prompt REGISTRY_IMAGE "镜像名(小写)" "${REGISTRY_HOST}/hty1024/ai-app-database"
|
||
prompt REGISTRY_USER "登录用户名" ""
|
||
prompt REGISTRY_PASS "登录密码 / Token" "" yes
|
||
|
||
info "登录镜像仓库…"
|
||
echo "$REGISTRY_PASS" | docker login "$REGISTRY_HOST" \
|
||
-u "$REGISTRY_USER" --password-stdin || die "登录失败,请检查凭据"
|
||
ok "镜像仓库登录成功"
|
||
else
|
||
REGISTRY_IMAGE=""
|
||
fi
|
||
|
||
echo
|
||
# ── 公共配置 ──────────────────────────────────────────────────
|
||
local auto_secret; auto_secret=$(gen_secret)
|
||
prompt APP_PORT "应用端口" "5000"
|
||
prompt SECRET_KEY "应用密钥(留空自动生成)" "$auto_secret" yes
|
||
SECRET_KEY="${SECRET_KEY:-$auto_secret}"
|
||
|
||
prompt ADMIN_USERNAME "管理员用户名" "admin"
|
||
prompt ADMIN_PASSWORD "管理员密码" "Admin@123456" yes
|
||
prompt ADMIN_EMAIL "管理员邮箱" "admin@example.com"
|
||
prompt MAX_UPLOAD_MB "最大上传大小(MB)" "500"
|
||
prompt GUNICORN_WORKERS "Gunicorn worker 数量" "4"
|
||
|
||
# ── 模式专属配置 ──────────────────────────────────────────────
|
||
if [ "$DEPLOY_MODE" = "new-mysql" ]; then
|
||
echo
|
||
info "MySQL 配置(新建数据库)"
|
||
local auto_root_pw; auto_root_pw=$(gen_secret | cut -c1-24)
|
||
local auto_user_pw; auto_user_pw=$(gen_secret | cut -c1-24)
|
||
prompt MYSQL_ROOT_PASSWORD "MySQL root 密码(留空自动生成)" "$auto_root_pw" yes
|
||
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-$auto_root_pw}"
|
||
prompt MYSQL_PASSWORD "MySQL 应用用户密码(留空自动生成)" "$auto_user_pw" yes
|
||
MYSQL_PASSWORD="${MYSQL_PASSWORD:-$auto_user_pw}"
|
||
prompt MYSQL_EXPOSE_PORT "MySQL 对外端口(留空=不暴露)" "127.0.0.1:3306"
|
||
else
|
||
echo
|
||
info "外部数据库配置"
|
||
echo " 格式: mysql+pymysql://用户名:密码@主机:端口/数据库名"
|
||
prompt DATABASE_URL "数据库连接串" ""
|
||
[ -n "$DATABASE_URL" ] || die "外部 MySQL 模式必须提供 DATABASE_URL"
|
||
prompt DB_WAIT_SECONDS "等待数据库就绪超时(秒)" "30"
|
||
fi
|
||
|
||
# ── 是否启用 Nginx ──────────────────────────────────────────
|
||
echo
|
||
ask "是否启用 Nginx 反向代理?[y/N](默认: N): "
|
||
read -r NGINX_INPUT
|
||
USE_NGINX="${NGINX_INPUT:-N}"
|
||
if echo "$USE_NGINX" | grep -qiE "^y(es)?$"; then
|
||
USE_NGINX="yes"
|
||
prompt NGINX_HTTP_PORT "Nginx HTTP 端口" "80"
|
||
prompt NGINX_HTTPS_PORT "Nginx HTTPS 端口" "443"
|
||
else
|
||
USE_NGINX="no"
|
||
fi
|
||
}
|
||
|
||
# ── 写入 env 文件 ─────────────────────────────────────────────────
|
||
write_env_file() {
|
||
section "写入配置文件"
|
||
local env_path="$PROJECT_DIR/$ENV_FILE"
|
||
|
||
# 备份旧文件
|
||
if [ -f "$env_path" ]; then
|
||
cp "$env_path" "${env_path}.bak.$(date +%Y%m%d%H%M%S)"
|
||
warn "已备份旧配置: ${env_path}.bak.*"
|
||
fi
|
||
|
||
{
|
||
echo "# 个人资料库部署配置 — 生成于 $(date '+%Y-%m-%d %H:%M:%S')"
|
||
echo "# 请勿提交此文件到版本控制系统!"
|
||
echo ""
|
||
echo "SECRET_KEY=${SECRET_KEY}"
|
||
echo "FLASK_ENV=production"
|
||
echo "APP_PORT=${APP_PORT}"
|
||
echo "MAX_UPLOAD_SIZE_MB=${MAX_UPLOAD_MB}"
|
||
echo "GUNICORN_WORKERS=${GUNICORN_WORKERS}"
|
||
echo "GUNICORN_TIMEOUT=120"
|
||
echo "LOG_LEVEL=info"
|
||
echo ""
|
||
echo "ADMIN_USERNAME=${ADMIN_USERNAME}"
|
||
echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}"
|
||
echo "ADMIN_EMAIL=${ADMIN_EMAIL}"
|
||
echo ""
|
||
|
||
if [ "$DEPLOY_MODE" = "new-mysql" ]; then
|
||
echo "MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}"
|
||
echo "MYSQL_DATABASE=resource_library"
|
||
echo "MYSQL_USER=resource_library"
|
||
echo "MYSQL_PASSWORD=${MYSQL_PASSWORD}"
|
||
echo "MYSQL_EXPOSE_PORT=${MYSQL_EXPOSE_PORT:-127.0.0.1:3306}"
|
||
else
|
||
echo "DATABASE_URL=${DATABASE_URL}"
|
||
echo "DB_WAIT_SECONDS=${DB_WAIT_SECONDS:-30}"
|
||
fi
|
||
|
||
if [ "${USE_NGINX}" = "yes" ]; then
|
||
echo ""
|
||
echo "NGINX_HTTP_PORT=${NGINX_HTTP_PORT:-80}"
|
||
echo "NGINX_HTTPS_PORT=${NGINX_HTTPS_PORT:-443}"
|
||
fi
|
||
} > "$env_path"
|
||
|
||
chmod 600 "$env_path"
|
||
ok "配置文件已写入: $env_path"
|
||
}
|
||
|
||
# ── 写入部署状态文件 ──────────────────────────────────────────────
|
||
write_state_file() {
|
||
{
|
||
echo "DEPLOY_MODE=${DEPLOY_MODE}"
|
||
echo "COMPOSE_FILE=${COMPOSE_FILE}"
|
||
echo "ENV_FILE=${ENV_FILE}"
|
||
echo "IMAGE_SOURCE=${IMAGE_SOURCE}"
|
||
echo "REGISTRY_IMAGE=${REGISTRY_IMAGE:-}"
|
||
echo "APP_PORT=${APP_PORT}"
|
||
echo "USE_NGINX=${USE_NGINX}"
|
||
echo "DEPLOYED_AT=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||
} > "$STATE_FILE"
|
||
ok "状态文件已写入: $STATE_FILE"
|
||
}
|
||
|
||
# ── 构建或拉取镜像 ────────────────────────────────────────────────
|
||
prepare_image() {
|
||
section "准备镜像"
|
||
cd "$PROJECT_DIR"
|
||
|
||
if [ "$IMAGE_SOURCE" = "registry" ]; then
|
||
info "从镜像仓库拉取: ${REGISTRY_IMAGE}:latest …"
|
||
docker pull "${REGISTRY_IMAGE}:latest" || die "镜像拉取失败"
|
||
ok "镜像拉取完成"
|
||
else
|
||
info "本地构建镜像(首次构建可能需要几分钟)…"
|
||
docker build -t resource-library:latest . 2>&1 | tee -a "$LOG_FILE"
|
||
ok "本地构建完成"
|
||
fi
|
||
}
|
||
|
||
# ── 启动服务 ──────────────────────────────────────────────────────
|
||
start_services() {
|
||
section "启动服务"
|
||
cd "$PROJECT_DIR"
|
||
|
||
local compose_args=(--env-file "$ENV_FILE" -f "$COMPOSE_FILE")
|
||
|
||
if [ "$USE_NGINX" = "yes" ]; then
|
||
compose_args+=(--profile nginx)
|
||
fi
|
||
|
||
info "执行: $COMPOSE_CMD ${compose_args[*]} up -d"
|
||
$COMPOSE_CMD "${compose_args[@]}" up -d 2>&1 | tee -a "$LOG_FILE"
|
||
ok "容器已启动"
|
||
}
|
||
|
||
# ── 健康检查 ──────────────────────────────────────────────────────
|
||
health_check() {
|
||
section "健康检查"
|
||
local max_wait=120 elapsed=0 interval=5
|
||
local url="http://localhost:${APP_PORT}/auth/login"
|
||
|
||
info "等待服务就绪(最长 ${max_wait}s)…"
|
||
while [ "$elapsed" -lt "$max_wait" ]; do
|
||
if curl -fsSL --max-time 5 "$url" > /dev/null 2>&1; then
|
||
ok "服务已就绪!耗时: ${elapsed}s"
|
||
return 0
|
||
fi
|
||
printf '.'
|
||
sleep "$interval"
|
||
elapsed=$((elapsed + interval))
|
||
done
|
||
echo
|
||
err "健康检查超时(${max_wait}s)"
|
||
warn "查看日志: $COMPOSE_CMD -f $COMPOSE_FILE logs --tail=50"
|
||
return 1
|
||
}
|
||
|
||
# ── 部署完成提示 ──────────────────────────────────────────────────
|
||
print_success() {
|
||
local local_ip
|
||
local_ip=$(hostname -I 2>/dev/null | awk '{print $1}') || local_ip="<服务器IP>"
|
||
|
||
echo
|
||
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════${NC}"
|
||
echo -e "${GREEN}${BOLD} 🎉 部署成功! ${NC}"
|
||
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════${NC}"
|
||
echo
|
||
echo -e " 访问地址: ${CYAN}http://${local_ip}:${APP_PORT}${NC}"
|
||
if [ "$USE_NGINX" = "yes" ]; then
|
||
echo -e " Nginx: ${CYAN}http://${local_ip}:${NGINX_HTTP_PORT:-80}${NC}"
|
||
fi
|
||
echo
|
||
echo -e " 管理员账号: ${YELLOW}${ADMIN_USERNAME}${NC}"
|
||
echo -e " 管理员密码: ${YELLOW}(部署时设置)${NC}"
|
||
echo
|
||
echo -e " 查看日志: ${BLUE}$COMPOSE_CMD -f $COMPOSE_FILE logs -f${NC}"
|
||
echo -e " 更新应用: ${BLUE}bash scripts/update.sh${NC}"
|
||
echo -e " 停止服务: ${BLUE}$COMPOSE_CMD -f $COMPOSE_FILE down${NC}"
|
||
echo
|
||
echo -e " 部署日志: $LOG_FILE"
|
||
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════${NC}"
|
||
echo
|
||
}
|
||
|
||
# ── 主流程 ────────────────────────────────────────────────────────
|
||
main() {
|
||
banner
|
||
log "部署日志: $LOG_FILE"
|
||
|
||
check_bash_version
|
||
check_root
|
||
check_internet
|
||
detect_os
|
||
|
||
install_docker
|
||
detect_compose
|
||
|
||
collect_config
|
||
write_env_file
|
||
write_state_file
|
||
prepare_image
|
||
start_services
|
||
health_check
|
||
print_success
|
||
}
|
||
|
||
main "$@"
|