Files
d4rk_media/server/src/config.ts
T
D4rkst3randClaude Opus 5 5edbe6c45f feat: ein Name statt zwei, und PUBLIC_URL ist nur noch der Ursprung
fivecdn.d4rkst3r.de und fivemanage.d4rkst3r.de fallen weg -- wir betreiben
kein Fivemanage, also tragen unsere Adressen auch nicht dessen Namen.
Dashboard und API liegen an der Wurzel, die Dateien unter /f/.

Der Ein-Namen-Betrieb war bis jetzt nicht ausdrueckbar. Der Kommentar in
config.ts versprach "leer lassen, wenn alles unter einem Namen laufen
soll", aber optional() faellt bei leerem Wert auf den Host aus PUBLIC_URL
zurueck -- und dann gilt JEDE Anfrage als Anfrage an den Dateiwirt: kein
Dashboard, keine API, /health gibt 404 und die Anmeldung 405 "hier gibt
es nur Dateien". Nichts ist kaputt, und niemand kommt darauf. Deshalb ist
der Ein-Namen-Betrieb jetzt der Standard und der zweite Name die Ansage.

PUBLIC_URL ist nur noch der Ursprung; das /f haengt der Dienst selbst an.
Wer es mitschriebe, bekaeme Adressen mit /f/f/, und das faellt erst auf,
wenn das erste Bild fehlt. Praefix und Route haengen jetzt an derselben
Stelle (config.filePrefix), damit die zurueckgegebene Adresse und die
Route, die sie ausliefert, nicht auseinanderlaufen koennen.

Beide Betriebsarten durchgemessen: mit einem Namen kommt
.../f/vehicles/adder.png zurueck und liefert die Datei; mit gesetztem
FILES_HOST kommt sie ohne Praefix, liegt unter dem Dateihost, ist am
Dashboard-Namen zusaetzlich unter /f/ erreichbar, und unter dem Dateihost
gibt es weiterhin keine API. Welche Betriebsart laeuft, sagt der Dienst
in der zweiten Startzeile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:58:20 +02:00

100 lines
3.8 KiB
TypeScript

// Alle Einstellungen an einer Stelle, alle aus der Umgebung.
//
// Was fehlt, faellt beim START auf und nicht beim ersten Upload. Das ist
// Absicht: ein Dienst, der bereitwillig hochfaehrt und erst Stunden spaeter
// merkt, dass ihm die halbe Konfiguration fehlt, kostet mehr Zeit als einer,
// der sich sofort weigert.
import { existsSync, mkdirSync } from 'node:fs'
import { resolve } from 'node:path'
function required(name: string): string {
const value = process.env[name]
if (!value) {
console.error(`[config] ${name} fehlt. Ohne die Variable startet der Dienst nicht.`)
process.exit(1)
}
return value
}
function optional(name: string, fallback: string): string {
return process.env[name] || fallback
}
function number(name: string, fallback: number): number {
const raw = process.env[name]
if (!raw) return fallback
const parsed = Number(raw)
if (!Number.isFinite(parsed) || parsed <= 0) {
console.error(`[config] ${name}="${raw}" ist keine sinnvolle Zahl.`)
process.exit(1)
}
return parsed
}
/** Wohin die Dateien und die Datenbank kommen. EIN Ordner, damit eine
* Sicherung aus genau einem `cp -a` besteht. */
const dataDir = resolve(optional('DATA_DIR', './data'))
const filesDir = resolve(dataDir, 'files')
for (const dir of [dataDir, filesDir]) {
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
}
/** Unter welcher Adresse der Dienst oeffentlich steht. Daraus wird die URL
* gebaut, die nach dem Upload zurueckkommt — und die im Handy in der Vorlage
* steht. Nur der Ursprung, ohne Pfad und ohne abschliessenden Schraegstrich:
* https://media.d4rkst3r.de
*
* Das /f/ im Ein-Namen-Betrieb haengt der Dienst selbst an. Wer es hier
* mitschriebe, bekaeme Adressen mit /f/f/ — und die faende niemand, bis das
* erste Bild fehlt. */
const publicUrl = required('PUBLIC_URL').replace(/\/+$/, '')
/** Ein Name oder zwei?
*
* Gesetzt → ZWEI Namen: unter diesem Host gibt es ausschliesslich Dateien,
* kein Dashboard, keine API. Die oeffentliche Adresse hat damit
* keine Angriffsflaeche, und die URLs kommen ohne Praefix aus.
* Leer → EIN Name: Dashboard und API an der Wurzel, Dateien unter /f/.
*
* Frueher war der Standard der Host aus PUBLIC_URL. Das ist bei zwei Namen
* richtig und bei einem toedlich: dann gilt JEDE Anfrage als Anfrage an den
* Dateiwirt, /health antwortet 404 und die Anmeldung 405 "hier gibt es nur
* Dateien". Nichts ist kaputt, und niemand kommt darauf. Deshalb ist der
* Ein-Namen-Betrieb jetzt der Standard und der zweite Name die Ansage. */
const filesHost = (process.env.FILES_HOST ?? '').trim().toLowerCase()
export const config = {
port: number('PORT', 8080),
dataDir,
filesDir,
dbPath: resolve(dataDir, 'media.db'),
publicUrl,
/** Der Hostname, unter dem NUR Dateien ausgeliefert werden — oder leer,
* wenn alles unter einem Namen laeuft. Siehe oben. */
filesHost,
/** Was zwischen Adresse und Pfad steht. Bei zwei Namen nichts, bei einem
* "/f" — beides an genau EINER Stelle entschieden, damit die gebaute URL
* und die Route, die sie ausliefert, nicht auseinanderlaufen koennen. */
filePrefix: filesHost ? '' : '/f',
/** Wird beim Start angelegt, falls es noch keinen Benutzer gibt. */
adminPassword: required('ADMIN_PASSWORD'),
adminUsername: optional('ADMIN_USERNAME', 'admin'),
/** Groesste erlaubte Datei. Ein freigestelltes Fahrzeugbild wiegt rund
* 200 KB; 64 MB lassen Luft fuer Videos, ohne dass ein Fehlgriff die
* Platte fuellt. */
maxUploadBytes: number('MAX_UPLOAD_MB', 64) * 1024 * 1024,
/** Wie lange eine Anmeldung im Dashboard haelt. */
sessionDays: number('SESSION_DAYS', 30),
dev: process.env.NODE_ENV !== 'production',
} as const
export type Config = typeof config