diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index a8c3a1f4..bb6d22d1 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -7,45 +7,39 @@ name: release # push tag vYYYY.MM.DD.HHMM → :latest (fresh APK bundled) # workflow_dispatch → manual trigger (same rules based on the ref) # -# That is the whole tag map, and it is family rule 145 + 147 as written. +# That is the whole tag map. # # : on main is the ROLLBACK UNIT — every production commit addressable # without a release ceremony. It is minted only on main, where rollback is -# actually worth having: merges are gated (rule 2) so they number in the dozens -# per year, while on dev they would be one per push, forever, for a channel -# whose entire contract is that it moves. +# actually worth having: merges to main are deliberate, so they number in the +# dozens per year, while on dev they would be one per push, forever, for a +# channel whose entire contract is that it moves. # -# There are NO : image tags. This repo published :vYYYY.MM.DD.HHMM -# until 2026-09-10 and it was the inverse of the rule on both counts — minting -# a version tag nobody pinned while the rollback unit the rule names did not -# exist here at all. Git and the build's own self-reported version answer -# "which build is this"; a third name for the same thing is upkeep for a model -# we do not run. Operator, 2026-09-10: "only things like the APK need that kind -# of versioning for their update process." +# There are NO : image tags. Git and the build's own self-reported +# version answer "which build is this"; a third name for the same thing is +# upkeep for a model we do not run. Only the APK needs version numbers, for +# its update process. # -# There is no :main either. :latest tracks main's tip with no gate between them -# (rule 147), so a second name for the same image sends readers looking for a +# There is no :main either. :latest tracks main's tip with no gate between +# them, so a second name for the same image sends readers looking for a # distinction that does not exist. # -# The dev channel exists so testing a build does not require shipping one. -# Before it, the only way to get an APK onto a phone was to cut a release, -# which made `main` the staging area by default. `:dev` carries its own -# freshly-built APK, signed with the SAME key as release builds — a different -# key cannot install over the stable app, so anyone crossing channels would -# have to uninstall and lose their data. +# The dev channel exists so testing a build does not require shipping one: +# without it the only way to get an APK onto a phone is to cut a release, +# which makes `main` the staging area. `:dev` carries its own freshly-built +# APK, signed with the SAME key as release builds — a different key cannot +# install over the stable app, so anyone crossing channels would have to +# uninstall and lose their data. # # :dev is published ALONE, with no per-commit tag. A rolling channel is # rolling by definition; a commit-addressable image for it would be a # rollback target nobody ever pulls, kept forever. Recovery on dev is to fix # forward. # -# Note what this repo does NOT need: a cross-repo dispatch to refresh the -# channel when its bundled APK is rebuilt. That mechanism exists elsewhere in -# the family because the app and the server live in separate repos. Minstrel -# is a monorepo — one push builds the APK and the image in the same run from -# the same commit, so the channel cannot go stale against its own artifact. -# The requirement is satisfied structurally; copying the mechanism would add -# a moving part to fix a problem that does not exist here. +# No cross-repo dispatch is needed to refresh a channel when its bundled APK +# is rebuilt: Minstrel is a monorepo, so one push builds the APK and the +# image in the same run from the same commit, and the channel cannot go +# stale against its own artifact. # # Release model: the tag IS the artifact's version name with a `v` in front. # `v2026.09.10.1432` and `2026.09.10.1432` are the same string, derived from @@ -55,15 +49,9 @@ name: release # # TAGS ARE IMMUTABLE. Never move, retarget or delete a published tag. A # same-day second release is not a collision — HHMM makes every tag unique -# by construction, so the answer is simply another tag. -# -# This block used to say the opposite: that the per-day tag was -# "intentionally mutable" and that a same-day re-cut should -# `git push -f origin vYYYY.MM.DD`. That instruction is what the family -# rulebook now forbids outright, and it has incidents behind it — moving a -# same-day tag forward once took a published release down with it. Anyone -# installing from a tag is holding something the tag no longer points at, -# which is a worse failure than an extra row in the tag list. +# by construction, so the answer is simply another tag. Anyone installing +# from a moved tag would hold something the tag no longer points at, which +# is a worse failure than an extra row in the tag list. # # :latest is updated by every main push AND every tag push, so it always # reflects the newest blessed image. @@ -83,15 +71,14 @@ name: release # recomputed ones — so no rebuild is needed, just a rebundle. Tag builds # keep bundling their own freshly-built APK. # -# THE GATE (rule 177, M462 #4984). Every verifying lane lives in this file — +# THE GATE. Every verifying lane lives in this file — # Go (vet, lint, short tests), the Postgres integration suite, the web app # (npm audit, svelte-check, vitest), Android (ktlint, detekt, unit tests) and # govulncheck — and every job that publishes something names each of them in # `needs:` and requires `success` from each, by name. Nothing publishes on red. # -# They used to be three separate workflows (test-go, test-web, android) on the -# same push trigger as this one. Separate workflows cannot see each other's -# verdict, so :dev meant "it built", never "it passed": a red test run and a +# One workflow, because separate workflows cannot see each other's verdict: +# :dev would mean "it built", never "it passed", and a red test run and a # fresh :dev could carry the same timestamp. One graph is the only place the # edge can be written. # @@ -125,10 +112,9 @@ on: type: boolean default: false -# A rapid re-push to main should supersede the in-flight build — the -# operator explicitly wants the later commit to win. Tags no longer enter -# into this: they are immutable and unique, so no tag build can ever be -# superseded by another run on the same ref. +# A rapid re-push to main supersedes the in-flight build: the later commit +# wins. Tags are immutable and unique, so no tag build can be superseded by +# another run on the same ref. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -173,14 +159,14 @@ jobs: # Full `go test -race` against an ephemeral Postgres. # # DB wiring follows the act_runner shared-daemon pattern: the runner's Docker - # daemon also runs the operator's dev compose stack, so service containers - # get NO published ports (collision) and no service-name DNS. We discover the + # daemon also runs a dev compose stack, so service containers get NO + # published ports (collision) and no service-name DNS. We discover the # service container through the mounted docker socket and reach it by bridge # IP. The exactly-one assertion is a hard guard — pointing tests at the dev - # Postgres would truncate it (the disaster Fable #339 exists to prevent). + # Postgres would truncate it. # - # The key stays `integration` with no `name:` (rule 80): act_runner derives - # the service container's name from the job's display name. + # The key stays `integration` with no `name:`: act_runner derives the + # service container's name from the job's display name. # # `web/build/` has a committed placeholder index.html so go:embed succeeds # without the SPA being built first. @@ -195,7 +181,7 @@ jobs: POSTGRES_USER: minstrel POSTGRES_PASSWORD: minstrel POSTGRES_DB: minstrel_test - # No `ports:` — the runner shares the operator's dev compose + # No `ports:` — the runner shares the dev compose # Docker daemon; publishing a fixed host port collides. steps: @@ -209,13 +195,13 @@ jobs: # mounted docker socket. act_runner attaches the job # container and its service container(s) to a shared per-job # network, so scope discovery to a postgres that sits on a - # network THIS job container is also on. The old - # `--filter name=integration` matched EVERY concurrent - # integration run's postgres (a dev push + the main-merge run - # overlap → 2 candidates → false "expected exactly 1" abort). - # The operator's dev compose `minstrel-postgres-*` is never on - # this job's network; skip it explicitly as belt-and-suspenders - # (a wrong target would truncate real data). + # network THIS job container is also on. A name filter alone + # would match EVERY concurrent integration run's postgres (a dev + # push and a main-merge run overlapping give 2 candidates and a + # false "expected exactly 1" abort). The dev compose + # `minstrel-postgres-*` is never on this job's network; skip it + # explicitly as belt-and-suspenders (a wrong target would + # truncate real data). SELF=$(cat /etc/hostname) SELF_NETS=$(docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' "$SELF") test -n "$SELF_NETS" @@ -236,11 +222,11 @@ jobs: export MINSTREL_TEST_DATABASE_URL="postgres://minstrel:minstrel@${PG_IP}:5432/minstrel_test?sslmode=disable" # Wait for Postgres to accept connections. Asked of the service - # container itself: the run: shell is dash (rule 81), where the - # old `/dev/tcp` probe never connects and the loop silently - # burned its full two minutes on every run. + # container itself: the run: shell is dash, where a `/dev/tcp` + # probe never connects and the loop would silently burn its full + # two minutes. # - # Over TCP (-h 127.0.0.1), not the unix socket (#5426). On first + # Over TCP (-h 127.0.0.1), not the unix socket. On first # boot the image's entrypoint runs initdb under a temporary server # that listens on the socket only, then stops it and starts the # real one. A socket probe can catch the temporary server, and the @@ -267,7 +253,7 @@ jobs: # synchronous_commit is user-context, so pg_reload_conf() picks # all three up with no restart. # Non-fatal: a failure degrades to "slower", never red CI. But - # slower is ~15x on internal/api (#5426), so retry a few times and + # slower is ~15x on internal/api, so retry a few times and # print what took effect. relaxed="" for i in 1 2 3 4 5; do @@ -291,10 +277,10 @@ jobs: # every integration package TRUNCATEs the one shared test DB; # concurrent package binaries → TRUNCATE deadlocks. Serialize # package execution (the documented local invocation too). - # -timeout 20m: internal/api alone takes ~6.5 min under -race on - # an idle runner, and a dev and a main run sharing the runner - # pushed it past go test's default 10m (run 8368, 600.016s, the - # running test 2s old — nothing hung). + # -timeout 20m: internal/api alone takes several minutes under + # -race on an idle runner, and a dev and a main run sharing the + # runner can push it past go test's default 10m with nothing + # hung. MINSTREL_DATABASE_URL="$MINSTREL_TEST_DATABASE_URL" go run ./cmd/minstrel migrate go test -p 1 -race -timeout 20m ./... @@ -314,11 +300,8 @@ jobs: - name: Install deps run: npm ci - # The whole tree, build and test tooling included. Until #5021 this - # audited only what ships to browsers (`--omit=dev`), because vite, - # vitest, tailwind and kit carried advisories that needed major - # upgrades. Those upgrades landed and the full tree audits clean, so - # the tooling that builds the shipped bundle is held to the same bar. + # The whole tree, build and test tooling included: the tooling that + # builds the shipped bundle is held to the same bar as the bundle. - name: npm audit (all dependencies) run: npm audit --audit-level=moderate @@ -329,8 +312,8 @@ jobs: run: npm test android: - # Using flutter-ci runner label because it's the only proven-working - # label with docker that can pull our container.image. + # The flutter-ci runner label is the only proven-working label with + # docker that can pull our container.image. runs-on: flutter-ci container: image: git.fabledsword.com/bvandeusen/ci-android:36 @@ -376,11 +359,10 @@ jobs: - name: Unit tests run: ./gradlew testDebugUnitTest - # No debug APK is built or uploaded here. Main used to upload a - # debug-signed app-debug.apk: a build signed by a key regenerated in - # every container, which no install can update (family idea #5103, - # practice 2). Phones get builds from android-release, signed with - # the one release key, on dev and on tags. + # No debug APK is built or uploaded here: a debug build is signed by a + # key regenerated in every container, which no install can update. + # Phones get builds from android-release, signed with the one release + # key, on dev and on tags. # Known vulnerabilities in the Go code and the standard library it is built # with. Runs in the SAME image the Dockerfile's builder stage uses, so the @@ -388,7 +370,7 @@ jobs: # the ci-go image carries its own Go and would be checking a different # toolchain. Keep this image and the Dockerfile's builder in step. # - # govulncheck is fetched at CI time, unpinned (rule 154): the vulnerability + # govulncheck is fetched at CI time, unpinned: the vulnerability # database and the tool that reads it should both be current. govulncheck: runs-on: go-ci @@ -397,7 +379,7 @@ jobs: steps: # Plain git, not actions/checkout: that action runs on node, which the - # golang image does not carry. This step is dash (rule 81). + # golang image does not carry. This step is dash. - name: Checkout env: TOKEN: ${{ github.token }} @@ -420,7 +402,7 @@ jobs: android-release: name: Build signed APK (releases and dev) # Also builds on `dev`, which is what makes a test channel possible at - # all. Without it the only way to get a build onto a phone was to cut a + # all: without it the only way to get a build onto a phone is to cut a # release, which quietly turns `main` into the staging area. if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/dev' runs-on: flutter-ci @@ -453,7 +435,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - # Full history. The version name now reads only the tip commit's + # Full history. The version name reads only the tip commit's # timestamp, so a shallow clone would technically serve — but this # job derives a value that ships to devices, and a shallow checkout # changes what git-derived values resolve to WITHOUT failing. The @@ -487,9 +469,9 @@ jobs: # Checked BEFORE the expensive work, not after it. "Attach APK to gitea # Release" below resolves the release by tag and fails if it is absent — - # but that is the final step, so a tag pushed without a release built an - # APK for several minutes first and only then discovered it had nowhere to - # put it. Same check, seconds in instead of minutes. + # but that is the final step, so a tag pushed without a release would + # build an APK for several minutes first and only then find it has + # nowhere to put it. Same check, seconds in instead of minutes. # # Releases are normally created through the API (which creates the tag and # the release together, so this passes). A bare `git push origin vX` is the @@ -549,16 +531,15 @@ jobs: # secret) reaches no installed phone. The certificate's digest is # pinned below; it is public, not a secret. Gradle signs with the # release key or leaves the APK unsigned, and an unsigned build fails - # here too, as there is no app-release.apk to verify (family idea - # #5103, practice 3). apksigner, not keytool: keytool prints nothing - # for a v2-only APK. + # here too, as there is no app-release.apk to verify. apksigner, not + # keytool: keytool prints nothing for a v2-only APK. # # Rotating the key on purpose means every install must be removed and # reinstalled; change the digest here in the same commit. - name: The APK carries the release key shell: bash env: - # CN=Minstrel, O=FabledSword. Read from run 8446 (#5116). + # CN=Minstrel, O=FabledSword. RELEASE_CERT_SHA256: 43d183307bc46b821789d90444a960b137f78f2166ff431efb2406d0fceaf612 run: | set -euo pipefail @@ -580,11 +561,10 @@ jobs: fi - name: Upload APK as workflow artifact - # Stock action (snippet #2271) — never @v3, which uploads something Gitea - # will never serve back. This is the producing half of a pair: - # image-release downloads `minstrel-apk` below. Any upload v4+ pairs with - # any download v4+ on this forge (every combination tested 2026-09-10, - # Scribe spike #3843), so the two pins need not move together. + # Stock action — never @v3, which uploads something Gitea will never + # serve back. This is the producing half of a pair: image-release + # downloads `minstrel-apk` below. Any upload v4+ pairs with any + # download v4+ on this forge, so the two pins need not move together. uses: actions/upload-artifact@v7 with: name: minstrel-apk @@ -687,7 +667,7 @@ jobs: image-release: name: Build + push container image - # Every lane must have SUCCEEDED, each named here (rule 177). Then the + # Every lane must have SUCCEEDED, each named here. Then the # APK: tag and dev pushes build one and it must have succeeded; main # pushes skip android-release and bundle the latest release's APK # instead, so for main alone a skipped android-release is expected. @@ -713,15 +693,13 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - # Full history, and rule 149 names this specifically: any job that - # DERIVES the version name needs it, because a shallow clone changes - # what git-derived values resolve to WITHOUT failing — a too-low - # value, silently, with every lane green. + # Full history: any job that DERIVES the version name needs it, + # because a shallow clone changes what git-derived values resolve to + # WITHOUT failing — a too-low value, silently, with every lane green. # - # This job was depth-1 while it took the version from GITHUB_REF. It - # now runs ci/version.sh itself, because with : image tags - # gone the server's self-reported version is the only thing that says - # which build an image is. + # This job runs ci/version.sh itself: with no : image tags, + # the server's self-reported version is the only thing that says which + # build an image is. fetch-depth: 0 - name: Detect buildable project @@ -743,21 +721,16 @@ jobs: set -euo pipefail # THE VERSION, and it is derived the same way on every ref — the - # branch decides the CHANNEL, never the version (family rule 149). + # branch decides the CHANNEL, never the version. # - # This used to be three different things: the literal string "main" - # on main, "dev" on dev, and the tag name on a tag. None of them - # ordered, and the first two were the same string forever — two dev - # images eight weeks apart were indistinguishable in the UI. That - # mattered little while :vYYYY.MM.DD.HHMM existed to identify a - # build; with version image tags gone, this IS how an operator tells - # which build a container is running. + # A per-ref string ("main", "dev", the tag name) would not order, and + # "dev" would be the same string forever — two dev images eight weeks + # apart indistinguishable in the UI. With no version image tags, this + # is how an admin tells which build a container is running. # # `sed -n s///p` rather than `grep`: it exits 0 when nothing matches, # so the empty check below is actually reachable. A grep here would - # kill the step at the assignment under the runner's pipefail — the - # exact bug that took down the first main build after the version - # rework. + # kill the step at the assignment under the runner's pipefail. VERSION="$(ci/version.sh HEAD | sed -n 's/^name=//p')" if [ -z "${VERSION}" ]; then echo "::error::could not derive a build version from ci/version.sh" @@ -770,13 +743,13 @@ jobs: # The tag build exists to produce the signed APK and attach it to # the release; the image it rebuilds is the SAME SOURCE as the main # build minutes earlier, differing only in which APK is baked in. - # Rule 145 is explicit about that case: when the same source is - # rebuilt with different contents, publish the moving channel tag - # and never a commit-addressable one. + # When the same source is rebuilt with different contents, + # publish the moving channel tag and never a commit-addressable + # one. # # :latest must move here rather than waiting for the next main # push, or the channel would carry the PREVIOUS release's APK - # indefinitely — a channel that cannot refresh itself (rule 146). + # indefinitely — a channel that cannot refresh itself. CHANNEL=stable echo "args=-t ${IMAGE}:latest" >> "$GITHUB_OUTPUT" echo "::notice::Release build ${VERSION}: refreshing :latest around the new APK" @@ -790,10 +763,9 @@ jobs: echo "args=-t ${IMAGE}:dev" >> "$GITHUB_OUTPUT" echo "::notice::Dev-branch build ${VERSION}: :dev" else - # The production line: :latest tracks main's tip (rule 147) and - # : is the rollback unit (rule 145). Full 40-char SHA, matching - # the family's other repos, so a rollback target is addressable - # straight from the commit anyone is reading. + # The production line: :latest tracks main's tip and : is + # the rollback unit. Full 40-char SHA, so a rollback target is + # addressable straight from the commit anyone is reading. CHANNEL=stable echo "args=-t ${IMAGE}:latest -t ${IMAGE}:${GITHUB_SHA}" >> "$GITHUB_OUTPUT" echo "::notice::Main-branch build ${VERSION}: :latest + :${GITHUB_SHA}" @@ -818,8 +790,8 @@ jobs: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/dev') # Consuming half of the pair: stock download-artifact, which works here for # the same reason as the upload (gitea/runner 3.x edits the GHES refusal - # out of the bundle; snippet #2271). v8 runs on node24, which every - # CI-runner image carries — the runner uses the image's own node. + # out of the bundle). v8 runs on node24, which every CI-runner image + # carries — the runner uses the image's own node. uses: actions/download-artifact@v8 with: name: minstrel-apk @@ -887,10 +859,10 @@ jobs: curl -fsSL -H "Authorization: token ${CI_TOKEN}" -o client/minstrel.apk "${APK_URL}" # Take the version the release RECORDED rather than recomputing it. - # This used to re-derive the name from the tagged commit, which meant - # the formula lived in two files that had to be kept in step, and it - # could only ever recover the name — the ordering key is build-time - # minutes and does not exist anywhere after that build ends. + # Re-deriving it from the tagged commit would keep the formula in two + # files that must stay in step, and could only ever recover the name — + # the ordering key is build-time minutes and exists nowhere after that + # build ends. SIDECAR_URL="$(printf '%s' "${REL_JSON}" | grep -oP '"browser_download_url":\s*"\K[^"]+' | grep -E '\.apk\.version$' | head -1)" || true if [ -n "${SIDECAR_URL}" ]; then curl -fsSL -H "Authorization: token ${CI_TOKEN}" -o client/minstrel.apk.version "${SIDECAR_URL}" @@ -924,18 +896,15 @@ jobs: # Verifies a tag release actually ended up complete, and names the specific # thing that's missing if not. # - # Added 2026-08-07 after v2026.08.07 was re-cut. The android-release job never - # started — no log was written at all — so all eight of its steps reported - # `failure` with none executed and image-release showed `skipped`. The run was - # red, but the *release page rendered fine*, and `main`'s own push build had - # already moved `:latest`, so the code was deployable and nothing looked - # obviously wrong. The release was simply missing its APK and its image, - # which is easy to skim past. + # A release can be incomplete while looking fine. If android-release never + # starts (a runner failing to launch), its steps all report `failure` with + # none executed and image-release shows `skipped`. The run is red, but the + # release page renders and main's own push build has already moved + # `:latest`, so a release missing its APK and its image is easy to skim + # past. # - # This job cannot prevent that (the cause was a runner failing to launch, not - # anything in this file). What it does is turn an incomplete release into an - # explicit, named error instead of eight mystery step failures — so the - # consequence is legible without having to infer it. + # This job cannot prevent that; it turns an incomplete release into an + # explicit, named error instead of a row of mystery step failures. # # `if: always()` is the whole point: it has to report precisely when the jobs # above did NOT succeed. @@ -977,22 +946,16 @@ jobs: echo "::notice::APK attached: ${APK}" # The other half. Checking only the APK would report success on a release - # whose image push failed — which is precisely the second thing that was - # missing when v2026.08.07 had to be re-cut. `always()` on this job means - # it runs even when image-release failed, so without this the guard would - # cheerfully verify an incomplete release. + # whose image push failed. `always()` on this job means it runs even when + # image-release failed, so without this the guard would cheerfully verify + # an incomplete release. # - # This asserted `:${TAG}` — the :vYYYY.MM.DD.HHMM image — until - # 2026-09-10. Version image tags are no longer published (rule 145), so - # that assertion would now fail every release for a tag nothing mints. - # The rollback target it was really protecting is the : image, which - # main's own build published for this same commit before the tag was cut. - # - # Checking it here earns its keep twice over: it still catches an image - # push that silently did not happen, and it additionally proves the - # ORDERING — a tag cut on a commit whose main build never completed has - # no rollback target, and that is worth failing on rather than - # discovering during an incident. + # The image checked is the : rollback target, which main's own build + # published for this same commit before the tag was cut (there are no + # version image tags). It catches an image push that silently did not + # happen, and it proves the ORDERING — a tag cut on a commit whose main + # build never completed has no rollback target, and that is worth failing + # on rather than discovering during an incident. - name: Rollback image must exist for the tagged commit shell: bash run: | diff --git a/Dockerfile b/Dockerfile index 0af41d99..d69a99d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,11 +19,10 @@ ENV CGO_ENABLED=0 # (YYYY.MM.DD.HHMM) and the lane's channel; a local `docker build` falls back # to "dev"/"local". Both are surfaced at /healthz. # -# These are two values on purpose (family rule 149): the same commit built on -# dev and on main reports the same NAME and differs only in CHANNEL. Folding -# the channel into the version string is what the rule forbids — the version -# used to BE the channel word here ("main"/"dev"), which meant two dev images -# eight weeks apart were indistinguishable. +# These are two values on purpose: the same commit built on dev and on main +# reports the same NAME and differs only in CHANNEL. Folding the channel +# into the version string would make two dev images eight weeks apart +# indistinguishable. ARG MINSTREL_VERSION=dev ARG MINSTREL_CHANNEL=local RUN go build -trimpath \ @@ -36,8 +35,8 @@ FROM debian:bookworm-slim # ffmpeg: duration probes and the exact-tier audio hash (a SHA-256 of the # encoded audio packets, so no decode). libchromaprint-tools: fpcalc, the # acoustic fingerprint that tells the same recording at two bitrates apart -# from two different recordings (M400). Both are baked in at build time so a -# deployed instance never fetches either (rule 164); fpcalc is shelled out +# from two different recordings. Both are baked in at build time so a +# deployed instance never fetches either; fpcalc is shelled out # rather than bound because CGO_ENABLED=0 above rules out cgo. RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates ffmpeg libchromaprint-tools \ @@ -57,10 +56,12 @@ COPY config.example.yaml /etc/smartmusic/config.yaml RUN mkdir -p /app/data /app/client && chown -R minstrel:minstrel /app WORKDIR /app -# In-app update channel (#397). client/ in the build context holds -# minstrel.apk + minstrel.apk.version (populated by release.yml on tag -# pushes; .gitkeep + README otherwise). Endpoints return 404 when the -# APK files aren't present, so non-tag images degrade gracefully. +# In-app update channel. client/ in the build context holds +# minstrel.apk + minstrel.apk.version, which release.yml fills on every +# build (dev and tag pushes bundle their own APK, main bundles the latest +# release's); a local build has only .gitkeep + README. Endpoints return +# 404 when the APK files aren't present, so such an image degrades +# gracefully. COPY --chown=minstrel:minstrel client/ /app/client/ USER minstrel diff --git a/tools/gen-brand-assets.py b/tools/gen-brand-assets.py index 8a5c3c3b..8badb6d8 100644 --- a/tools/gen-brand-assets.py +++ b/tools/gen-brand-assets.py @@ -180,11 +180,10 @@ def main(): " Literal colours, for any consumer that cannot inline the SVG and\n" " therefore cannot supply a currentColor. -->\n")) - # The tab icon is the full mark too (#5267). A hand-drawn reduced hat used - # to stand in at tab size because the art loses detail at 16px, but a - # redrawing reads as a different logo. The full mark keeps the logo's own - # shape, and a tab on a high-resolution screen draws it from 32px, where it - # holds. Do not swap a redrawn form back in here. + # The tab icon is the full mark too. A hand-drawn reduced form would lose + # less detail at 16px, but a redrawing reads as a different logo; the full + # mark keeps the logo's own shape, and a tab on a high-resolution screen + # draws it from 32px, where it holds. Do not swap a redrawn form in here. (ROOT / "web/static/brand/favicon.svg").write_text(svg_doc( vb, tr, "currentColor", ACCENT, hat_d, notes_d, head="