208 lines
4.8 KiB
Bash
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 "$@"
|