feat: ein Einstellungen-Tab -- und die Sicherung meldet sich zurueck

Die Zugangsdaten der Sicherung standen in einer Textdatei auf der Platte. Jetzt
stehen sie im Panel, zusammen mit der Discord-Einrichtung, die vom Konto
dorthin umgezogen ist.

DER EIGENTLICHE GEWINN IST NICHT DIE BEQUEMLICHKEIT. Eine Textdatei sagt
niemandem, ob die Sicherung heute Nacht gelaufen ist -- und eine Sicherung, die
still aufhoert zu laufen, ist der eigentliche Schaden, nicht die eine Nacht, in
der sie fehlt. Das Skript schreibt sein Ergebnis deshalb nach jedem Lauf in die
Datenbank, und die Karte zeigt es als Erstes: wann, ob erfolgreich, wie viele
Dateien, wie gross, und ob es in der Nextcloud gelandet ist oder nur lokal.
Nachgemessen: {"dateien":228,"groesse":12063419,"hoch":false,"ok":true}.

DAS PASSWORT GEHT NICHT UEBER DIE API. Das Skript laeuft auf dem Wirt, nicht im
Dienst -- es liest die Werte per `docker exec` DIREKT aus media.db. Ueber HTTP
geht das Passwort nur beim Eintragen im Browser, und das ueber TLS. Zurueck gibt
die API nur "hatPasswort: true", wie beim Discord-Geheimnis.

nextcloud.txt neben den Sicherungen gilt weiter und hat VORRANG: wer sie
angelegt hat, soll nicht suchen muessen, warum sie ploetzlich ignoriert wird.

Zwei Fussangeln beim Melden, beide beim Bauen aufgefallen:
- Der JSON-Text geht ueber die STANDARDEINGABE in den Container und nicht als
  Argument -- Anfuehrungszeichen ueberleben den Weg durch zwei Shells nicht.
- Auch dieses Hier-Dokument braucht das -replace "`r": dieselbe CRLF-Falle wie
  bei der Pruefung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 19:39:16 +02:00
co-authored by Claude Opus 5
parent 74cf56ce81
commit 4e613dd40d
7 changed files with 436 additions and 27 deletions
+74 -1
View File
@@ -33,7 +33,15 @@ import {
} from '../auth.js'
import { readdir, readFile, rm, stat } from 'node:fs/promises'
import { join } from 'node:path'
import { DISCORD_KEYS, ausDatenbank, discordConfig, setSetting } from '../settings.js'
import {
DISCORD_KEYS,
SICHERUNG_KEYS,
ausDatenbank,
discordConfig,
getSetting,
setSetting,
sicherungConfig,
} from '../settings.js'
import { discordRoutes, redirectUri } from './discord.js'
import {
absolutePath,
@@ -701,6 +709,19 @@ dashRoutes.get('/settings', (c) => {
!ausDatenbank(DISCORD_KEYS.roleIdAlt),
},
},
sicherung: (() => {
const sc = sicherungConfig()
let letzte: unknown = null
try {
const roh = getSetting(SICHERUNG_KEYS.letzte)
letzte = roh ? JSON.parse(roh) : null
} catch {
// Ein kaputter Eintrag ist kein Grund, die ganze Seite
// scheitern zu lassen -- dann steht dort eben "unbekannt".
letzte = null
}
return { ...sc, letzte }
})(),
})
})
@@ -712,9 +733,61 @@ dashRoutes.post('/settings', async (c) => {
guildId?: string
roleIds?: string | string[]
}
sicherung?: {
basis?: string
benutzer?: string
passwort?: string
ordner?: string
behalten?: number | string
}
}>(c)
if (!body) return c.json(KEIN_JSON, 400)
// -- Sicherung ----------------------------------------------------------
if (body.sicherung) {
const sic = body.sicherung
if (sic.basis !== undefined) {
const v = sic.basis.trim().replace(/\/+$/, '')
if (v && !/^https?:\/\//.test(v)) {
return c.json({ error: 'Adresse muss mit http:// oder https:// anfangen' }, 400)
}
setSetting(SICHERUNG_KEYS.basis, v)
}
if (sic.benutzer !== undefined) setSetting(SICHERUNG_KEYS.benutzer, sic.benutzer.trim())
if (sic.ordner !== undefined) {
// Kein fuehrender Schraegstrich: der Pfad haengt hinter der
// WebDAV-Wurzel, und "//Sicherungen" ergibt dort einen Ordner
// ohne Namen.
setSetting(SICHERUNG_KEYS.ordner, sic.ordner.trim().replace(/^\/+|\/+$/g, ''))
}
if (sic.behalten !== undefined) {
const n = Number(sic.behalten)
if (!Number.isFinite(n) || n < 1 || n > 365) {
return c.json({ error: 'Behalten: zwischen 1 und 365' }, 400)
}
setSetting(SICHERUNG_KEYS.behalten, String(Math.round(n)))
}
// Wie beim Discord-Geheimnis: leer heisst unveraendert, "-" entfernt.
if (sic.passwort !== undefined) {
const v = sic.passwort.trim()
if (v === '-') setSetting(SICHERUNG_KEYS.passwort, '')
else if (v) setSetting(SICHERUNG_KEYS.passwort, v)
}
if (!body.discord) {
const jetzt = sicherungConfig()
return c.json({
ok: true,
bereit: jetzt.bereit,
fehlt: [
!jetzt.basis && 'Adresse',
!jetzt.benutzer && 'Benutzername',
!jetzt.hatPasswort && 'App-Passwort',
].filter(Boolean),
})
}
}
const d = body.discord
if (!d) return c.json({ error: 'nichts zu speichern' }, 400)