~bigbes/sr-ht-spec

ref: 824788ab8269bd6c58de5848bc4545450fac7aaf sr-ht-spec/.build.yml -rw-r--r-- 15.9 KiB
824788ab — Eugene Blikh mcpsrv: mark /mcp uncacheable, fail closed on origin, split tool errors from faults 2 days ago
                                                                                
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
# builds.sr.ht manifest for spec.sr.ht. One linear pipeline: install the cache
# helper, assemble the shared SCSS, stamp a version, restore caches, start a
# Postgres in the VM, test, package with abuild, publish, save caches, upload
# this build's own coverage and benchmarks to cov.sr.ht and bench.sr.ht.
#
# The reasoning behind every task lives in docs/ci.md, not here: builds.sr.ht
# stores the submitted manifest in a varchar(16384), so a manifest over 16 KiB
# cannot be submitted at all — and the failure is a branch with no CI, not a red
# build. Add paragraphs to docs/ci.md and a pointer here.
image: alpine/edge
packages:
  - abuild
  - curl
  - go
  - git
  - rclone
  - sassc
  - minify
  # For the database suites, not for the package — see docs/ci.md#packages.
  - postgresql
  - postgresql-client
secrets:
  # File secret `apk-ci-s3`, installed at ~/.apk-ci.env, containing
  # APK_CI_S3_ACCESS_KEY / APK_CI_S3_SECRET_KEY for the Garage `repo` bucket.
  - apk-ci-s3
  # S3 credentials for the cacher CI cache (Garage `docker-cache` bucket),
  # same pair the bencher and ci-cacher builds use.
  - 7dde4219-0783-4581-a67d-c94749de3600   # ~/.s3-cache-key-id
  - 0e5b3530-6f19-4f30-9b73-9339dd382e46   # ~/.s3-cache-key-secret
  # One tokens.sr.ht working token, the same one the siblings mount, carrying
  # artifacts:upload, cov:upload and bench:upload. See docs/ci.md#secrets.
  - c7968415-1a6d-4ca0-a188-150fb7f57b65   # ~/.srht-token
sources:
  - https://git.srht.bigb.es/~bigbes/sr-ht-spec
environment:
  REPO: sr-ht-spec
  APK_REPO: alpine/v3.22/bigbes/x86_64
  S3_BUCKET: repo
  S3_ENDPOINT: https://s3.bigb.es
  # The second destination of the same apk: one channel of artifacts.sr.ht
  # holds the packages of every sibling service, so a consumer adds one
  # repository line. See docs/ci.md#publish_artifacts.
  ARTIFACTS_ENDPOINT: https://artifacts.srht.bigb.es
  ARTIFACTS_CHANNEL: "~bigbes/main"
  ARTIFACTS_DIST: v3.22
  # Must track the srht deployment's SRHT_CORE_VER, or this service's theme
  # drifts from the rest of the instance. BOOTSTRAP_REV is the submodule commit
  # core.sr.ht pins at that tag; bump the two together.
  CORE_VER: "0.84.5"
  BOOTSTRAP_REV: 779ad9f174ea5ab7e755f6df0ec9e5912d67dd16
  # Dogfooding. Both repo names are the `sources:` line read as ~owner/repo.
  # docs/ci.md#coverage, docs/ci.md#bench.
  COVER_ORIGIN: https://cov.srht.bigb.es
  COVER_REPO: "~bigbes/sr-ht-spec"
  BENCH_ORIGIN: https://bench.srht.bigb.es
  BENCH_REPO: "~bigbes/sr-ht-spec"
# Literal paths relative to $HOME, and not a fallback. docs/ci.md#artifacts.
artifacts:
  - cover.out
  - bench.txt
submitter:
  git.sr.ht:
    allow-refs:
      - refs/heads/master
      # Tags build too, now that the version task reads them: pushing v0.9.0 is
      # what produces the 0.9.0 apk. See docs/ci.md#version.
      - "refs/tags/v*"
tasks:
  # S3-backed CI cache helper; installed first so scss can already use it.
  # install.sh's PATH export goes to ~/.buildenv, which only the NEXT task
  # sources — hence two tasks and not one. See docs/ci.md#cacher.
  - cacher_install: |
      curl -fsSL https://bigbes.pages.srht.bigb.es/ci-cacher/install.sh | sh
  - cacher_init: |
      cacher init \
        --endpoint    https://s3.bigb.es \
        --region      garage \
        --bucket      docker-cache \
        --prefix      sr-ht-spec/deps \
        --key-file    ~/.s3-cache-key-id \
        --secret-file ~/.s3-cache-key-secret
  - scss: |
      # Assemble the shared sourcehut partials no apk ships, the way
      # core.sr.ht's `make install-scss` would, cached by the two pins so an
      # outage at git.sr.ht or github.com can't fail us. --exec runs on a miss
      # and seeds the cache after; it sees exported vars only, hence the inline
      # key and the single quotes. See docs/ci.md#scss.
      cacher dir download "scss/${CORE_VER}-${BOOTSTRAP_REV}.tar.zst" ~/scss --exec '
        git clone --depth 1 --branch "$CORE_VER" \
          https://git.sr.ht/~sircmpwn/core.sr.ht /tmp/core
        mkdir -p ~/scss/bootstrap
        cp /tmp/core/scss/*.scss /tmp/core/scss/*.css ~/scss/
        git init -q /tmp/bootstrap
        git -C /tmp/bootstrap remote add origin https://github.com/twbs/bootstrap
        git -C /tmp/bootstrap fetch -q --depth 1 origin "$BOOTSTRAP_REV"
        git -C /tmp/bootstrap checkout -q FETCH_HEAD
        cp -r /tmp/bootstrap/scss ~/scss/bootstrap/scss
      '
      sudo mkdir -p /usr/share/sourcehut
      sudo cp -r ~/scss /usr/share/sourcehut/scss
  - keygen: |
      # Throwaway signing key, and -i is not optional: docs/ci.md#keygen.
      SUDO=sudo abuild-keygen -a -n -i -q
  - version: |
      # ONE `git describe` decides the apk pkgver: a tag, else tag_git<n>, else
      # the family's commit count. The raw describe output is not a legal
      # pkgver and _git sorts AFTER the release: docs/ci.md#version.
      #
      # EXPORTED rather than sed-ed into the APKBUILD (which reads $PKGVER)
      # because rewriting a tracked file would flip the VCS stamp Go records in
      # the binary to dirty — do not "tidy" it back into a sed. The tree is
      # printed because this is the last moment it is provably clean.
      cd "$REPO"
      desc=$(git describe --tags --always --dirty)
      base=${desc%-dirty}
      case "$base" in
        v*-g*) n=${base%-g*}; ver="${n%-*}"; ver="${ver#v}_git${n##*-}" ;;
        v*)    ver="${base#v}" ;;
        *)     ver="0.0.$(git rev-list --count HEAD)" ;;
      esac
      echo "export PKGVER=$ver" >> ~/.buildenv
      echo "building $ver from $desc"
      git status --porcelain
  - cache_restore: |
      # Restore the Go module and build caches, both keyed by go.sum: the
      # dependency tree dominates compile time, and it only changes when go.sum
      # does. A miss is just a cold build, never an error.
      KEY_MOD=$(cacher key "gomod/{hash}.tar.zst" --hash-from "$REPO/go.sum")
      KEY_GOC=$(cacher key "gocache/{hash}.tar.zst" --hash-from "$REPO/go.sum")
      echo "export KEY_MOD=$KEY_MOD KEY_GOC=$KEY_GOC" >> ~/.buildenv
      # abuild redirects the Go caches into its throwaway $tmpdir (and an
      # upstream typo slaves GOMODCACHE to GOCACHE), so env exports here can't
      # stick — the APKBUILD's build() re-pins both to these home locations.
      # --optional makes a miss a cold build, not an error.
      cacher dir download "$KEY_MOD" ~/go/pkg/mod        --optional
      cacher dir download "$KEY_GOC" ~/.cache/go-build   --optional
      # Repair block for the HALF-restored module cache — the normal failure
      # here, not a freak one, and it reads like a code bug. Do not remove and
      # do not soften to `|| true`:
      # docs/ci.md#the-half-restored-module-cache.
      cd "$REPO"
      chmod -R u+w ~/go/pkg/mod 2>/dev/null || true
      if ! go mod verify >/dev/null 2>&1; then
        echo "restored module cache did not verify — discarding it"
        rm -rf ~/go/pkg/mod
      fi
      # `go mod download` and NOT `go mod download all`: the `all` pattern
      # resolves the whole module graph, test dependencies of dependencies
      # included, and APPENDS their hashes to the tracked go.sum — 170 lines on
      # this tree, measured. A modified tracked file is a "-dirty" apk, which is
      # the failure this whole commit is about. docs/ci.md#cache_restore.
      go mod download
      go mod verify
      # And the tree is printed because the two lines above are the last thing
      # that touches it before abuild does. docs/ci.md#cache_restore.
      git status --porcelain
  - postgres: |
      # A real Postgres in the VM. Without it 67 tests of db/, service/ and
      # cmd/specsrht-migrate/ skip themselves and the build goes green having
      # exercised none of the persistence layer — the migration-agreement check
      # included. Every flag below is load-bearing: docs/ci.md#postgres.
      sudo install -d -o postgres -g postgres /run/postgresql /var/lib/postgresql/data
      sudo -u postgres initdb -D /var/lib/postgresql/data
      sudo -u postgres pg_ctl -D /var/lib/postgresql/data -l /tmp/pg.log -w start \
        -o "-k /run/postgresql -h 127.0.0.1 \
            -c fsync=off -c full_page_writes=off -c synchronous_commit=off"
      sudo -u postgres createuser -s "$(id -un)"
      sudo -u postgres createdb -O "$(id -un)" specsrht_test
      echo "export SPECSRHT_TEST_PG='postgresql://$(id -un)@127.0.0.1/specsrht_test?sslmode=disable'" \
        >> ~/.buildenv
  - test: |
      cd "$REPO"
      # An empty DSN would skip every Postgres-backed suite and leave the build
      # green over untested code — and `options="!check"` in the APKBUILD means
      # this task is the only place the suites run at all. It also catches a
      # reordering of the two tasks. docs/ci.md#test.
      if [ -z "$SPECSRHT_TEST_PG" ]; then
        echo "SPECSRHT_TEST_PG is unset: the postgres task did not export it," >&2
        echo "so every database suite would skip and this build would lie." >&2
        exit 1
      fi
      test -z "$(gofmt -l .)" || { gofmt -l .; echo "gofmt: files above need formatting" >&2; exit 1; }
      go vet ./...
      # `make cover`, not a bare `go test ./...`: the Makefile names the -timeout
      # and the coverage flags, and it is the suites `make test` runs, so the
      # profile is a by-product of the gate. docs/ci.md#test.
      make cover COVERPROFILE="$HOME/cover.out"
  - build: |
      cd "$REPO"
      # -d: makedepends come from `packages:`. The APKBUILD runs `make css`
      # before `make build` and asserts the result with `make check-css`.
      # See docs/ci.md#build.
      REPODEST=$HOME/packages abuild -d
      find "$HOME/packages" -name '*.apk'
  - publish: |
      # The gate is the honest answer to a build that was handed no secrets, not
      # a fallback: with ~/.apk-ci.env absent every earlier task has still run
      # and a signed apk is sitting in $HOME/packages. On a push the secret is
      # there and this publishes. See docs/ci.md#publish.
      if [ ! -r ~/.apk-ci.env ]; then
        echo "no ~/.apk-ci.env: this build has no apk repo credentials"
        echo "the package was built and signed, and is not published"
        exit 0
      fi
      set +x   # never echo the S3 credentials into the build log
      . ~/.apk-ci.env
      export RCLONE_CONFIG_GARAGE_TYPE=s3
      export RCLONE_CONFIG_GARAGE_PROVIDER=Other
      export RCLONE_CONFIG_GARAGE_ENDPOINT="$S3_ENDPOINT"
      export RCLONE_CONFIG_GARAGE_REGION=garage
      export RCLONE_CONFIG_GARAGE_FORCE_PATH_STYLE=true
      export RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID="$APK_CI_S3_ACCESS_KEY"
      export RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY="$APK_CI_S3_SECRET_KEY"
      set -x
      # Upload only; never delete. Old versions stay so a pinned deployment can
      # always be rebuilt — the same reason the upstream mirror is append-only.
      find "$HOME/packages" -name '*.apk' -print | while read -r f; do
        rclone copyto "$f" "garage:$S3_BUCKET/$APK_REPO/$(basename "$f")"
        echo "uploaded $(basename "$f")"
      done
      echo "published; apk-mirror on phoebe re-indexes within 15 minutes"
  - publish_artifacts: |
      # The same apk into the artifacts.sr.ht channel, beside the S3 copy above.
      # The two destinations are independent on purpose: S3 is the road phoebe
      # still walks, this one is the road being opened. Its failure is its own,
      # and it never rolls back the upload that already succeeded.
      # See docs/ci.md#publish_artifacts.
      if [ ! -r ~/.srht-token ]; then
        echo "no ~/.srht-token: nothing was published to artifacts.sr.ht"
        exit 0
      fi
      count=$(find "$HOME/packages" -name '*.apk' | wc -l)
      test "$count" -gt 0 || { echo "no .apk under $HOME/packages" >&2; exit 1; }
      url="$ARTIFACTS_ENDPOINT/api/v1/pkg/$ARTIFACTS_CHANNEL/apk/$ARTIFACTS_DIST"
      # A `find | while read` would run the body in a subshell under ash and
      # lose $failed with it, so the loop reads a word list instead.
      failed=
      for file in $(find "$HOME/packages" -name '*.apk'); do
        set +x   # the token must not reach the log
        code=$(curl -sS -o /tmp/artifacts.out -w '%{http_code}' -X PUT \
          -H "Authorization: Bearer $(cat ~/.srht-token)" \
          --data-binary "@$file" "$url")
        set -x
        case "$code" in
        200|201)
          echo "published $(basename "$file") -> $code"
          ;;
        409)
          # Same version, other bytes. abuild stamps mtimes into the archive,
          # so resubmitting one commit builds a byte-different apk under the
          # same pkgver; the published copy stands and the build stays green.
          echo "WARNING: $(basename "$file") already published with other bytes; kept the published copy"
          ;;
        *)
          echo "FAILED $(basename "$file") -> $code" >&2
          cat /tmp/artifacts.out >&2
          echo >&2
          failed=1
          ;;
        esac
      done
      test -z "$failed" || exit 1
      echo "index: $ARTIFACTS_ENDPOINT/$ARTIFACTS_CHANNEL/apk/$ARTIFACTS_DIST/x86_64/APKINDEX.tar.gz"
  - cache_save: |
      # AFTER publish so an S3 hiccup cannot strand a good apk, and fatal on
      # purpose. Without --force an upload skips a key already there, so no
      # `cacher exists ||` guard is needed. See docs/ci.md#cache_save.
      cacher dir upload "$KEY_MOD" ~/go/pkg/mod
      cacher dir upload "$KEY_GOC" ~/.cache/go-build
  - coverage: |
      # Dogfooding: the profile the test task wrote, POSTed to this instance's
      # own cov.sr.ht. Before bench, whose run is minutes. docs/ci.md#coverage.
      cd "$REPO"
      # Missing or empty is a 400 about a body rather than about the build.
      test -s "$HOME/cover.out" || { echo "no ~/cover.out" >&2; exit 1; }
      if [ ! -r ~/.srht-token ]; then
        echo "no ~/.srht-token: no cov.sr.ht credentials in this build"
        echo "the profile is this build's cover.out artifact and is not lost"
        exit 0
      fi
      # Both ref prefixes stripped (this builds tags too), key is the idempotency
      # key, no Content-Type (the service sniffs), set +x so the header stays out
      # of the log, --fail-with-body so a rejection is loud and readable.
      # docs/ci.md#the-two-requests.
      ref="${GIT_REF#refs/heads/}"; ref="${ref#refs/tags/}"
      url="$COVER_ORIGIN/api/v1/repos/$COVER_REPO/reports"
      url="$url?commit=$(git rev-parse HEAD)&ref=$ref&key=$JOB_ID&job_url=$JOB_URL"
      echo "uploading cover.out to $url"
      set +x
      curl -sS --fail-with-body -X POST \
        -H "Authorization: Bearer $(cat ~/.srht-token)" \
        --data-binary "@$HOME/cover.out" \
        "$url"
      echo
  - bench: |
      # Dogfooding: this service's own benchmarks, to this instance's own
      # bench.sr.ht. Last and its own task on purpose, and this VM measures a
      # shape rather than a number. docs/ci.md#bench.
      cd "$REPO"
      # -s so the recipe is not echoed into the body, and a redirect and a cat
      # and NOT `| tee` — tee's exit status would let a failed run pass.
      make -s bench > "$HOME/bench.txt"
      cat "$HOME/bench.txt"
      # `go test -bench` matching nothing prints `ok` and exits 0, and an empty
      # body is valid benchfmt, so the names are checked. docs/ci.md#the-two-greps
      grep -q '^BenchmarkCompare' "$HOME/bench.txt"
      grep -q '^BenchmarkLinkPass' "$HOME/bench.txt"
      if [ ! -r ~/.srht-token ]; then
        echo "no ~/.srht-token: no bench.sr.ht credentials in this build"
        echo "the run is above and is this build's bench.txt artifact"
        exit 0
      fi
      # The coverage request's shape, plus visibility= — which acts only on the
      # POST that creates $BENCH_REPO.
      ref="${GIT_REF#refs/heads/}"; ref="${ref#refs/tags/}"
      url="$BENCH_ORIGIN/api/v1/repos/$BENCH_REPO/runs"
      url="$url?commit=$(git rev-parse HEAD)&ref=$ref&key=$JOB_ID&job_url=$JOB_URL"
      url="$url&visibility=public"
      echo "uploading bench.txt to $url"
      set +x
      curl -sS --fail-with-body -X POST \
        -H "Authorization: Bearer $(cat ~/.srht-token)" \
        --data-binary "@$HOME/bench.txt" \
        "$url"
      echo