Repository navigation
Expand file tree
/
Copy pathTaskfile.yml
More file actions
401 lines (356 loc) · 17.8 KB
/
Copy pathTaskfile.yml
File metadata and controls
401 lines (356 loc) · 17.8 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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
version: '3'
dotenv: ['.env']
vars:
# Build version derived from git (v2.* tags, via scripts/version.sh) so
# `task`-built and `task run` daemons carry the same semver as the RPM
# builds — feeds internal/buildinfo.Version → User-Agent, ADIF PROGRAMVERSION
# and the `version` field on every log record. (-X on a symbol that no longer
# exists exits 0 and stamps nothing: main.Version was removed 2026-08-01.)
SM_VERSION:
sh: bash -c '. scripts/version.sh && sm_git_version'
# Container runtime for the smcloud dev Postgres. Defaults to podman (Fedora-
# native, rootless); override with `PG_RUNTIME=docker task db:pg:up`.
PG_RUNTIME: '{{default "podman" .PG_RUNTIME}}'
tasks:
default:
desc: Run vet, build, and test — the CI-equivalent local smoke check
deps: [vet, build, test]
vet:
desc: Run go vet over the whole module
cmds:
- go vet ./...
build:
desc: Compile the whole module and build the daemon binary (CGO on → live FT8 capture)
cmds:
- CGO_ENABLED=1 go build ./...
- CGO_ENABLED=1 go build -tags dev -ldflags="-X github.com/ColonelBlimp/station-manager/internal/buildinfo.Version={{.SM_VERSION}}" -o build/bin/smd ./cmd/smd
# --- SM Cloud P1 (ADR 0040) — dev Postgres + sqlboiler codegen -------------
# Workflow: `task db:pg:up && task migrate:cloud:up && task models:cloud`.
# CLI tools (install once):
# sqlboiler + psql driver: go install github.com/aarondl/sqlboiler/v4@latest
# go install github.com/aarondl/sqlboiler/v4/drivers/sqlboiler-psql@latest
# golang-migrate: go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
db:pg:up:
desc: Start a local Postgres for smcloud codegen + integration tests ({{.PG_RUNTIME}}; user/pass/db = smcloud)
cmds:
# Fully-qualified image: podman's short-name resolution needs a TTY to
# prompt for a registry, which a `task` run has not — so `postgres:16`
# fails with "short-name resolution enforced but cannot prompt". docker
# accepts the qualified name unchanged.
- '{{.PG_RUNTIME}} run -d --name sm-pg -e POSTGRES_USER=smcloud -e POSTGRES_PASSWORD=smcloud -e POSTGRES_DB=smcloud -p 5432:5432 docker.io/library/postgres:16'
- 'until {{.PG_RUNTIME}} exec sm-pg pg_isready -U smcloud >/dev/null 2>&1; do sleep 0.5; done'
db:pg:down:
desc: Stop + remove the local smcloud Postgres
cmds:
- '{{.PG_RUNTIME}} rm -f sm-pg'
migrate:cloud:up:
desc: Apply smcloud Postgres migrations to the local dev DB (needs the golang-migrate CLI)
cmds:
- migrate -path internal/cloud/store/migrations -database "postgres://smcloud:smcloud@localhost:5432/smcloud?sslmode=disable" up
models:cloud:
desc: Regenerate smcloud sqlboiler models from the migrated dev Postgres (read-only artifacts)
dir: internal/cloud/store
cmds:
- sqlboiler psql
frontend:install:
desc: Install dependencies for the embedded app SPA
deps: [frontend:app:install]
frontend:app:install:
desc: Install app-SPA dependencies (the consolidated shell, ADR 0044); `npm ci` if a lockfile is present, else `npm install`
dir: frontend/app
cmds:
- sh -c 'if [ -f package-lock.json ]; then npm ci; else npm install; fi'
frontend:app:dev:
desc: Run the consolidated app SPA (ADR 0044) Vite dev server with HMR on :5176 (proxies /v1/* to daemon on :8080; runs alongside the other frontend:*:dev servers)
dir: frontend/app
cmds:
- npm run dev
frontend:app:build:
desc: Production build of the consolidated app SPA (ADR 0044); embedded by the daemon and served at /app/.
dir: frontend/app
cmds:
- npm run build
frontend:app:check:
desc: Type-check the consolidated app SPA (svelte-check)
dir: frontend/app
cmds:
- npm run check
frontend:build:all:
desc: Build the embedded app SPA (the sole operator client). The build:smd* tasks depend on this so the daemon embeds a fresh bundle.
deps: [frontend:app:build]
manual:build:
desc: Build the operator manual (Hugo → manual/public/) for the daemon's go:embed. Single self-contained zero-JS page (ADR 0036).
dir: manual
cmds:
- hugo --quiet
manual:serve:
desc: Preview the operator manual with live reload on http://localhost:1313 while writing content. Dev-only; not the embedded build.
dir: manual
cmds:
- hugo server --buildDrafts
build:smd:
desc: |
Build the daemon with a freshly built embedded SPA. CGO on (live FT8
capture; dynamically linked against core glibc) — the default dev build.
For the static CGO-free shape (headless/aggregator + the shipped-release
shape) use build:smd:static; for faster decode use build:smd:pocketfft.
deps: [frontend:build:all, manual:build]
cmds:
- CGO_ENABLED=1 go build -tags dev -ldflags="-X github.com/ColonelBlimp/station-manager/internal/buildinfo.Version={{.SM_VERSION}}" -o build/bin/smd ./cmd/smd
build:smd:static:
desc: |
Build the daemon (embedded SPA) CGO-free + static — pure-Go gonum FFT, no
audio capture, so the FT8 subsystem stays idle. This is the shipped-release
shape (matches release-rpm.sh's CGO_ENABLED=0 default) and the right build
for a headless / network-only aggregator node.
deps: [frontend:build:all, manual:build]
cmds:
- CGO_ENABLED=0 go build -ldflags="-X github.com/ColonelBlimp/station-manager/internal/buildinfo.Version={{.SM_VERSION}}" -o build/bin/smd ./cmd/smd
build:smd:pocketfft:
desc: |
Build the daemon with the PocketFFT (CGO) FFT backend instead of the
default pure-Go gonum one — ~2x faster FT8 decode, but dynamically
linked (needs a C toolchain). The backend is go-ft8's //go:build pocketfft
gate. build:smd is also CGO now (live capture) but uses the gonum FFT;
this task swaps in the faster PocketFFT FFT. build:smd:static is the
CGO-free shape.
deps: [frontend:build:all, manual:build]
cmds:
- CGO_ENABLED=1 go build -tags "dev pocketfft" -ldflags="-X github.com/ColonelBlimp/station-manager/internal/buildinfo.Version={{.SM_VERSION}}" -o build/bin/smd ./cmd/smd
build:smcloud:
desc: |
Build the SM Cloud backup service (cmd/smcloud) as a fully STATIC
linux binary — pure Go throughout (lib/pq + golang-migrate, no CGO),
so the one file scp's to any Linux box with zero runtime deps.
Default amd64; SMCLOUD_ARCH=arm64 task build:smcloud for a Pi-class
LAN staging box. Output: build/bin/smcloud. Runbook: docs/smcloud-deploy.md.
vars:
SMCLOUD_ARCH: '{{default "amd64" .SMCLOUD_ARCH}}'
cmds:
- CGO_ENABLED=0 GOOS=linux GOARCH={{.SMCLOUD_ARCH}} go build -trimpath -ldflags="-s -w -X main.Version={{.SM_VERSION}}" -o build/bin/smcloud ./cmd/smcloud
rpm:smcloud:
desc: |
Build the deployable smcloud RPM → build/release/smcloud.x86_64.rpm
(binary + system unit + /etc/smcloud/smcloud.env config skeleton).
Static pure-Go build — no container, no glibc floor; builds right on
the dev box. SMCLOUD_ARCH=arm64 for an aarch64 RPM. Install on the
target: sudo dnf install -y ./smcloud.x86_64.rpm, then edit
/etc/smcloud/smcloud.env and systemctl enable --now smcloud.
Runbook: docs/smcloud-deploy.md.
cmds:
- scripts/smcloud-rpm.sh
rpm:dev:
desc: |
Build a development RPM ready for local install. This is the PRIVATE
(keyed) build path (ST-7): it bakes the ClubLog key and so REQUIRES a
non-empty CLUBLOG_API_KEY (from .env); its output is the non-distributable
build/private/station-manager-dev.x86_64.rpm (carries a
PRIVATE-BUILD-DO-NOT-DISTRIBUTE marker). Version is git-derived (also
injected as internal/buildinfo.Version → daemon User-Agent and ADIF
PROGRAMVERSION). For a one-command build→install→restart, use
`task deploy:local:dev` instead. CGO-free/static by default; for the
PocketFFT (CGO) variant use rpm:dev:pocketfft. Distributable RPMs come
from `task rpm:release` (the public path, also keyed — ADR 0083).
cmds:
- scripts/dev-rpm.sh
rpm:release:
desc: |
Build the PUBLIC (distributable, key-free) release RPM — the CGO +
PocketFFT (live FT8) build, compiled in an AlmaLinux 8 container so it
runs on every RPM distro from RHEL 8 / glibc 2.28 up (Fedora, RHEL,
Rocky, Alma, recent openSUSE). Wraps scripts/release.sh, the release
entrypoint. Usage: task rpm:release RELEASE_VERSION=2.0.0-alpha.3
Output: build/release/station-manager-<version>-1.x86_64.rpm. Needs
podman, Go and Node on the host, and network on the first run (it builds
and caches the builder image). Carries the ClubLog application key from
.env (ADR 0083) — refuses to build without it; never commit the key.
requires:
vars: [RELEASE_VERSION]
cmds:
- scripts/release.sh {{.RELEASE_VERSION}}
rpm:dev:pocketfft:
desc: |
Like rpm:dev but builds the PocketFFT (CGO) backend by setting
SM_FFT=pocketfft. Dynamically linked, ~2x faster decode, needs gcc.
Standalone RPM only (no install/restart) — the one-command dogfood
deploy `task deploy:local:dev` already defaults to this backend.
cmds:
- SM_FFT=pocketfft scripts/dev-rpm.sh
deploy:local:dev:
desc: |
One-command local-dogfood install or update: builds the dev RPM,
stops the user-level smd daemon (if running), reinstalls the RPM via
`sudo rpm -Uvh --replacepkgs` (handles the same-NVR case the
"dev" version string forces), runs systemd daemon-reload, and
enables and starts smd (enable is idempotent; it makes a first
install start at login). Lingering stays your explicit choice. Final step checks systemctl is-active and
points you at journalctl on failure. Sudo prompts for the
package install only — systemctl commands run as your user.
Defaults to the PocketFFT (CGO) backend so live FT8 capture + decode
work out of the box (the pure-Go static build leaves FT8 idle —
capture needs CGO). For the static CGO-free build instead, override
with: SM_FFT=gonum task deploy:local:dev.
cmds:
- bash scripts/deploy-local-dev.sh
reset:local:dev:
desc: |
Fully UNINSTALL the local dogfood install — leave the system as if Station
Manager had never been installed. The inverse of deploy:local:dev:
disable + stop the smd user service (systemctl --user disable --now),
disable user lingering (loginctl disable-linger), remove the package
(sudo dnf remove station-manager — drops the binary + systemd unit), then
DELETE the working dir (~/.local/share/station-manager: config.json + QSO
database + logs). DESTRUCTIVE + needs sudo — prompts to confirm
(SM_RESET_YES=1 to skip). Re-install with `task deploy:local:dev`.
interactive: true
cmds:
- bash scripts/reset-local-dev.sh
test:
desc: |
Run tests with race detector and -short. The destructive smcloud
integration tests SKIP unless you opt in explicitly — they run schema
teardown, so a routine `task test` must never authorize wiping whatever
sits at the default localhost DSN (on a dogfood box that could be a REAL
smcloud database). To run them, point at a disposable database yourself:
`SMCLOUD_TEST_DSN=<disposable> task test`, or, against `task db:pg:up`,
`SMCLOUD_TEST_ALLOW_DEFAULT=1 task test`. CI runs them against its own
ephemeral Postgres. See store.ResolveTestDSN (codex round-3 P1 on
8fd6c7e5): the opt-in is deliberately NOT wired into this generic task.
cmds:
- go test -race -short ./...
test:qrz-live:
desc: |
Run the manual QRZ.com live-integration test (quick round-trip,
no pauses). Requires QRZ_TEST_API_KEY and QRZ_TEST_CALLSIGN in
.env (for the operator's test logbook). Makes real HTTP calls
to QRZ and cleans up the test record via an in-test DELETE.
cmds:
- go test -v -count=1 -tags manual -run TestLive_InsertThenUpdate ./internal/forwarding/qrz/...
test:qrz-live-interactive:
desc: |
Interactive QRZ.com live flow: INSERT → pause → UPDATE → pause →
DELETE, pausing after each operation so you can verify the
record on QRZ.com's website between steps. Requires
QRZ_TEST_API_KEY and QRZ_TEST_CALLSIGN in .env. Ctrl+C between
steps leaves the record in the logbook.
interactive: true
cmds:
- go test -v -count=1 -tags manual -run TestLive_InteractiveFlow ./internal/forwarding/qrz/...
test:qrzcq-lookup-live:
desc: |
Run one read-only QRZCQ XML lookup against the configured premium
account. Loads QRZCQ_PASSWORD from the root .env, keeps the provider
disabled in config.json, and looks up 7Q8AC by default.
env:
QRZCQ_LIVE_CONFIG: '{{.ROOT_DIR}}/build/config.json'
QRZCQ_LIVE_CALL: 7Q8AC
preconditions:
- sh: test -n "$QRZCQ_PASSWORD"
msg: QRZCQ_PASSWORD is not set in the root .env
cmds:
- go test -v -count=1 -timeout 30s -run '^TestLiveQRZCQPremiumAccount$' ./internal/lookup/qrzcq
run:
desc: Build and run the daemon using SM_WORKING_DIR from .env
deps: [build]
cmds:
# Stop the systemd smd first so the two daemons don't fight over :8080.
# Tolerant: no-op if it isn't running / the unit isn't installed.
- echo "stopping systemd smd (if running) to free :8080 - restart it later with systemctl --user start smd"
- systemctl --user stop smd 2>/dev/null || true
- ./build/bin/smd
run:smd:
desc: |
Run the daemon directly via `go run` for SPA development. Pair with
`task frontend:app:dev` in another terminal — Vite serves the app SPA on
:5176 and proxies /v1/* to this daemon. Uses SM_WORKING_DIR from .env.
CGO on, so live FT8 capture works without a deploy — the fast FT8
iteration loop (no RPM). Automatically stops the systemd `smd` first so
the two don't contend for :8080 / the audio + serial ports; restart the
dogfood daemon afterwards with `systemctl --user start smd`.
cmds:
# Free :8080 + the audio/serial ports: a running systemd smd would
# otherwise collide with this dev daemon (the "address already in use"
# restart flap). Tolerant of not-running / unit-not-installed.
- echo "stopping systemd smd (if running) to free :8080 - restart it later with systemctl --user start smd"
- systemctl --user stop smd 2>/dev/null || true
- CGO_ENABLED=1 go run -tags dev -ldflags="-X github.com/ColonelBlimp/station-manager/internal/buildinfo.Version={{.SM_VERSION}}" ./cmd/smd
tag:
desc: |
Create an annotated release tag, guarded: refuses unless the working
tree is clean (git status --porcelain) and the tag doesn't already
exist; validates vMAJOR.MINOR.PATCH[-prerelease]. Does NOT push.
Usage: task tag -- v2.0.0-alpha.2 ["message"]
cmds:
- bash scripts/tag.sh {{.CLI_ARGS}}
tidy:
desc: Tidy go.mod and go.sum
cmds:
- go mod tidy
docs:agent-context:
desc: Check agent-instruction, current-capsule, and automatic-context budgets
cmds:
- bash scripts/check-agent-context.sh
docs:generate:
desc: Generate the GitHub documentation map from the live-document catalog
cmds:
- go run ./cmd/docscatalog generate
docs:check:
desc: Validate the live-document catalog and its generated GitHub map
cmds:
- go run ./cmd/docscatalog check
docs:find:
desc: Find live documents by topic or repository path (QUERY required)
requires:
vars: [QUERY]
env:
DOCS_QUERY: '{{.QUERY}}'
cmds:
- go run ./cmd/docscatalog find
ci:lint:
desc: |
Static analysis of .github/workflows/*.yml via actionlint —
catches YAML typos, bad action versions, shell-script smell
inside `run:` blocks. Install once with:
go install github.com/rhysd/actionlint/cmd/actionlint@latest
cmds:
- actionlint
lint:go:
desc: |
Maintainability metrics over the Go tree (.golangci.yml) — cognitive and
cyclomatic complexity, duplication, maintainability index. Shape, not
correctness: gofmt/vet/race cover that and run in ci:local.
Also run as part of ci:local and in CI (pinned to the same version).
Install once with:
sudo dnf install golangci-lint
To RE-BASELINE, disable the three suppressing defaults or the numbers lie:
golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 \
--uniq-by-line=false ./cmd/... ./internal/...
cmds:
- golangci-lint run ./cmd/... ./internal/...
metrics:maintainability:
desc: |
Report Go/frontend complexity, maintainability index, duplication, and
baseline-debt trends; fail only on a new or unratcheted regression.
Outputs Markdown and normalized JSON under /tmp/station-manager-maintainability
unless MAINTAINABILITY_OUTPUT_DIR selects another directory.
cmds:
- bash scripts/maintainability-metrics.sh
ci:local:
desc: |
Local mirror of .github/workflows/ci.yml — runs every gate the
"releasability" CI job runs, for the app SPA (the sole embedded
operator client): lint / prettier format / type-check / test / build,
then the Go gate (gofmt, vet, maintainability gate/report, race
tests, daemon embed-build), plus the documentation catalog and
agent-context budgets. Answers
"would CI pass right now?"
without pushing. Set SKIP_NPM_CI=1 to reuse existing node_modules
during tight iteration (~30s saved across the three SPAs).
cmds:
- bash scripts/ci-local.sh
ci:
desc: |
Full CI check — actionlint over the workflow YAML, then the
local mirror of the workflow itself. Use before pushing when
you've touched anything that could affect releasability.
deps: [ci:lint, ci:local]