#!/usr/bin/env sh set -eu if [ "$#" -ne 4 ]; then echo "用法: $0 <镜像仓库> <镜像标签> <逗号分隔服务> <环境文件>" >&2 exit 2 fi IMAGE_REPOSITORY="$1" IMAGE_TAG="$2" SERVICE_CSV="$3" ENV_FILE="$4" SSH_TARGET="${DEVELOPMENT_SSH_TARGET:-deploy@172.31.198.8}" SSH_KEY="${DEVELOPMENT_SSH_KEY:-/var/jenkins_home/.ssh/dev_deploy}" SSH_KNOWN_HOSTS="${DEVELOPMENT_SSH_KNOWN_HOSTS:-/var/jenkins_home/.ssh/known_hosts}" REMOTE_DIR="${DEVELOPMENT_DEPLOY_DIR:-/opt/firstleg/application}" COMPOSE_FILE="${DEPLOY_COMPOSE_FILE:-deploy/docker-compose.yml}" PROJECT_NAME="${DEPLOY_PROJECT_NAME:-firstleg-development}" if [ ! -f "$SSH_KEY" ]; then echo "开发环境 SSH 私钥不存在: $SSH_KEY" >&2 exit 2 fi if [ ! -f "$SSH_KNOWN_HOSTS" ]; then echo "SSH 主机指纹文件不存在: $SSH_KNOWN_HOSTS" >&2 exit 2 fi if [ ! -f "$ENV_FILE" ]; then echo "开发环境变量文件不存在: $ENV_FILE" >&2 exit 2 fi if [ ! -f "$COMPOSE_FILE" ]; then echo "Compose 文件不存在: $COMPOSE_FILE" >&2 exit 2 fi SERVICES=$(printf '%s' "$SERVICE_CSV" | tr ',' ' ') for service in $SERVICES; do case "$service" in *[!A-Za-z0-9_-]*|'') echo "非法服务名: $service" >&2 exit 2 ;; esac done if [ "${DEVELOPMENT_DEPLOY_LOCAL:-false}" = "true" ]; then echo "开发镜像已在开发节点本机构建,直接部署: $SERVICE_CSV" install -d -m 750 "$REMOTE_DIR" SUFFIX="${IMAGE_TAG}.$$.${SERVICE_CSV}" LOCAL_ENV_TMP="$REMOTE_DIR/.env.$SUFFIX.tmp" LOCAL_COMPOSE_TMP="$REMOTE_DIR/docker-compose.yml.$SUFFIX.tmp" install -m 600 "$ENV_FILE" "$LOCAL_ENV_TMP" install -m 644 "$COMPOSE_FILE" "$LOCAL_COMPOSE_TMP" mv -f "$LOCAL_ENV_TMP" "$REMOTE_DIR/.env" mv -f "$LOCAL_COMPOSE_TMP" "$REMOTE_DIR/docker-compose.yml" for service in $SERVICES; do docker image inspect "$IMAGE_REPOSITORY/$service:$IMAGE_TAG" >/dev/null done cd "$REMOTE_DIR" export DEPLOY_ENV=development export IMAGE_REPOSITORY IMAGE_TAG docker compose \ --project-name "$PROJECT_NAME" \ --env-file .env \ -f docker-compose.yml \ pull $SERVICES docker compose \ --project-name "$PROJECT_NAME" \ --env-file .env \ -f docker-compose.yml \ up -d --remove-orphans --pull never $SERVICES docker compose \ --project-name "$PROJECT_NAME" \ --env-file .env \ -f docker-compose.yml \ ps $SERVICES exit 0 fi run_ssh() { ssh -i "$SSH_KEY" \ -o BatchMode=yes \ -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \ -o ConnectTimeout=10 \ "$SSH_TARGET" "$@" } run_scp() { scp -i "$SSH_KEY" \ -o BatchMode=yes \ -o StrictHostKeyChecking=yes \ -o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \ -o ConnectTimeout=10 \ "$@" } run_ssh install -d -m 750 "$REMOTE_DIR" SUFFIX="${IMAGE_TAG}.$$.${SERVICE_CSV}" REMOTE_ENV_TMP="$REMOTE_DIR/.env.$SUFFIX.tmp" REMOTE_COMPOSE_TMP="$REMOTE_DIR/docker-compose.yml.$SUFFIX.tmp" run_scp "$ENV_FILE" "$SSH_TARGET:$REMOTE_ENV_TMP" run_scp "$COMPOSE_FILE" "$SSH_TARGET:$REMOTE_COMPOSE_TMP" run_ssh sh -s -- "$REMOTE_DIR" "$REMOTE_ENV_TMP" "$REMOTE_COMPOSE_TMP" <<'REMOTE_PREPARE' set -eu remote_dir="$1" env_tmp="$2" compose_tmp="$3" chmod 600 "$env_tmp" chmod 644 "$compose_tmp" mv -f "$env_tmp" "$remote_dir/.env" mv -f "$compose_tmp" "$remote_dir/docker-compose.yml" REMOTE_PREPARE for service in $SERVICES; do image="$IMAGE_REPOSITORY/$service:$IMAGE_TAG" docker image inspect "$image" >/dev/null echo "传输开发镜像: $image" docker save "$image" | gzip -1 | run_ssh 'gzip -dc | docker load' done run_ssh sh -s -- "$REMOTE_DIR" "$IMAGE_REPOSITORY" "$IMAGE_TAG" "$SERVICE_CSV" "$PROJECT_NAME" <<'REMOTE_DEPLOY' set -eu remote_dir="$1" image_repository="$2" image_tag="$3" service_csv="$4" project_name="$5" services=$(printf '%s' "$service_csv" | tr ',' ' ') cd "$remote_dir" export DEPLOY_ENV=development export IMAGE_REPOSITORY="$image_repository" export IMAGE_TAG="$image_tag" docker compose \ --project-name "$project_name" \ --env-file .env \ -f docker-compose.yml \ up -d --remove-orphans --pull never $services docker compose \ --project-name "$project_name" \ --env-file .env \ -f docker-compose.yml \ ps $services REMOTE_DEPLOY