Später · 20 Konfig-Sicherung ins private Gitea-Repo, Geheimnisse geschwärzt

- kern/schwaerzen.py + dieselben Regeln im Ausführer (Test vergleicht beide): private Schlüssel, Werte hinter
  password/secret/token/key…, Passwort-Hashes, htpasswd, Zugangsdaten in Adressen, Authorization-Kopfzeilen,
  Token-Formate, lange Hex-Ketten
- Ausführer: sammelt Proxmox (Gäste, Speicher, Aufträge, Netz), AdGuard, NPMplus (ohne tls/), schwärzt auf dem Host,
  sendet alle 6 h und auf Abruf (Aktion konfig_senden) an POST /api/homelab/ausfuehrer/konfig
- Homelab-Teil: GET /api/homelab/konfig (frisch=1), dazu Compose und .env der Arcane-Projekte, geschwärzt
- KI-Box: services/konfig_sicherung.py legt das Repo beim ersten Lauf privat an, Arbeitskopie je Lauf auf Gitea-Stand,
  Commit nur bei Änderungen; Timer täglich 02:30, Knopf „Jetzt sichern“ unter Einstellungen → Homelab
- Totmannschalter und Wächter kennen den Timer; Doku

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-26 14:23:19 +02:00
co-authored by Claude Opus 5.5
parent 2972cdfeca
commit 75c6037431
43 changed files with 937 additions and 40 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{i as e,r as t}from"./dist-aE7UmubC.js";import{G as n,K as r}from"./index-Cdq4iteU.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-DG7u1mB-.js";var l=t(),u=e();function d(e){let t=(0,l.c)(8),{onSchliessen:d}=e,f;t[0]===d?f=t[1]:(f=e=>!e&&d(),t[0]=d,t[1]=f);let p;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(p=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(n,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=p):p=t[2];let m;t[3]===d?m=t[4]:(m=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-7 border-white/[0.06] bg-[#0a0d14] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[p,(0,u.jsx)(r,{onGewaehlt:d})]}),t[3]=d,t[4]=m);let h;return t[5]!==f||t[6]!==m?(h=(0,u.jsx)(c,{open:!0,onOpenChange:f,children:m}),t[5]=f,t[6]=m,t[7]=h):h=t[7],h}export{d as MenueSchublade};
import{i as e,r as t}from"./dist-aE7UmubC.js";import{G as n,K as r}from"./index-hnnYWPzT.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-DG7u1mB-.js";var l=t(),u=e();function d(e){let t=(0,l.c)(8),{onSchliessen:d}=e,f;t[0]===d?f=t[1]:(f=e=>!e&&d(),t[0]=d,t[1]=f);let p;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(p=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(n,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=p):p=t[2];let m;t[3]===d?m=t[4]:(m=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-7 border-white/[0.06] bg-[#0a0d14] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[p,(0,u.jsx)(r,{onGewaehlt:d})]}),t[3]=d,t[4]=m);let h;return t[5]!==f||t[6]!==m?(h=(0,u.jsx)(c,{open:!0,onOpenChange:f,children:m}),t[5]=f,t[6]=m,t[7]=h):h=t[7],h}export{d as MenueSchublade};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -9,11 +9,11 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<title>Homelab Orchestrator</title>
<script type="module" crossorigin src="/assets/index-Cdq4iteU.js"></script>
<script type="module" crossorigin src="/assets/index-hnnYWPzT.js"></script>
<link rel="modulepreload" crossorigin href="/assets/dist-aE7UmubC.js">
<link rel="modulepreload" crossorigin href="/assets/button-BHVKhtQ6.js">
<link rel="modulepreload" crossorigin href="/assets/dist-CakcBaML.js">
<link rel="modulepreload" crossorigin href="/assets/abfragen-DNMpmgyt.js">
<link rel="modulepreload" crossorigin href="/assets/abfragen-CLTLeU-R.js">
<link rel="modulepreload" crossorigin href="/assets/useSelector-DBNxxw17.js">
<link rel="modulepreload" crossorigin href="/assets/useMatch-DDVpPKJI.js">
<link rel="modulepreload" crossorigin href="/assets/useRouterState-DJr4hrdj.js">
+16 -1
View File
@@ -11,7 +11,7 @@ import { stromVerbunden } from "./strom"
import type {
Ablauf, AktionsErgebnis, Altrest, AufraeumKandidat, BoxMesswerte, EinstellungenStand, Hinweis, HomelabEinstellungen,
HomelabLauf, HomelabMesswerte, HomelabSpeicher, HomelabStand, HomelabZertifikate, NeuesGeraet, NeuerStack, NeuWahl, DockerLage, Neuerungen, HomelabWartung, WartungsRegel,
LucyAutonomie, LucyStufe, Rueckfragen, Wochenbericht, Platte, HomelabPlatten, AusreisserLage, Instanz, InstanzMitStand, Job, Modell, Nutzung, PartnerStatus,
LucyAutonomie, LucyStufe, Rueckfragen, Wochenbericht, Platte, HomelabPlatten, AusreisserLage, KonfigSicherung, Instanz, InstanzMitStand, Job, Modell, Nutzung, PartnerStatus,
ProtokollEintrag, Radar, SammelPlanSchritt, Sammellauf, Sicherung, Start, TelegramTest, UpdateDetails, ZielAktion,
ZielStand, Zeitraum,
} from "./typen"
@@ -512,6 +512,21 @@ export const useNeuerungen = (zid: string, baustein: string, aktiv: boolean) =>
retry: false,
})
/** Konfig-Sicherung ins private Gitea-Repo (seit 26.09.2026). Nach „Jetzt sichern“ alle 5 s nachsehen. */
export const useKonfigSicherung = (schnell: boolean) =>
useQuery({
queryKey: ["konfig-sicherung"],
queryFn: () => api<KonfigSicherung>("/api/konfig-sicherung"),
refetchInterval: schnell ? 5000 : 60_000,
retry: false,
})
export const useKonfigSichern = () =>
useAktion(() => post<AktionsErgebnis>("/api/konfig-sicherung?warten=0"), {
erfolg: (_, e) => (e as AktionsErgebnis).text ?? "Die Sicherung läuft.",
neuLaden: ["konfig-sicherung"],
})
/** Ausreißer ohne feste Schwellen (seit 26.09.2026): Lernstand und was gerade auffällt, je Bereich. */
export const useAusreisser = (bereich: "box" | "homelab", aktiv = true) =>
useQuery({
+15
View File
@@ -926,3 +926,18 @@ export interface AusreisserLage {
stunde: number | null
auffaellig: { quelle: string; name: string; folge: number; wert: number; normal: number; fach: number; titel: string }[]
}
// --- Konfig-Sicherung (GET /api/konfig-sicherung; seit 26.09.2026) -------------------------------------------------
export interface KonfigSicherung {
zuletzt: {
zeit: number
ergebnis: "gesichert" | "unveraendert" | "fehler"
text: string
commit?: string
dateien?: number
geschwaerzt?: number
fehler?: string[]
} | null
repo: string | null
}
+48 -1
View File
@@ -9,7 +9,7 @@ import { Freiheiten, WochenberichtAbschnitt } from "@/components/lucy/LucyEinste
import { OffeneRueckfragen, RueckfragenVerlauf } from "@/components/lucy/Rueckfragen"
import { Button } from "@/components/ui/button"
import {
useAdguardZugang, useArcaneEcht, useNasSnmp, useArcaneSchluessel, useEinstellungenStand, useHomelabEinstellungen, useInstanz, usePartner,
useAdguardZugang, useArcaneEcht, useKonfigSichern, useKonfigSicherung, useNasSnmp, useArcaneSchluessel, useEinstellungenStand, useHomelabEinstellungen, useInstanz, usePartner,
useRadarSchalten, useSoftwareStand, useTelegramTest, useUpdateFensterSetzen,
} from "@/lib/abfragen"
import { api, post } from "@/lib/api"
@@ -643,6 +643,52 @@ function NasSnmp({ h }: { h: HomelabEinstellungen }) {
)
}
/** Konfig-Sicherung (seit 26.09.2026): täglich 02:30 geschwärzt ins private Gitea-Repo; hier der letzte Lauf. */
function KonfigSicherungAbschnitt() {
const [seit, setSeit] = useState<number | null>(null)
const q = useKonfigSicherung(seit !== null)
const sichern = useKonfigSichern()
const z = q.data?.zuletzt
// Nach dem Knopf schnell nachsehen, bis ein neuer Lauf dasteht (höchstens drei Minuten).
useEffect(() => {
if (seit === null) return
if (z && z.zeit * 1000 >= seit) {
setSeit(null)
return
}
const uhr = window.setTimeout(() => setSeit(null), 180_000)
return () => window.clearTimeout(uhr)
}, [seit, z])
const art = z?.ergebnis === "fehler" ? "rot" : z?.ergebnis === "gesichert" ? "gruen" : "grau"
return (
<Abschnitt titel="Konfig-Sicherung">
<p className="text-sm text-text-2">
Täglich um 02:30 wandern die Einstellungen von Proxmox, AdGuard, NPMplus, den Arcane-Projekten und der KI-Box in
dein privates Gitea-Repo — Geheimnisse geschwärzt. Jede Änderung ist dort als Vorher-nachher zu sehen.
</p>
{z ? (
<div className="flex flex-wrap items-center gap-2 text-sm text-text-2">
<Chip art={art}>{z.ergebnis === "gesichert" ? "gesichert" : z.ergebnis === "fehler" ? "gescheitert" : "unverändert"}</Chip>
<span>{flugplanZeit(new Date(z.zeit * 1000).toISOString()).toLowerCase()} · {z.text}</span>
</div>
) : (
<p className="text-sm text-text-3">{q.isPending ? "Wird gelesen …" : "Noch kein Lauf."}</p>
)}
<div className="flex flex-wrap items-center gap-3">
<Button size="sm" variant="outline" disabled={sichern.isPending || seit !== null}
onClick={() => sichern.mutate(undefined, { onSuccess: () => setSeit(Date.now()) })}>
{seit !== null ? "Sichert …" : "Jetzt sichern"}
</Button>
{q.data?.repo && (
<a href={q.data.repo} target="_blank" rel="noopener" className="inline-flex items-center gap-1 text-sm text-cyan hover:underline">
In Gitea ansehen <ExternalLink className="size-3.5" aria-hidden />
</a>
)}
</div>
</Abschnitt>
)
}
function HomelabBetrieb({ h }: { h: HomelabEinstellungen }) {
const f = h.ausfuehrer
return (
@@ -680,6 +726,7 @@ function HomelabKarte() {
<ArcaneSchluessel h={h.data} />
<AdguardZugang h={h.data} />
<NasSnmp h={h.data} />
<KonfigSicherungAbschnitt />
<Abschnitt titel="Wartungsfenster und Regel je Gerät"><Wartungsfenster aktiv={aktiv} /></Abschnitt>
<HomelabBetrieb h={h.data} />
</>