-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathTaskfile.yml
More file actions
298 lines (273 loc) · 13 KB
/
Copy pathTaskfile.yml
File metadata and controls
298 lines (273 loc) · 13 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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
version: '3'
vars:
ENGINE:
sh: echo "${ENGINE:-$(command -v podman >/dev/null 2>&1 && echo podman || echo docker)}"
COMPOSE: '{{.ENGINE}} compose --env-file .env.development -f compose.dev.yml'
PSQL_IMPORT: "{{.COMPOSE}} exec -T db bash -lc 'psql -v ON_ERROR_STOP=1 -U ${POSTGRES_USER:-hackorum} -d ${POSTGRES_DB:-hackorum_development}'"
tasks:
default:
desc: List available tasks
cmds:
- task --list
silent: true
dev:
desc: Start dev stack (foreground)
interactive: true
cmds:
- '{{.COMPOSE}} up --build'
dev-detach:
desc: Start dev stack in background
cmds:
- '{{.COMPOSE}} up -d --build'
dev-prod-detach:
desc: Start dev stack but run Rails in production mode (uses dev compose & env)
env:
RAILS_ENV: production
NODE_ENV: production
RAILS_SERVE_STATIC_FILES: 1
RAILS_LOG_TO_STDOUT: 1
FORCE_SSL: false
cmds:
- '{{.COMPOSE}} up -d --build'
down:
desc: Stop dev stack
cmds:
- '{{.COMPOSE}} stop'
- rm -f tmp/pids/server.pid
shell:
desc: Open a shell in the web container
interactive: true
cmds:
- '{{.COMPOSE}} exec web bash'
console:
desc: Open Rails console in the web container
interactive: true
cmds:
- '{{.COMPOSE}} exec web bin/rails console'
test:
desc: Run RSpec in a one-off container (uses test database). Pass args after --, e.g. task test -- spec/models
cmds:
# Use `run --rm` (a dedicated throwaway container) rather than `exec` into the
# running dev server: that avoids loading rspec on top of the live bin/dev
# process tree, where a foreman/server crash or an OOM victim selection would
# take the exec'd rspec down with it (SIGKILL/exit 137 in CI).
- '{{.COMPOSE}} run --rm -e RAILS_ENV=test -e DATABASE_URL=postgresql://hackorum:hackorum@db:5432/hackorum_test web bin/rails db:prepare'
- '{{.COMPOSE}} run --rm -e RAILS_ENV=test -e DATABASE_URL=postgresql://hackorum:hackorum@db:5432/hackorum_test web bundle exec rspec {{.CLI_ARGS}}'
db-migrate:
desc: Run db:migrate
cmds:
- '{{.COMPOSE}} exec web bin/rails db:migrate'
db-reset:
desc: Drop and setup (create/migrate/seed) - stops web if running, restarts after
cmds:
- |
WEB_WAS_RUNNING=$({{.COMPOSE}} ps --status running --format '{{ `{{.Service}}` }}' | grep -q '^web$' && echo 1 || echo 0)
if [ "$WEB_WAS_RUNNING" = "1" ]; then
echo "Stopping web container..."
{{.COMPOSE}} stop web
fi
echo "Running db:drop and db:setup..."
{{.COMPOSE}} run --rm web bin/rails db:drop db:setup
if [ "$WEB_WAS_RUNNING" = "1" ]; then
echo "Restarting web container..."
{{.COMPOSE}} start web
fi
db-import:
desc: 'Import new-format dumps (vars: SCHEMA=/path/schema-YYYY-MM.sql.gz PUBLIC_DATA=/path/public-data-YYYY-MM.sql.gz [PRIVATE_DATA=/path/private-data-YYYY-MM.sql.gz]) - stops web if running, restarts after'
requires:
vars: [SCHEMA, PUBLIC_DATA]
preconditions:
- sh: test -f "{{.SCHEMA}}"
msg: 'SCHEMA file not found: {{.SCHEMA}}'
- sh: test -f "{{.PUBLIC_DATA}}"
msg: 'PUBLIC_DATA file not found: {{.PUBLIC_DATA}}'
- sh: '[ -z "{{.PRIVATE_DATA}}" ] || test -f "{{.PRIVATE_DATA}}"'
msg: 'PRIVATE_DATA file not found: {{.PRIVATE_DATA}}'
cmds:
- |
import_file() {
if echo "$1" | grep -qE '\.gz$'; then
gzip -cd "$1" | {{.PSQL_IMPORT}}
else
cat "$1" | {{.PSQL_IMPORT}}
fi
if [ $? -ne 0 ]; then echo "$2 import failed"; return 1; fi
}
WEB_WAS_RUNNING=$({{.COMPOSE}} ps --status running --format '{{ `{{.Service}}` }}' | grep -q '^web$' && echo 1 || echo 0)
if [ "$WEB_WAS_RUNNING" = "1" ]; then
echo "Stopping web container..."
{{.COMPOSE}} stop web
fi
echo "Ensuring db container is running..."
{{.COMPOSE}} up -d db
echo "Waiting for db to accept connections..."
for i in $(seq 1 60); do
if {{.COMPOSE}} exec -T db bash -lc 'pg_isready -U ${POSTGRES_USER:-hackorum} -d postgres' >/dev/null 2>&1; then break; fi
sleep 1
done
echo "Dropping and recreating database..."
{{.COMPOSE}} exec -T db bash -lc 'psql -v ON_ERROR_STOP=1 -U ${POSTGRES_USER:-hackorum} -d postgres -c "DROP DATABASE IF EXISTS ${POSTGRES_DB:-hackorum_development};" -c "CREATE DATABASE ${POSTGRES_DB:-hackorum_development};"' || { echo "drop/create failed"; exit 1; }
echo "Importing schema {{.SCHEMA}}..."
import_file "{{.SCHEMA}}" schema || exit 1
echo "Importing public data {{.PUBLIC_DATA}}..."
import_file "{{.PUBLIC_DATA}}" public-data || exit 1
if [ -n "{{.PRIVATE_DATA}}" ]; then
echo "Importing private data {{.PRIVATE_DATA}}..."
import_file "{{.PRIVATE_DATA}}" private-data || exit 1
else
echo "No PRIVATE_DATA provided; skipping private data load."
fi
if [ "$WEB_WAS_RUNNING" = "1" ]; then
echo "Restarting web container..."
{{.COMPOSE}} start web
fi
db-import-legacy:
desc: 'Import legacy split dumps (vars: DUMP=/path/public-YYYY-MM.sql.gz PDUMP=/path/private-schema-YYYY-MM.sql.gz). Filters cross-dump FKs out of public and reapplies them after private loads.'
requires:
vars: [DUMP, PDUMP]
preconditions:
- sh: test -f "{{.DUMP}}"
msg: 'DUMP file not found: {{.DUMP}}'
- sh: test -f "{{.PDUMP}}"
msg: 'PDUMP file not found: {{.PDUMP}}'
- sh: test -f deploy/backup/private_tables.txt
msg: 'deploy/backup/private_tables.txt not found'
- sh: test -f deploy/backup/split_cross_fks.awk
msg: 'deploy/backup/split_cross_fks.awk not found'
cmds:
- |
import_file() {
if echo "$1" | grep -qE '\.gz$'; then
gzip -cd "$1" | {{.PSQL_IMPORT}}
else
cat "$1" | {{.PSQL_IMPORT}}
fi
if [ $? -ne 0 ]; then echo "$2 import failed"; return 1; fi
}
WEB_WAS_RUNNING=$({{.COMPOSE}} ps --status running --format '{{ `{{.Service}}` }}' | grep -q '^web$' && echo 1 || echo 0)
if [ "$WEB_WAS_RUNNING" = "1" ]; then
echo "Stopping web container..."
{{.COMPOSE}} stop web
fi
echo "Ensuring db container is running..."
{{.COMPOSE}} up -d db
echo "Waiting for db to accept connections..."
for i in $(seq 1 60); do
if {{.COMPOSE}} exec -T db bash -lc 'pg_isready -U ${POSTGRES_USER:-hackorum} -d postgres' >/dev/null 2>&1; then break; fi
sleep 1
done
echo "Dropping and recreating database..."
{{.COMPOSE}} exec -T db bash -lc 'psql -v ON_ERROR_STOP=1 -U ${POSTGRES_USER:-hackorum} -d postgres -c "DROP DATABASE IF EXISTS ${POSTGRES_DB:-hackorum_development};" -c "CREATE DATABASE ${POSTGRES_DB:-hackorum_development};"' || { echo "drop/create failed"; exit 1; }
PRIVATE_REGEX=$(grep -E '^[a-z0-9_]+$' deploy/backup/private_tables.txt | tr '\n' '|' | sed 's/|$//')
FK_TMP=$(mktemp)
trap 'rm -f "$FK_TMP"' EXIT
echo "Importing public dump (filtered) from {{.DUMP}}..."
if echo "{{.DUMP}}" | grep -qE '\.gz$'; then DECOMP="gzip -cd {{.DUMP}}"; else DECOMP="cat {{.DUMP}}"; fi
$DECOMP | awk -v t="$PRIVATE_REGEX" -v fkfile="$FK_TMP" -f deploy/backup/split_cross_fks.awk \
| {{.PSQL_IMPORT}} || { echo "public (filtered) import failed"; exit 1; }
echo "Extracted $(grep -c '^ALTER TABLE ONLY' $FK_TMP) cross-dump FK constraints to reapply after private load."
echo "Importing private schema {{.PDUMP}}..."
import_file "{{.PDUMP}}" private-schema || exit 1
echo "Reapplying cross-dump FK constraints..."
cat "$FK_TMP" | {{.PSQL_IMPORT}} || { echo "cross-FK reapply failed"; exit 1; }
if [ "$WEB_WAS_RUNNING" = "1" ]; then
echo "Restarting web container..."
{{.COMPOSE}} start web
fi
mbox-import:
desc: 'Import mbox files (vars: LIST=id MBOX_DIR=/path MBOX_FILES="*.mbox"; extra args after --, e.g. -- --update-body)'
requires:
vars: [LIST, MBOX_DIR, MBOX_FILES]
cmds:
- |
files=""
for f in {{.MBOX_FILES}}; do files="$files /import/$f"; done
{{.COMPOSE}} run --rm \
-v {{.MBOX_DIR}}:/import:ro,z \
web bash -c "ruby script/mbox_import.rb --list {{.LIST}} {{.CLI_ARGS}}$files"
commit-import:
desc: 'Import postgres commits from a local checkout (vars: PG_REPO=./postgres; extra args after --, e.g. -- --limit 100)'
preconditions:
- sh: git -C "{{.PG_REPO | default "./postgres"}}" rev-parse --git-dir >/dev/null 2>&1
msg: 'not a git repository: {{.PG_REPO | default "./postgres"}} - clone postgresql there, or pass PG_REPO=/path/to/postgres'
cmds:
# The checkout is mounted read-only at /pgrepo (the path the importer
# defaults to), so it is never fetched into or otherwise touched; hence
# --no-fetch. safe.directory is needed because the container runs as root
# while the bind-mounted repo is owned by the host user.
- |
REPO_ABS=$(cd "{{.PG_REPO | default "./postgres"}}" && pwd) || exit 1
{{.COMPOSE}} run --rm \
-v "$REPO_ABS":/pgrepo:ro,z \
web bash -c 'git config --global --add safe.directory /pgrepo && ruby script/commit_import.rb --no-fetch {{.CLI_ARGS}}'
stats:
desc: 'Rebuild stats (vars: GRANULARITY=all|daily|weekly|monthly)'
cmds:
- '{{.COMPOSE}} exec web bundle exec ruby script/build_stats.rb {{.GRANULARITY | default "all"}}'
orchestrator:
desc: 'Run the CI orchestrator (needs HACKORUM_GITHUB_TOKEN, e.g. export HACKORUM_GITHUB_TOKEN=$(gh auth token), and HACKORUM_SSH_KEY - path to the push deploy key, exported or set in .env.development). Flags after --, e.g. task orchestrator -- --once --dry-run, or -- --max-pushes 5. First cycle of a non-dry run also fires the hourly maintenance pass: full upstream fetch plus a commit import into the dev DB; -- --once --dry-run skips it.'
interactive: true
dotenv: ['.env.development']
preconditions:
- sh: '[ -n "$HACKORUM_GITHUB_TOKEN" ]'
msg: 'HACKORUM_GITHUB_TOKEN is not set - export HACKORUM_GITHUB_TOKEN=$(gh auth token)'
- sh: test -d ./postgres/.git
msg: 'postgres checkout not found at ./postgres'
- sh: '[ -n "$HACKORUM_SSH_KEY" ]'
msg: 'HACKORUM_SSH_KEY is not set - path to the deploy key for hackorum-dev/postgres (export it or set it in .env.development)'
- sh: test -f "$HACKORUM_SSH_KEY"
msg: 'HACKORUM_SSH_KEY does not point to a file'
cmds:
# run --rm rather than exec: the daemon must not share the web
# container's fate (restarts, OOM). The checkout lives inside the /app
# mount at /app/postgres, which is bin/orchestrator's default. Only the
# deploy key is mounted (to a staging path) and copied into place, so
# ssh sees root-owned 600 perms whatever the host uid/mode is;
# accept-new stands in for the host known_hosts we no longer mount.
# safe.directory is needed because the container runs as root over a
# host-owned checkout, and root's git config is ephemeral in a --rm
# container. A second concurrent invocation aborts on the advisory lock.
- '{{.COMPOSE}} run --rm
-e HACKORUM_GITHUB_TOKEN
-e RAILS_LOG_LEVEL=${RAILS_LOG_LEVEL:-warn}
-e GIT_SSH_COMMAND="ssh -i /root/.ssh/deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
-v "$HACKORUM_SSH_KEY":/run/hackorum_deploy_key:ro,z
web bash -c "mkdir -p /root/.ssh && install -m 600 /run/hackorum_deploy_key /root/.ssh/deploy_key && git config --global --add safe.directory \"*\" && bin/orchestrator {{.CLI_ARGS}}"'
psql:
desc: Open psql against the dev DB
interactive: true
env:
COMPOSE_PROFILES: tools
cmds:
- '{{.COMPOSE}} run --rm psql'
imap:
desc: Start stack with IMAP worker profile
interactive: true
cmds:
- '{{.COMPOSE}} --profile imap up --build'
logs:
desc: Follow web logs
interactive: true
cmds:
- '{{.COMPOSE}} logs -f web'
sim-email-once:
desc: 'Send a single simulated email (env: SENT_OFFSET_SECONDS, EXISTING_ALIAS_PROB, EXISTING_TOPIC_PROB)'
cmds:
- '{{.COMPOSE}} exec web ruby script/simulate_email_once.rb'
sim-email-stream:
desc: 'Start a continuous simulated email stream (env: MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, EXISTING_ALIAS_PROB, EXISTING_TOPIC_PROB)'
interactive: true
cmds:
- '{{.COMPOSE}} exec web ruby script/simulate_email_stream.rb'
backfill-patch-submissions:
desc: Recompute messages.is_patch_submission (dry run by default; pass -- --fix to apply)
cmds:
- '{{.COMPOSE}} exec web ruby script/backfill_patch_submissions.rb {{.CLI_ARGS}}'
rubocop:
desc: Run rubocop
cmds:
- '{{.COMPOSE}} exec web bundle exec rubocop'
brakeman:
desc: Run brakeman
cmds:
- '{{.COMPOSE}} exec web bin/brakeman'