@@ -2,91 +2,136 @@ name: Deploy
22
33on :
44 push :
5+ branches :
6+ - main
57 paths :
68 - " server/**"
79 - " database/**"
810 - " client/**"
911 - " deploy/timekeeper/**"
12+ - " .github/workflows/deploy.yml"
1013 - " vars.yml"
1114 - " Cargo.toml"
1215 - " Cargo.lock"
1316 - " rust-toolchain.toml"
17+ workflow_dispatch :
1418
1519jobs :
16- build :
20+ # API image. The Flutter build is deliberately not in it — see build-web.
21+ build-api :
1722 runs-on : ubuntu-latest
1823 permissions :
1924 contents : read
2025 packages : write
21- outputs :
22- tag : ${{ steps.image.outputs.tag }}
2326 steps :
24- - uses : actions/checkout@v4
27+ - uses : actions/checkout@v5
2528
2629 - uses : docker/login-action@v3
2730 with :
2831 registry : ghcr.io
2932 username : ${{ github.actor }}
3033 password : ${{ secrets.GITHUB_TOKEN }}
3134
32- - id : image
33- run : echo "tag=ghcr.io/roboticswest/timekeeper:${{ github.sha }}" >> "$GITHUB_OUTPUT"
34-
3535 - uses : docker/build-push-action@v6
3636 with :
3737 context : .
3838 file : deploy/timekeeper/Dockerfile
3939 push : true
40- tags : ${{ steps.image.outputs.tag }}
40+ tags : ghcr.io/roboticswest/timekeeper:${{ github.sha }}
41+ cache-from : type=gha
42+ cache-to : type=gha,mode=max
4143
42- # Web image: nginx serving the Flutter build from the main image + proxying
43- # API paths to the timekeeper container. Built last since it copies the web
44- # build out of the main image (via the TK_IMAGE build arg).
45- - id : web-image
46- run : echo "tag=ghcr.io/roboticswest/timekeeper-web:${{ github.sha }}" >> "$GITHUB_OUTPUT"
44+ # Static Flutter web build, uploaded to the server and served directly by Caddy.
45+ build- web:
46+ runs-on : ubuntu-latest
47+ steps :
48+ - uses : actions/checkout@v5
4749
48- - uses : docker/build-push- action@v6
50+ - uses : subosito/flutter- action@v2
4951 with :
50- context : .
51- file : deploy/timekeeper/web/Dockerfile
52- build-args : TK_IMAGE=${{ steps.image.outputs.tag }}
53- push : true
54- tags : ${{ steps.web-image.outputs.tag }}
52+ channel : stable
53+ cache : true
54+
55+ - working-directory : client
56+ run : flutter pub get
57+
58+ - working-directory : client
59+ run : dart run flutter_launcher_icons
60+
61+ - working-directory : client
62+ run : flutter build web --release --wasm --no-web-resources-cdn
63+
64+ - uses : actions/upload-artifact@v4
65+ with :
66+ name : web-build
67+ path : client/build/web
68+ retention-days : 1
5569
5670 deploy :
57- needs : build
58- if : github.ref == 'refs/heads/main'
71+ needs : [build-api, build-web]
5972 runs-on : ubuntu-latest
6073 environment : production
6174 steps :
62- - uses : actions/checkout@v4
75+ - uses : actions/checkout@v5
76+
77+ - uses : actions/download-artifact@v4
78+ with :
79+ name : web-build
80+ path : web
6381
6482 - uses : tailscale/github-action@v3
6583 with :
6684 oauth-client-id : ${{ secrets.TS_OAUTH_CLIENT_ID }}
6785 oauth-secret : ${{ secrets.TS_OAUTH_CLIENT_SECRET }}
6886 tags : tag:ci
6987
70- # keeps the server's compose file identical to what's in git
71- - uses : appleboy/scp-action@v1
88+ # Emptied first so files removed between builds don't linger and get served.
89+ - name : Prepare target directories
90+ uses : appleboy/ssh-action@v1
7291 with :
7392 host : ${{ secrets.SSH_HOST }}
7493 username : ${{ secrets.SSH_USERNAME }}
7594 key : ${{ secrets.SSH_PRIVATE_KEY }}
7695 port : ${{ secrets.SSH_PORT }}
77- source : " deploy/timekeeper/production/docker-compose.yml"
96+ script : |
97+ mkdir -p /srv/timekeeper/web
98+ find /srv/timekeeper/web -mindepth 1 -delete
99+
100+ # Keeps the server's compose file identical to what's in git.
101+ - name : Upload compose file
102+ uses : appleboy/scp-action@v1
103+ with :
104+ host : ${{ secrets.SSH_HOST }}
105+ username : ${{ secrets.SSH_USERNAME }}
106+ key : ${{ secrets.SSH_PRIVATE_KEY }}
107+ port : ${{ secrets.SSH_PORT }}
108+ source : " deploy/timekeeper/docker-compose.yml"
78109 target : " /srv/timekeeper"
79- strip_components : 3
110+ strip_components : 2
111+ overwrite : true
112+
113+ - name : Upload web build
114+ uses : appleboy/scp-action@v1
115+ with :
116+ host : ${{ secrets.SSH_HOST }}
117+ username : ${{ secrets.SSH_USERNAME }}
118+ key : ${{ secrets.SSH_PRIVATE_KEY }}
119+ port : ${{ secrets.SSH_PORT }}
120+ source : " web/*"
121+ target : " /srv/timekeeper/web"
122+ strip_components : 1
80123 overwrite : true
81124
82- - uses : appleboy/ssh-action@v1
125+ - name : Roll out API container
126+ uses : appleboy/ssh-action@v1
83127 with :
84128 host : ${{ secrets.SSH_HOST }}
85129 username : ${{ secrets.SSH_USERNAME }}
86130 key : ${{ secrets.SSH_PRIVATE_KEY }}
87131 port : ${{ secrets.SSH_PORT }}
88- envs : IMAGE_TAG,DATABASE_URL,TK_ADMIN_PASSWORD,GH_TOKEN,GH_ACTOR,TLS
132+ envs : IMAGE_TAG,DATABASE_URL,TK_ADMIN_PASSWORD,GH_TOKEN,GH_ACTOR
89133 script : |
134+ set -e
90135 cat > /srv/timekeeper/.env <<ENVEOF
91136 IMAGE_TAG=${IMAGE_TAG}
92137 DATABASE_URL=${DATABASE_URL}
@@ -96,12 +141,11 @@ jobs:
96141 echo "$GH_TOKEN" | docker login ghcr.io -u "$GH_ACTOR" --password-stdin
97142 cd /srv/timekeeper
98143 docker compose pull
99- docker compose up -d
144+ docker compose up -d --remove-orphans
100145 docker logout ghcr.io
101146 env :
102147 IMAGE_TAG : ${{ github.sha }}
103148 DATABASE_URL : ${{ secrets.DATABASE_URL }}
104149 TK_ADMIN_PASSWORD : ${{ secrets.TK_ADMIN_PASSWORD }}
105150 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
106151 GH_ACTOR : ${{ github.actor }}
107- TLS : true
0 commit comments