diff --git a/.woodpecker.yml b/.woodpecker.yml index 09ca3b1..590e41a 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,18 +1,10 @@ steps: - - name: build - image: golang:1.22-alpine - commands: - - go build ./cmd/server/ - - - name: vet + - name: ci image: golang:1.22-alpine commands: - go vet ./... - - - name: test - image: golang:1.22-alpine - commands: - go test ./... + - go build ./cmd/server/ - name: docker-build image: plugins/docker @@ -30,7 +22,7 @@ steps: event: tag ref: refs/tags/v* - - name: deploy + - name: deploy-tag image: appleboy/drone-ssh settings: host: @@ -41,8 +33,30 @@ steps: from_secret: deploy_key script: - cd /opt/cast-ghl-provider - - docker compose pull + - docker compose pull bridge - docker compose up -d --remove-orphans + - sleep 5 + - docker compose ps bridge when: event: tag ref: refs/tags/v* + + - name: deploy-main + image: appleboy/drone-ssh + settings: + host: + from_secret: deploy_host + username: + from_secret: deploy_user + key: + from_secret: deploy_key + script: + - cd /opt/cast-ghl-provider + - git pull --ff-only + - docker compose build --no-cache bridge + - docker compose up -d --remove-orphans + - sleep 5 + - docker compose ps bridge + when: + branch: main + event: push diff --git a/deploy/deploy.sh b/deploy/deploy.sh index dbd9d50..73e10bc 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -1,33 +1,35 @@ #!/usr/bin/env bash -# deploy.sh — Pull latest code and redeploy via Docker Compose -# Run from /opt/cast-ghl-plugin after initial server setup. -# Usage: bash deploy/deploy.sh +# deploy.sh — Manual redeploy on an existing Docker server. +# Normally Woodpecker CI handles deploys automatically on push to main or tag. +# Use this script only for manual/emergency redeploys. +# Usage: bash deploy/deploy.sh [--from-registry] +# --from-registry Pull the pre-built image from registry instead of building locally set -euo pipefail APP_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "$APP_DIR" +FROM_REGISTRY=false +[[ "${1:-}" == "--from-registry" ]] && FROM_REGISTRY=true + echo "==> Pulling latest code" git pull --ff-only -echo "==> Building and restarting services" -docker compose pull mongo # pull latest Mongo image if updated -docker compose build --no-cache bridge +if $FROM_REGISTRY; then + echo "==> Pulling pre-built image from registry" + docker compose pull bridge +else + echo "==> Building image locally" + docker compose build --no-cache bridge +fi + +echo "==> Restarting services" docker compose up -d --remove-orphans echo "==> Waiting for health check" sleep 5 -STATUS=$(docker compose ps --format json | python3 -c " -import sys, json -for line in sys.stdin: - s = json.loads(line) - if s.get('Service') == 'bridge': - print(s.get('Health', s.get('State', 'unknown'))) -" 2>/dev/null || echo "unknown") -echo "Bridge container status: $STATUS" - -echo "==> Tailing last 20 log lines" +docker compose ps bridge docker compose logs --tail=20 bridge echo "" diff --git a/deploy/nginx/ghl.cast.ph.conf b/deploy/nginx/ghl.cast.ph.conf deleted file mode 100644 index b11e50f..0000000 --- a/deploy/nginx/ghl.cast.ph.conf +++ /dev/null @@ -1,42 +0,0 @@ -server { - listen 80; - server_name ghl.cast.ph; - - # Let's Encrypt ACME challenge - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl; - server_name ghl.cast.ph; - - ssl_certificate /etc/letsencrypt/live/ghl.cast.ph/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/ghl.cast.ph/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - - # Security headers - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header Referrer-Policy no-referrer; - - location / { - proxy_pass http://127.0.0.1:3002; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Timeouts — GHL expects quick 200 for webhook; 30s is generous - proxy_connect_timeout 10s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } -} diff --git a/deploy/setup-server.sh b/deploy/setup-server.sh deleted file mode 100644 index ce61108..0000000 --- a/deploy/setup-server.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -# setup-server.sh — Bootstrap a fresh Ubuntu 22.04/24.04 LTS Vultr VPS -# Run once as root (or with sudo) after provisioning. -# Usage: bash setup-server.sh - -set -euo pipefail - -DOMAIN="ghl.cast.ph" -APP_DIR="/opt/cast-ghl-plugin" -REPO_URL="https://github.com/CAST-ph/cast-ghl-plugin.git" # adjust if needed - -echo "==> Updating system packages" -apt-get update -q && apt-get upgrade -y -q - -echo "==> Installing dependencies" -apt-get install -y -q \ - ca-certificates curl gnupg ufw \ - nginx certbot python3-certbot-nginx \ - git - -echo "==> Installing Docker" -install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ - | gpg --dearmor -o /etc/apt/keyrings/docker.gpg -chmod a+r /etc/apt/keyrings/docker.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ -https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ - | tee /etc/apt/sources.list.d/docker.list > /dev/null -apt-get update -q -apt-get install -y -q docker-ce docker-ce-cli containerd.io docker-compose-plugin -systemctl enable --now docker - -echo "==> Configuring firewall" -ufw default deny incoming -ufw default allow outgoing -ufw allow ssh -ufw allow 'Nginx Full' -ufw --force enable - -echo "==> Cloning application" -mkdir -p "$APP_DIR" -if [ -d "$APP_DIR/.git" ]; then - git -C "$APP_DIR" pull -else - git clone "$REPO_URL" "$APP_DIR" -fi - -echo "==> Installing Nginx config" -cp "$APP_DIR/deploy/nginx/ghl.cast.ph.conf" /etc/nginx/sites-available/"$DOMAIN" -ln -sf /etc/nginx/sites-available/"$DOMAIN" /etc/nginx/sites-enabled/"$DOMAIN" -rm -f /etc/nginx/sites-enabled/default -nginx -t && systemctl reload nginx - -echo "==> Obtaining Let's Encrypt certificate" -certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos -m ops@cast.ph -systemctl reload nginx - -echo "" -echo "=== Setup complete ===" -echo "Next: copy .env to $APP_DIR/.env then run deploy/deploy.sh" diff --git a/docker-compose.yaml b/docker-compose.yaml index b6cd41b..636dd35 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,13 +1,20 @@ services: bridge: build: . - # Bind to localhost only — Nginx proxies from outside - ports: - - "127.0.0.1:${PORT:-3002}:${PORT:-3002}" + # No port binding — nginx-proxy routes traffic via the shared proxy network env_file: .env + environment: + # nginx-proxy / acme-companion auto-routing + - VIRTUAL_HOST=${VIRTUAL_HOST:-ghl.cast.ph} + - VIRTUAL_PORT=${PORT:-3002} + - LETSENCRYPT_HOST=${VIRTUAL_HOST:-ghl.cast.ph} + - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL:-ops@cast.ph} depends_on: mongo: condition: service_healthy + networks: + - internal + - proxy # shared nginx-proxy network — must match the nginx-proxy container's network restart: unless-stopped logging: driver: json-file @@ -20,6 +27,8 @@ services: # No ports exposed — only reachable by bridge on the internal network volumes: - mongo-data:/data/db + networks: + - internal restart: unless-stopped healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] @@ -28,5 +37,13 @@ services: retries: 5 start_period: 20s +networks: + internal: + # Private network for bridge ↔ mongo + proxy: + external: true + # Must match the name of the existing nginx-proxy Docker network on the server. + # Check with: docker network ls | grep proxy + volumes: mongo-data: