diff --git a/.mokogit/workflows/deploy-dev.yml b/.mokogit/workflows/deploy-dev.yml new file mode 100644 index 0000000000..1d983a73ef --- /dev/null +++ b/.mokogit/workflows/deploy-dev.yml @@ -0,0 +1,136 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# BRIEF: Build and deploy to the Dev environment on push to the dev branch. +# Portable across Go server repos: ALL deployment config comes from repo +# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded. +# The dev branch is the ongoing integration branch. +# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3. +# +# Required repo VARIABLES (all tier-scoped so each environment is independent — +# a repo may host its rc/dev/prod tiers on different machines): +# DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME - SSH deploy target for the dev tier +# DEV_REGISTRY, DEV_REGISTRY_USER, DEV_IMAGE - container registry + login user + image +# DEV_CONTAINER - compose service/container to recreate +# DEV_COMPOSE_PROJECT - docker compose -p project name +# DEV_COMPOSE_DIR - dir containing docker-compose.yml on host +# DEV_SOURCE_DIR - build source checkout on host +# DEV_TAG_ENV - compose env-var name that pins the image tag +# DEV_HEALTH_URL - external URL to verify after deploy +# Required SECRETS (already configured org-wide; reused, not re-set): +# DEPLOY_SSH_KEY - deploy private key (repo/org secret) +# MOKOGIT_TOKEN - registry/API token (org secret) + +name: Deploy (Dev) + +on: + push: + branches: + - dev + # Manual trigger for isolated end-to-end tests. + # Runs on the ref it is dispatched from. + workflow_dispatch: + +concurrency: + group: deploy-dev + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + deploy-dev: + name: "Build & Deploy to Dev" + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine version + id: config + run: | + VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)") + echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT + echo "Version: ${VERSION}-dev" + + - name: Write deploy key + env: + DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + run: | + mkdir -p ~/.ssh + echo "$DEPLOY_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + - name: Build and deploy to RC via SSH + env: + REGISTRY_TOKEN: ${{ secrets.MOKOGIT_TOKEN }} + TAG: ${{ steps.config.outputs.tag }} + run: | + # Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell + # via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the + # remote. Repo variables (vars.*) are substituted inline by Actions before ssh. + ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} \ + -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -o ServerAliveInterval=30 -o ServerAliveCountMax=10 \ + ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} \ + "TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF' + set -e + echo 'SSH connected to Dev environment' + + if [ -z "$TAG" ]; then + echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2 + exit 1 + fi + + HEALTH_FMT='{{.State.Health.Status}}' + + echo 'Cleaning Docker build cache...' + docker builder prune -af 2>/dev/null || true + docker image prune -af 2>/dev/null || true + + echo 'Pulling source...' + SOURCE_DIR='${{ vars.DEV_SOURCE_DIR }}' + if [ ! -d "$SOURCE_DIR/.git" ]; then + git clone -b dev https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git "$SOURCE_DIR" + fi + cd "$SOURCE_DIR" + git remote set-url origin https://x-access-token:${REGISTRY_TOKEN}@git.mokoconsulting.tech/${{ github.repository }}.git 2>/dev/null || true + git fetch origin dev + git reset --hard origin/dev + + echo "Building image: ${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" + docker build --no-cache --build-arg GOFLAGS='-p 1' \ + --tag "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" \ + -f Dockerfile . + + echo 'Pushing to registry...' + echo "$REGISTRY_TOKEN" | docker login ${{ vars.DEV_REGISTRY }} -u ${{ vars.DEV_REGISTRY_USER }} --password-stdin + docker push "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" + + echo 'Restarting Dev container...' + cd '${{ vars.DEV_COMPOSE_DIR }}' + # Drive the rc service image tag via its compose env-var (no sed on the shared + # file); remove any lingering fixed-name container first, then force-recreate. + docker rm -f '${{ vars.DEV_CONTAINER }}' 2>/dev/null || true + ${{ vars.DEV_TAG_ENV }}="$TAG" docker compose -p '${{ vars.DEV_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.DEV_CONTAINER }}' + + echo 'Health check...' + for i in 1 2 3 4 5 6 7 8; do + sleep 15 + if docker inspect --format="$HEALTH_FMT" '${{ vars.DEV_CONTAINER }}' 2>/dev/null | grep -q healthy; then + echo 'Dev container healthy!' + exit 0 + fi + echo "Waiting... (attempt $i/8)" + done + echo 'Health check failed' + docker logs '${{ vars.DEV_CONTAINER }}' --tail 20 + exit 1 + DEPLOY_EOF + + - name: Verify Dev instance + continue-on-error: true + run: | + sleep 5 + ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} "curl -sf '${{ vars.DEV_HEALTH_URL }}'" && echo " Dev API healthy"