docker/ci: enable v4 and push again #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docker CI | |
| on: | |
| pull_request: | |
| paths: | |
| - 'docker/**' | |
| - 'docker-compose.yaml' | |
| - '.dockerignore' | |
| - 'chatmaild/**' | |
| - 'cmdeploy/**' | |
| - '.github/workflows/docker-ci.yaml' | |
| push: | |
| branches: | |
| - main | |
| - j4n/docker-pr | |
| paths: | |
| - 'docker/**' | |
| - 'docker-compose.yaml' | |
| - '.dockerignore' | |
| - 'chatmaild/**' | |
| - 'cmdeploy/**' | |
| - '.github/workflows/docker-ci.yaml' | |
| tags: | |
| - 'v*' | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| build: | |
| name: Build Docker image | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| image: ${{ steps.image-ref.outputs.image }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| if: github.event_name == 'push' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| # Tagged releases: v1.2.3 -> :1.2.3, :1.2, :latest | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| # Branch pushes: j4n/docker → :j4n-docker | |
| type=ref,event=branch | |
| # Always: :sha-<hash> | |
| type=sha | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: docker/chatmail_relay.dockerfile | |
| push: ${{ github.event_name == 'push' }} | |
| load: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| build-args: | | |
| GIT_HASH=${{ github.sha }} | |
| - name: Output image reference | |
| id: image-ref | |
| run: | | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| IMAGE="${{ env.REGISTRY }}/$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]'):sha-${SHORT_SHA}" | |
| echo "image=${IMAGE}" >> "$GITHUB_OUTPUT" | |
| - name: Save image as tarball | |
| run: docker save ${{ steps.image-ref.outputs.image }} | zstd -T0 -o /tmp/chatmail-image.tar.zst | |
| - name: Upload image artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: chatmail-image | |
| path: /tmp/chatmail-image.tar.zst | |
| retention-days: 1 | |
| deploy: | |
| name: Deploy docker image to ${{ matrix.host }} | |
| needs: build | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - host: staging2.testrun.org | |
| acme_dir: acme | |
| dkim_dir: dkimkeys | |
| zone_file: staging.testrun.org-default.zone | |
| disable_ipv6: false | |
| add_ssh_keys: true | |
| - host: staging-ipv4.testrun.org | |
| acme_dir: acme-ipv4 | |
| dkim_dir: dkimkeys-ipv4 | |
| zone_file: staging-ipv4.testrun.org-default.zone | |
| disable_ipv6: true | |
| add_ssh_keys: false | |
| environment: | |
| name: ${{ matrix.host }} | |
| url: https://${{ matrix.host }}/ | |
| concurrency: ${{ matrix.host }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: prepare SSH | |
| env: | |
| HOST: ${{ matrix.host }} | |
| ACME_DIR: ${{ matrix.acme_dir }} | |
| DKIM_DIR: ${{ matrix.dkim_dir }} | |
| ZONE: ${{ matrix.zone_file }} | |
| run: | | |
| mkdir ~/.ssh | |
| echo "${{ secrets.STAGING_SSH_KEY }}" >> ~/.ssh/id_ed25519 | |
| chmod 600 ~/.ssh/id_ed25519 | |
| ssh-keyscan ${HOST} > ~/.ssh/known_hosts | |
| # save previous acme & dkim state (Docker bind-mount paths) | |
| rsync -avz root@${HOST}:/srv/chatmail/certs/ ${ACME_DIR}/ || true | |
| rsync -avz root@${HOST}:/srv/chatmail/dkim/ ${DKIM_DIR}/ || true | |
| # store previous acme & dkim state on ns.testrun.org, if it contains useful certs | |
| if [ -f ${DKIM_DIR}/opendkim.private ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" ${DKIM_DIR} root@ns.testrun.org:/tmp/ || true; fi | |
| if [ "$(ls -A ${ACME_DIR}/certs 2>/dev/null)" ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" ${ACME_DIR} root@ns.testrun.org:/tmp/ || true; fi | |
| # make sure CAA record isn't set | |
| scp -o StrictHostKeyChecking=accept-new .github/workflows/${ZONE} root@ns.testrun.org:/etc/nsd/${HOST}.zone | |
| ssh root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/${HOST}.zone | |
| ssh root@ns.testrun.org nsd-checkzone ${HOST} /etc/nsd/${HOST}.zone | |
| ssh root@ns.testrun.org systemctl reload nsd | |
| - name: rebuild ${{ matrix.host }} to have a clean VPS | |
| env: | |
| SERVER_ID: ${{ matrix.host == 'staging2.testrun.org' && secrets.STAGING_SERVER_ID || secrets.STAGING_IPV4_SERVER_ID }} | |
| run: | | |
| curl -X POST \ | |
| -H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"image":"debian-12"}' \ | |
| "https://api.hetzner.cloud/v1/servers/${SERVER_ID}/actions/rebuild" | |
| - run: scripts/initenv.sh | |
| - name: append venv/bin to PATH | |
| run: echo venv/bin >>$GITHUB_PATH | |
| - name: wait for VPS rebuild | |
| run: | | |
| rm ~/.ssh/known_hosts | |
| while ! ssh -o ConnectTimeout=180 -o StrictHostKeyChecking=accept-new root@${{ matrix.host }} id -u ; do sleep 1 ; done | |
| - name: install Docker on VPS | |
| env: | |
| HOST: ${{ matrix.host }} | |
| run: | | |
| ssh root@${HOST} 'apt-get update && apt-get install -y ca-certificates curl' | |
| ssh root@${HOST} 'install -m 0755 -d /etc/apt/keyrings' | |
| ssh root@${HOST} 'curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && chmod a+r /etc/apt/keyrings/docker.asc' | |
| ssh root@${HOST} 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list' | |
| ssh root@${HOST} 'apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin' | |
| - name: restore ACME & DKIM state | |
| env: | |
| HOST: ${{ matrix.host }} | |
| ACME_DIR: ${{ matrix.acme_dir }} | |
| DKIM_DIR: ${{ matrix.dkim_dir }} | |
| run: | | |
| # download from ns.testrun.org | |
| rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/${ACME_DIR} acme-restore || true | |
| rsync -avz root@ns.testrun.org:/tmp/${DKIM_DIR} dkimkeys-restore || true | |
| # restore to VPS host paths (will be bind-mounted into container) | |
| ssh root@${HOST} mkdir -p /srv/chatmail/dkim /srv/chatmail/certs | |
| rsync -avz acme-restore/${ACME_DIR}/ root@${HOST}:/srv/chatmail/certs/ || true | |
| rsync -avz dkimkeys-restore/${DKIM_DIR}/ root@${HOST}:/srv/chatmail/dkim/ || true | |
| - name: generate chatmail.ini | |
| env: | |
| HOST: ${{ matrix.host }} | |
| DISABLE_IPV6: ${{ matrix.disable_ipv6 }} | |
| run: | | |
| cmdeploy init ${HOST} | |
| if [ "${DISABLE_IPV6}" = "true" ]; then | |
| sed -i 's/disable_ipv6 = False/disable_ipv6 = True/' chatmail.ini | |
| fi | |
| sed -i 's/#\s*mtail_address/mtail_address/' chatmail.ini | |
| scp chatmail.ini root@${HOST}:/srv/chatmail/chatmail.ini | |
| - name: Download image artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: chatmail-image | |
| path: /tmp | |
| - name: upload repo and deploy with Docker | |
| env: | |
| HOST: ${{ matrix.host }} | |
| run: | | |
| GHCR_IMAGE="${{ needs.build.outputs.image }}" | |
| rsync -avz --exclude='.git' --exclude='venv' --exclude='__pycache__' ./ root@${HOST}:/srv/chatmail/relay/ | |
| # Load pre-built image from artifact | |
| zstd -d /tmp/chatmail-image.tar.zst -o /tmp/chatmail-image.tar | |
| scp /tmp/chatmail-image.tar root@${HOST}:/tmp/chatmail-image.tar | |
| ssh root@${HOST} "docker load -i /tmp/chatmail-image.tar && rm /tmp/chatmail-image.tar" | |
| # # Login to GHCR on VPS and pull pre-built image | |
| # echo "${{ secrets.GITHUB_TOKEN }}" | ssh root@${HOST} 'docker login ghcr.io -u ${{ github.actor }} --password-stdin' | |
| # ssh root@${HOST} "docker pull ${GHCR_IMAGE}" | |
| ssh root@${HOST} "cd /srv/chatmail/relay && CHATMAIL_IMAGE=${GHCR_IMAGE} MAIL_DOMAIN=${HOST} docker compose -f docker-compose.yaml -f docker/docker-compose.ci.yaml up -d" | |
| - name: wait for container to become healthy | |
| env: | |
| HOST: ${{ matrix.host }} | |
| run: | | |
| for i in $(seq 1 60); do | |
| status=$(ssh root@${HOST} 'docker inspect --format={{.State.Health.Status}} chatmail 2>/dev/null' || echo "missing") | |
| echo " [$i/60] status=$status" | |
| if [ "$status" = "healthy" ]; then | |
| echo "Container is healthy." | |
| exit 0 | |
| fi | |
| sleep 5 | |
| done | |
| echo "Container did not become healthy in time." | |
| ssh root@${HOST} 'cd /srv/chatmail/relay && docker compose logs --tail=200' | |
| exit 1 | |
| - name: add ssh keys to staging server | |
| if: matrix.add_ssh_keys | |
| run: ssh root@${{ matrix.host }} 'curl -s https://github.com/hpk42.keys https://github.com/j4n.keys >> .ssh/authorized_keys' | |
| - name: run deploy-chatmail offline tests | |
| run: CHATMAIL_DOCKER=chatmail pytest --pyargs cmdeploy | |
| - name: set DNS entries | |
| env: | |
| HOST: ${{ matrix.host }} | |
| ZONE: ${{ matrix.zone_file }} | |
| run: | | |
| ssh root@${HOST} 'docker exec chatmail chown opendkim:opendkim -R /etc/dkimkeys' | |
| # run cmdeploy dns inside the container | |
| ssh root@${HOST} 'docker exec chatmail cmdeploy dns --ssh-host @local --zonefile /opt/chatmail/staging.zone --verbose' | |
| ssh root@${HOST} 'docker cp chatmail:/opt/chatmail/staging.zone /tmp/staging.zone' | |
| scp root@${HOST}:/tmp/staging.zone staging-generated.zone | |
| cat staging-generated.zone >> .github/workflows/${ZONE} | |
| cat .github/workflows/${ZONE} | |
| scp .github/workflows/${ZONE} root@ns.testrun.org:/etc/nsd/${HOST}.zone | |
| ssh root@ns.testrun.org nsd-checkzone ${HOST} /etc/nsd/${HOST}.zone | |
| ssh root@ns.testrun.org systemctl reload nsd | |
| - name: cmdeploy test | |
| run: CHATMAIL_DOCKER=chatmail CHATMAIL_DOMAIN2=ci-chatmail.testrun.org cmdeploy test --slow | |
| - name: cmdeploy dns | |
| run: | | |
| ssh root@${{ matrix.host }} 'docker exec chatmail cmdeploy dns -v --ssh-host @local' |