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:
co-authored by
Claude Opus 5.5
parent
2972cdfeca
commit
75c6037431
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
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
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -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};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-2
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
-11
File diff suppressed because one or more lines are too long
+11
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -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">
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user