Compare commits

..

5 Commits

Author SHA1 Message Date
Head of Product & Engineering
72345a5129 fix: bump Go image to 1.26 to match go.mod requirement
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
go.mod declares go 1.26.1; docker build was failing with
"go.mod requires go >= 1.26.1 (running go 1.22.12; GOTOOLCHAIN=local)"

- Dockerfile: golang:1.22-alpine → golang:1.26-alpine
- .woodpecker.yml: test image 1.22-alpine → 1.26-alpine;
  gosec image 1.22-bookworm → 1.26-bookworm
- CLAUDE.md: update stack note to Go 1.26+

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 18:34:21 +02:00
Head of Product & Engineering
7745d205cb feat: add production deployment artifacts for ghl.cast.ph (Vultr)
Align Woodpecker CI pipeline with team standard (cast-backend pattern):
- Replace plugins/docker with woodpeckerci/plugin-docker-buildx
- Use git.sds.dev registry; tag with CI_COMMIT_SHA short + latest
- Use team secret names: registry_user/password, deploy_ssh_key
- Add golangci-lint, semgrep, gosec, trivy-fs, trivy-secrets security gates
- Deploy on push to main (not on tag): build-and-push then deploy step
  calls bash /opt/cast-ghl-provider/deploy/deploy.sh on server
- Add Telegram notification on success/failure

docker-compose.yaml: add image: git.sds.dev/cast/cast-ghl-provider:latest
(server pulls from registry; build: kept for local dev only)

deploy/deploy.sh: simplified to docker compose pull + up
(build now happens in CI, not on the server)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 14:12:27 +02:00
Head of Product & Engineering
877895f43e fix: correct domain from ghl.cast.ph to hl.cast.ph throughout
Update all active config and documentation files to use the correct
production domain hl.cast.ph (not ghl.cast.ph).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 14:09:43 +02:00
Head of Product & Engineering
f29b39b40c feat: adapt deployment for existing Docker server with nginx-proxy + Woodpecker
- docker-compose.yaml: remove port binding; add VIRTUAL_HOST/LETSENCRYPT_HOST
  env vars for nginx-proxy auto-routing; add internal + external proxy networks
- .woodpecker.yml: consolidate build steps into single ci step; add deploy-main
  step that builds + deploys on every push to main; keep deploy-tag for
  registry-pull deploys on version tags
- deploy/deploy.sh: simplify for manual/emergency use on existing server;
  add --from-registry flag for registry pull vs local build
- Remove deploy/setup-server.sh and deploy/nginx/ (not needed on existing server)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 14:06:21 +02:00
Head of Product & Engineering
f99772d8c0 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>
2026-04-05 01:46:26 +02:00
8 changed files with 136 additions and 40 deletions

View File

@ -1,5 +1,5 @@
PORT=3002 PORT=3002
BASE_URL=https://ghl.cast.ph BASE_URL=https://hl.cast.ph
# GHL OAuth # GHL OAuth
GHL_CLIENT_ID= GHL_CLIENT_ID=

View File

@ -1,34 +1,64 @@
steps: steps:
- name: build - name: lint
image: golang:1.22-alpine image: golangci/golangci-lint:latest
commands: commands:
- go build ./cmd/server/ - golangci-lint run ./...
- name: vet
image: golang:1.22-alpine
commands:
- go vet ./...
- name: test - name: test
image: golang:1.22-alpine image: golang:1.26-alpine
commands: commands:
- go test ./... - go test ./...
- name: docker-build - name: semgrep
image: plugins/docker image: semgrep/semgrep:latest
settings: commands:
repo: registry.sds.dev/cast/cast-ghl-provider - >
registry: registry.sds.dev semgrep scan --config auto --error
tags: --exclude-rule go.lang.security.audit.net.cookie-missing-secure.cookie-missing-secure
- latest --exclude-rule go.lang.security.audit.net.unescaped-data-in-url.unescaped-data-in-url
- "${CI_COMMIT_TAG}" --exclude-rule go.lang.security.audit.xss.template-html-does-not-escape.unsafe-template-type
username: --exclude-rule html.security.audit.missing-integrity.missing-integrity
from_secret: docker_username .
password:
from_secret: docker_password
when: when:
event: tag - event: [push, pull_request]
ref: refs/tags/v*
- name: gosec
image: golang:1.26-bookworm
commands:
- go install github.com/securego/gosec/v2/cmd/gosec@latest
- gosec -exclude=G120,G706,G101,G115,G203,G124 ./...
when:
- event: [push, pull_request]
- name: trivy-fs
image: aquasec/trivy:latest
commands:
- trivy fs --severity HIGH,CRITICAL --exit-code 1 .
when:
- event: [push, pull_request]
- name: trivy-secrets
image: aquasec/trivy:latest
commands:
- trivy fs --scanners secret --exit-code 1 .
when:
- event: [push, pull_request]
- name: build-and-push
image: woodpeckerci/plugin-docker-buildx
settings:
repo: git.sds.dev/cast/cast-ghl-provider
registry: git.sds.dev
tag:
- ${CI_COMMIT_SHA:0:8}
- latest
username:
from_secret: registry_user
password:
from_secret: registry_password
when:
- branch: main
event: push
- name: deploy - name: deploy
image: appleboy/drone-ssh image: appleboy/drone-ssh
@ -38,11 +68,29 @@ steps:
username: username:
from_secret: deploy_user from_secret: deploy_user
key: key:
from_secret: deploy_key from_secret: deploy_ssh_key
script: script:
- cd /opt/cast-ghl-provider - bash /opt/cast-ghl-provider/deploy/deploy.sh
- docker compose pull
- docker compose up -d --remove-orphans
when: when:
event: tag - branch: main
ref: refs/tags/v* event: push
- name: notify-telegram
image: appleboy/drone-telegram
settings:
token:
from_secret: telegram_bot_token
to:
from_secret: telegram_chat_id
message: >
{{#success build.status}}✅{{else}}❌{{/success}} **{{repo.name}}**
Branch: `{{commit.branch}}`
Status: **{{build.status}}**
Commit: `{{commit.message}}`
{{build.link}}
when:
- status: [success, failure]

View File

@ -6,7 +6,7 @@
## Stack ## Stack
- **Language:** Go 1.22+ - **Language:** Go 1.26+
- **HTTP:** `net/http` (stdlib) + `chi` router (lightweight) - **HTTP:** `net/http` (stdlib) + `chi` router (lightweight)
- **Database:** MongoDB (OAuth token storage) - **Database:** MongoDB (OAuth token storage)
- **Mongo driver:** `go.mongodb.org/mongo-driver/v2` - **Mongo driver:** `go.mongodb.org/mongo-driver/v2`
@ -116,7 +116,7 @@ cast-ghl-provider/
| Variable | Required | Default | Description | | Variable | Required | Default | Description |
|----------|----------|---------|-------------| |----------|----------|---------|-------------|
| `PORT` | No | `3002` | Server listen port | | `PORT` | No | `3002` | Server listen port |
| `BASE_URL` | Yes | — | Public URL (e.g. `https://ghl.cast.ph`) | | `BASE_URL` | Yes | — | Public URL (e.g. `https://hl.cast.ph`) |
| `GHL_CLIENT_ID` | Yes | — | GHL Marketplace app client ID | | `GHL_CLIENT_ID` | Yes | — | GHL Marketplace app client ID |
| `GHL_CLIENT_SECRET` | Yes | — | GHL Marketplace app client secret | | `GHL_CLIENT_SECRET` | Yes | — | GHL Marketplace app client secret |
| `GHL_WEBHOOK_PUBLIC_KEY` | Yes | — | PEM-encoded ECDSA public key for webhook sig | | `GHL_WEBHOOK_PUBLIC_KEY` | Yes | — | PEM-encoded ECDSA public key for webhook sig |

View File

@ -1,4 +1,4 @@
FROM golang:1.22-alpine AS builder FROM golang:1.26-alpine AS builder
WORKDIR /app WORKDIR /app
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN go mod download RUN go mod download

View File

@ -243,7 +243,7 @@ Version: 2021-04-15
1. Type: **SMS** 1. Type: **SMS**
2. Name: **Cast SMS** 2. Name: **Cast SMS**
3. Delivery URL: `https://ghl.cast.ph/api/ghl/v1/webhook/messages` 3. Delivery URL: `https://hl.cast.ph/api/ghl/v1/webhook/messages`
4. Do NOT check "Is this a Custom Conversation Provider" 4. Do NOT check "Is this a Custom Conversation Provider"
### Enabling the Provider (per sub-account) ### Enabling the Provider (per sub-account)

View File

@ -199,7 +199,7 @@ cast-ghl-provider/
```env ```env
# Server # Server
PORT=3002 PORT=3002
BASE_URL=https://ghl.cast.ph # Public URL for OAuth redirects + webhooks BASE_URL=https://hl.cast.ph # Public URL for OAuth redirects + webhooks
# GHL OAuth # GHL OAuth
GHL_CLIENT_ID=xxx GHL_CLIENT_ID=xxx
@ -250,7 +250,7 @@ volumes:
- **Host:** Vultr (existing Cast infrastructure) - **Host:** Vultr (existing Cast infrastructure)
- **Reverse proxy:** Nginx or Caddy with HTTPS - **Reverse proxy:** Nginx or Caddy with HTTPS
- **Domain:** `ghl.cast.ph` (or similar) - **Domain:** `hl.cast.ph`
- **CI/CD:** Woodpecker CI at `git.sds.dev` - **CI/CD:** Woodpecker CI at `git.sds.dev`
--- ---

22
deploy/deploy.sh Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
# deploy.sh — Called by Woodpecker CI on every push to main.
# Can also be run manually for emergency redeployments.
# Pulls the latest image from the registry and restarts the stack.
set -euo pipefail
APP_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$APP_DIR"
echo "==> Pulling latest image from registry"
docker compose pull bridge
echo "==> Restarting services"
docker compose up -d --remove-orphans
echo "==> Status"
sleep 3
docker compose ps bridge
echo ""
echo "=== Deploy complete — https://hl.cast.ph/health ==="

View File

@ -1,12 +1,21 @@
services: services:
bridge: bridge:
build: . image: git.sds.dev/cast/cast-ghl-provider:latest
ports: build: . # used only for local dev (docker compose up --build); production uses the registry image
- "${PORT:-3002}:${PORT:-3002}" # No port binding — nginx-proxy routes traffic via the shared proxy network
env_file: .env env_file: .env
environment:
# nginx-proxy / acme-companion auto-routing
- VIRTUAL_HOST=${VIRTUAL_HOST:-hl.cast.ph}
- VIRTUAL_PORT=${PORT:-3002}
- LETSENCRYPT_HOST=${VIRTUAL_HOST:-hl.cast.ph}
- LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL:-ops@cast.ph}
depends_on: depends_on:
mongo: mongo:
condition: service_started condition: service_healthy
networks:
- internal
- proxy # shared nginx-proxy network — must match the nginx-proxy container's network
restart: unless-stopped restart: unless-stopped
logging: logging:
driver: json-file driver: json-file
@ -16,9 +25,26 @@ 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
networks:
- internal
restart: unless-stopped restart: unless-stopped
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
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: volumes:
mongo-data: mongo-data: