#!/usr/bin/env bash # <服务> 部署脚本(每个服务目录各一份,服务名取自所在目录名) # # 用法: ./deploy.sh [action] (在 deploy/<服务>/ 下运行) # env : local | dev | prod # action : dev/prod → deploy(默认) | up | build | pull | down | restart | logs | ps # local → up(默认) | build | pull | down | restart | logs | ps # # deploy = 构建推送镜像 + 同步配置 + 远程 pull + up(一键发布,仅 dev/prod) # up = 远程:仅同步配置 + 远程 pull + up(用已推送镜像);本机:起容器 # # 例(在 deploy/console/ 下): # ./deploy.sh prod # 一键发布生产 console # ./deploy.sh prod logs # 看生产 console 日志 # ./deploy.sh local up # 本机起 console # ./deploy.sh dev build # 只构建推送 dev 镜像 set -euo pipefail SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 本服务部署目录(deploy/<服务>) SERVICE="$(basename "$SVC_DIR")" # 服务名 = 目录名(console / admin) REPO_ROOT="$(cd "$SVC_DIR/../.." && pwd)" # 仓库根(deploy/<服务>/../..) blue() { printf "\033[34m%s\033[0m\n" "$*"; } green() { printf "\033[32m%s\033[0m\n" "$*"; } red() { printf "\033[31m%s\033[0m\n" "$*"; } # ╔══════════════════════════════════════════════════════════════════════╗ # ║ 部署目标 / 私有仓库配置 —— 写死在这里,改这里即可(按环境) ║ # ║ console、admin 同机不同目录;DEPLOY_DIR 自动 = /opt/yunyan/<服务名> ║ # ║ 注意:本文件纳入 git,下面的密码/IP 会随仓库提交(私有仓库内可接受) ║ # ╚══════════════════════════════════════════════════════════════════════╝ deploy_profile() { DEPLOY_HOST=""; REGISTRY_PASS="" # 占位,local 用不到;dev/prod 在下面填 # —— 各环境通用默认 —— REGISTRY=docker-registry.ideapsound.com # 私有镜像仓库地址 REGISTRY_USER=admin # 仓库账号 DEPLOY_PORT=22 # 服务器 SSH 端口 DEPLOY_USER=root # 服务器 SSH 用户 DEPLOY_KEY=~/.ssh/id_rsa # 本机访问服务器的 SSH 私钥路径 case "$ENV_NAME" in dev) DEPLOY_HOST="" # ← 开发服务器 IP(必填) REGISTRY_PASS="" # ← 私有仓库密码(必填) ;; prod) DEPLOY_HOST="" # ← 生产服务器 IP(必填) REGISTRY_PASS="" # ← 私有仓库密码(必填) ;; local) ;; # 本机不走 SSH,无需填 esac DEPLOY_DIR="/opt/yunyan/$SERVICE" # 远程部署目录(自动按服务名) } usage() { cat >&2 < [action] (在 deploy/$SERVICE/ 下运行) env : local | dev | prod action : dev/prod → deploy(默认) | up | build | pull | down | restart | logs | ps local → up(默认) | build | pull | down | restart | logs | ps 例: ./deploy.sh prod # 一键发布生产 $SERVICE ./deploy.sh prod logs # 看生产 $SERVICE 日志 ./deploy.sh local up # 本机起 $SERVICE ./deploy.sh dev build # 只构建推送 dev 镜像 EOF exit 1 } # ── 参数解析与校验 ── ENV_NAME="${1:-}"; ACTION="${2:-}" [ -n "$ENV_NAME" ] || usage [ -f "$SVC_DIR/docker-compose.yml" ] || { red "✗ $SERVICE 目录缺 docker-compose.yml(deploy.sh 须放在服务目录内)"; exit 1; } case "$ENV_NAME" in local|dev|prod) ;; *) red "✗ 未知环境: ${ENV_NAME}(应为 local|dev|prod)"; usage ;; esac # 默认动作:远程发布默认 deploy,本机默认 up [ -n "$ACTION" ] || { [ "$ENV_NAME" = local ] && ACTION=up || ACTION=deploy; } # 从 ENV_FILE 安全读取单个变量值(去除可能的外层双引号) _envget() { { grep -E "^$1=" "$ENV_FILE" || true; } | head -1 | cut -d= -f2- | sed -e 's/^"//' -e 's/"$//' } # 载入 env/<环境>.env —— 现在只放容器运行时配置(DSN / Redis / NATS / 密钥 / 端口 / TAG / 网络)。 # 部署目标与仓库凭据已写死在 deploy_profile,不再放 env 文件。脚本只需从这里取 compose 仍要的 TAG/网络。 load_env() { ENV_FILE="$SVC_DIR/env/$ENV_NAME.env" if [ ! -f "$ENV_FILE" ]; then red "✗ 缺少 $SERVICE/env/$ENV_NAME.env —— 请先从 $SERVICE/env/$ENV_NAME.env.example 复制并填写" exit 1 fi TAG="$(_envget TAG)"; TAG="${TAG:-latest}" DOCKER_NETWORK="$(_envget DOCKER_NETWORK)"; DOCKER_NETWORK="${DOCKER_NETWORK:-1panel-network}" } # 生成 compose 用 .env = 服务运行时 env 文件 + 注入写死的 REGISTRY(镜像地址替换用) # 先滤掉源文件里可能残留的 REGISTRY 行,保证唯一、不依赖 compose 的覆盖顺序。 gen_env() { { grep -v '^REGISTRY=' "$ENV_FILE" || true; } > "$SVC_DIR/.env" printf 'REGISTRY=%s\n' "$REGISTRY" >> "$SVC_DIR/.env" } # 构建并推送本服务镜像(复用仓库根目录 build.sh,目标 = 服务名)。 # local 环境只构建不推送(PUSH=0),dev/prod 构建并推送到私有仓库。 build_push() { local push=1; [ "$ENV_NAME" = local ] && push=0 blue ">>> 构建$([ "$push" = 1 ] && echo 推送) $SERVICE 镜像 (TAG=${TAG:-latest}, PUSH=$push)" TAG="${TAG:-latest}" REGISTRY="$REGISTRY" PUSH="$push" "$REPO_ROOT/build.sh" "$SERVICE" } ensure_network_local() { docker network inspect "$DOCKER_NETWORK" >/dev/null 2>&1 || docker network create "$DOCKER_NETWORK" } # 本机 docker compose(gen_env 落为同目录 .env,既做变量替换又做容器 env_file) compose_local() { gen_env ensure_network_local ( cd "$SVC_DIR" && docker compose --env-file .env "$@" ) } # ── 远程 SSH 基础 ── _ssh() { ssh -p "${DEPLOY_PORT:-22}" -i "${DEPLOY_KEY/#\~/$HOME}" -o StrictHostKeyChecking=accept-new "${DEPLOY_USER:-root}@${DEPLOY_HOST}" "$@"; } _scp() { scp -P "${DEPLOY_PORT:-22}" -i "${DEPLOY_KEY/#\~/$HOME}" -o StrictHostKeyChecking=accept-new "$@"; } _dest() { echo "${DEPLOY_USER:-root}@${DEPLOY_HOST}:$1"; } # 远程发布:同步配置 + 远程 login + pull + up remote_deploy() { : "${DEPLOY_HOST:?请在 deploy.sh 顶部 deploy_profile 的 $ENV_NAME 段填 DEPLOY_HOST}" gen_env blue ">>> 同步 $SERVICE 配置到 ${DEPLOY_USER:-root}@${DEPLOY_HOST}:${DEPLOY_DIR}" _ssh "mkdir -p '$DEPLOY_DIR'" _scp "$SVC_DIR/docker-compose.yml" "$(_dest "$DEPLOY_DIR/docker-compose.yml")" _scp "$SVC_DIR/.env" "$(_dest "$DEPLOY_DIR/.env")" # 带 confs/ 的服务(console)一并同步配置并预建 log 目录 if [ -d "$SVC_DIR/confs" ]; then _ssh "mkdir -p '$DEPLOY_DIR/confs' '$DEPLOY_DIR/log'" _scp -r "$SVC_DIR/confs/." "$(_dest "$DEPLOY_DIR/confs/")" fi blue ">>> 远程拉起 $SERVICE" [ -n "${REGISTRY_PASS:-}" ] && \ _ssh "docker login '$REGISTRY' -u '${REGISTRY_USER:-admin}' -p '$REGISTRY_PASS'" _ssh "docker network inspect '$DOCKER_NETWORK' >/dev/null 2>&1 || docker network create '$DOCKER_NETWORK'" _ssh "cd '$DEPLOY_DIR' && docker compose --env-file .env pull && docker compose --env-file .env up -d --force-recreate" } # 远程运维(down/restart/logs/ps/pull) remote_compose() { : "${DEPLOY_HOST:?请在 deploy.sh 顶部 deploy_profile 的 $ENV_NAME 段填 DEPLOY_HOST}" _ssh "cd '$DEPLOY_DIR' && docker compose --env-file .env $*" } # ── 分发 ── deploy_profile # 写死的部署目标 / 仓库凭据 load_env # env 文件里的运行时配置(TAG / 网络) if [ "$ENV_NAME" = local ]; then case "$ACTION" in up) compose_local up -d --force-recreate ;; down) compose_local down ;; restart) compose_local restart ;; logs) compose_local logs -f --tail=120 ;; ps) compose_local ps ;; pull) compose_local pull ;; build) build_push ;; *) red "未知操作: ${ACTION}(local 支持 up|down|restart|logs|ps|pull|build)"; exit 1 ;; esac else case "$ACTION" in deploy) build_push; remote_deploy ;; up) remote_deploy ;; build) build_push ;; pull) remote_compose pull ;; down) remote_compose down ;; restart) remote_compose restart ;; ps) remote_compose ps ;; logs) remote_compose logs -f --tail=120 ;; *) red "未知操作: ${ACTION}(dev/prod 支持 deploy|up|build|pull|down|restart|logs|ps)"; exit 1 ;; esac fi green "✓ $SERVICE $ENV_NAME $ACTION 完成"