Skip to content

Commit 367c1b7

Browse files
committed
docker: consolidate ci files
1 parent 9a3649f commit 367c1b7

1 file changed

Lines changed: 344 additions & 0 deletions

File tree

.github/workflows/docker-ci.yaml

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
name: Docker CI
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'docker/**'
7+
- 'docker-compose.yaml'
8+
- '.dockerignore'
9+
- 'chatmaild/**'
10+
- 'cmdeploy/**'
11+
- '.github/workflows/docker-ci.yaml'
12+
push:
13+
branches:
14+
- main
15+
- j4n/docker-pr
16+
paths:
17+
- 'docker/**'
18+
- 'docker-compose.yaml'
19+
- '.dockerignore'
20+
- 'chatmaild/**'
21+
- 'cmdeploy/**'
22+
- '.github/workflows/docker-ci.yaml'
23+
tags:
24+
- 'v*'
25+
26+
env:
27+
REGISTRY: ghcr.io
28+
IMAGE_NAME: ${{ github.repository }}
29+
30+
jobs:
31+
build:
32+
name: Build Docker image
33+
runs-on: ubuntu-latest
34+
permissions:
35+
contents: read
36+
packages: write
37+
outputs:
38+
image: ${{ steps.image-ref.outputs.image }}
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Set up Docker Buildx
43+
uses: docker/setup-buildx-action@v3
44+
45+
- name: Login to GHCR
46+
if: github.event_name == 'push'
47+
uses: docker/login-action@v3
48+
with:
49+
registry: ${{ env.REGISTRY }}
50+
username: ${{ github.actor }}
51+
password: ${{ secrets.GITHUB_TOKEN }}
52+
53+
- name: Extract metadata (tags, labels)
54+
id: meta
55+
uses: docker/metadata-action@v5
56+
with:
57+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
58+
tags: |
59+
# Tagged releases: v1.2.3 → :1.2.3, :1.2, :latest
60+
type=semver,pattern={{version}}
61+
type=semver,pattern={{major}}.{{minor}}
62+
# Branch pushes: j4n/docker → :j4n-docker
63+
type=ref,event=branch
64+
# Always: :sha-<hash>
65+
type=sha
66+
67+
- name: Build and push
68+
uses: docker/build-push-action@v6
69+
with:
70+
context: .
71+
file: docker/chatmail_relay.dockerfile
72+
push: ${{ github.event_name == 'push' }}
73+
tags: ${{ steps.meta.outputs.tags }}
74+
labels: ${{ steps.meta.outputs.labels }}
75+
cache-from: type=gha
76+
cache-to: type=gha,mode=max
77+
build-args: |
78+
GIT_HASH=${{ github.sha }}
79+
80+
- name: Output image reference
81+
id: image-ref
82+
run: |
83+
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
84+
IMAGE="${{ env.REGISTRY }}/$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]'):sha-${SHORT_SHA}"
85+
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
86+
87+
deploy-staging2:
88+
name: Deploy to staging2.testrun.org
89+
needs: build
90+
if: github.event_name == 'push'
91+
runs-on: ubuntu-latest
92+
timeout-minutes: 30
93+
environment:
94+
name: staging2.testrun.org
95+
url: https://staging2.testrun.org/
96+
concurrency: staging2.testrun.org
97+
steps:
98+
- uses: actions/checkout@v4
99+
100+
- name: prepare SSH
101+
run: |
102+
mkdir ~/.ssh
103+
echo "${{ secrets.STAGING_SSH_KEY }}" >> ~/.ssh/id_ed25519
104+
chmod 600 ~/.ssh/id_ed25519
105+
ssh-keyscan staging2.testrun.org > ~/.ssh/known_hosts
106+
# save previous acme & dkim state
107+
rsync -avz root@staging2.testrun.org:/var/lib/acme . || true
108+
rsync -avz root@staging2.testrun.org:/etc/dkimkeys . || true
109+
# store previous acme & dkim state on ns.testrun.org, if it contains useful certs
110+
if [ -f dkimkeys/opendkim.private ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" dkimkeys root@ns.testrun.org:/tmp/ || true; fi
111+
if [ "$(ls -A acme/certs)" ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" acme root@ns.testrun.org:/tmp/ || true; fi
112+
# make sure CAA record isn't set
113+
scp -o StrictHostKeyChecking=accept-new .github/workflows/staging.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging2.testrun.org.zone
114+
ssh root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/staging2.testrun.org.zone
115+
ssh root@ns.testrun.org nsd-checkzone staging2.testrun.org /etc/nsd/staging2.testrun.org.zone
116+
ssh root@ns.testrun.org systemctl reload nsd
117+
118+
- name: rebuild staging2.testrun.org to have a clean VPS
119+
run: |
120+
curl -X POST \
121+
-H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \
122+
-H "Content-Type: application/json" \
123+
-d '{"image":"debian-12"}' \
124+
"https://api.hetzner.cloud/v1/servers/${{ secrets.STAGING_SERVER_ID }}/actions/rebuild"
125+
126+
- name: wait for VPS rebuild
127+
run: |
128+
rm ~/.ssh/known_hosts
129+
while ! ssh -o ConnectTimeout=180 -o StrictHostKeyChecking=accept-new root@staging2.testrun.org id -u ; do sleep 1 ; done
130+
131+
- run: scripts/initenv.sh
132+
- name: append venv/bin to PATH
133+
run: echo venv/bin >>$GITHUB_PATH
134+
135+
- name: install Docker on VPS
136+
run: |
137+
ssh root@staging2.testrun.org 'apt-get update && apt-get install -y ca-certificates curl'
138+
ssh root@staging2.testrun.org 'install -m 0755 -d /etc/apt/keyrings'
139+
ssh root@staging2.testrun.org 'curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && chmod a+r /etc/apt/keyrings/docker.asc'
140+
ssh root@staging2.testrun.org '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'
141+
ssh root@staging2.testrun.org 'apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin'
142+
143+
- name: restore ACME & DKIM state
144+
run: |
145+
# download from ns.testrun.org
146+
rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme acme-restore || true
147+
rsync -avz root@ns.testrun.org:/tmp/dkimkeys dkimkeys-restore || true
148+
# restore to VPS host paths (will be bind-mounted into container)
149+
ssh root@staging2.testrun.org mkdir -p /srv/chatmail/dkim /srv/chatmail/certs
150+
rsync -avz acme-restore/acme/ root@staging2.testrun.org:/srv/chatmail/certs/ || true
151+
rsync -avz dkimkeys-restore/dkimkeys/ root@staging2.testrun.org:/srv/chatmail/dkim/ || true
152+
153+
- name: generate chatmail.ini
154+
run: |
155+
cmdeploy init staging2.testrun.org
156+
sed -i 's/#\s*mtail_address/mtail_address/' chatmail.ini
157+
scp chatmail.ini root@staging2.testrun.org:/srv/chatmail/chatmail.ini
158+
159+
- name: upload repo and deploy with Docker
160+
run: |
161+
GHCR_IMAGE="${{ needs.build.outputs.image }}"
162+
rsync -avz --exclude='.git' --exclude='venv' --exclude='__pycache__' ./ root@staging2.testrun.org:/srv/chatmail/relay/
163+
# Override: bind-mount data dirs + custom chatmail.ini + pre-built image
164+
ssh root@staging2.testrun.org "cat > /srv/chatmail/relay/docker-compose.override.yaml << EOF
165+
services:
166+
chatmail:
167+
image: ${GHCR_IMAGE}
168+
volumes:
169+
- /srv/chatmail/dkim:/etc/dkimkeys
170+
- /srv/chatmail/certs:/var/lib/acme
171+
- /srv/chatmail/chatmail.ini:/etc/chatmail/chatmail.ini
172+
EOF"
173+
# Login to GHCR on VPS and pull pre-built image
174+
echo "${{ secrets.GITHUB_TOKEN }}" | ssh root@staging2.testrun.org 'docker login ghcr.io -u ${{ github.actor }} --password-stdin'
175+
ssh root@staging2.testrun.org "docker pull ${GHCR_IMAGE}"
176+
ssh root@staging2.testrun.org 'cd /srv/chatmail/relay && MAIL_DOMAIN=staging2.testrun.org docker compose up -d'
177+
178+
- name: wait for container to become healthy
179+
run: |
180+
for i in $(seq 1 60); do
181+
status=$(ssh root@staging2.testrun.org 'docker inspect --format={{.State.Health.Status}} chatmail 2>/dev/null' || echo "missing")
182+
echo " [$i/60] status=$status"
183+
if [ "$status" = "healthy" ]; then
184+
echo "Container is healthy."
185+
exit 0
186+
fi
187+
sleep 5
188+
done
189+
echo "Container did not become healthy in time."
190+
ssh root@staging2.testrun.org 'cd /srv/chatmail/relay && docker compose logs --tail=200'
191+
exit 1
192+
193+
- name: add hpk42 key to staging server
194+
run: ssh root@staging2.testrun.org 'curl -s https://github.com/hpk42.keys >> .ssh/authorized_keys'
195+
196+
- name: run deploy-chatmail offline tests
197+
run: pytest --pyargs cmdeploy
198+
199+
- name: set DNS entries
200+
run: |
201+
ssh root@staging2.testrun.org chown opendkim:opendkim -R /srv/chatmail/dkim
202+
# run cmdeploy dns inside the container
203+
ssh root@staging2.testrun.org 'docker exec chatmail cmdeploy dns --ssh-host @local --zonefile /tmp/staging.zone --verbose'
204+
ssh root@staging2.testrun.org 'docker cp chatmail:/tmp/staging.zone /tmp/staging.zone'
205+
scp root@staging2.testrun.org:/tmp/staging.zone staging-generated.zone
206+
cat staging-generated.zone >> .github/workflows/staging.testrun.org-default.zone
207+
cat .github/workflows/staging.testrun.org-default.zone
208+
scp .github/workflows/staging.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging2.testrun.org.zone
209+
ssh root@ns.testrun.org nsd-checkzone staging2.testrun.org /etc/nsd/staging2.testrun.org.zone
210+
ssh root@ns.testrun.org systemctl reload nsd
211+
212+
- name: cmdeploy test
213+
run: CHATMAIL_DOMAIN2=ci-chatmail.testrun.org cmdeploy test --slow
214+
215+
- name: cmdeploy dns
216+
run: |
217+
ssh root@staging2.testrun.org 'docker exec chatmail cmdeploy dns -v --ssh-host @local'
218+
219+
deploy-staging-ipv4:
220+
name: Deploy to staging-ipv4.testrun.org
221+
needs: build
222+
if: github.event_name == 'push'
223+
runs-on: ubuntu-latest
224+
timeout-minutes: 30
225+
environment:
226+
name: staging-ipv4.testrun.org
227+
url: https://staging-ipv4.testrun.org/
228+
concurrency: staging-ipv4.testrun.org
229+
steps:
230+
- uses: actions/checkout@v4
231+
232+
- name: prepare SSH
233+
run: |
234+
mkdir ~/.ssh
235+
echo "${{ secrets.STAGING_SSH_KEY }}" >> ~/.ssh/id_ed25519
236+
chmod 600 ~/.ssh/id_ed25519
237+
ssh-keyscan staging-ipv4.testrun.org > ~/.ssh/known_hosts
238+
# save previous acme & dkim state
239+
rsync -avz root@staging-ipv4.testrun.org:/var/lib/acme acme-ipv4 || true
240+
rsync -avz root@staging-ipv4.testrun.org:/etc/dkimkeys dkimkeys-ipv4 || true
241+
# store previous acme & dkim state on ns.testrun.org, if it contains useful certs
242+
if [ -f dkimkeys-ipv4/dkimkeys/opendkim.private ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" dkimkeys-ipv4 root@ns.testrun.org:/tmp/ || true; fi
243+
if [ "$(ls -A acme-ipv4/acme/certs)" ]; then rsync -avz -e "ssh -o StrictHostKeyChecking=accept-new" acme-ipv4 root@ns.testrun.org:/tmp/ || true; fi
244+
# make sure CAA record isn't set
245+
scp -o StrictHostKeyChecking=accept-new .github/workflows/staging-ipv4.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging-ipv4.testrun.org.zone
246+
ssh root@ns.testrun.org sed -i '/CAA/d' /etc/nsd/staging-ipv4.testrun.org.zone
247+
ssh root@ns.testrun.org nsd-checkzone staging-ipv4.testrun.org /etc/nsd/staging-ipv4.testrun.org.zone
248+
ssh root@ns.testrun.org systemctl reload nsd
249+
250+
- name: rebuild staging-ipv4.testrun.org to have a clean VPS
251+
run: |
252+
curl -X POST \
253+
-H "Authorization: Bearer ${{ secrets.HETZNER_API_TOKEN }}" \
254+
-H "Content-Type: application/json" \
255+
-d '{"image":"debian-12"}' \
256+
"https://api.hetzner.cloud/v1/servers/${{ secrets.STAGING_IPV4_SERVER_ID }}/actions/rebuild"
257+
258+
- run: scripts/initenv.sh
259+
- name: append venv/bin to PATH
260+
run: echo venv/bin >>$GITHUB_PATH
261+
262+
- name: wait for VPS rebuild
263+
run: |
264+
rm ~/.ssh/known_hosts
265+
while ! ssh -o ConnectTimeout=180 -o StrictHostKeyChecking=accept-new root@staging-ipv4.testrun.org id -u ; do sleep 1 ; done
266+
267+
- name: install Docker on VPS
268+
run: |
269+
ssh root@staging-ipv4.testrun.org 'apt-get update && apt-get install -y ca-certificates curl'
270+
ssh root@staging-ipv4.testrun.org 'install -m 0755 -d /etc/apt/keyrings'
271+
ssh root@staging-ipv4.testrun.org 'curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && chmod a+r /etc/apt/keyrings/docker.asc'
272+
ssh root@staging-ipv4.testrun.org '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'
273+
ssh root@staging-ipv4.testrun.org 'apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin'
274+
275+
- name: restore ACME & DKIM state
276+
run: |
277+
rsync -e "ssh -o StrictHostKeyChecking=accept-new" -avz root@ns.testrun.org:/tmp/acme-ipv4/acme acme-restore || true
278+
rsync -avz root@ns.testrun.org:/tmp/dkimkeys-ipv4/dkimkeys dkimkeys-restore || true
279+
ssh root@staging-ipv4.testrun.org mkdir -p /srv/chatmail/dkim /srv/chatmail/certs
280+
rsync -avz acme-restore/acme/ root@staging-ipv4.testrun.org:/srv/chatmail/certs/ || true
281+
rsync -avz dkimkeys-restore/dkimkeys/ root@staging-ipv4.testrun.org:/srv/chatmail/dkim/ || true
282+
283+
- name: generate chatmail.ini with disable_ipv6
284+
run: |
285+
cmdeploy init staging-ipv4.testrun.org
286+
sed -i 's/disable_ipv6 = False/disable_ipv6 = True/' chatmail.ini
287+
sed -i 's/#\s*mtail_address/mtail_address/' chatmail.ini
288+
scp chatmail.ini root@staging-ipv4.testrun.org:/srv/chatmail/chatmail.ini
289+
290+
- name: upload repo and deploy with Docker
291+
run: |
292+
GHCR_IMAGE="${{ needs.build.outputs.image }}"
293+
rsync -avz --exclude='.git' --exclude='venv' --exclude='__pycache__' ./ root@staging-ipv4.testrun.org:/srv/chatmail/relay/
294+
# Override: bind-mount data dirs + custom chatmail.ini + pre-built image
295+
ssh root@staging-ipv4.testrun.org "cat > /srv/chatmail/relay/docker-compose.override.yaml << EOF
296+
services:
297+
chatmail:
298+
image: ${GHCR_IMAGE}
299+
volumes:
300+
- /srv/chatmail/dkim:/etc/dkimkeys
301+
- /srv/chatmail/certs:/var/lib/acme
302+
- /srv/chatmail/chatmail.ini:/etc/chatmail/chatmail.ini
303+
EOF"
304+
# Login to GHCR on VPS and pull pre-built image
305+
echo "${{ secrets.GITHUB_TOKEN }}" | ssh root@staging-ipv4.testrun.org 'docker login ghcr.io -u ${{ github.actor }} --password-stdin'
306+
ssh root@staging-ipv4.testrun.org "docker pull ${GHCR_IMAGE}"
307+
ssh root@staging-ipv4.testrun.org 'cd /srv/chatmail/relay && MAIL_DOMAIN=staging-ipv4.testrun.org docker compose up -d'
308+
309+
- name: wait for container to become healthy
310+
run: |
311+
for i in $(seq 1 60); do
312+
status=$(ssh root@staging-ipv4.testrun.org 'docker inspect --format={{.State.Health.Status}} chatmail 2>/dev/null' || echo "missing")
313+
echo " [$i/60] status=$status"
314+
if [ "$status" = "healthy" ]; then
315+
echo "Container is healthy."
316+
exit 0
317+
fi
318+
sleep 5
319+
done
320+
echo "Container did not become healthy in time."
321+
ssh root@staging-ipv4.testrun.org 'cd /srv/chatmail/relay && docker compose logs --tail=200'
322+
exit 1
323+
324+
- name: run deploy-chatmail offline tests
325+
run: pytest --pyargs cmdeploy
326+
327+
- name: set DNS entries
328+
run: |
329+
ssh root@staging-ipv4.testrun.org chown opendkim:opendkim -R /srv/chatmail/dkim
330+
ssh root@staging-ipv4.testrun.org 'docker exec chatmail cmdeploy dns --ssh-host @local --zonefile /tmp/staging.zone --verbose'
331+
ssh root@staging-ipv4.testrun.org 'docker cp chatmail:/tmp/staging.zone /tmp/staging.zone'
332+
scp root@staging-ipv4.testrun.org:/tmp/staging.zone staging-generated.zone
333+
cat staging-generated.zone >> .github/workflows/staging-ipv4.testrun.org-default.zone
334+
cat .github/workflows/staging-ipv4.testrun.org-default.zone
335+
scp .github/workflows/staging-ipv4.testrun.org-default.zone root@ns.testrun.org:/etc/nsd/staging-ipv4.testrun.org.zone
336+
ssh root@ns.testrun.org nsd-checkzone staging-ipv4.testrun.org /etc/nsd/staging-ipv4.testrun.org.zone
337+
ssh root@ns.testrun.org systemctl reload nsd
338+
339+
- name: cmdeploy test
340+
run: CHATMAIL_DOMAIN2=ci-chatmail.testrun.org cmdeploy test --slow
341+
342+
- name: cmdeploy dns
343+
run: |
344+
ssh root@staging-ipv4.testrun.org 'docker exec chatmail cmdeploy dns -v --ssh-host @local'

0 commit comments

Comments
 (0)