Files
kra-new/deploy/docker/deploy.sh
T

124 lines
4.7 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
TAG="${1:?Usage: deploy.sh TAG [--build-only]}"
MODE="${2:-}"
[[ "$TAG" =~ ^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$ ]] || { echo 'Invalid release tag' >&2; exit 1; }
[[ -z "$MODE" || "$MODE" == '--build-only' ]] || { echo 'Unknown mode' >&2; exit 1; }
BASE_DIR="${SW_DEPLOY_BASE:-/www/wwwroot/sw2.fuhua91.com}"
RELEASE="$BASE_DIR/releases/$TAG"
BACKEND_REPOSITORY=harbor.fuhua91.com/game/sw2-backend
FRONTEND_REPOSITORY=harbor.fuhua91.com/game/sw2
BACKEND_IMAGE="$BACKEND_REPOSITORY:$TAG"
FRONTEND_IMAGE="$FRONTEND_REPOSITORY:$TAG"
NETWORK=sw2-net
NAMES=(sw2-backend sw2-frontend)
command -v docker >/dev/null
command -v curl >/dev/null
test -f "$RELEASE/backend/Dockerfile"
test -f "$RELEASE/frontend/Dockerfile"
docker build --build-arg "VERSION=$TAG" -t "$BACKEND_IMAGE" "$RELEASE/backend"
docker build --build-arg "VERSION=$TAG" -t "$FRONTEND_IMAGE" "$RELEASE/frontend"
if [[ -s "$BASE_DIR/harbor-docker/config.json" ]]; then export DOCKER_CONFIG="$BASE_DIR/harbor-docker"; fi
publish_image() {
local repository=$1
local image="$repository:$TAG"
local digests reference
# Explicitly propagate failures inside command substitutions on Bash 4.2.
docker push "$image" >&2 || return 1
docker pull "$image" >&2 || return 1
digests=$(docker inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$image") || return 1
while IFS= read -r reference; do
if [[ "$reference" == "$repository@sha256:"* && "${reference##*@}" =~ ^sha256:[a-f0-9]{64}$ ]]; then
printf '%s\n' "$reference"
return 0
fi
done <<< "$digests"
echo "Harbor image digest is missing: $image" >&2
return 1
}
BACKEND_IMAGE=$(publish_image "$BACKEND_REPOSITORY")
FRONTEND_IMAGE=$(publish_image "$FRONTEND_REPOSITORY")
docker run --rm "$BACKEND_IMAGE" /app/kratos-admin -h >/dev/null
docker run --rm --add-host sw2-backend:127.0.0.1 "$FRONTEND_IMAGE" nginx -t
if [[ "$MODE" == '--build-only' ]]; then exit 0; fi
test -s "$BASE_DIR/config/config.yaml"
test -f "$BASE_DIR/runtime.env"
install -d -o 1000 -g 1000 -m 0750 "$BASE_DIR/config" "$BASE_DIR/log" "$BASE_DIR/uploads"
chown 1000:1000 "$BASE_DIR/config/config.yaml"
chmod 0600 "$BASE_DIR/config/config.yaml" "$BASE_DIR/runtime.env"
if ! docker network inspect "$NETWORK" >/dev/null 2>&1; then
docker network create --label com.sw2.application=sw2 "$NETWORK"
fi
managed() {
[[ "$(docker inspect --format '{{index .Config.Labels "com.sw2.application"}}' "$1")" == sw2 ]]
}
exists() { docker inspect "$1" >/dev/null 2>&1; }
for name in "${NAMES[@]}"; do
for candidate in "$name" "$name-previous"; do
if exists "$candidate"; then
managed "$candidate" || { echo "Refusing to replace unmanaged container: $candidate" >&2; exit 1; }
fi
done
done
moved=()
created=()
rollback() {
result=$?
trap - ERR
set +eu
for name in "${created[@]}"; do docker rm -f "$name" >/dev/null 2>&1; done
for name in "${moved[@]}"; do
if exists "$name-previous"; then docker rename "$name-previous" "$name"; fi
docker start "$name"
done
echo 'Deployment failed; previous containers restored where present.' >&2
exit "$result"
}
trap rollback ERR
for name in "${NAMES[@]}"; do
if exists "$name-previous"; then docker rm "$name-previous" >/dev/null; fi
if exists "$name"; then
moved+=("$name")
docker stop -t 30 "$name" >/dev/null
docker rename "$name" "$name-previous"
fi
done
created+=(sw2-backend)
docker run -d --name sw2-backend --label com.sw2.application=sw2 \
--restart unless-stopped --network "$NETWORK" \
--log-opt max-size=20m --log-opt max-file=3 \
-p 127.0.0.1:9602:8000 --env-file "$BASE_DIR/runtime.env" \
-v "$BASE_DIR/config:/data/conf" -v "$BASE_DIR/log:/app/logs" -v "$BASE_DIR/uploads:/app/uploads" \
"$BACKEND_IMAGE"
ready=false
for attempt in $(seq 1 45); do
if curl -fsS --max-time 3 http://127.0.0.1:9602/health >/dev/null 2>&1; then ready=true; break; fi
sleep 2
done
[[ "$ready" == true ]] || { echo 'Backend health check failed' >&2; false; }
created+=(sw2-frontend)
docker run -d --name sw2-frontend --label com.sw2.application=sw2 \
--restart unless-stopped --network "$NETWORK" \
--log-opt max-size=20m --log-opt max-file=3 \
-p 127.0.0.1:9601:80 "$FRONTEND_IMAGE"
ready=false
for attempt in $(seq 1 15); do
if curl -fsS --max-time 3 http://127.0.0.1:9601/api/health >/dev/null 2>&1; then ready=true; break; fi
sleep 2
done
[[ "$ready" == true ]] || { echo 'Frontend API proxy health check failed' >&2; false; }
trap - ERR
printf '%s\n' "$TAG" > "$BASE_DIR/current-release"
install -m 0750 "$RELEASE/deploy.sh" "$BASE_DIR/deploy.sh"
docker ps --filter label=com.sw2.application=sw2 --format '{{.Names}} {{.Image}} {{.Status}} {{.Ports}}'