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: on:
push: push:
branches: [main] branches: [main]
env: env:
SERVER_IP: ${{ secrets.SERVER_IP }} # REGISTRY secret = "git.vpn.cusano.net/logan" (full image prefix)
SSH_USER: drb REGISTRY: ${{ secrets.REGISTRY }}
jobs: 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: deploy:
name: Deploy to VM name: Deploy to VM
needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -21,26 +66,24 @@ jobs:
- name: Deploy - name: Deploy
run: | run: |
ssh -o StrictHostKeyChecking=no -i /tmp/deploy_key \ ssh -o StrictHostKeyChecking=no \
${{ env.SSH_USER }}@${{ env.SERVER_IP }} << 'ENDSSH' -o HostKeyAlgorithms=ssh-ed25519,rsa-sha2-256,rsa-sha2-512 \
-i /tmp/deploy_key \
drb@${{ secrets.SERVER_IP }} << 'ENDSSH'
set -e set -e
cd /opt/drb cd /opt/drb
# Pull latest code # Update compose files + mosquitto config
git pull origin main git pull origin main
# Rebuild and restart changed services # Pull pre-built images and restart (no build on the VM)
docker compose up -d --build --remove-orphans 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
# Reload Caddy if Caddyfile changed
sudo systemctl reload caddy
# Clean up old images
docker image prune -f docker image prune -f
ENDSSH ENDSSH
- name: Verify health - name: Health check
run: | run: |
sleep 15 sleep 20
curl -f https://api.${{ secrets.DRB_DOMAIN }}/health || \ curl -f https://api.${{ secrets.DRB_DOMAIN }}/health || \
(echo "Health check failed" && exit 1) (echo "Health check failed" && exit 1)
+3 -2
View File
@@ -17,17 +17,17 @@ services:
- mosquitto_data:/mosquitto/data - mosquitto_data:/mosquitto/data
c2-core: c2-core:
image: ${REGISTRY}/c2-core:${TAG:-latest}
build: ./drb-c2-core build: ./drb-c2-core
restart: unless-stopped restart: unless-stopped
ports: ports:
- "8888:8000" - "8888:8000"
env_file: ./drb-c2-core/.env env_file: ./drb-c2-core/.env
volumes:
- ./drb-c2-core/gcp-key.json:/app/gcp-key.json:ro
depends_on: depends_on:
- mosquitto - mosquitto
discord-bot: discord-bot:
image: ${REGISTRY}/discord-bot:${TAG:-latest}
build: ./drb-server-discord-bot build: ./drb-server-discord-bot
restart: unless-stopped restart: unless-stopped
env_file: ./drb-server-discord-bot/.env env_file: ./drb-server-discord-bot/.env
@@ -35,6 +35,7 @@ services:
- c2-core - c2-core
frontend: frontend:
image: ${REGISTRY}/frontend:${TAG:-latest}
build: ./drb-frontend build: ./drb-frontend
restart: unless-stopped restart: unless-stopped
ports: 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 - name: Clone repo (skipped if already present)
synchronize: git:
src: "{{ local_repo_path }}" repo: "{{ repo_url }}"
dest: "{{ app_dir }}/" dest: "{{ app_dir }}"
delete: true version: main
recursive: true update: false
rsync_opts: become: false
- "--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: Set ownership of app directory - name: Set ownership of app directory
file: file:
@@ -25,10 +18,7 @@
group: "{{ ssh_user }}" group: "{{ ssh_user }}"
recurse: true recurse: true
# No gcp-key.json needed — the VM authenticates to GCS/Firestore via ADC - name: Template top-level .env (docker-compose MQTT creds + registry)
# (GCE metadata server). IAM roles are granted by Terraform.
- name: Template top-level .env (docker-compose MQTT creds)
template: template:
src: root.env.j2 src: root.env.j2
dest: "{{ app_dir }}/.env" dest: "{{ app_dir }}/.env"
@@ -69,18 +59,19 @@
mode: "0644" mode: "0644"
notify: Reload Caddy 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: community.docker.docker_compose_v2:
project_src: "{{ app_dir }}" project_src: "{{ app_dir }}"
build: always files:
- docker-compose.yml
- docker-compose.prod.yml
pull: always
build: never
state: present 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. # Managed by Ansible. Do not edit manually.
MQTT_C2_USER={{ vault_mqtt_c2_user }} MQTT_C2_USER={{ vault_mqtt_c2_user }}
MQTT_C2_PASS={{ vault_mqtt_c2_pass }} MQTT_C2_PASS={{ vault_mqtt_c2_pass }}
MQTT_NODE_USER={{ vault_mqtt_node_user }} MQTT_NODE_USER={{ vault_mqtt_node_user }}
MQTT_NODE_PASS={{ vault_mqtt_node_pass }} 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 - vault.yml
pre_tasks: pre_tasks:
- name: Install rsync
apt:
name: rsync
state: present
update_cache: false
- name: Wait for Docker (startup.sh runs async on first boot) - name: Wait for Docker (startup.sh runs async on first boot)
command: docker info command: docker info
register: _docker register: _docker
+6
View File
@@ -19,6 +19,12 @@ vault_gemini_api_key: ""
vault_gcs_bucket: "your-gcs-bucket-name" vault_gcs_bucket: "your-gcs-bucket-name"
vault_firestore_database: "c2-server" 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 ─────────────────────────────────────────────────────────────── # ── Discord Bot ───────────────────────────────────────────────────────────────
vault_discord_token: "" vault_discord_token: ""