deploy via Gitea CI registry; provision GCP infra with Terraform

- Terraform: e2-micro VM (us-east1-b, free tier), static IP, SSH/web
  firewall rules, IAM bindings for Firestore + GCS; imports existing
  drb-calls bucket and c2-server Firestore database into state
- Gitea CI: build c2-core, discord-bot, frontend images and push to
  git.vpn.cusano.net registry; SSH deploy pulls pre-built images (no
  build on VM)
- Ansible: first-time setup only — git clone, env files from vault,
  Caddyfile, docker login + compose pull + up; no rsync or on-VM builds
- docker-compose: add image: ${REGISTRY}/name:latest alongside build:
  so local dev and CI registry both work
- gitignore: add Terraform state, lock, tfvars, ansible secrets
This commit is contained in:
Logan
2026-06-22 02:31:28 -04:00
parent 33700448bf
commit 9fdcad1c46
6 changed files with 100 additions and 50 deletions
+58 -15
View File
@@ -1,16 +1,61 @@
name: Deploy
name: Build & Deploy
on:
push:
branches: [main]
env:
SERVER_IP: ${{ secrets.SERVER_IP }}
SSH_USER: drb
# REGISTRY secret = "git.vpn.cusano.net/logan" (full image prefix)
REGISTRY: ${{ secrets.REGISTRY }}
jobs:
build:
name: Build & push images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: git.vpn.cusano.net
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.BUILD_TOKEN }}
- name: Build & push c2-core
uses: docker/build-push-action@v5
with:
context: ./drb-c2-core
push: true
tags: |
${{ env.REGISTRY }}/c2-core:latest
${{ env.REGISTRY }}/c2-core:${{ gitea.sha }}
- name: Build & push discord-bot
uses: docker/build-push-action@v5
with:
context: ./drb-server-discord-bot
push: true
tags: |
${{ env.REGISTRY }}/discord-bot:latest
${{ env.REGISTRY }}/discord-bot:${{ gitea.sha }}
- name: Build & push frontend
uses: docker/build-push-action@v5
with:
context: ./drb-frontend
push: true
tags: |
${{ env.REGISTRY }}/frontend:latest
${{ env.REGISTRY }}/frontend:${{ gitea.sha }}
deploy:
name: Deploy to VM
needs: build
runs-on: ubuntu-latest
steps:
@@ -21,26 +66,24 @@ jobs:
- name: Deploy
run: |
ssh -o StrictHostKeyChecking=no -i /tmp/deploy_key \
${{ env.SSH_USER }}@${{ env.SERVER_IP }} << 'ENDSSH'
ssh -o StrictHostKeyChecking=no \
-o HostKeyAlgorithms=ssh-ed25519,rsa-sha2-256,rsa-sha2-512 \
-i /tmp/deploy_key \
drb@${{ secrets.SERVER_IP }} << 'ENDSSH'
set -e
cd /opt/drb
# Pull latest code
# Update compose files + mosquitto config
git pull origin main
# Rebuild and restart changed services
docker compose up -d --build --remove-orphans
# Reload Caddy if Caddyfile changed
sudo systemctl reload caddy
# Clean up old images
# Pull pre-built images and restart (no build on the VM)
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --remove-orphans
docker image prune -f
ENDSSH
- name: Verify health
- name: Health check
run: |
sleep 15
sleep 20
curl -f https://api.${{ secrets.DRB_DOMAIN }}/health || \
(echo "Health check failed" && exit 1)
+3 -2
View File
@@ -17,17 +17,17 @@ services:
- mosquitto_data:/mosquitto/data
c2-core:
image: ${REGISTRY}/c2-core:${TAG:-latest}
build: ./drb-c2-core
restart: unless-stopped
ports:
- "8888:8000"
env_file: ./drb-c2-core/.env
volumes:
- ./drb-c2-core/gcp-key.json:/app/gcp-key.json:ro
depends_on:
- mosquitto
discord-bot:
image: ${REGISTRY}/discord-bot:${TAG:-latest}
build: ./drb-server-discord-bot
restart: unless-stopped
env_file: ./drb-server-discord-bot/.env
@@ -35,6 +35,7 @@ services:
- c2-core
frontend:
image: ${REGISTRY}/frontend:${TAG:-latest}
build: ./drb-frontend
restart: unless-stopped
ports:
+23 -32
View File
@@ -1,21 +1,14 @@
---
# Sync code, write secrets, bring the stack up.
# First-time setup: clone repo, write secrets, pull pre-built images and start stack.
# Images are built and pushed by Gitea CI — this role never builds on the VM.
- name: Sync app code from local machine
synchronize:
src: "{{ local_repo_path }}"
dest: "{{ app_dir }}/"
delete: true
recursive: true
rsync_opts:
- "--exclude=.git"
- "--exclude=**/__pycache__"
- "--exclude=**/.env"
- "--exclude=**/gcp-key.json"
- "--exclude=**/node_modules"
- "--exclude=drb-c2-core/gcp-key.json"
- "--exclude=infra/"
become: false # rsync runs as the SSH user, not root
- name: Clone repo (skipped if already present)
git:
repo: "{{ repo_url }}"
dest: "{{ app_dir }}"
version: main
update: false
become: false
- name: Set ownership of app directory
file:
@@ -25,10 +18,7 @@
group: "{{ ssh_user }}"
recurse: true
# No gcp-key.json needed — the VM authenticates to GCS/Firestore via ADC
# (GCE metadata server). IAM roles are granted by Terraform.
- name: Template top-level .env (docker-compose MQTT creds)
- name: Template top-level .env (docker-compose MQTT creds + registry)
template:
src: root.env.j2
dest: "{{ app_dir }}/.env"
@@ -69,18 +59,19 @@
mode: "0644"
notify: Reload Caddy
- name: Bring the stack up (builds images if changed)
- name: Log in to container registry
command: >
docker login {{ vault_registry_host }}
-u {{ vault_registry_user }}
-p {{ vault_registry_token }}
no_log: true
- name: Pull pre-built images and start stack
community.docker.docker_compose_v2:
project_src: "{{ app_dir }}"
build: always
files:
- docker-compose.yml
- docker-compose.prod.yml
pull: always
build: never
state: present
pull: never
become: true
environment:
DOCKER_BUILDKIT: "1"
- name: Prune unused Docker images
community.docker.docker_prune:
images: true
images_filters:
dangling: true
@@ -1,7 +1,10 @@
# Top-level docker-compose environment — MQTT credentials for the broker container.
# Top-level docker-compose environment — MQTT credentials and registry prefix.
# Managed by Ansible. Do not edit manually.
MQTT_C2_USER={{ vault_mqtt_c2_user }}
MQTT_C2_PASS={{ vault_mqtt_c2_pass }}
MQTT_NODE_USER={{ vault_mqtt_node_user }}
MQTT_NODE_PASS={{ vault_mqtt_node_pass }}
# Container registry prefix — docker compose uses this for image: ${REGISTRY}/name:latest
REGISTRY={{ vault_registry }}
+6
View File
@@ -12,6 +12,12 @@
- vault.yml
pre_tasks:
- name: Install rsync
apt:
name: rsync
state: present
update_cache: false
- name: Wait for Docker (startup.sh runs async on first boot)
command: docker info
register: _docker
+6
View File
@@ -19,6 +19,12 @@ vault_gemini_api_key: ""
vault_gcs_bucket: "your-gcs-bucket-name"
vault_firestore_database: "c2-server"
# ── Gitea Container Registry ──────────────────────────────────────────────────
vault_registry_host: "git.vpn.cusano.net"
vault_registry_user: "logan"
vault_registry_token: "" # Gitea access token with package:write scope
vault_registry: "git.vpn.cusano.net/logan" # full image prefix
# ── Discord Bot ───────────────────────────────────────────────────────────────
vault_discord_token: ""