diff --git a/.github/workflows/docker-deploy.yaml b/.github/workflows/docker-deploy.yaml new file mode 100644 index 00000000..5b514d72 --- /dev/null +++ b/.github/workflows/docker-deploy.yaml @@ -0,0 +1,125 @@ +name: Docker deploy + +on: + workflow_call: + inputs: + staging_host: + required: true + type: string + description: 'SSH hostname (e.g. staging2.testrun.org)' + mail_domain: + required: true + type: string + description: 'MAIL_DOMAIN for docker compose' + zone_file: + required: true + type: string + description: 'Default zone file basename (e.g. staging.testrun.org-default.zone)' + +jobs: + deploy-docker: + name: Docker deploy on ${{ inputs.staging_host }} + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: + name: ${{ inputs.staging_host }} + url: https://${{ inputs.staging_host }}/ + concurrency: ${{ inputs.staging_host }} + env: + VPS: root@${{ inputs.staging_host }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Setup SSH + run: | + mkdir ~/.ssh + echo "${{ secrets.STAGING_SSH_KEY }}" >> ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan ${{ inputs.staging_host }} > ~/.ssh/known_hosts + # Reuse TCP connection for all subsequent ssh/scp calls + echo -e "Host ${{ inputs.staging_host }}\n ControlMaster auto\n ControlPath ~/.ssh/ctrl-%r@%h:%p\n ControlPersist 10m" >> ~/.ssh/config + + - name: stop bare services, install Docker, prepare mounts + run: | + ssh $VPS bash -s <<'EOF' + systemctl stop postfix dovecot nginx opendkim unbound filtermail doveauth chatmail-metadata iroh-relay mtail fcgiwrap acmetool 2>/dev/null || true + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && chmod a+r /etc/apt/keyrings/docker.asc + 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 + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + mkdir -p /srv/chatmail/certs /srv/chatmail/dkim + cp -a /var/lib/acme/. /srv/chatmail/certs/ || true + cp -a /etc/dkimkeys/. /srv/chatmail/dkim/ || true + cp /etc/chatmail/chatmail.ini /srv/chatmail/chatmail.ini + EOF + + - name: deploy with Docker + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + GHCR_IMAGE="ghcr.io/chatmail/docker:sha-${SHORT_SHA}" + rsync -avz --exclude='.git' --exclude='venv' --exclude='__pycache__' ./ $VPS:/srv/chatmail/relay/ + echo "${{ secrets.GITHUB_TOKEN }}" | ssh $VPS "docker login ghcr.io -u ${{ github.actor }} --password-stdin && \ + docker pull ${GHCR_IMAGE} && \ + cd /srv/chatmail/relay && CHATMAIL_IMAGE=${GHCR_IMAGE} MAIL_DOMAIN=${{ inputs.mail_domain }} docker compose -f docker/docker-compose.yaml -f docker/docker-compose.ci.yaml up -d" + + - name: wait for container healthy + run: | + ssh $VPS 'docker exec chatmail journalctl -f --no-pager' & + LOG_PID=$! + trap "kill $LOG_PID 2>/dev/null || true" EXIT + for i in $(seq 1 60); do + status=$(ssh $VPS '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 + if [ "$status" = "unhealthy" ]; then + echo "Container is unhealthy!" + break + fi + sleep 5 + done + echo "Container did not become healthy." + kill $LOG_PID 2>/dev/null || true + ssh $VPS bash -s <<'EOF' + echo "--- failed units ---" + docker exec chatmail systemctl --failed --no-pager || true + echo "--- service logs ---" + docker exec chatmail journalctl -u dovecot -u postfix -u nginx -u unbound --no-pager -n 50 || true + echo "--- listening ports ---" + docker exec chatmail ss -tlnp || true + echo "--- chatmail.ini ---" + docker exec chatmail cat /etc/chatmail/chatmail.ini || true + EOF + exit 1 + + - name: show container state + run: | + ssh $VPS bash -s <<'EOF' + echo "--- listening ports ---" + docker exec chatmail ss -tlnp + echo "--- chatmail.ini ---" + docker exec chatmail cat /etc/chatmail/chatmail.ini + EOF + + - name: Docker integration tests + run: ssh $VPS 'docker exec chatmail cmdeploy test --slow --ssh-host @local' + + - name: Docker DNS + run: | + git checkout .github/workflows/${{ inputs.zone_file }} + ssh $VPS bash -s <<'EOF' + docker exec chatmail chown opendkim:opendkim -R /etc/dkimkeys + docker exec chatmail cmdeploy dns --ssh-host @local --zonefile /opt/chatmail/staging.zone --verbose + docker cp chatmail:/opt/chatmail/staging.zone /tmp/staging.zone + EOF + scp $VPS:/tmp/staging.zone staging-generated.zone + cat staging-generated.zone >> .github/workflows/${{ inputs.zone_file }} + cat .github/workflows/${{ inputs.zone_file }} + scp .github/workflows/${{ inputs.zone_file }} root@ns.testrun.org:/etc/nsd/${{ inputs.staging_host }}.zone + ssh root@ns.testrun.org "nsd-checkzone ${{ inputs.staging_host }} /etc/nsd/${{ inputs.staging_host }}.zone && systemctl reload nsd" + + - name: Docker final DNS check + run: ssh $VPS 'docker exec chatmail cmdeploy dns -v --ssh-host @local' diff --git a/.github/workflows/test-and-deploy-ipv4only.yaml b/.github/workflows/test-and-deploy-ipv4only.yaml index 990963ec..29a2deb8 100644 --- a/.github/workflows/test-and-deploy-ipv4only.yaml +++ b/.github/workflows/test-and-deploy-ipv4only.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + - j4n/docker-pr pull_request: paths-ignore: - 'scripts/**' @@ -12,6 +13,11 @@ on: - 'LICENSE' jobs: + trigger-docker-build: + if: github.event_name == 'push' + uses: ./.github/workflows/trigger-docker-build.yaml + secrets: inherit + deploy: name: deploy on staging-ipv4.testrun.org, and run tests runs-on: ubuntu-latest @@ -22,6 +28,8 @@ jobs: concurrency: staging-ipv4.testrun.org steps: - uses: actions/checkout@v4 + with: + submodules: true - name: prepare SSH run: | @@ -63,13 +71,13 @@ jobs: # download acme & dkim state from ns.testrun.org rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4/acme acme-restore || true rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4/dkimkeys dkimkeys-restore || true - # restore acme & dkim state to staging2.testrun.org + # restore acme & dkim state to staging-ipv4.testrun.org rsync -avz acme-restore/acme root@staging-ipv4.testrun.org:/var/lib/ || true rsync -avz dkimkeys-restore/dkimkeys root@staging-ipv4.testrun.org:/etc/ || true ssh -o StrictHostKeyChecking=accept-new -v root@staging-ipv4.testrun.org chown root:root -R /var/lib/acme || true - - name: run deploy-chatmail offline tests - run: pytest --pyargs cmdeploy + - name: run deploy-chatmail offline tests + run: pytest --pyargs cmdeploy - name: setup dependencies run: | @@ -102,3 +110,12 @@ jobs: - name: cmdeploy dns run: ssh root@staging-ipv4.testrun.org "cd relay && scripts/cmdeploy dns -v --ssh-host localhost" + deploy-docker: + needs: [deploy, trigger-docker-build] + if: github.event_name == 'push' + uses: ./.github/workflows/docker-deploy.yaml + with: + staging_host: staging-ipv4.testrun.org + mail_domain: staging-ipv4.testrun.org + zone_file: staging-ipv4.testrun.org-default.zone + secrets: inherit diff --git a/.github/workflows/test-and-deploy.yaml b/.github/workflows/test-and-deploy.yaml index 2f744cb8..1b446403 100644 --- a/.github/workflows/test-and-deploy.yaml +++ b/.github/workflows/test-and-deploy.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + - j4n/docker-pr pull_request: paths-ignore: - 'scripts/**' @@ -12,6 +13,11 @@ on: - 'LICENSE' jobs: + trigger-docker-build: + if: github.event_name == 'push' + uses: ./.github/workflows/trigger-docker-build.yaml + secrets: inherit + deploy: name: deploy on staging2.testrun.org, and run tests runs-on: ubuntu-latest @@ -95,3 +101,12 @@ jobs: - name: cmdeploy dns run: cmdeploy dns -v + deploy-docker: + needs: [deploy, trigger-docker-build] + if: github.event_name == 'push' + uses: ./.github/workflows/docker-deploy.yaml + with: + staging_host: staging2.testrun.org + mail_domain: staging2.testrun.org + zone_file: staging.testrun.org-default.zone + secrets: inherit diff --git a/.github/workflows/trigger-docker-build.yaml b/.github/workflows/trigger-docker-build.yaml new file mode 100644 index 00000000..231bb854 --- /dev/null +++ b/.github/workflows/trigger-docker-build.yaml @@ -0,0 +1,24 @@ +name: Trigger Docker image build + +on: + workflow_call: + +jobs: + trigger-docker-build: + name: Trigger Docker image build + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + github-token: ${{ secrets.CHATMAIL_DOCKER_DISPATCH_TOKEN }} + script: | + await github.rest.repos.createDispatchEvent({ + owner: 'chatmail', + repo: 'docker', + event_type: 'relay-updated', + client_payload: { + relay_ref: context.ref, + relay_sha: context.sha, + relay_sha_short: context.sha.slice(0, 7) + } + }) diff --git a/doc/source/getting_started.rst b/doc/source/getting_started.rst index 28781f28..77fd9060 100644 --- a/doc/source/getting_started.rst +++ b/doc/source/getting_started.rst @@ -98,6 +98,13 @@ steps. Please substitute it with your own domain. configure at your DNS provider (it can take some time until they are public). +Docker installation +------------------- + +There is experimental support for running chatmail via Docker Compose. +See the `chatmail/docker README `_ +for full setup instructions. + Other helpful commands ----------------------