Initial implementation: core vertical slice
Implements the core of SPEC.md — the manifest exchange, less audio-tier matching (§3) and federation (§9a), both of which the spec sequences as later work. - §2 Jmanifest format and series bundles - §3 cut matching: exact / runtime / loose tiers - §4 API, less POST /manifests/search - §5 rate limiting; §5a trust model, anonymous bearer tokens - §6 upload validation, all four stages - §7 relational storage, no JSON blob on the write path - §8 Rust + Axum + SQLite, single serialized writer, in-process job queue - §9a content addressing, computed on upload Reconciled against the system spec: - anneal_sec removed, withdrawn upstream by AR-012/AR-013. Presence follows track extent, so a track survives its own gaps and there is nothing to anneal. Its successor extinction_sec and the new gallery_scope are accepted and stored; scope enters the §7 ranking. A manifest still carrying anneal_sec is a hard 400, not silently ignored — it came from a pipeline whose window semantics differ from what this server assumes. - Audio signature: media under 120 s now emits no signature at all, matching scene-actor-extraction IR-007. The earlier §3 draft allowed a shortened window under 150 s, which was the weaker rule — a caller-varying length is the property SR-004 forbids. - UR IDs regularised to UR-nnn; docs/requirements.md registers 32 requirements, each tracing to an SR-nnn or PR-nnn. 189 tests: unit, end-to-end through the real router, and an injection suite covering SQL, JSON, header and Unicode payloads. Writing that suite found two real gaps, both fixed here: compatibility homoglyphs passed the §5a character class, and a one-frame audio signature was accepted on a feature-length item. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+89
@@ -0,0 +1,89 @@
|
||||
# Deploy image for jray-server.
|
||||
#
|
||||
# §8 is explicit that neither Docker nor Compose should be *required* — the
|
||||
# primary artifact is a single static binary plus one database file, and that is
|
||||
# deliberately the lowest-friction thing a hobbyist operator can deploy. This
|
||||
# image is the optional convenience, not the intended path.
|
||||
#
|
||||
# It builds against musl so the runtime stage can be `scratch`: no libc, no shell,
|
||||
# no package manager, nothing to keep patched. rusqlite is built with `bundled`
|
||||
# (SQLite compiled in) and reqwest with rustls rather than OpenSSL, so there is
|
||||
# genuinely nothing left to link against.
|
||||
#
|
||||
# docker build -t jray-server .
|
||||
# docker run --rm -p 8080:8080 -v jray-data:/data \
|
||||
# -e JRAY_TMDB_API_KEY=... jray-server
|
||||
|
||||
FROM rust:1.92-alpine AS builder
|
||||
|
||||
# `musl-dev` for the C toolchain rusqlite's bundled SQLite needs; `file` for the
|
||||
# static-linkage assertion below.
|
||||
RUN apk add --no-cache musl-dev file
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Dependencies first, in their own layer, so editing source does not re-download
|
||||
# and rebuild the entire tree.
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
RUN mkdir -p src \
|
||||
&& echo 'fn main() {}' > src/main.rs \
|
||||
&& echo '' > src/lib.rs \
|
||||
&& cargo build --release --target x86_64-unknown-linux-musl \
|
||||
&& rm -rf src
|
||||
|
||||
COPY src ./src
|
||||
|
||||
# `touch` defeats the cargo staleness check that the dummy-source trick above
|
||||
# would otherwise leave in place.
|
||||
RUN touch src/main.rs src/lib.rs \
|
||||
&& cargo build --release --target x86_64-unknown-linux-musl \
|
||||
&& strip target/x86_64-unknown-linux-musl/release/jray-server
|
||||
|
||||
# Verify the result is genuinely static. A dynamically-linked binary would fail at
|
||||
# runtime on `scratch`, and failing here is far easier to diagnose.
|
||||
#
|
||||
# Asserted with `file`, not `ldd`: the musl target produces a **static-PIE**, and
|
||||
# `ldd` prints the musl loader path for one, so an `ldd`-based check reports a
|
||||
# static binary as dynamic. `file` reports "static-pie linked" and is unambiguous.
|
||||
RUN file target/x86_64-unknown-linux-musl/release/jray-server | tee /tmp/linkage \
|
||||
&& grep -qE 'static-pie linked|statically linked' /tmp/linkage \
|
||||
|| (echo "binary is not statically linked; it will not run on scratch" && exit 1)
|
||||
|
||||
# Stage the data directory with the runtime uid's ownership. `scratch` has no
|
||||
# shell, so this cannot be done in the final stage — and a bare `VOLUME` there
|
||||
# would be created root-owned, leaving the non-root process unable to create the
|
||||
# database at all.
|
||||
RUN mkdir -p /staged-data && chown 65534:65534 /staged-data
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/jray-server /jray-server
|
||||
|
||||
# The database lives on a volume; §8 warns that a plain file copy of a live WAL
|
||||
# database is not a valid backup, so back it up with `VACUUM INTO` from the host
|
||||
# rather than by archiving this directory.
|
||||
#
|
||||
# Copied from the builder so it arrives owned by the runtime uid. Docker seeds a
|
||||
# named volume from the image's directory, ownership included, so the server can
|
||||
# create the database on first run. A bind mount is *not* seeded this way — the
|
||||
# host directory keeps its own ownership, so it must be made writable by uid
|
||||
# 65534 (`chown 65534:65534 /path/on/host`).
|
||||
COPY --from=builder --chown=65534:65534 /staged-data /data
|
||||
VOLUME ["/data"]
|
||||
|
||||
# Non-root. `scratch` has no /etc/passwd, so this is a bare uid — which is all the
|
||||
# kernel needs, and the binary touches nothing outside /data.
|
||||
USER 65534:65534
|
||||
|
||||
ENV JRAY_BIND=0.0.0.0:8080 \
|
||||
JRAY_DB=/data/jray.db
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# No HEALTHCHECK: it would need a shell or curl, and `scratch` has neither.
|
||||
# §4 provides `GET /health` (liveness) and `/ready` (database and migrations) for
|
||||
# an orchestrator to probe externally, which is the right place for it.
|
||||
|
||||
ENTRYPOINT ["/jray-server"]
|
||||
Reference in New Issue
Block a user