test-go / test (push) Successful in 1m9s
test-go / integration (push) Successful in 3m28s
release / Build signed APK (releases and dev) (push) Successful in 4m38s
release / Build + push container image (push) Successful in 1m26s
release / Verify release artifacts (tag releases only) (push) Skipped
Two identities per track, because they answer different questions: - audio_stream_sha256: SHA-256 of the ENCODED audio packets (ffmpeg -map 0:a -c:a copy -f hash). Equal means identical audio whatever the tags say. Measured against the #3885 pair: the two WWW files hash identically here and differently as whole files. Packets rather than decoded samples, so an ffmpeg upgrade cannot silently change every stored hash, and nothing is decoded. - chromaprint: fpcalc -raw -signed. The same recording at another bitrate or codec, for the acoustic tier. fpcalc ships in the image (libchromaprint-tools); shelled out because CGO_ENABLED=0 rules out bindings. Stored in a track_fingerprints table rather than on tracks: eight queries read tracks with SELECT *, including album pages, search and the Subsonic surface, and a ~4 KB array there would be de-TOASTed on every one of them. The scan fingerprints only bytes it has not seen (a new path, or mtime past the row's). A tag-repair pass leaves fingerprints alone, and unchanged files with no fingerprint are the backfill's job (#3908). Folding that into the skip check would re-decode the whole library on the first scan after upgrade and push a sync change per track. A failure that says nothing about the file (timeout, cancelled scan, tool not installed) is never stored, and on changed bytes it removes the old row. A tool that rejects the file stores NULL at the current version, so the backfill does not retry it every boot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
71 lines
3.0 KiB
Docker
71 lines
3.0 KiB
Docker
# syntax=docker/dockerfile:1.6
|
|
|
|
FROM node:22-bookworm-slim AS web
|
|
WORKDIR /web
|
|
COPY web/package.json web/package-lock.json ./
|
|
RUN npm ci
|
|
COPY web/ ./
|
|
RUN npm run build
|
|
|
|
FROM golang:1.25-bookworm AS builder
|
|
WORKDIR /src
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
COPY . .
|
|
# Overwrite the committed placeholder with the freshly-built SPA assets.
|
|
COPY --from=web /web/build ./web/build
|
|
ENV CGO_ENABLED=0
|
|
# Version stamping. release.yml passes the DERIVED version name
|
|
# (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.
|
|
ARG MINSTREL_VERSION=dev
|
|
ARG MINSTREL_CHANNEL=local
|
|
RUN go build -trimpath \
|
|
-ldflags="-s -w \
|
|
-X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=${MINSTREL_VERSION}' \
|
|
-X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerChannel=${MINSTREL_CHANNEL}'" \
|
|
-o /out/minstrel ./cmd/minstrel
|
|
|
|
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
|
|
# 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 \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN groupadd --system --gid 1000 minstrel \
|
|
&& useradd --system --uid 1000 --gid minstrel --shell /usr/sbin/nologin minstrel
|
|
|
|
COPY --from=builder /out/minstrel /usr/local/bin/minstrel
|
|
COPY config.example.yaml /etc/smartmusic/config.yaml
|
|
|
|
# Pre-create the data directory owned by the runtime user. Cached artifacts
|
|
# (playlist cover collages, artist art, album-cover fallbacks) all land here.
|
|
# A non-writable path at this location silently breaks every downstream
|
|
# cache, so we create + chown it once at image build. Operators mount a
|
|
# named volume on top to persist across container recreates.
|
|
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.
|
|
COPY --chown=minstrel:minstrel client/ /app/client/
|
|
|
|
USER minstrel
|
|
EXPOSE 4533
|
|
ENV MINSTREL_STORAGE_DATA_DIR=/app/data
|
|
ENTRYPOINT ["/usr/local/bin/minstrel"]
|
|
CMD ["--config", "/etc/smartmusic/config.yaml"]
|