feat: add production deployment artifacts for ghl.cast.ph (Vultr)
- deploy/nginx/ghl.cast.ph.conf: Nginx reverse proxy with SSL (Let's Encrypt) - deploy/setup-server.sh: one-shot Ubuntu VPS bootstrap (Docker, Nginx, Certbot, UFW) - deploy/deploy.sh: pull-and-redeploy script using Docker Compose - docker-compose.yaml: bind bridge to 127.0.0.1 only; add Mongo healthcheck; bridge waits for Mongo healthy before starting Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
c2fd0e2f98
commit
f99772d8c0
35
deploy/deploy.sh
Normal file
35
deploy/deploy.sh
Normal file
@ -0,0 +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
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$APP_DIR"
|
||||||
|
|
||||||
|
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
|
||||||
|
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 logs --tail=20 bridge
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Deploy complete ==="
|
||||||
|
echo "Health endpoint: https://ghl.cast.ph/health"
|
||||||
42
deploy/nginx/ghl.cast.ph.conf
Normal file
42
deploy/nginx/ghl.cast.ph.conf
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
60
deploy/setup-server.sh
Normal file
60
deploy/setup-server.sh
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#!/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"
|
||||||
@ -1,12 +1,13 @@
|
|||||||
services:
|
services:
|
||||||
bridge:
|
bridge:
|
||||||
build: .
|
build: .
|
||||||
|
# Bind to localhost only — Nginx proxies from outside
|
||||||
ports:
|
ports:
|
||||||
- "${PORT:-3002}:${PORT:-3002}"
|
- "127.0.0.1:${PORT:-3002}:${PORT:-3002}"
|
||||||
env_file: .env
|
env_file: .env
|
||||||
depends_on:
|
depends_on:
|
||||||
mongo:
|
mongo:
|
||||||
condition: service_started
|
condition: service_healthy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
@ -16,9 +17,16 @@ services:
|
|||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:7
|
image: mongo:7
|
||||||
|
# No ports exposed — only reachable by bridge on the internal network
|
||||||
volumes:
|
volumes:
|
||||||
- mongo-data:/data/db
|
- mongo-data:/data/db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mongo-data:
|
mongo-data:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user