diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 844e558..79c8553 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,106 +25,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts - - - name: Create target dir - run: | - ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" "sudo mkdir -p '${REMOTE_DIR}' && sudo chown -R \$USER:\$USER '${REMOTE_DIR}'" - - - name: Reset any local changes on VPS - run: | - ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \ - "export REMOTE_DIR='${{ secrets.REMOTE_DIR }}'; bash -se" <<'EOF' - set -euo pipefail - cd "$REMOTE_DIR" - # Force reset any local changes and pull latest - if [ -d ".git" ]; then - echo "Git repository detected, resetting and pulling..." - git reset --hard HEAD - git clean -fd - git pull origin main - else - echo "No git repository found, creating backup of modified files..." - # Backup any existing files that might conflict - [ -f "docker-compose.yml" ] && cp docker-compose.yml docker-compose.yml.backup.$(date +%s) || true - fi - EOF - - - name: Sync repo to VPS (rsync) - run: | - rsync -az --delete \ - -e "ssh -p ${{ secrets.SSH_PORT }}" \ - --exclude ".git" \ - --exclude ".github" \ - --exclude ".env" \ - ./ "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${REMOTE_DIR}/" - - - name: Write .env on VPS (from GitHub Secrets) - run: | - ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \ - "export REMOTE_DIR='${{ secrets.REMOTE_DIR }}'; bash -se" <<'EOF' - set -euo pipefail - cd "$REMOTE_DIR" - - cat > .env <<'ENVVARS' - # --- Domain / Timezone --- - DOMAIN=${{ secrets.DOMAIN }} - TZ=${{ secrets.TZ }} - - # --- ACME / Let's Encrypt (email only) --- - ACME_EMAIL=${{ secrets.ACME_EMAIL }} - - # --- Basic Authentication --- - BASIC_AUTH_USERS=${{ secrets.BASIC_AUTH_USERS }} - - # --- Namecheap DNS API --- - NAMECHEAP_API_USER=${{ secrets.NAMECHEAP_API_USER }} - NAMECHEAP_API_KEY=${{ secrets.NAMECHEAP_API_KEY }} - - # --- CrowdSec --- - CROWDSEC_BOUNCER_KEY=${{ secrets.CROWDSEC_BOUNCER_KEY }} - - # --- Umami (PostgreSQL) --- - UMAMI_DB_USER=${{ secrets.UMAMI_DB_USER }} - UMAMI_DB_PASS=${{ secrets.UMAMI_DB_PASS }} - UMAMI_DB_NAME=${{ secrets.UMAMI_DB_NAME }} - UMAMI_APP_SECRET=${{ secrets.UMAMI_APP_SECRET }} - - # --- Grafana --- - GRAFANA_ADMIN_USER=${{ secrets.GRAFANA_ADMIN_USER }} - GRAFANA_ADMIN_PASS=${{ secrets.GRAFANA_ADMIN_PASS }} - ENVVARS - EOF - - - name: Pre-flight checks (docker + compose) - run: | - ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" " - docker version && docker compose version - " - - name: Deploy (pull, up, prune) run: | ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}" \ "export REMOTE_DIR='${{ secrets.REMOTE_DIR }}'; bash -se" <<'EOF' set -euo pipefail cd "$REMOTE_DIR" - # Warm up networks/volumes and pull images + + git pull origin main docker compose pull - - # Bring up (idempotent), remove old orphans docker compose up -d --remove-orphans - - # Light cleanup of old images (keeps running ones) docker image prune -af || true - - echo - echo '--- Running containers ---' - docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}' EOF - name: Post-deploy smoke checks @@ -134,3 +45,4 @@ jobs: echo "Portainer: https://portainer.gate.${{ secrets.DOMAIN }}" echo "Uptime Kuma: https://uptime.gate.${{ secrets.DOMAIN }}" echo "Umami: https://umami.gate.${{ secrets.DOMAIN }}" + echo "pgAdmin: https://pgadmin.gate.${{ secrets.DOMAIN }}" diff --git a/docker-compose.yml b/docker-compose.yml index f95cb01..ee0425a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -199,25 +199,25 @@ services: volumes: - umami_data:/var/lib/postgresql/data - ## ───────────────────────────────────────────── - ## pgAdmin — PostgreSQL administration - ## ───────────────────────────────────────────── - # pgadmin: - # image: dpage/pgadmin4:latest - # container_name: pgadmin - # restart: unless-stopped - # networks: [traefik_proxy] - # environment: - # PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} - # PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} - # PGADMIN_CONFIG_SERVER_MODE: 'False' - # PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' - # volumes: - # - pgadmin_data:/var/lib/pgadmin - # labels: - # - traefik.enable=true - # - traefik.http.routers.pgadmin.rule=Host(`pgadmin.gate.${DOMAIN}`) - # - traefik.http.routers.pgadmin.entrypoints=websecure - # - traefik.http.routers.pgadmin.tls.certresolver=le - # - traefik.http.routers.pgadmin.middlewares=basic-auth,security-headers - # - traefik.http.services.pgadmin.loadbalancer.server.port=80 + # ───────────────────────────────────────────── + # pgAdmin — PostgreSQL administration + # ───────────────────────────────────────────── + pgadmin: + image: dpage/pgadmin4:latest + container_name: pgadmin + restart: unless-stopped + networks: [traefik_proxy] + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} + PGADMIN_CONFIG_SERVER_MODE: 'False' + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' + volumes: + - pgadmin_data:/var/lib/pgadmin + labels: + - traefik.enable=true + - traefik.http.routers.pgadmin.rule=Host(`pgadmin.gate.${DOMAIN}`) + - traefik.http.routers.pgadmin.entrypoints=websecure + - traefik.http.routers.pgadmin.tls.certresolver=le + - traefik.http.routers.pgadmin.middlewares=basic-auth,security-headers + - traefik.http.services.pgadmin.loadbalancer.server.port=80