diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 3650739..fce2758 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -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) diff --git a/docker-compose.yml b/docker-compose.yml index a7b1118..3509f53 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/infra/ansible/roles/deploy/tasks/main.yml b/infra/ansible/roles/deploy/tasks/main.yml index b7a5a81..a10c2af 100644 --- a/infra/ansible/roles/deploy/tasks/main.yml +++ b/infra/ansible/roles/deploy/tasks/main.yml @@ -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 diff --git a/infra/ansible/roles/deploy/templates/root.env.j2 b/infra/ansible/roles/deploy/templates/root.env.j2 index e34f230..d71cefb 100644 --- a/infra/ansible/roles/deploy/templates/root.env.j2 +++ b/infra/ansible/roles/deploy/templates/root.env.j2 @@ -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 }} diff --git a/infra/ansible/site.yml b/infra/ansible/site.yml index 2a1897b..2f89a40 100644 --- a/infra/ansible/site.yml +++ b/infra/ansible/site.yml @@ -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 diff --git a/infra/ansible/vault.yml.example b/infra/ansible/vault.yml.example index edaa44e..d630361 100644 --- a/infra/ansible/vault.yml.example +++ b/infra/ansible/vault.yml.example @@ -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: ""