feat(server): /api/client/version + /api/client/apk endpoints (#397 phase 1)

Phase 1 of the in-app update flow — server side. Endpoints serve the
bundled Android APK + sidecar version file from /app/client/.
Returns 404 gracefully when files aren't present, so dev environments
and pre-CI-wiring images degrade cleanly to "no update available."

- internal/api/client_assets.go: handleClientVersion + handleClientAPK.
  Both unauthenticated (matches /healthz) so install flow doesn't
  depend on a live session. APK served with proper
  application/vnd.android.package-archive Content-Type +
  http.ServeContent so Range requests work for resumable downloads
  on flaky networks.
- Path resolves to /app/client/ by default; MINSTREL_CLIENT_APK_DIR
  env var overrides for dev.
- Dockerfile creates /app/client/ + commented COPY hooks for the CI
  sequencing phase.
- Tests cover all four states: missing apk, apk-but-no-version,
  both present (200 with correct shape), apk stream (200 with
  correct Content-Type + body bytes).

Phases 2 (Flutter client provider + banner + install intent + Android
manifest changes) and 3 (CI sequencing to bake the APK into the image)
land in follow-up commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:35:24 -04:00
parent 27f123f7d9
commit a2bea8601a
4 changed files with 235 additions and 1 deletions
+10 -1
View File
@@ -33,9 +33,18 @@ COPY config.example.yaml /etc/smartmusic/config.yaml
# 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 && chown -R minstrel:minstrel /app
RUN mkdir -p /app/data /app/client && chown -R minstrel:minstrel /app
WORKDIR /app
# In-app update channel (#397): bundled Android APK + sidecar version
# file. CI populates these on tag releases via a build context COPY;
# leaving the COPY commented during scaffold means the endpoints return
# 404 until the CI sequencing lands. Operators can also drop these in
# at runtime via a volume mount on /app/client.
#
# COPY minstrel.apk /app/client/minstrel.apk
# COPY minstrel.apk.version /app/client/minstrel.apk.version
USER minstrel
EXPOSE 4533
ENV MINSTREL_STORAGE_DATA_DIR=/app/data