Files
vault-dash/scripts/deploy.sh
Bu5hm4nn 00a68bc767 Initial commit: Vault Dashboard for options hedging
- 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
2026-03-21 19:21:40 +01:00

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