You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

184 lines
8.8 KiB

#!/usr/bin/env bash
# <服务> 部署脚本(每个服务目录各一份,服务名取自所在目录名)
#
# 用法: ./deploy.sh <env> [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=liwei # 仓库账号
DEPLOY_PORT=22 # 服务器 SSH 端口
DEPLOY_USER=root # 服务器 SSH 用户
DEPLOY_KEY=~/Documents/灵谱/lingpu-hk.pem # 本机访问服务器的 SSH 私钥路径
case "$ENV_NAME" in
dev)
DEPLOY_HOST="8.217.193.140" # 灵谱香港阿里云(lingpu-hk)
REGISTRY_PASS="li13451234" # 私有仓库密码(liwei 用户)
;;
prod)
DEPLOY_HOST="" # ← 生产服务器 IP(必填)
REGISTRY_PASS="" # ← 私有仓库密码(必填)
;;
local) ;; # 本机不走 SSH,无需填
esac
DEPLOY_DIR="/home/work/starpivot-$SERVICE" # 远程部署目录(与 /home/work 下其余服务一致)
}
usage() {
cat >&2 <<EOF
用法: ./deploy.sh <env> [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 完成"