Files
new-api/scripts/deploy-ubt.sh
T
2026-04-09 22:57:17 +08:00

208 lines
4.8 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REMOTE_HOST="${REMOTE_HOST:-ubt}"
DEPLOY_DIR="${DEPLOY_DIR:-/root/docker/newapi}"
APP_CONTAINER="${APP_CONTAINER:-new-api}"
APP_PORT="${APP_PORT:-3000}"
PUBLIC_URL="${PUBLIC_URL:-https://leexx.site}"
NODE_IMAGE="${NODE_IMAGE:-node:22-bullseye}"
GO_IMAGE="${GO_IMAGE:-golang:1.26.1-alpine}"
GOPROXY_VALUE="${GOPROXY_VALUE:-https://goproxy.cn,direct}"
FRONT_BUILD_HEAP_MB="${FRONT_BUILD_HEAP_MB:-8192}"
SYNC_EXCLUDES=(
".git"
".env"
"docker-compose.yml"
"data"
"logs"
"redis"
"backups"
".update_tmp"
"web/node_modules"
)
usage() {
cat <<'EOF'
Usage:
scripts/deploy-ubt.sh
Optional environment variables:
REMOTE_HOST SSH host alias, default: ubt
DEPLOY_DIR Remote deploy dir, default: /root/docker/newapi
APP_CONTAINER Container name, default: new-api
APP_PORT Local bind port on remote host, default: 3000
PUBLIC_URL Public URL for final check, default: https://leexx.site
NODE_IMAGE Node image for frontend build, default: node:22-bullseye
GO_IMAGE Go image for backend build, default: golang:1.26.1-alpine
GOPROXY_VALUE GOPROXY for remote build, default: https://goproxy.cn,direct
FRONT_BUILD_HEAP_MB Node heap limit in MB, default: 8192
EOF
}
log() {
printf '[deploy-ubt] %s\n' "$*"
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
printf 'Missing required command: %s\n' "$1" >&2
exit 1
}
}
remote_bash() {
local script="$1"
shift
ssh "$REMOTE_HOST" 'bash -s' -- "$@" <<<"$script"
}
sync_source() {
log "Syncing source to ${REMOTE_HOST}:${DEPLOY_DIR}"
local rsync_args=(
-az
--delete
)
local exclude
for exclude in "${SYNC_EXCLUDES[@]}"; do
rsync_args+=(--exclude "$exclude")
done
rsync "${rsync_args[@]}" "${ROOT_DIR}/" "${REMOTE_HOST}:${DEPLOY_DIR}/"
}
verify_remote_layout() {
log "Checking remote layout"
remote_bash '
set -euo pipefail
deploy_dir="$1"
test -d "$deploy_dir"
test -f "$deploy_dir/.env"
test -f "$deploy_dir/docker-compose.yml"
test -f "$deploy_dir/web/package.json"
test -f "$deploy_dir/web/package-lock.json"
test -f "$deploy_dir/web/src/helpers/render.jsx"
test -d "$deploy_dir/data"
test -d "$deploy_dir/logs"
' "$DEPLOY_DIR"
}
build_frontend() {
log "Building frontend dist on ${REMOTE_HOST}"
remote_bash '
set -euo pipefail
deploy_dir="$1"
node_image="$2"
heap_mb="$3"
docker run --rm --network host \
-v "${deploy_dir}:/src" \
"${node_image}" \
sh -lc "
set -e
rm -rf /tmp/web
cp -a /src/web /tmp/web
cd /tmp/web
npm ci --legacy-peer-deps
NODE_OPTIONS=--max-old-space-size=${heap_mb} npm run build
rm -rf /src/web/dist
cp -a dist /src/web/dist
"
' "$DEPLOY_DIR" "$NODE_IMAGE" "$FRONT_BUILD_HEAP_MB"
}
build_backend() {
log "Building backend binary on ${REMOTE_HOST}"
remote_bash '
set -euo pipefail
deploy_dir="$1"
go_image="$2"
goproxy_value="$3"
docker run --rm --network host \
-e "GOPROXY=${goproxy_value}" \
-v "${deploy_dir}:/src" \
-w /src \
"${go_image}" \
sh -lc "
set -e
export PATH=/usr/local/go/bin:\$PATH
VERSION_VALUE=\$(cat VERSION 2>/dev/null || true)
go mod download
go build -ldflags \"-s -w -X github.com/QuantumNous/new-api/common.Version=\${VERSION_VALUE}\" -o new-api
"
' "$DEPLOY_DIR" "$GO_IMAGE" "$GOPROXY_VALUE"
}
swap_binary() {
log "Replacing binary in ${APP_CONTAINER}"
remote_bash '
set -euo pipefail
deploy_dir="$1"
app_container="$2"
backup_dir="${deploy_dir}/backups"
mkdir -p "$backup_dir"
ts="$(date +%Y%m%d%H%M%S)"
docker inspect "$app_container" >/dev/null 2>&1
docker cp "${app_container}:/new-api" "${backup_dir}/new-api.${ts}.bak" || true
docker cp "${deploy_dir}/new-api" "${app_container}:/new-api"
docker restart "$app_container" >/dev/null
' "$DEPLOY_DIR" "$APP_CONTAINER"
}
verify_deploy() {
log "Checking local health endpoint"
remote_bash '
set -euo pipefail
app_container="$1"
app_port="$2"
public_url="$3"
local_url="http://127.0.0.1:${app_port}/api/status"
ok=0
for _ in $(seq 1 45); do
if curl -fsS "$local_url" >/dev/null 2>&1; then
ok=1
break
fi
sleep 2
done
if [[ "$ok" != "1" ]]; then
echo "Health check failed: ${local_url}" >&2
docker logs --tail 120 "$app_container" || true
exit 1
fi
docker ps --filter "name=${app_container}" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
curl -fsS "$local_url" >/dev/null
curl -I -m 20 "$public_url" | sed -n "1,12p"
' "$APP_CONTAINER" "$APP_PORT" "$PUBLIC_URL"
}
main() {
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
require_cmd ssh
require_cmd rsync
log "Starting deploy to ${REMOTE_HOST}"
sync_source
verify_remote_layout
build_frontend
build_backend
swap_binary
verify_deploy
log "Deploy completed successfully"
}
main "$@"