Ein Vorspann ist das, was in jeder Folge einer Staffel gleich klingt. Sonst ist an einer Folge nichts, woran er sich erkennen liesse: er sieht aus wie Handlung und klingt wie Handlung, er wiederholt sich nur. `Tonabgleich` sucht das längste Stück, das zwei Abdruckreihen teilen — reine Rechnung, ohne Datei und ohne ffmpeg, deshalb mit sieben Prüfungen belegt. Darunter die wichtigere Richtung: zwei Folgen ohne Gemeinsamkeit ergeben nichts. Ein nicht gefundener Vorspann kostet einen Knopf, ein erfundener schneidet dem Zuschauer eine halbe Minute Handlung weg. Die Abdrücke holt `fpcalc` aus Chromaprint, nicht selbstgeschriebene Signalverarbeitung. Dasselbe tut Jellyfins Intro Skipper, aus demselben Grund. Das Programk liegt ab jetzt im Abbild; fehlt es, gibt es eben keine Marken — sie sind eine Zugabe, kein Teil der Wiedergabe. Noch ruft das niemand auf. Tabelle, Lauf und Knöpfe kommen als Nächstes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
5.0 KiB
Docker
118 lines
5.0 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# Die Oberfläche zuerst, in einem eigenen Abschnitt: sie braucht Node, der
|
|
# Server nicht. So landet Node nie im fertigen Image.
|
|
FROM --platform=$BUILDPLATFORM node:22-alpine AS web
|
|
WORKDIR /web
|
|
COPY web/app/package.json web/app/package-lock.json ./
|
|
RUN npm ci
|
|
COPY web/app/ ./
|
|
RUN npm run build
|
|
|
|
|
|
# Gebaut wird auf der Architektur des Bauwirts, übersetzt wird für die des
|
|
# Ziels — `dotnet publish -a` kann das. Ohne diese Trennung liefe der Bau für
|
|
# arm64 unter Emulation und dauerte ein Vielfaches.
|
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
|
ARG TARGETARCH
|
|
WORKDIR /src
|
|
|
|
# Erst die Projektdateien, dann wiederherstellen: so bleibt die
|
|
# Paket-Wiederherstellung in der Ebenenzwischenablage, solange sich keine
|
|
# Abhängigkeit ändert. Ohne diesen Schritt lädt jeder Bau alles neu.
|
|
# `--parents` behält die Verzeichnisse bei. **Ein Muster statt einer Liste**,
|
|
# weil eine Liste bei jedem neuen Projekt vergessen wird — und dann scheitert
|
|
# erst der Bau im Container, nicht der auf dem Arbeitsrechner.
|
|
COPY --parents server/src/*/*.csproj ./
|
|
RUN dotnet restore server/src/Flaro.Api/Flaro.Api.csproj -a $TARGETARCH
|
|
|
|
COPY server/ ./server/
|
|
RUN dotnet publish server/src/Flaro.Api/Flaro.Api.csproj -a $TARGETARCH -c Release -o /app --no-restore
|
|
|
|
|
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
|
|
|
# ffmpeg und die Treiber für Hardware-Beschleunigung.
|
|
#
|
|
# **Als eigenes Programm, nicht als Bibliothek.** Flaro ruft es als Prozess
|
|
# auf; damit bleiben die Lizenzen getrennt (ffmpeg steht unter GPL) und ein
|
|
# Absturz dort nimmt den Server nicht mit.
|
|
#
|
|
# `mesa-va-drivers` bedient AMD und ältere Intel, `intel-media-va-driver` die
|
|
# neueren Intel — beide über VAAPI an `/dev/dri`. Welcher greift, entscheidet
|
|
# sich zur Laufzeit danach, was durchgereicht wurde.
|
|
#
|
|
# **Die Intel-Treiber nur auf amd64.** Es gibt sie für arm64 nicht, und ein
|
|
# unbedingtes `apt-get install` liess den Bau dort mit Code 100 abbrechen —
|
|
# eine Meldung, die nicht verrät, welches der sechs Pakete fehlt.
|
|
ARG TARGETARCH
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
ffmpeg libva2 libva-drm2 mesa-va-drivers curl \
|
|
libchromaprint-tools \
|
|
&& if [ "$TARGETARCH" = "amd64" ]; then \
|
|
apt-get install -y --no-install-recommends intel-media-va-driver i965-va-driver; \
|
|
fi \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# **Die Verzeichnisse müssen hier angelegt werden, nicht erst beim Start.**
|
|
# Docker legt den Einhängepunkt eines frischen benannten Volumes sonst als
|
|
# `root` an und übernimmt für ein leeres Volume die Rechte aus dem Image —
|
|
# gibt es das Verzeichnis im Image nicht, erbt es `root:root`, und der erste
|
|
# Start scheitert für jeden, der kein Bind Mount benutzt.
|
|
RUN mkdir -p /config /cache /transcode && chown -R 1000:1000 /config /cache /transcode
|
|
|
|
# **Kein root.** Die Kennung 1000 ist die des ersten Benutzers auf fast jedem
|
|
# Linux und auf den üblichen NAS-Geräten — wessen Dateien anders gehören, setzt
|
|
# in der Compose-Datei `user: "<uid>:<gid>"`. Das ist Docker-Bordmittel und
|
|
# braucht weder ein Startskript noch root im Container.
|
|
USER 1000:1000
|
|
|
|
# **Kein eigenes ASPNETCORE_URLS.** Das Basisimage setzt seit .NET 8 selbst
|
|
# ASPNETCORE_HTTP_PORTS=8080; beides zusammen ergab bei jedem Start die
|
|
# Warnung „Overriding HTTP_PORTS '8080'". Zwei Wege zum selben Port sind
|
|
# einer zu viel.
|
|
ENV FLARO_DATA=/config \
|
|
FLARO_CACHE=/cache \
|
|
FLARO_TRANSCODE=/transcode \
|
|
DOTNET_gcServer=0
|
|
|
|
# **Welches Abbild das hier ist.**
|
|
#
|
|
# Sechs Zeichen, bei jedem Bau neu gewürfelt — auch dann, wenn am Quelltext
|
|
# nichts anders ist. Die Version sagt, welcher Stand gemeint ist; diese
|
|
# Kennung sagt, welches Abbild wirklich läuft.
|
|
#
|
|
# Das ist keine Spielerei: zwei Funktionen standen nachweislich im Quelltext
|
|
# und waren auf dem Server trotzdem nicht zu sehen. Ob dort ein älteres
|
|
# Abbild lief, war nicht feststellbar, und die Fehlersuche lief einen halben
|
|
# Tag ins Leere. Seitdem beantwortet ein Blick die erste aller Fragen.
|
|
#
|
|
# Ganz unten, weil jede Änderung daran alle folgenden Schichten neu baut —
|
|
# und das sind hier nur noch die beiden COPY-Zeilen.
|
|
ARG FLARO_BUILD=DEV
|
|
ENV FLARO_BUILD=${FLARO_BUILD}
|
|
|
|
WORKDIR /app
|
|
COPY --from=build /app .
|
|
COPY --from=web /web/dist ./wwwroot
|
|
|
|
EXPOSE 8080
|
|
|
|
# **Eine Gesundheitsprüfung, die den Server fragt — nicht die Laufzeit.**
|
|
#
|
|
# Ohne sie weiss Docker nur, dass ein Prozess läuft. Die Oberfläche eines NAS
|
|
# zeigt dann minutenlang „wird bereitgestellt", während der Server längst
|
|
# antwortet, und niemand kann unterscheiden, ob er noch wandert oder hängt.
|
|
#
|
|
# `/health` und nicht `dotnet --info`: letzteres sagt nur, dass die Laufzeit
|
|
# da ist, und das war sie auch, als der Server noch migrierte.
|
|
#
|
|
# 60 Sekunden Anlaufzeit, weil die Migrationen beim ersten Start einer
|
|
# gefüllten Datenbank länger brauchen als ein Startvorgang sonst.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
|
CMD curl -fsS http://localhost:8080/health || exit 1
|
|
VOLUME ["/config", "/cache", "/transcode"]
|
|
|
|
ENTRYPOINT ["dotnet", "Flaro.Api.dll"]
|