- FastAPI + NiceGUI web application - QuantLib-based Black-Scholes pricing with Greeks - Protective put, laddered, and LEAPS strategies - Real-time WebSocket updates - TradingView-style charts via Lightweight-Charts - Docker containerization - GitLab CI/CD pipeline for VPS deployment - VPN-only access configuration
92 lines
3.7 KiB
Bash
Executable File
92 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
: "${DEPLOY_USER:?DEPLOY_USER is required}"
|
|
: "${DEPLOY_HOST:?DEPLOY_HOST is required}"
|
|
: "${CI_REGISTRY_IMAGE:?CI_REGISTRY_IMAGE is required}"
|
|
: "${CI_REGISTRY_USER:?CI_REGISTRY_USER is required}"
|
|
: "${CI_REGISTRY_PASSWORD:?CI_REGISTRY_PASSWORD is required}"
|
|
|
|
DEPLOY_PORT="${DEPLOY_PORT:-22}"
|
|
DEPLOY_PATH="${DEPLOY_PATH:-/opt/vault-dash}"
|
|
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.deploy.yml}"
|
|
COMPOSE_SERVICE="${COMPOSE_SERVICE:-vault-dash}"
|
|
DEPLOY_TIMEOUT="${DEPLOY_TIMEOUT:-120}"
|
|
HEALTHCHECK_URL="${HEALTHCHECK_URL:-http://127.0.0.1:${APP_PORT:-8000}/health}"
|
|
IMAGE_TAG="${IMAGE_TAG:-${CI_COMMIT_SHA}}"
|
|
APP_IMAGE="${APP_IMAGE:-${CI_REGISTRY_IMAGE}:${IMAGE_TAG}}"
|
|
REMOTE_ENV_FILE="${REMOTE_ENV_FILE:-$DEPLOY_PATH/.env}"
|
|
SSH_OPTS=(-p "$DEPLOY_PORT" -o StrictHostKeyChecking=no)
|
|
REMOTE_TARGET="${DEPLOY_USER}@${DEPLOY_HOST}"
|
|
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p '$DEPLOY_PATH'"
|
|
|
|
if [[ -n "${APP_ENV_FILE:-}" ]]; then
|
|
printf '%s\n' "$APP_ENV_FILE" | ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "cat > '$REMOTE_ENV_FILE'"
|
|
else
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "cat > '$REMOTE_ENV_FILE'" <<EOF
|
|
APP_IMAGE=$APP_IMAGE
|
|
APP_ENV=${APP_ENV:-production}
|
|
APP_NAME=${APP_NAME:-Vault Dashboard}
|
|
APP_PORT=${APP_PORT:-8000}
|
|
APP_BIND_ADDRESS=${APP_BIND_ADDRESS:-127.0.0.1}
|
|
REDIS_URL=${REDIS_URL:-}
|
|
DEFAULT_SYMBOL=${DEFAULT_SYMBOL:-GLD}
|
|
CACHE_TTL=${CACHE_TTL:-300}
|
|
WEBSOCKET_INTERVAL_SECONDS=${WEBSOCKET_INTERVAL_SECONDS:-5}
|
|
NICEGUI_MOUNT_PATH=${NICEGUI_MOUNT_PATH:-/}
|
|
NICEGUI_STORAGE_SECRET=${NICEGUI_STORAGE_SECRET:-}
|
|
CORS_ORIGINS=${CORS_ORIGINS:-*}
|
|
EOF
|
|
fi
|
|
|
|
scp "${SSH_OPTS[@]}" docker-compose.deploy.yml "$REMOTE_TARGET:$DEPLOY_PATH/$COMPOSE_FILE"
|
|
|
|
ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "export CI_REGISTRY='${CI_REGISTRY:-registry.gitlab.com}' CI_REGISTRY_USER='$CI_REGISTRY_USER' CI_REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD' DEPLOY_PATH='$DEPLOY_PATH' COMPOSE_FILE='$COMPOSE_FILE' COMPOSE_SERVICE='$COMPOSE_SERVICE' APP_IMAGE='$APP_IMAGE' HEALTHCHECK_URL='$HEALTHCHECK_URL' DEPLOY_TIMEOUT='$DEPLOY_TIMEOUT' REMOTE_ENV_FILE='$REMOTE_ENV_FILE'; bash -s" <<'EOF'
|
|
set -Eeuo pipefail
|
|
cd "$DEPLOY_PATH"
|
|
|
|
if [[ -f .last_successful_image ]]; then
|
|
PREVIOUS_IMAGE="$(cat .last_successful_image)"
|
|
else
|
|
PREVIOUS_IMAGE=""
|
|
fi
|
|
|
|
if docker compose -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE" ps -q "$COMPOSE_SERVICE" >/dev/null 2>&1; then
|
|
CURRENT_CONTAINER="$(docker compose -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE" ps -q "$COMPOSE_SERVICE" || true)"
|
|
if [[ -n "$CURRENT_CONTAINER" ]]; then
|
|
CURRENT_IMAGE="$(docker inspect -f '{{ .Config.Image }}' "$CURRENT_CONTAINER")"
|
|
if [[ -n "$CURRENT_IMAGE" ]]; then
|
|
PREVIOUS_IMAGE="$CURRENT_IMAGE"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
|
|
|
|
docker pull "$APP_IMAGE"
|
|
sed -i.bak "/^APP_IMAGE=/d" "$REMOTE_ENV_FILE"
|
|
printf 'APP_IMAGE=%s\n' "$APP_IMAGE" | cat - "$REMOTE_ENV_FILE.bak" > "$REMOTE_ENV_FILE"
|
|
rm -f "$REMOTE_ENV_FILE.bak"
|
|
|
|
docker compose -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE" up -d --remove-orphans
|
|
|
|
end_time=$((SECONDS + DEPLOY_TIMEOUT))
|
|
until curl -fsS "$HEALTHCHECK_URL" >/dev/null; do
|
|
if (( SECONDS >= end_time )); then
|
|
echo "Deployment health check failed, attempting rollback" >&2
|
|
if [[ -n "$PREVIOUS_IMAGE" ]]; then
|
|
sed -i.bak "/^APP_IMAGE=/d" "$REMOTE_ENV_FILE"
|
|
printf 'APP_IMAGE=%s\n' "$PREVIOUS_IMAGE" | cat - "$REMOTE_ENV_FILE.bak" > "$REMOTE_ENV_FILE"
|
|
rm -f "$REMOTE_ENV_FILE.bak"
|
|
docker pull "$PREVIOUS_IMAGE" || true
|
|
docker compose -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE" up -d --remove-orphans
|
|
fi
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
echo "$APP_IMAGE" > .last_successful_image
|
|
EOF
|