-
Notifications
You must be signed in to change notification settings - Fork 15
244 lines (232 loc) · 9.48 KB
/
delulu-deploy.yaml
File metadata and controls
244 lines (232 loc) · 9.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
name: delulu
# Unified CI + CD pipeline for the delulu Discord bot.
#
# Delulu is a two-app system:
# - apps/delulu_discord — Discord bot on a VPS (droplet)
# - apps/delulu_sandbox_modal — Modal sandbox function
#
# This one workflow covers both apps, with six jobs:
# - changes paths filter (bot / bot_runtime /
# sandbox / sandbox_runtime)
# - pr-title-lint Conventional Commits on PR titles
# - delulu-discord-ci CI — ruff + pytest for the bot
# - delulu-sandbox-modal-ci CI — ruff + pytest for the sandbox
# - delulu-sandbox-modal-deploy CD — modal deploy from the GH runner
# - delulu-discord-deploy CD — SSH droplet + rebuild container
#
# CI and PR-title linting run on pull_request and on push to main.
# CD jobs only run on push to main (guarded with
# ``github.event_name != 'pull_request'``). This gives PR authors
# pre-merge feedback without touching prod.
on:
push:
branches: [main]
paths:
- 'apps/**'
- 'Makefile'
- '.github/workflows/delulu-deploy.yaml'
pull_request:
# Includes ``edited`` so the pr-title-lint job re-runs when the
# title is corrected without a new commit push. No ``paths`` filter
# here — if we filter by paths, a title-only edit wouldn't fire
# the workflow at all and the linter wouldn't re-check.
types: [opened, edited, reopened, synchronize]
workflow_dispatch:
jobs:
# ── PR title lint (independent) ──────────────────────────────
#
# Enforces Conventional Commits on PR titles. Since main uses
# squash-merge with the PR title as the commit subject, this is what
# actually keeps main's history conventional — no commit-msg hook
# required. Runs on every pull_request event so title edits
# re-validate without a code push.
pr-title-lint:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# ``prd`` is not part of the Conventional Commits spec but is
# used in this repo for changes under ``prd/`` — adding or
# revising a Product Requirements Document. ``docs:`` is
# technically accurate for a plan-doc commit but ``prd:`` is
# more specific and already in use on main (e.g. PR #44).
types: |
feat
fix
docs
refactor
test
chore
ci
build
perf
style
prd
requireScope: false
subjectPattern: ^[A-Za-z].+[^.]$
subjectPatternError: |
PR title subject must start with a letter and not end with a period.
# ── Paths filter ─────────────────────────────────────────────
#
# Decide which CI and deploy jobs to run based on the files touched.
# Two flavors of filter per app:
# - ``bot`` / ``sandbox``: matches *any* change under the app,
# including tests. Gates the CI jobs — test-only changes must
# still run the test suite.
# - ``bot_runtime`` / ``sandbox_runtime``: excludes ``tests/**``.
# Gates the deploy jobs — we don't redeploy Modal or rebuild
# the bot container for a test-only change.
# On ``workflow_dispatch`` every gated job bypasses the filter so
# the "Re-run" button always redeploys end-to-end.
changes:
runs-on: ubuntu-latest
outputs:
bot: ${{ steps.filter.outputs.bot }}
bot_runtime: ${{ steps.filter.outputs.bot_runtime }}
sandbox: ${{ steps.filter.outputs.sandbox }}
sandbox_runtime: ${{ steps.filter.outputs.sandbox_runtime }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
bot:
- 'apps/delulu_discord/**'
- '.github/workflows/delulu-deploy.yaml'
bot_runtime:
- 'apps/delulu_discord/**'
- '!apps/delulu_discord/tests/**'
- '.github/workflows/delulu-deploy.yaml'
sandbox:
- 'apps/delulu_sandbox_modal/**'
- '.github/workflows/delulu-deploy.yaml'
sandbox_runtime:
- 'apps/delulu_sandbox_modal/**'
- '!apps/delulu_sandbox_modal/tests/**'
- '.github/workflows/delulu-deploy.yaml'
# ── CI ──────────────────────────────────────────────────────
# CI for apps/delulu_discord. Runs ruff check, ruff format --check,
# and pytest inside the app. Gated on ``bot`` (matches any change
# under the app, including tests) so test-only edits still run the
# suite.
delulu-discord-ci:
needs: changes
if: needs.changes.outputs.bot == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/delulu_discord
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: install dev deps
run: uv sync --extra dev
- name: ruff check
run: uv run ruff check .
- name: ruff format --check
run: uv run ruff format --check .
- name: pytest
run: uv run pytest
# CI for apps/delulu_sandbox_modal. Separate job (not chained steps
# in a single runner) so a failure in the discord suite doesn't
# hide a sandbox regression, and so both suites run in parallel on
# independent runners.
delulu-sandbox-modal-ci:
needs: changes
if: needs.changes.outputs.sandbox == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/delulu_sandbox_modal
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: install dev deps
run: uv sync --extra dev
- name: ruff check
run: uv run ruff check .
- name: ruff format --check
run: uv run ruff format --check .
- name: pytest
run: uv run pytest
# ── CD ──────────────────────────────────────────────────────
# Deploy the Modal sandbox app directly from the GitHub runner. Uses
# a dedicated CI Modal token (``MODAL_TOKEN_ID`` / ``MODAL_TOKEN_SECRET``
# repository secrets) so this path is independent of the droplet —
# the VPS no longer needs Modal credentials or a Python toolchain
# just to ship sandbox code.
#
# Gated on:
# - not a PR (CD never runs on pull_request events)
# - sandbox CI passing
# - an actual runtime change (``sandbox_runtime`` excludes
# tests/**) so test-only edits don't redeploy the function
delulu-sandbox-modal-deploy:
needs: [changes, delulu-sandbox-modal-ci]
if: |
always() &&
github.event_name != 'pull_request' &&
needs.delulu-sandbox-modal-ci.result == 'success' &&
(needs.changes.outputs.sandbox_runtime == 'true' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: deploy modal sandbox app
working-directory: apps/delulu_sandbox_modal
env:
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
MODAL_IMAGE_BUILDER_VERSION: '2025.06'
run: |
uv sync
uv run modal deploy src/delulu_sandbox_modal/app.py
# Rebuild and restart the bot container on the droplet.
#
# Gated on:
# - not a PR
# - discord CI passing (broken tests don't deploy)
# - the sandbox deploy being done OR skipped (so a combined
# sandbox+bot push never restarts the bot against a stale
# Modal function, but a bot-only push doesn't wait for a
# sandbox deploy that isn't happening)
# - ``bot_runtime`` being true (test-only bot edits don't
# rebuild the Docker image)
# The ``always()`` wrapper is what lets the "done or skipped" gate
# fire even when ``delulu-sandbox-modal-deploy`` wasn't needed.
delulu-discord-deploy:
needs: [changes, delulu-discord-ci, delulu-sandbox-modal-deploy]
if: |
always() &&
github.event_name != 'pull_request' &&
needs.delulu-discord-ci.result == 'success' &&
(needs.delulu-sandbox-modal-deploy.result == 'success' || needs.delulu-sandbox-modal-deploy.result == 'skipped') &&
(needs.changes.outputs.bot_runtime == 'true' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
steps:
- name: SSH into droplet, rebuild, and restart
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DROPLET_HOST }}
username: root
key: ${{ secrets.DROPLET_SSH_KEY }}
script: |
set -euo pipefail
export PATH="$HOME/.local/bin:$PATH"
cd /root/SMILE-factory
git pull --ff-only
make -C apps/delulu_discord deploy
docker image prune -f
echo "Bot deploy complete"