#!/bin/bash # Shared installation/update checks. Never print compose config or credentials. init_deployment_docker_config() { local file="$1" configured fallback configured="$(read_env_value "$file" DOCKER_CONFIG_FILE)" if [ -n "$configured" ] && [ -f "$configured" ]; then return 0; fi if [ -n "$configured" ] && [ "$configured" != /root/.docker/config.json ]; then echo '❌ DOCKER_CONFIG_FILE 不是可读取的配置文件;未启动服务' >&2 return 1 fi if [ -f "${DOCKER_CONFIG:-$HOME/.docker}/config.json" ]; then write_env_value "$file" DOCKER_CONFIG_FILE "${DOCKER_CONFIG:-$HOME/.docker}/config.json" return 0 fi # Anonymous installations have no ~/.docker/config.json. A missing short # bind source would otherwise be created by Docker as a DIRECTORY. fallback="$PWD/data/docker-auth/config.json" mkdir -p "$(dirname "$fallback")" chmod 700 "$(dirname "$fallback")" if [ ! -e "$fallback" ]; then (umask 077; printf '{}\n' > "$fallback"); fi [ -f "$fallback" ] || { echo '❌ 安装目录的镜像认证配置不是文件' >&2; return 1; } write_env_value "$file" DOCKER_CONFIG_FILE "$fallback" } verify_deployment_images() { local compose="$1" configuration shift configuration="$(sudo $compose config --format json)" || return 1 printf '%s' "$configuration" | python3 ./scripts/verify-deployment-images.py --sudo --services "$@" } check_deployment_storage() { local compose="$1" configuration shift configuration="$(sudo $compose config --format json)" || return 1 printf '%s' "$configuration" | python3 ./scripts/check-deployment-storage.py --sudo --services "$@" } verify_pulled_release() { local compose="$1" configuration shift configuration="$(sudo $compose config --format json)" || return 1 printf '%s' "$configuration" | python3 ./scripts/verify-pulled-release.py --sudo --services "$@" } wait_for_deployment_services() { local compose="$1" timeout="$2" deadline service container_id state running health all_ready passes=0 shift 2 local services=("$@") [[ "$timeout" =~ ^[1-9][0-9]*$ ]] || { echo '❌ 健康检查等待秒数必须为正整数' >&2; return 1; } if [ "${#services[@]}" -eq 0 ]; then while IFS= read -r service; do [ -z "$service" ] || services+=("$service"); done < <(sudo $compose config --services) fi [ "${#services[@]}" -gt 0 ] || return 1 deadline=$((SECONDS + timeout)) echo "⏳ 等待 ${#services[@]} 个服务恢复可用(最长 ${timeout} 秒)..." while (( SECONDS < deadline )); do all_ready=true for service in "${services[@]}"; do container_id="$(sudo $compose ps -q "$service" 2>/dev/null || true)" if [ -z "$container_id" ]; then all_ready=false; continue; fi state="$(sudo docker inspect --format '{{.State.Running}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || true)" running="${state%%|*}"; health="${state#*|}" if [ "$running" != true ] || { [ "$health" != none ] && [ "$health" != healthy ]; }; then all_ready=false elif [ "$service" = watchtower ] && [ "$health" = none ]; then sudo docker exec "$container_id" /watchtower --health-check >/dev/null 2>&1 || all_ready=false fi done if [ "$all_ready" = true ]; then passes=$((passes + 1)) if [ "$passes" -ge 2 ]; then echo '✅ 所选服务已恢复可用'; return 0; fi sleep 2 else passes=0 sleep 5 fi done echo '❌ 服务未在规定时间内恢复可用,部署未完成' >&2 sudo $compose ps "${services[@]}" || true echo "请运行:$compose logs --tail=100 ${services[*]}" >&2 return 1 }