Weiter zum Inhalt
  • Home
  • Aktuell
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. Proxmox-Updater (Host/LXC/VM) auch ioBroker,piHole,etc

NEWS

  • Der neue Monatsrückblick für September 2026 ist online!
    BluefoxB
    Bluefox
    8
    1
    292

  • Neu im Blog: Was die neue Website alles kann
    BluefoxB
    Bluefox
    7
    1
    253

  • Node.js 24 ist jetzt offiziell empfohlen!
    apollon77A
    apollon77
    9
    1
    522

Proxmox-Updater (Host/LXC/VM) auch ioBroker,piHole,etc

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
134 Beiträge 16 Kommentatoren 20.5k Aufrufe 23 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • T
    T
    TT-Tom
    schrieb am zuletzt editiert von
    #94

    @bass-t

    das gleiche Problem habe ich auch mit meinem MySql-Server gehabt. Dort wurde auch bei
    dist-upgrade ein Auswahlfenster geöffnet. Habe das Ganze dann genau abgeschlossen, wie du es hier beschrieben hast. Habe den LXC erstmal ausgeklammert.

    Gruß Tom

    Wenn meine Hilfe erfolgreich war, benutze bitte das Voting unten rechts im Beitrag

    Public Transport
    https://github.com/tt-tom17/ioBroker.public-transport
    https://github.com/tt-tom17/ioBroker.public-transport/wiki

    NSPanel Adapter Wiki
    https://github.com/ticaki/ioBroker.nspanel-lovelace-ui/wiki

    Bass-TB 1 Antwort Letzte Antwort
    0
    • T TT-Tom

      @bass-t

      das gleiche Problem habe ich auch mit meinem MySql-Server gehabt. Dort wurde auch bei
      dist-upgrade ein Auswahlfenster geöffnet. Habe das Ganze dann genau abgeschlossen, wie du es hier beschrieben hast. Habe den LXC erstmal ausgeklammert.

      Bass-TB
      Bass-TB
      Bass-T
      schrieb am zuletzt editiert von
      #95

      @TT-Tom ja, genau derselbe Grund.

      Das Problem ist inzwischen eindeutig identifiziert und der technische Fix ist auf develop bereits weitgehend umgesetzt.

      Interaktive Paketdialoge laufen jetzt über einen persistenten PTY-Kanal, sodass sich der Benutzer nach einem Disconnect wieder an den laufenden Job hängen und die Abfrage beantworten kann.

      Der CLI-Teil funktioniert bereits:

      ultimate-updater job attach <JOB_ID>

      Damit wurden auf dem Testcluster echte dpkg-/whiptail-Prompts inklusive Disconnect/Reconnect erfolgreich getestet.

      Was aktuell noch fehlt, ist die direkte Eingabe über die WebUI. Das ist der nächste Schritt.

      Für v5.1.2 bleibt dein Workaround daher momentan sinnvoll:
      den betroffenen LXC entweder vorübergehend ausklammern oder bei einem interaktiven Prompt wie beschrieben manuell sauber zu Ende konfigurieren.

      Der vollständige Fix wird dann mit einem kommenden Release aus develop übernommen.

      Proxmox Cluster

      • NUC7i5 ioBroker Server (Proxmox)
      • HP Elitedesk 800 G4 SSF (Proxmox)
      • HP Elitedesk 800 G2 DM (Proxmox)
        mein Proxmox-Updater für euch

      KNX / Broadlink / Tasmota ESPs / Zigbee
      Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

      1 Antwort Letzte Antwort
      1
      • NegaleinN
        NegaleinN
        Negalein
        schrieb am zuletzt editiert von
        #96

        @bass-t

        wie verhält es sich, wenn der Host noch auf 7.4-20 ist?

        Vorher selber auf 9.x updaten?
        Oder schafft das der Updater?

        ° Node.js & System Update ---> sudo apt update, iob stop, sudo apt full-upgrade
        ° Node.js Fixer ---> iob nodejs-update
        ° Fixer ---> iob fix

        1 Antwort Letzte Antwort
        0
        • Bass-TB
          Bass-TB
          Bass-T
          schrieb am zuletzt editiert von Bass-T
          #97

          @negalein es könnte funktionieren, ich habe es aber nie produktiv gestestet.
          Lediglich von 8 auf 9. Dass hatte funktioniert.

          Persöhnlich würde ich einen sprung von 2 Maintain Versionen glaub nicht automatisiert laufen lassen - persönliche einschätzung.

          Natürlich zum Updaten innerhalb der Version - sollte kein Thema sein und wunderprächtig funktionieren

          Ich muss mich revidieren. Ein Versionssprung macht mein Updater NICHT - sry

          Proxmox Cluster

          • NUC7i5 ioBroker Server (Proxmox)
          • HP Elitedesk 800 G4 SSF (Proxmox)
          • HP Elitedesk 800 G2 DM (Proxmox)
            mein Proxmox-Updater für euch

          KNX / Broadlink / Tasmota ESPs / Zigbee
          Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

          1 Antwort Letzte Antwort
          0
          • David G.D
            David G.D
            David G.
            schrieb am zuletzt editiert von
            #98

            Hey,

            ich hatte @Nagelein ja gefragt wo die Daten aus seiner mal her sind - leider automatisch aus dem Script.

            Ich habe mit gestern mit Claude ein Script geschrieben, was mir alles in den iobroker holt.

            Status & Datenpunkte

            • Kompletter Status aller Targets (Host, VMs, CTs) als Datenpunkte, automatisch je
              Ziel angelegt (inkl. Betriebssystem, erreichbar/offline, Anzahl offener Updates,
              Security- vs. normale Updates, Reboot-noetig-Flag, letzter Check/Update, u.v.m.)
            • Health-Zusammenfassung auf einen Blick: Summe offener Security-/Normal-Updates,
              Liste der Targets mit offenen Updates, nicht erreichbare Targets, Targets mit
              ausstehendem Reboot
            • Job-Historie (laufende & vergangene Check-/Update-Jobs je Target) inkl.
              automatischem Nachladen der Logzeilen bei fehlgeschlagenen/abgebrochenen Jobs
            • Live-Indikator "laeuft gerade ein Job" mit adaptivem Poll-Takt (schnell waehrend
              etwas laeuft, langsam wenn nichts ansteht - erkennt auch kurze Jobs von nur
              wenigen Sekunden zuverlaessig)
            • Automatisches Aufraeumen: nicht mehr existierende Targets verschwinden auch aus
              ioBroker wieder

            Aktionen per Knopfdruck (aus ioBroker/VIS)

            • Update einzeln je Host/VM/CT
            • Update Sammel-Button: alle VMs/CTs auf einmal (Host bewusst ausgenommen)
            • Update Sammel-Button: nur Targets mit Security-relevanten Updates
            • Check einzeln je Host/VM/CT (nur Status aktualisieren, kein Update)
            • Check Sammel-Button: alle sofort auf einmal (schnell, kurzzeitig hoehere Last)
            • Schutz gegen Doppelausloesung: neue Aktion wird blockiert, solange bereits ein
              Job laeuft

            Automatisierung

            • Stuendlicher voller Status-Refresh
            • Naechtlicher gestaffelter Check aller Targets: geht nacheinander mit mehreren
              Minuten Abstand durch (statt alle gleichzeitig) - schont Host und Netzwerk
            • Alle Zeiten/Intervalle zentral als Variablen im Skriptkopf einstellbar

            Sicherheit

            • Zugangsdaten nur lokal im Skript, Session-Cookie + CSRF-Token werden bei jedem
              Login frisch geholt und nicht dauerhaft gespeichert
            // ioBroker JavaScript-Adapter Script
            // Liest den Status aller Proxmox-Hosts/VMs/CTs aus der "Ultimate Updater"-Web-API
            // (https://github.com/BassT23/Proxmox) und legt fuer jedes Target alle verfuegbaren
            // Felder als Datenpunkte an. Zusaetzlich Buttons zum Aktualisieren - einzeln pro
            // VM/CT, einzeln fuer den Host, und ein Sammel-Button fuer alle VMs/CTs (ohne Host).
            // Aktualisiert sich automatisch stuendlich.
            //
            // WICHTIG: Benutzername/Passwort direkt hier im Script-Editor eintragen - NICHT in
            // einem Chat/einer KI-Anfrage teilen. Die Web-API nutzt Session-Cookie + CSRF-Token,
            // beides wird bei jedem Login frisch geholt und nicht dauerhaft gespeichert.
            
            const UU_BASE = "https://192.168.99.58:8765";
            const UU_USERNAME = "root";
            const UU_PASSWORD = "xxxxxxxx"; 
            
            const ROOT = "0_userdata.0.proxmoxUpdater";
            
            // ---- Alle Zeit-/Intervall-Einstellungen zentral an einem Ort ----
            const REFRESH_CRON = "0 * * * *"; // voller Status-Refresh (alle Targets, Cleanup, Zusammenfassung, ...)
            const POLL_INTERVAL_BUSY_MS = 2000; // Job-Status-Poll-Takt, solange ein Check/Update-Job laeuft
            const POLL_INTERVAL_IDLE_MS = 30000; // Job-Status-Poll-Takt, wenn nichts laeuft
            const STAGGERED_CHECK_CRON = "0 4 * * *"; // Start des naechtlichen gestaffelten Checks aller Targets
            const STAGGERED_CHECK_DELAY_MINUTES = 3; // Abstand zwischen den einzelnen Targets beim gestaffelten Check
            const STAGGERED_CHECK_DELAY_MS = STAGGERED_CHECK_DELAY_MINUTES * 60 * 1000;
            
            function sanitize(s) {
                return String(s).replace(/[^a-zA-Z0-9_]/g, "_");
            }
            
            // Reiner Name reicht nicht als Ordner-Key - mehrere Targets koennen denselben Namen
            // tragen (z.B. mehrere CTs heissen "AgentDVR"), dann wuerden sie sich sonst beim
            // Anlegen der Datenpunkte gegenseitig ueberschreiben. Die ID ist dagegen garantiert
            // eindeutig, wird also immer mit angehaengt; der Name bleibt trotzdem lesbar im Key
            // und zusaetzlich als common.name des Channels sichtbar.
            function targetKey(t) {
                return `${sanitize(t.name || t.id)}_${sanitize(t.id)}`;
            }
            
            function createChannelP(id, name) {
                return setObjectAsync(id, { type: "channel", common: { name }, native: {} });
            }
            
            // read-only per Default (reine Anzeigewerte); Buttons ueberschreiben write:true selbst.
            function createStateP(id, common, val) {
                return createStateAsync(id, val, { read: true, write: false, ...common });
            }
            
            // httpGetAsync/httpPostAsync liefern per Default responseType "text", nicht "json"
            // (siehe getHttpRequestConfig in ioBroker.javascript) - ohne explizites responseType:"json"
            // kommt ein roher String zurueck und z.B. "status.targets" ist dann still undefined statt
            // eines Fehlers. Zusaetzlich JSON.parse-Fallback, falls trotzdem ein String zurueckkommt.
            function parseJson(data) {
                return typeof data === "string" ? JSON.parse(data) : data;
            }
            
            async function uuLogin() {
                const res = await httpPostAsync(
                    `${UU_BASE}/api/login`,
                    { username: UU_USERNAME, password: UU_PASSWORD },
                    { validateCertificate: false, timeout: 15000, responseType: "json" },
                );
                if (res.statusCode !== 200) {
                    throw new Error(`Login fehlgeschlagen: HTTP ${res.statusCode}`);
                }
                const setCookie = res.headers["set-cookie"];
                const cookie = Array.isArray(setCookie)
                    ? setCookie.map(c => c.split(";")[0]).join("; ")
                    : String(setCookie || "").split(";")[0];
                const data = parseJson(res.data);
                return { cookie, csrf: data && data.csrf };
            }
            
            async function uuGet(path, session) {
                const res = await httpGetAsync(`${UU_BASE}${path}`, {
                    headers: { Cookie: session.cookie },
                    validateCertificate: false,
                    timeout: 15000,
                    responseType: "json",
                });
                if (res.statusCode !== 200) {
                    throw new Error(`${path} -> HTTP ${res.statusCode}`);
                }
                return parseJson(res.data);
            }
            
            async function uuPost(path, session, body) {
                const res = await httpPostAsync(`${UU_BASE}${path}`, body || {}, {
                    headers: { Cookie: session.cookie, "X-CSRF-Token": session.csrf },
                    validateCertificate: false,
                    timeout: 15000,
                    responseType: "json",
                });
                if (res.statusCode !== 200) {
                    throw new Error(`${path} -> HTTP ${res.statusCode}`);
                }
                return parseJson(res.data);
            }
            
            async function uuLogout(session) {
                try {
                    await uuPost("/api/logout", session, {});
                } catch (e) {
                    // egal - Session laeuft sonst einfach serverseitig aus
                }
            }
            
            // Server-seitige Definition von "beschaeftigt" (server.py: state in {running, pending, starting}) -
            // nicht nur "running", damit ein gerade erst gestarteter Job nicht uebersehen wird.
            const BUSY_STATES = ["running", "pending", "starting"];
            
            // Bei diesen Endzustaenden lohnt es sich, das Log nachzuladen (bei "completed" nicht
            // noetig - da ist eh alles gut gegangen).
            const FAILURE_STATES = ["failed", "interrupted"];
            
            async function isAnyJobRunning(session) {
                const jobsResp = await uuGet("/api/jobs", session);
                const jobs = jobsResp.jobs || [];
                return jobs.some(j => BUSY_STATES.includes(j.state));
            }
            
            // Legt rekursiv alle Felder eines Targets (auch verschachtelte wie "updates") als
            // Datenpunkte an. Arrays werden als JSON-String gespeichert.
            async function flattenAndSet(prefix, obj) {
                for (const [key, val] of Object.entries(obj)) {
                    const id = `${prefix}.${sanitize(key)}`;
                    if (val !== null && typeof val === "object" && !Array.isArray(val)) {
                        await createChannelP(id, key);
                        await flattenAndSet(id, val);
                    } else if (Array.isArray(val)) {
                        await createStateP(id, { name: key, type: "string", role: "json" }, JSON.stringify(val));
                    } else {
                        const type = typeof val === "boolean" ? "boolean" : typeof val === "number" ? "number" : "string";
                        const safeVal = val === undefined || val === null ? (type === "string" ? "" : null) : val;
                        await createStateP(id, { name: key, type, role: "value" }, safeVal);
                    }
                }
            }
            
            // Entfernt Target-Ordner, die beim letzten Refresh noch da waren, aber jetzt nicht mehr
            // von der API geliefert werden (VM/CT geloescht, oder - einmalig - Umstellung von
            // ID- auf Name-basierte Ordner). Vergleicht ueber das ".id"-Feld jedes Ordners.
            async function cleanupStaleTargets(currentTargets) {
                const currentPrefixes = new Set(currentTargets.map(targetKey));
                const existingPrefixes = new Set();
                $(`${ROOT}.targets.*.id`).each(id => {
                    const prefix = id.replace(`${ROOT}.targets.`, "").replace(/\.id$/, "");
                    if (!prefix.includes(".")) {
                        existingPrefixes.add(prefix);
                    }
                });
                for (const prefix of existingPrefixes) {
                    if (!currentPrefixes.has(prefix)) {
                        const fullId = `${ROOT}.targets.${prefix}`;
                        await deleteObjectAsync(fullId, true);
                        log(`[proxmoxUpdater] Nicht mehr gelieferter Target-Ordner entfernt: ${fullId}`);
                    }
                }
            }
            
            // Leichtgewichtiger Check (nur /api/jobs, kein voller Status) - dafuer viel oefter
            // aufgerufen als der schwere refreshStatus(), damit "laeuft gerade was" auch bei
            // kurzen Update-Jobs (oft nur Sekunden bis wenige Minuten) nicht zwischen zwei
            // stuendlichen Refreshs verpasst wird. session wird wiederverwendet, falls schon
            // eine besteht (z.B. direkt nach einem Button-Klick), sonst wird selbst eingeloggt.
            async function refreshJobStatus(session) {
                const jobsResp = await uuGet("/api/jobs", session);
                const jobs = jobsResp.jobs || [];
                const jobRunning = jobs.some(j => BUSY_STATES.includes(j.state));
                await createStateP(`${ROOT}.jobRunning`, { name: "Läuft gerade ein Check/Update-Job?", type: "boolean", role: "indicator.working" }, jobRunning);
            
                // Job-Historie: letzte Checks/Updates pro Target (unit, target, state, exit_code, Zeiten ...)
                await createChannelP(`${ROOT}.jobs`, "Job-Historie");
                await createStateP(`${ROOT}.jobs.count`, { name: "Anzahl Jobs (laufend + Historie)", type: "number", role: "value" }, jobs.length);
                for (const j of jobs) {
                    const jobPrefix = `${ROOT}.jobs.${sanitize(j.unit)}`;
                    await createChannelP(jobPrefix, `${j.target || j.unit} (${j.state})`);
                    await flattenAndSet(jobPrefix, j);
                    if (FAILURE_STATES.includes(j.state)) {
                        await fetchJobLogIfMissing(jobPrefix, j.unit, session);
                    }
                }
                return jobRunning;
            }
            
            // Holt die letzten Logzeilen eines fehlgeschlagenen/abgebrochenen Jobs nach
            // (GET /api/jobs/<unit>/log - liefert die letzten 200 Zeilen aus journalctl).
            // Nur einmal pro Job noetig, daher Ueberspringen falls schon vorhanden.
            async function fetchJobLogIfMissing(jobPrefix, unit, session) {
                const logId = `${jobPrefix}.log`;
                const existing = await getStateAsync(logId);
                if (existing && existing.val) {
                    return;
                }
                try {
                    const logResp = await uuGet(`/api/jobs/${encodeURIComponent(unit)}/log`, session);
                    await createStateP(logId, { name: "Log (letzte Zeilen bei Fehlschlag)", type: "string", role: "text" }, String(logResp.log || ""));
                } catch (e) {
                    log(`[proxmoxUpdater] Log fuer Job ${unit} konnte nicht geholt werden: ${e.message}`, "warn");
                }
            }
            
            // Selbstjustierender Poll-Takt, wie ihn auch die Original-Web-GUI von Ultimate
            // Updater selbst verwendet (server.py: loadJobs() pollt alle 2s solange ein Job
            // "running" ist, sonst alle 10s - wir nehmen 2s/30s, damit unser normaler Takt
            // erhalten bleibt): solange ein Job laeuft, alle 2s pruefen, damit auch kurze
            // Jobs von nur 10-15s sicher erfasst werden; sonst normaler 30s-Rhythmus.
            // pollTimer wird bei jedem Tick neu gesetzt (nie parallel mehrere Timer aktiv).
            let pollTimer = null;
            
            async function pollJobStatusOnce() {
                let session;
                let jobRunning = false;
                try {
                    session = await uuLogin();
                    jobRunning = await refreshJobStatus(session);
                } catch (e) {
                    log(`[proxmoxUpdater] Job-Status-Check fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    if (session) {
                        await uuLogout(session);
                    }
                }
                pollTimer = setTimeout(
                    () => { pollJobStatusOnce().catch(e => log(`[proxmoxUpdater] Job-Status-Poll fehlgeschlagen: ${e.message}`, "error")); },
                    jobRunning ? POLL_INTERVAL_BUSY_MS : POLL_INTERVAL_IDLE_MS,
                );
            }
            
            // Fuer den sofortigen Check direkt nach einem Button-Klick: laufenden Timer verwerfen
            // und sofort neu pruefen (nicht awaiten - der Button-Handler soll nicht darauf warten).
            function kickJobStatusPoll() {
                clearTimeout(pollTimer);
                pollJobStatusOnce().catch(e => log(`[proxmoxUpdater] Job-Status-Poll fehlgeschlagen: ${e.message}`, "error"));
            }
            
            async function refreshStatus() {
                let session;
                try {
                    session = await uuLogin();
                } catch (e) {
                    log(`[proxmoxUpdater] Login fehlgeschlagen: ${e.message}`, "error");
                    return;
                }
            
                try {
                    const status = await uuGet("/api/status", session);
                    const targets = status.targets || [];
            
                    await createChannelP(ROOT, "Proxmox Ultimate Updater");
                    await createStateP(`${ROOT}.lastRefresh`, { name: "Letzte Aktualisierung", type: "number", role: "value.time" }, Date.now());
                    await createStateP(`${ROOT}.schemaVersion`, { name: "Schema-Version", type: "string", role: "text" }, String(status.schema_version || ""));
                    await createStateP(`${ROOT}.generatedAt`, { name: "Generiert am (Server)", type: "string", role: "text" }, String(status.generated_at || ""));
                    await createStateP(`${ROOT}.targetCount`, { name: "Anzahl Targets", type: "number", role: "value" }, targets.length);
            
                    await refreshJobStatus(session);
            
                    // Zusammenfassung ueber alle Targets - erspart das Durchklicken aller Ordner,
                    // um zu sehen ob/wo/wie viel gerade ansteht.
                    const targetsWithUpdates = targets.filter(t => (t.security_updates || 0) > 0 || (t.normal_updates || 0) > 0);
                    const targetsUnreachable = targets.filter(t => t.reachable === false);
                    const targetsRebootRequired = targets.filter(t => t.reboot_required === true);
                    await createChannelP(`${ROOT}.summary`, "Zusammenfassung");
                    await createStateP(`${ROOT}.summary.securityUpdatesTotal`, { name: "Offene Security-Updates (Summe ueber alle Targets)", type: "number", role: "value" }, targets.reduce((sum, t) => sum + (t.security_updates || 0), 0));
                    await createStateP(`${ROOT}.summary.normalUpdatesTotal`, { name: "Offene normale Updates (Summe ueber alle Targets)", type: "number", role: "value" }, targets.reduce((sum, t) => sum + (t.normal_updates || 0), 0));
                    await createStateP(`${ROOT}.summary.targetsWithUpdatesCount`, { name: "Anzahl Targets mit offenen Updates", type: "number", role: "value" }, targetsWithUpdates.length);
                    await createStateP(`${ROOT}.summary.targetsWithUpdatesList`, { name: "Targets mit offenen Updates", type: "string", role: "json" }, JSON.stringify(targetsWithUpdates.map(t => t.name || t.id)));
                    await createStateP(`${ROOT}.summary.unreachableCount`, { name: "Anzahl nicht erreichbarer Targets (gestoppte VMs/CTs zaehlen nicht mit)", type: "number", role: "value" }, targetsUnreachable.length);
                    await createStateP(`${ROOT}.summary.unreachableList`, { name: "Nicht erreichbare Targets", type: "string", role: "json" }, JSON.stringify(targetsUnreachable.map(t => t.name || t.id)));
                    await createStateP(`${ROOT}.summary.rebootRequiredCount`, { name: "Anzahl Targets mit ausstehendem Reboot", type: "number", role: "value" }, targetsRebootRequired.length);
                    await createStateP(`${ROOT}.summary.rebootRequiredList`, { name: "Targets mit ausstehendem Reboot", type: "string", role: "json" }, JSON.stringify(targetsRebootRequired.map(t => t.name || t.id)));
            
                    // Sammel-Buttons
                    await createStateAsync(`${ROOT}.updateAllVmsCtsNow`, false, {
                        name: "Alle VMs/CTs aktualisieren (ohne Host)",
                        type: "boolean",
                        role: "button",
                        read: true,
                        write: true,
                    });
                    await createStateAsync(`${ROOT}.updateSecurityOnlyNow`, false, {
                        name: "Nur Security-Updates aktualisieren (VMs/CTs, ohne Host)",
                        type: "boolean",
                        role: "button",
                        read: true,
                        write: true,
                    });
                    await createStateAsync(`${ROOT}.checkAllNow`, false, {
                        name: "Alle pruefen, inkl. Host (nur Status, kein Update)",
                        type: "boolean",
                        role: "button",
                        read: true,
                        write: true,
                    });
            
                    await createChannelP(`${ROOT}.targets`, "Targets");
                    await cleanupStaleTargets(targets);
            
                    for (const t of targets) {
                        const isHost = t.type === "host";
                        const prefix = `${ROOT}.targets.${targetKey(t)}`;
                        await createChannelP(prefix, `${t.name || t.id} (${t.type})`);
                        await flattenAndSet(prefix, t);
            
                        await createStateAsync(`${prefix}.updateNow`, false, {
                            name: isHost ? "Diesen Host aktualisieren" : "Dieses VM/CT aktualisieren",
                            type: "boolean",
                            role: "button",
                            read: true,
                            write: true,
                        });
                        await createStateAsync(`${prefix}.checkNow`, false, {
                            name: isHost ? "Diesen Host pruefen (kein Update)" : "Dieses VM/CT pruefen (kein Update)",
                            type: "boolean",
                            role: "button",
                            read: true,
                            write: true,
                        });
                    }
            
                    log(`[proxmoxUpdater] Status aktualisiert: ${targets.length} Targets`);
                } catch (e) {
                    log(`[proxmoxUpdater] Status-Abruf fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    await uuLogout(session);
                }
            }
            
            // ---- Einzel-Update-Button je Target ----
            on({ id: new RegExp("^0_userdata\\.0\\.proxmoxUpdater\\.targets\\.[^.]+\\.updateNow$"), change: "ne" }, async obj => {
                if (!obj.state || obj.state.val !== true || obj.state.ack) {
                    return;
                }
                setState(obj.id, false, true);
            
                const prefix = obj.id.replace(/\.updateNow$/, "");
                const typeState = await getStateAsync(`${prefix}.type`);
                const isHost = typeState && typeState.val === "host";
            
                let session;
                try {
                    session = await uuLogin();
                    if (await isAnyJobRunning(session)) {
                        log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Aktion nicht ausgelöst.", "warn");
                        return;
                    }
                    if (isHost) {
                        const nodeState = await getStateAsync(`${prefix}.node`);
                        const node = nodeState && nodeState.val;
                        const res = await uuPost(`/api/update-node/${encodeURIComponent(node)}`, session, {});
                        log(`[proxmoxUpdater] Host-Update gestartet (${node}): ${res.message || "OK"}`);
                    } else {
                        const idState = await getStateAsync(`${prefix}.id`);
                        const id = idState && idState.val;
                        const res = await uuPost(`/api/update/${encodeURIComponent(id)}`, session, {});
                        log(`[proxmoxUpdater] Update gestartet (${id}): ${res.message || "OK"}`);
                    }
                    // sofort neu pruefen und danach automatisch in den schnellen 2s-Takt wechseln,
                    // solange der Job laeuft - siehe kickJobStatusPoll()/pollJobStatusOnce()
                    kickJobStatusPoll();
                } catch (e) {
                    log(`[proxmoxUpdater] Update fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    if (session) {
                        await uuLogout(session);
                    }
                }
            });
            
            // ---- Sammel-Button: alle VMs/CTs (ohne Host) ----
            on({ id: `${ROOT}.updateAllVmsCtsNow`, change: "ne" }, async obj => {
                if (!obj.state || obj.state.val !== true || obj.state.ack) {
                    return;
                }
                setState(obj.id, false, true);
            
                let session;
                try {
                    session = await uuLogin();
                    if (await isAnyJobRunning(session)) {
                        log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Sammel-Update nicht ausgelöst.", "warn");
                        return;
                    }
                    const status = await uuGet("/api/status", session);
                    const targets = (status.targets || []).filter(t => t.type !== "host");
                    log(`[proxmoxUpdater] Starte Update fuer ${targets.length} VMs/CTs (Host ausgenommen)`);
                    for (const t of targets) {
                        try {
                            await uuPost(`/api/update/${encodeURIComponent(t.id)}`, session, {});
                        } catch (e) {
                            log(`[proxmoxUpdater] Update fuer ${t.id} fehlgeschlagen: ${e.message}`, "warn");
                        }
                    }
                    // sofort neu pruefen und danach automatisch in den schnellen 2s-Takt wechseln,
                    // solange der Job laeuft - siehe kickJobStatusPoll()/pollJobStatusOnce()
                    kickJobStatusPoll();
                } catch (e) {
                    log(`[proxmoxUpdater] Sammel-Update fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    if (session) {
                        await uuLogout(session);
                    }
                }
            });
            
            // ---- Einzel-Check-Button je Target (nur pruefen, kein Update) ----
            on({ id: new RegExp("^0_userdata\\.0\\.proxmoxUpdater\\.targets\\.[^.]+\\.checkNow$"), change: "ne" }, async obj => {
                if (!obj.state || obj.state.val !== true || obj.state.ack) {
                    return;
                }
                setState(obj.id, false, true);
            
                const prefix = obj.id.replace(/\.checkNow$/, "");
                const typeState = await getStateAsync(`${prefix}.type`);
                const isHost = typeState && typeState.val === "host";
            
                let session;
                try {
                    session = await uuLogin();
                    if (await isAnyJobRunning(session)) {
                        log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Check nicht ausgelöst.", "warn");
                        return;
                    }
                    if (isHost) {
                        const nodeState = await getStateAsync(`${prefix}.node`);
                        const node = nodeState && nodeState.val;
                        const res = await uuPost(`/api/check-node/${encodeURIComponent(node)}`, session, {});
                        log(`[proxmoxUpdater] Host-Check gestartet (${node}): ${res.message || "OK"}`);
                    } else {
                        const idState = await getStateAsync(`${prefix}.id`);
                        const id = idState && idState.val;
                        const res = await uuPost(`/api/check/${encodeURIComponent(id)}`, session, {});
                        log(`[proxmoxUpdater] Check gestartet (${id}): ${res.message || "OK"}`);
                    }
                    kickJobStatusPoll();
                } catch (e) {
                    log(`[proxmoxUpdater] Check fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    if (session) {
                        await uuLogout(session);
                    }
                }
            });
            
            // ---- Sammel-Button: alle pruefen, inkl. Host (read-only, daher unbedenklich) ----
            on({ id: `${ROOT}.checkAllNow`, change: "ne" }, async obj => {
                if (!obj.state || obj.state.val !== true || obj.state.ack) {
                    return;
                }
                setState(obj.id, false, true);
            
                let session;
                try {
                    session = await uuLogin();
                    if (await isAnyJobRunning(session)) {
                        log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Sammel-Check nicht ausgelöst.", "warn");
                        return;
                    }
                    const res = await uuPost("/api/check-all", session, {});
                    log(`[proxmoxUpdater] Sammel-Check gestartet: ${res.message || "OK"}`);
                    kickJobStatusPoll();
                } catch (e) {
                    log(`[proxmoxUpdater] Sammel-Check fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    if (session) {
                        await uuLogout(session);
                    }
                }
            });
            
            // ---- Sammel-Button: nur Targets mit Security-Updates aktualisieren (ohne Host) ----
            on({ id: `${ROOT}.updateSecurityOnlyNow`, change: "ne" }, async obj => {
                if (!obj.state || obj.state.val !== true || obj.state.ack) {
                    return;
                }
                setState(obj.id, false, true);
            
                let session;
                try {
                    session = await uuLogin();
                    if (await isAnyJobRunning(session)) {
                        log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Security-Update nicht ausgelöst.", "warn");
                        return;
                    }
                    const status = await uuGet("/api/status", session);
                    const targets = (status.targets || []).filter(t => t.type !== "host" && (t.security_updates || 0) > 0);
                    log(`[proxmoxUpdater] Starte Security-Update fuer ${targets.length} VMs/CTs`);
                    for (const t of targets) {
                        try {
                            await uuPost(`/api/update/${encodeURIComponent(t.id)}`, session, {});
                        } catch (e) {
                            log(`[proxmoxUpdater] Security-Update fuer ${t.id} fehlgeschlagen: ${e.message}`, "warn");
                        }
                    }
                    kickJobStatusPoll();
                } catch (e) {
                    log(`[proxmoxUpdater] Security-Update fehlgeschlagen: ${e.message}`, "error");
                } finally {
                    if (session) {
                        await uuLogout(session);
                    }
                }
            });
            
            function delay(ms) {
                return new Promise(resolve => setTimeout(resolve, ms));
            }
            
            // Gestaffelter Check aller Targets nacheinander (statt alle gleichzeitig wie
            // checkAllNow) - schont Host/Netzwerk, dauert dafuer entsprechend laenger
            // (28 Targets * STAGGERED_CHECK_DELAY_MINUTES ≈ 1,5h). Fuer den schnellen,
            // manuellen "jetzt sofort alle" mit kurzer hoher Last gibt es weiterhin den
            // checkAllNow-Datenpunkt/Button.
            async function staggeredCheckAll() {
                let targets;
                try {
                    const session = await uuLogin();
                    try {
                        const status = await uuGet("/api/status", session);
                        targets = status.targets || [];
                    } finally {
                        await uuLogout(session);
                    }
                } catch (e) {
                    log(`[proxmoxUpdater] Gestaffelter Check: Target-Liste konnte nicht geholt werden: ${e.message}`, "error");
                    return;
                }
            
                log(`[proxmoxUpdater] Gestaffelter Check gestartet (${targets.length} Targets, ${STAGGERED_CHECK_DELAY_MINUTES} min Abstand)`);
                for (let i = 0; i < targets.length; i++) {
                    const t = targets[i];
                    const label = t.name || t.id;
                    let session;
                    try {
                        session = await uuLogin();
                        if (await isAnyJobRunning(session)) {
                            log(`[proxmoxUpdater] Gestaffelter Check: ${label} übersprungen - es läuft bereits ein anderer Job.`, "warn");
                        } else if (t.type === "host") {
                            await uuPost(`/api/check-node/${encodeURIComponent(t.node)}`, session, {});
                            log(`[proxmoxUpdater] Gestaffelter Check ${i + 1}/${targets.length}: Host ${label} gestartet`);
                        } else {
                            await uuPost(`/api/check/${encodeURIComponent(t.id)}`, session, {});
                            log(`[proxmoxUpdater] Gestaffelter Check ${i + 1}/${targets.length}: ${label} gestartet`);
                        }
                    } catch (e) {
                        log(`[proxmoxUpdater] Gestaffelter Check: ${label} fehlgeschlagen: ${e.message}`, "warn");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                    kickJobStatusPoll();
                    if (i < targets.length - 1) {
                        await delay(STAGGERED_CHECK_DELAY_MS);
                    }
                }
                log(`[proxmoxUpdater] Gestaffelter Check abgeschlossen (${targets.length} Targets)`);
            }
            
            refreshStatus().catch(e => log(`[proxmoxUpdater] Abbruch: ${e.message}`, "error"));
            
            // stuendlich der volle, schwere Refresh (alle Targets, Cleanup, ...)
            schedule(REFRESH_CRON, refreshStatus);
            
            // naechtlich der gestaffelte Check aller Targets (siehe staggeredCheckAll())
            schedule(STAGGERED_CHECK_CRON, () => {
                staggeredCheckAll().catch(e => log(`[proxmoxUpdater] Gestaffelter Check abgebrochen: ${e.message}`, "error"));
            });
            
            // leichter Job-Status-Check startet selbstjustierend (busy/idle-Takt s.o.) -
            // schedule() kann keine Sub-Minuten-Intervalle, daher die eigene setTimeout-Kette
            // in pollJobStatusOnce()/kickJobStatusPoll() weiter oben.
            pollTimer = setTimeout(() => {
                pollJobStatusOnce().catch(e => log(`[proxmoxUpdater] Job-Status-Poll fehlgeschlagen: ${e.message}`, "error"));
            }, POLL_INTERVAL_IDLE_MS);
            
            

            Zeigt eure Lovelace-Visualisierung klick
            (Auch ideal um sich Anregungen zu holen)

            Meine Tabellen für eure Visualisierung klick

            NegaleinN 1 Antwort Letzte Antwort
            1
            • Bass-TB
              Bass-TB
              Bass-T
              schrieb am zuletzt editiert von
              #99

              @david-g. Schick, damit ist der Updater ja praktisch voll in die Visu integrierbar – top. 👌🏻

              Wenn ich mal wieder dafür Zeit finde, muss ich bei mir auch wieder ran.
              Umzug ist voll doof für Visu bauen. 😀

              Proxmox Cluster

              • NUC7i5 ioBroker Server (Proxmox)
              • HP Elitedesk 800 G4 SSF (Proxmox)
              • HP Elitedesk 800 G2 DM (Proxmox)
                mein Proxmox-Updater für euch

              KNX / Broadlink / Tasmota ESPs / Zigbee
              Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

              1 Antwort Letzte Antwort
              0
              • David G.D David G.

                Hey,

                ich hatte @Nagelein ja gefragt wo die Daten aus seiner mal her sind - leider automatisch aus dem Script.

                Ich habe mit gestern mit Claude ein Script geschrieben, was mir alles in den iobroker holt.

                Status & Datenpunkte

                • Kompletter Status aller Targets (Host, VMs, CTs) als Datenpunkte, automatisch je
                  Ziel angelegt (inkl. Betriebssystem, erreichbar/offline, Anzahl offener Updates,
                  Security- vs. normale Updates, Reboot-noetig-Flag, letzter Check/Update, u.v.m.)
                • Health-Zusammenfassung auf einen Blick: Summe offener Security-/Normal-Updates,
                  Liste der Targets mit offenen Updates, nicht erreichbare Targets, Targets mit
                  ausstehendem Reboot
                • Job-Historie (laufende & vergangene Check-/Update-Jobs je Target) inkl.
                  automatischem Nachladen der Logzeilen bei fehlgeschlagenen/abgebrochenen Jobs
                • Live-Indikator "laeuft gerade ein Job" mit adaptivem Poll-Takt (schnell waehrend
                  etwas laeuft, langsam wenn nichts ansteht - erkennt auch kurze Jobs von nur
                  wenigen Sekunden zuverlaessig)
                • Automatisches Aufraeumen: nicht mehr existierende Targets verschwinden auch aus
                  ioBroker wieder

                Aktionen per Knopfdruck (aus ioBroker/VIS)

                • Update einzeln je Host/VM/CT
                • Update Sammel-Button: alle VMs/CTs auf einmal (Host bewusst ausgenommen)
                • Update Sammel-Button: nur Targets mit Security-relevanten Updates
                • Check einzeln je Host/VM/CT (nur Status aktualisieren, kein Update)
                • Check Sammel-Button: alle sofort auf einmal (schnell, kurzzeitig hoehere Last)
                • Schutz gegen Doppelausloesung: neue Aktion wird blockiert, solange bereits ein
                  Job laeuft

                Automatisierung

                • Stuendlicher voller Status-Refresh
                • Naechtlicher gestaffelter Check aller Targets: geht nacheinander mit mehreren
                  Minuten Abstand durch (statt alle gleichzeitig) - schont Host und Netzwerk
                • Alle Zeiten/Intervalle zentral als Variablen im Skriptkopf einstellbar

                Sicherheit

                • Zugangsdaten nur lokal im Skript, Session-Cookie + CSRF-Token werden bei jedem
                  Login frisch geholt und nicht dauerhaft gespeichert
                // ioBroker JavaScript-Adapter Script
                // Liest den Status aller Proxmox-Hosts/VMs/CTs aus der "Ultimate Updater"-Web-API
                // (https://github.com/BassT23/Proxmox) und legt fuer jedes Target alle verfuegbaren
                // Felder als Datenpunkte an. Zusaetzlich Buttons zum Aktualisieren - einzeln pro
                // VM/CT, einzeln fuer den Host, und ein Sammel-Button fuer alle VMs/CTs (ohne Host).
                // Aktualisiert sich automatisch stuendlich.
                //
                // WICHTIG: Benutzername/Passwort direkt hier im Script-Editor eintragen - NICHT in
                // einem Chat/einer KI-Anfrage teilen. Die Web-API nutzt Session-Cookie + CSRF-Token,
                // beides wird bei jedem Login frisch geholt und nicht dauerhaft gespeichert.
                
                const UU_BASE = "https://192.168.99.58:8765";
                const UU_USERNAME = "root";
                const UU_PASSWORD = "xxxxxxxx"; 
                
                const ROOT = "0_userdata.0.proxmoxUpdater";
                
                // ---- Alle Zeit-/Intervall-Einstellungen zentral an einem Ort ----
                const REFRESH_CRON = "0 * * * *"; // voller Status-Refresh (alle Targets, Cleanup, Zusammenfassung, ...)
                const POLL_INTERVAL_BUSY_MS = 2000; // Job-Status-Poll-Takt, solange ein Check/Update-Job laeuft
                const POLL_INTERVAL_IDLE_MS = 30000; // Job-Status-Poll-Takt, wenn nichts laeuft
                const STAGGERED_CHECK_CRON = "0 4 * * *"; // Start des naechtlichen gestaffelten Checks aller Targets
                const STAGGERED_CHECK_DELAY_MINUTES = 3; // Abstand zwischen den einzelnen Targets beim gestaffelten Check
                const STAGGERED_CHECK_DELAY_MS = STAGGERED_CHECK_DELAY_MINUTES * 60 * 1000;
                
                function sanitize(s) {
                    return String(s).replace(/[^a-zA-Z0-9_]/g, "_");
                }
                
                // Reiner Name reicht nicht als Ordner-Key - mehrere Targets koennen denselben Namen
                // tragen (z.B. mehrere CTs heissen "AgentDVR"), dann wuerden sie sich sonst beim
                // Anlegen der Datenpunkte gegenseitig ueberschreiben. Die ID ist dagegen garantiert
                // eindeutig, wird also immer mit angehaengt; der Name bleibt trotzdem lesbar im Key
                // und zusaetzlich als common.name des Channels sichtbar.
                function targetKey(t) {
                    return `${sanitize(t.name || t.id)}_${sanitize(t.id)}`;
                }
                
                function createChannelP(id, name) {
                    return setObjectAsync(id, { type: "channel", common: { name }, native: {} });
                }
                
                // read-only per Default (reine Anzeigewerte); Buttons ueberschreiben write:true selbst.
                function createStateP(id, common, val) {
                    return createStateAsync(id, val, { read: true, write: false, ...common });
                }
                
                // httpGetAsync/httpPostAsync liefern per Default responseType "text", nicht "json"
                // (siehe getHttpRequestConfig in ioBroker.javascript) - ohne explizites responseType:"json"
                // kommt ein roher String zurueck und z.B. "status.targets" ist dann still undefined statt
                // eines Fehlers. Zusaetzlich JSON.parse-Fallback, falls trotzdem ein String zurueckkommt.
                function parseJson(data) {
                    return typeof data === "string" ? JSON.parse(data) : data;
                }
                
                async function uuLogin() {
                    const res = await httpPostAsync(
                        `${UU_BASE}/api/login`,
                        { username: UU_USERNAME, password: UU_PASSWORD },
                        { validateCertificate: false, timeout: 15000, responseType: "json" },
                    );
                    if (res.statusCode !== 200) {
                        throw new Error(`Login fehlgeschlagen: HTTP ${res.statusCode}`);
                    }
                    const setCookie = res.headers["set-cookie"];
                    const cookie = Array.isArray(setCookie)
                        ? setCookie.map(c => c.split(";")[0]).join("; ")
                        : String(setCookie || "").split(";")[0];
                    const data = parseJson(res.data);
                    return { cookie, csrf: data && data.csrf };
                }
                
                async function uuGet(path, session) {
                    const res = await httpGetAsync(`${UU_BASE}${path}`, {
                        headers: { Cookie: session.cookie },
                        validateCertificate: false,
                        timeout: 15000,
                        responseType: "json",
                    });
                    if (res.statusCode !== 200) {
                        throw new Error(`${path} -> HTTP ${res.statusCode}`);
                    }
                    return parseJson(res.data);
                }
                
                async function uuPost(path, session, body) {
                    const res = await httpPostAsync(`${UU_BASE}${path}`, body || {}, {
                        headers: { Cookie: session.cookie, "X-CSRF-Token": session.csrf },
                        validateCertificate: false,
                        timeout: 15000,
                        responseType: "json",
                    });
                    if (res.statusCode !== 200) {
                        throw new Error(`${path} -> HTTP ${res.statusCode}`);
                    }
                    return parseJson(res.data);
                }
                
                async function uuLogout(session) {
                    try {
                        await uuPost("/api/logout", session, {});
                    } catch (e) {
                        // egal - Session laeuft sonst einfach serverseitig aus
                    }
                }
                
                // Server-seitige Definition von "beschaeftigt" (server.py: state in {running, pending, starting}) -
                // nicht nur "running", damit ein gerade erst gestarteter Job nicht uebersehen wird.
                const BUSY_STATES = ["running", "pending", "starting"];
                
                // Bei diesen Endzustaenden lohnt es sich, das Log nachzuladen (bei "completed" nicht
                // noetig - da ist eh alles gut gegangen).
                const FAILURE_STATES = ["failed", "interrupted"];
                
                async function isAnyJobRunning(session) {
                    const jobsResp = await uuGet("/api/jobs", session);
                    const jobs = jobsResp.jobs || [];
                    return jobs.some(j => BUSY_STATES.includes(j.state));
                }
                
                // Legt rekursiv alle Felder eines Targets (auch verschachtelte wie "updates") als
                // Datenpunkte an. Arrays werden als JSON-String gespeichert.
                async function flattenAndSet(prefix, obj) {
                    for (const [key, val] of Object.entries(obj)) {
                        const id = `${prefix}.${sanitize(key)}`;
                        if (val !== null && typeof val === "object" && !Array.isArray(val)) {
                            await createChannelP(id, key);
                            await flattenAndSet(id, val);
                        } else if (Array.isArray(val)) {
                            await createStateP(id, { name: key, type: "string", role: "json" }, JSON.stringify(val));
                        } else {
                            const type = typeof val === "boolean" ? "boolean" : typeof val === "number" ? "number" : "string";
                            const safeVal = val === undefined || val === null ? (type === "string" ? "" : null) : val;
                            await createStateP(id, { name: key, type, role: "value" }, safeVal);
                        }
                    }
                }
                
                // Entfernt Target-Ordner, die beim letzten Refresh noch da waren, aber jetzt nicht mehr
                // von der API geliefert werden (VM/CT geloescht, oder - einmalig - Umstellung von
                // ID- auf Name-basierte Ordner). Vergleicht ueber das ".id"-Feld jedes Ordners.
                async function cleanupStaleTargets(currentTargets) {
                    const currentPrefixes = new Set(currentTargets.map(targetKey));
                    const existingPrefixes = new Set();
                    $(`${ROOT}.targets.*.id`).each(id => {
                        const prefix = id.replace(`${ROOT}.targets.`, "").replace(/\.id$/, "");
                        if (!prefix.includes(".")) {
                            existingPrefixes.add(prefix);
                        }
                    });
                    for (const prefix of existingPrefixes) {
                        if (!currentPrefixes.has(prefix)) {
                            const fullId = `${ROOT}.targets.${prefix}`;
                            await deleteObjectAsync(fullId, true);
                            log(`[proxmoxUpdater] Nicht mehr gelieferter Target-Ordner entfernt: ${fullId}`);
                        }
                    }
                }
                
                // Leichtgewichtiger Check (nur /api/jobs, kein voller Status) - dafuer viel oefter
                // aufgerufen als der schwere refreshStatus(), damit "laeuft gerade was" auch bei
                // kurzen Update-Jobs (oft nur Sekunden bis wenige Minuten) nicht zwischen zwei
                // stuendlichen Refreshs verpasst wird. session wird wiederverwendet, falls schon
                // eine besteht (z.B. direkt nach einem Button-Klick), sonst wird selbst eingeloggt.
                async function refreshJobStatus(session) {
                    const jobsResp = await uuGet("/api/jobs", session);
                    const jobs = jobsResp.jobs || [];
                    const jobRunning = jobs.some(j => BUSY_STATES.includes(j.state));
                    await createStateP(`${ROOT}.jobRunning`, { name: "Läuft gerade ein Check/Update-Job?", type: "boolean", role: "indicator.working" }, jobRunning);
                
                    // Job-Historie: letzte Checks/Updates pro Target (unit, target, state, exit_code, Zeiten ...)
                    await createChannelP(`${ROOT}.jobs`, "Job-Historie");
                    await createStateP(`${ROOT}.jobs.count`, { name: "Anzahl Jobs (laufend + Historie)", type: "number", role: "value" }, jobs.length);
                    for (const j of jobs) {
                        const jobPrefix = `${ROOT}.jobs.${sanitize(j.unit)}`;
                        await createChannelP(jobPrefix, `${j.target || j.unit} (${j.state})`);
                        await flattenAndSet(jobPrefix, j);
                        if (FAILURE_STATES.includes(j.state)) {
                            await fetchJobLogIfMissing(jobPrefix, j.unit, session);
                        }
                    }
                    return jobRunning;
                }
                
                // Holt die letzten Logzeilen eines fehlgeschlagenen/abgebrochenen Jobs nach
                // (GET /api/jobs/<unit>/log - liefert die letzten 200 Zeilen aus journalctl).
                // Nur einmal pro Job noetig, daher Ueberspringen falls schon vorhanden.
                async function fetchJobLogIfMissing(jobPrefix, unit, session) {
                    const logId = `${jobPrefix}.log`;
                    const existing = await getStateAsync(logId);
                    if (existing && existing.val) {
                        return;
                    }
                    try {
                        const logResp = await uuGet(`/api/jobs/${encodeURIComponent(unit)}/log`, session);
                        await createStateP(logId, { name: "Log (letzte Zeilen bei Fehlschlag)", type: "string", role: "text" }, String(logResp.log || ""));
                    } catch (e) {
                        log(`[proxmoxUpdater] Log fuer Job ${unit} konnte nicht geholt werden: ${e.message}`, "warn");
                    }
                }
                
                // Selbstjustierender Poll-Takt, wie ihn auch die Original-Web-GUI von Ultimate
                // Updater selbst verwendet (server.py: loadJobs() pollt alle 2s solange ein Job
                // "running" ist, sonst alle 10s - wir nehmen 2s/30s, damit unser normaler Takt
                // erhalten bleibt): solange ein Job laeuft, alle 2s pruefen, damit auch kurze
                // Jobs von nur 10-15s sicher erfasst werden; sonst normaler 30s-Rhythmus.
                // pollTimer wird bei jedem Tick neu gesetzt (nie parallel mehrere Timer aktiv).
                let pollTimer = null;
                
                async function pollJobStatusOnce() {
                    let session;
                    let jobRunning = false;
                    try {
                        session = await uuLogin();
                        jobRunning = await refreshJobStatus(session);
                    } catch (e) {
                        log(`[proxmoxUpdater] Job-Status-Check fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                    pollTimer = setTimeout(
                        () => { pollJobStatusOnce().catch(e => log(`[proxmoxUpdater] Job-Status-Poll fehlgeschlagen: ${e.message}`, "error")); },
                        jobRunning ? POLL_INTERVAL_BUSY_MS : POLL_INTERVAL_IDLE_MS,
                    );
                }
                
                // Fuer den sofortigen Check direkt nach einem Button-Klick: laufenden Timer verwerfen
                // und sofort neu pruefen (nicht awaiten - der Button-Handler soll nicht darauf warten).
                function kickJobStatusPoll() {
                    clearTimeout(pollTimer);
                    pollJobStatusOnce().catch(e => log(`[proxmoxUpdater] Job-Status-Poll fehlgeschlagen: ${e.message}`, "error"));
                }
                
                async function refreshStatus() {
                    let session;
                    try {
                        session = await uuLogin();
                    } catch (e) {
                        log(`[proxmoxUpdater] Login fehlgeschlagen: ${e.message}`, "error");
                        return;
                    }
                
                    try {
                        const status = await uuGet("/api/status", session);
                        const targets = status.targets || [];
                
                        await createChannelP(ROOT, "Proxmox Ultimate Updater");
                        await createStateP(`${ROOT}.lastRefresh`, { name: "Letzte Aktualisierung", type: "number", role: "value.time" }, Date.now());
                        await createStateP(`${ROOT}.schemaVersion`, { name: "Schema-Version", type: "string", role: "text" }, String(status.schema_version || ""));
                        await createStateP(`${ROOT}.generatedAt`, { name: "Generiert am (Server)", type: "string", role: "text" }, String(status.generated_at || ""));
                        await createStateP(`${ROOT}.targetCount`, { name: "Anzahl Targets", type: "number", role: "value" }, targets.length);
                
                        await refreshJobStatus(session);
                
                        // Zusammenfassung ueber alle Targets - erspart das Durchklicken aller Ordner,
                        // um zu sehen ob/wo/wie viel gerade ansteht.
                        const targetsWithUpdates = targets.filter(t => (t.security_updates || 0) > 0 || (t.normal_updates || 0) > 0);
                        const targetsUnreachable = targets.filter(t => t.reachable === false);
                        const targetsRebootRequired = targets.filter(t => t.reboot_required === true);
                        await createChannelP(`${ROOT}.summary`, "Zusammenfassung");
                        await createStateP(`${ROOT}.summary.securityUpdatesTotal`, { name: "Offene Security-Updates (Summe ueber alle Targets)", type: "number", role: "value" }, targets.reduce((sum, t) => sum + (t.security_updates || 0), 0));
                        await createStateP(`${ROOT}.summary.normalUpdatesTotal`, { name: "Offene normale Updates (Summe ueber alle Targets)", type: "number", role: "value" }, targets.reduce((sum, t) => sum + (t.normal_updates || 0), 0));
                        await createStateP(`${ROOT}.summary.targetsWithUpdatesCount`, { name: "Anzahl Targets mit offenen Updates", type: "number", role: "value" }, targetsWithUpdates.length);
                        await createStateP(`${ROOT}.summary.targetsWithUpdatesList`, { name: "Targets mit offenen Updates", type: "string", role: "json" }, JSON.stringify(targetsWithUpdates.map(t => t.name || t.id)));
                        await createStateP(`${ROOT}.summary.unreachableCount`, { name: "Anzahl nicht erreichbarer Targets (gestoppte VMs/CTs zaehlen nicht mit)", type: "number", role: "value" }, targetsUnreachable.length);
                        await createStateP(`${ROOT}.summary.unreachableList`, { name: "Nicht erreichbare Targets", type: "string", role: "json" }, JSON.stringify(targetsUnreachable.map(t => t.name || t.id)));
                        await createStateP(`${ROOT}.summary.rebootRequiredCount`, { name: "Anzahl Targets mit ausstehendem Reboot", type: "number", role: "value" }, targetsRebootRequired.length);
                        await createStateP(`${ROOT}.summary.rebootRequiredList`, { name: "Targets mit ausstehendem Reboot", type: "string", role: "json" }, JSON.stringify(targetsRebootRequired.map(t => t.name || t.id)));
                
                        // Sammel-Buttons
                        await createStateAsync(`${ROOT}.updateAllVmsCtsNow`, false, {
                            name: "Alle VMs/CTs aktualisieren (ohne Host)",
                            type: "boolean",
                            role: "button",
                            read: true,
                            write: true,
                        });
                        await createStateAsync(`${ROOT}.updateSecurityOnlyNow`, false, {
                            name: "Nur Security-Updates aktualisieren (VMs/CTs, ohne Host)",
                            type: "boolean",
                            role: "button",
                            read: true,
                            write: true,
                        });
                        await createStateAsync(`${ROOT}.checkAllNow`, false, {
                            name: "Alle pruefen, inkl. Host (nur Status, kein Update)",
                            type: "boolean",
                            role: "button",
                            read: true,
                            write: true,
                        });
                
                        await createChannelP(`${ROOT}.targets`, "Targets");
                        await cleanupStaleTargets(targets);
                
                        for (const t of targets) {
                            const isHost = t.type === "host";
                            const prefix = `${ROOT}.targets.${targetKey(t)}`;
                            await createChannelP(prefix, `${t.name || t.id} (${t.type})`);
                            await flattenAndSet(prefix, t);
                
                            await createStateAsync(`${prefix}.updateNow`, false, {
                                name: isHost ? "Diesen Host aktualisieren" : "Dieses VM/CT aktualisieren",
                                type: "boolean",
                                role: "button",
                                read: true,
                                write: true,
                            });
                            await createStateAsync(`${prefix}.checkNow`, false, {
                                name: isHost ? "Diesen Host pruefen (kein Update)" : "Dieses VM/CT pruefen (kein Update)",
                                type: "boolean",
                                role: "button",
                                read: true,
                                write: true,
                            });
                        }
                
                        log(`[proxmoxUpdater] Status aktualisiert: ${targets.length} Targets`);
                    } catch (e) {
                        log(`[proxmoxUpdater] Status-Abruf fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        await uuLogout(session);
                    }
                }
                
                // ---- Einzel-Update-Button je Target ----
                on({ id: new RegExp("^0_userdata\\.0\\.proxmoxUpdater\\.targets\\.[^.]+\\.updateNow$"), change: "ne" }, async obj => {
                    if (!obj.state || obj.state.val !== true || obj.state.ack) {
                        return;
                    }
                    setState(obj.id, false, true);
                
                    const prefix = obj.id.replace(/\.updateNow$/, "");
                    const typeState = await getStateAsync(`${prefix}.type`);
                    const isHost = typeState && typeState.val === "host";
                
                    let session;
                    try {
                        session = await uuLogin();
                        if (await isAnyJobRunning(session)) {
                            log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Aktion nicht ausgelöst.", "warn");
                            return;
                        }
                        if (isHost) {
                            const nodeState = await getStateAsync(`${prefix}.node`);
                            const node = nodeState && nodeState.val;
                            const res = await uuPost(`/api/update-node/${encodeURIComponent(node)}`, session, {});
                            log(`[proxmoxUpdater] Host-Update gestartet (${node}): ${res.message || "OK"}`);
                        } else {
                            const idState = await getStateAsync(`${prefix}.id`);
                            const id = idState && idState.val;
                            const res = await uuPost(`/api/update/${encodeURIComponent(id)}`, session, {});
                            log(`[proxmoxUpdater] Update gestartet (${id}): ${res.message || "OK"}`);
                        }
                        // sofort neu pruefen und danach automatisch in den schnellen 2s-Takt wechseln,
                        // solange der Job laeuft - siehe kickJobStatusPoll()/pollJobStatusOnce()
                        kickJobStatusPoll();
                    } catch (e) {
                        log(`[proxmoxUpdater] Update fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                });
                
                // ---- Sammel-Button: alle VMs/CTs (ohne Host) ----
                on({ id: `${ROOT}.updateAllVmsCtsNow`, change: "ne" }, async obj => {
                    if (!obj.state || obj.state.val !== true || obj.state.ack) {
                        return;
                    }
                    setState(obj.id, false, true);
                
                    let session;
                    try {
                        session = await uuLogin();
                        if (await isAnyJobRunning(session)) {
                            log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Sammel-Update nicht ausgelöst.", "warn");
                            return;
                        }
                        const status = await uuGet("/api/status", session);
                        const targets = (status.targets || []).filter(t => t.type !== "host");
                        log(`[proxmoxUpdater] Starte Update fuer ${targets.length} VMs/CTs (Host ausgenommen)`);
                        for (const t of targets) {
                            try {
                                await uuPost(`/api/update/${encodeURIComponent(t.id)}`, session, {});
                            } catch (e) {
                                log(`[proxmoxUpdater] Update fuer ${t.id} fehlgeschlagen: ${e.message}`, "warn");
                            }
                        }
                        // sofort neu pruefen und danach automatisch in den schnellen 2s-Takt wechseln,
                        // solange der Job laeuft - siehe kickJobStatusPoll()/pollJobStatusOnce()
                        kickJobStatusPoll();
                    } catch (e) {
                        log(`[proxmoxUpdater] Sammel-Update fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                });
                
                // ---- Einzel-Check-Button je Target (nur pruefen, kein Update) ----
                on({ id: new RegExp("^0_userdata\\.0\\.proxmoxUpdater\\.targets\\.[^.]+\\.checkNow$"), change: "ne" }, async obj => {
                    if (!obj.state || obj.state.val !== true || obj.state.ack) {
                        return;
                    }
                    setState(obj.id, false, true);
                
                    const prefix = obj.id.replace(/\.checkNow$/, "");
                    const typeState = await getStateAsync(`${prefix}.type`);
                    const isHost = typeState && typeState.val === "host";
                
                    let session;
                    try {
                        session = await uuLogin();
                        if (await isAnyJobRunning(session)) {
                            log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Check nicht ausgelöst.", "warn");
                            return;
                        }
                        if (isHost) {
                            const nodeState = await getStateAsync(`${prefix}.node`);
                            const node = nodeState && nodeState.val;
                            const res = await uuPost(`/api/check-node/${encodeURIComponent(node)}`, session, {});
                            log(`[proxmoxUpdater] Host-Check gestartet (${node}): ${res.message || "OK"}`);
                        } else {
                            const idState = await getStateAsync(`${prefix}.id`);
                            const id = idState && idState.val;
                            const res = await uuPost(`/api/check/${encodeURIComponent(id)}`, session, {});
                            log(`[proxmoxUpdater] Check gestartet (${id}): ${res.message || "OK"}`);
                        }
                        kickJobStatusPoll();
                    } catch (e) {
                        log(`[proxmoxUpdater] Check fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                });
                
                // ---- Sammel-Button: alle pruefen, inkl. Host (read-only, daher unbedenklich) ----
                on({ id: `${ROOT}.checkAllNow`, change: "ne" }, async obj => {
                    if (!obj.state || obj.state.val !== true || obj.state.ack) {
                        return;
                    }
                    setState(obj.id, false, true);
                
                    let session;
                    try {
                        session = await uuLogin();
                        if (await isAnyJobRunning(session)) {
                            log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Sammel-Check nicht ausgelöst.", "warn");
                            return;
                        }
                        const res = await uuPost("/api/check-all", session, {});
                        log(`[proxmoxUpdater] Sammel-Check gestartet: ${res.message || "OK"}`);
                        kickJobStatusPoll();
                    } catch (e) {
                        log(`[proxmoxUpdater] Sammel-Check fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                });
                
                // ---- Sammel-Button: nur Targets mit Security-Updates aktualisieren (ohne Host) ----
                on({ id: `${ROOT}.updateSecurityOnlyNow`, change: "ne" }, async obj => {
                    if (!obj.state || obj.state.val !== true || obj.state.ack) {
                        return;
                    }
                    setState(obj.id, false, true);
                
                    let session;
                    try {
                        session = await uuLogin();
                        if (await isAnyJobRunning(session)) {
                            log("[proxmoxUpdater] Läuft bereits ein Check/Update-Job - Security-Update nicht ausgelöst.", "warn");
                            return;
                        }
                        const status = await uuGet("/api/status", session);
                        const targets = (status.targets || []).filter(t => t.type !== "host" && (t.security_updates || 0) > 0);
                        log(`[proxmoxUpdater] Starte Security-Update fuer ${targets.length} VMs/CTs`);
                        for (const t of targets) {
                            try {
                                await uuPost(`/api/update/${encodeURIComponent(t.id)}`, session, {});
                            } catch (e) {
                                log(`[proxmoxUpdater] Security-Update fuer ${t.id} fehlgeschlagen: ${e.message}`, "warn");
                            }
                        }
                        kickJobStatusPoll();
                    } catch (e) {
                        log(`[proxmoxUpdater] Security-Update fehlgeschlagen: ${e.message}`, "error");
                    } finally {
                        if (session) {
                            await uuLogout(session);
                        }
                    }
                });
                
                function delay(ms) {
                    return new Promise(resolve => setTimeout(resolve, ms));
                }
                
                // Gestaffelter Check aller Targets nacheinander (statt alle gleichzeitig wie
                // checkAllNow) - schont Host/Netzwerk, dauert dafuer entsprechend laenger
                // (28 Targets * STAGGERED_CHECK_DELAY_MINUTES ≈ 1,5h). Fuer den schnellen,
                // manuellen "jetzt sofort alle" mit kurzer hoher Last gibt es weiterhin den
                // checkAllNow-Datenpunkt/Button.
                async function staggeredCheckAll() {
                    let targets;
                    try {
                        const session = await uuLogin();
                        try {
                            const status = await uuGet("/api/status", session);
                            targets = status.targets || [];
                        } finally {
                            await uuLogout(session);
                        }
                    } catch (e) {
                        log(`[proxmoxUpdater] Gestaffelter Check: Target-Liste konnte nicht geholt werden: ${e.message}`, "error");
                        return;
                    }
                
                    log(`[proxmoxUpdater] Gestaffelter Check gestartet (${targets.length} Targets, ${STAGGERED_CHECK_DELAY_MINUTES} min Abstand)`);
                    for (let i = 0; i < targets.length; i++) {
                        const t = targets[i];
                        const label = t.name || t.id;
                        let session;
                        try {
                            session = await uuLogin();
                            if (await isAnyJobRunning(session)) {
                                log(`[proxmoxUpdater] Gestaffelter Check: ${label} übersprungen - es läuft bereits ein anderer Job.`, "warn");
                            } else if (t.type === "host") {
                                await uuPost(`/api/check-node/${encodeURIComponent(t.node)}`, session, {});
                                log(`[proxmoxUpdater] Gestaffelter Check ${i + 1}/${targets.length}: Host ${label} gestartet`);
                            } else {
                                await uuPost(`/api/check/${encodeURIComponent(t.id)}`, session, {});
                                log(`[proxmoxUpdater] Gestaffelter Check ${i + 1}/${targets.length}: ${label} gestartet`);
                            }
                        } catch (e) {
                            log(`[proxmoxUpdater] Gestaffelter Check: ${label} fehlgeschlagen: ${e.message}`, "warn");
                        } finally {
                            if (session) {
                                await uuLogout(session);
                            }
                        }
                        kickJobStatusPoll();
                        if (i < targets.length - 1) {
                            await delay(STAGGERED_CHECK_DELAY_MS);
                        }
                    }
                    log(`[proxmoxUpdater] Gestaffelter Check abgeschlossen (${targets.length} Targets)`);
                }
                
                refreshStatus().catch(e => log(`[proxmoxUpdater] Abbruch: ${e.message}`, "error"));
                
                // stuendlich der volle, schwere Refresh (alle Targets, Cleanup, ...)
                schedule(REFRESH_CRON, refreshStatus);
                
                // naechtlich der gestaffelte Check aller Targets (siehe staggeredCheckAll())
                schedule(STAGGERED_CHECK_CRON, () => {
                    staggeredCheckAll().catch(e => log(`[proxmoxUpdater] Gestaffelter Check abgebrochen: ${e.message}`, "error"));
                });
                
                // leichter Job-Status-Check startet selbstjustierend (busy/idle-Takt s.o.) -
                // schedule() kann keine Sub-Minuten-Intervalle, daher die eigene setTimeout-Kette
                // in pollJobStatusOnce()/kickJobStatusPoll() weiter oben.
                pollTimer = setTimeout(() => {
                    pollJobStatusOnce().catch(e => log(`[proxmoxUpdater] Job-Status-Poll fehlgeschlagen: ${e.message}`, "error"));
                }, POLL_INTERVAL_IDLE_MS);
                
                
                NegaleinN
                NegaleinN
                Negalein
                schrieb am zuletzt editiert von
                #100

                @David-G. sagte:

                Ich habe mit gestern mit Claude ein Script geschrieben, was mir alles in den iobroker holt.

                Hi David!

                Ist das Script in Kombination mit dem Proxmox-Updater, oder läuft es auch ohne installiertem Proxmox-Updater?

                Hast du dir dafür schon eine VIS gebastelt? ;)

                ° Node.js & System Update ---> sudo apt update, iob stop, sudo apt full-upgrade
                ° Node.js Fixer ---> iob nodejs-update
                ° Fixer ---> iob fix

                David G.D 1 Antwort Letzte Antwort
                0
                • NegaleinN Negalein

                  @David-G. sagte:

                  Ich habe mit gestern mit Claude ein Script geschrieben, was mir alles in den iobroker holt.

                  Hi David!

                  Ist das Script in Kombination mit dem Proxmox-Updater, oder läuft es auch ohne installiertem Proxmox-Updater?

                  Hast du dir dafür schon eine VIS gebastelt? ;)

                  David G.D
                  David G.D
                  David G.
                  schrieb am zuletzt editiert von
                  #101

                  @Negalein

                  Baut auf dem Update auf.
                  Nutzt die Endpunkte der gui vom Updater.

                  Nein, noch nicht.

                  Zeigt eure Lovelace-Visualisierung klick
                  (Auch ideal um sich Anregungen zu holen)

                  Meine Tabellen für eure Visualisierung klick

                  1 Antwort Letzte Antwort
                  2
                  • Bass-TB
                    Bass-TB
                    Bass-T
                    schrieb am zuletzt editiert von
                    #102

                    Kurzer Zwischenstand von meiner Seite 👌🏻
                    Aus den letzten Rückmeldungen hier sind inzwischen einige Punkte direkt in develop gelandet:

                    • der Fehler beim Kopieren des Welcome-Screens nach der Installation ist behoben
                    • die Live-Ausgabe bei Checks und Updates wurde verbessert, sodass Fehler wie apt-get update jetzt direkt sichtbar sind
                    • Einzelchecks zeigen keine irreführende globale Filterauswahl mehr
                    • fehlgeschlagene Checks blockieren ein Update nicht mehr komplett – es gibt jetzt stattdessen eine Warnung und man kann bewusst trotzdem fortfahren
                    • die Status-Mail ist übersichtlicher geworden und trennt Security-/Normal-Updates klarer
                    • interaktive Updatejobs laufen serverseitig unabhängig vom Browser weiter und können grundsätzlich Eingaben an den laufenden Prozess zurückgeben
                    • ein Frontend-Freeze bei laufenden interaktiven Jobs wurde gefunden und behoben
                    • zusätzlich wurde der Interactive-Job-Lookup in der WebUI beschleunigt, damit Attach/Input nicht mehr durch die globale Jobliste ausgebremst werden

                    Gerade die interaktiven Tests haben aber auch gezeigt, dass die aktuelle Terminal-/Live-Log-Lösung in der WebUI zwar technisch funktioniert, sich bei echten Rückfragen von apt/dpkg/Whiptail aber noch nicht gut bedienen lässt.
                    Aktuell wird die Ausgabe noch per Polling nachgeladen. Dadurch springt die Ansicht beim Aktualisieren, Terminal-Steuerzeichen werden nicht sauber dargestellt und eine Rückmeldung ist zwar möglich, aber ziemlich unintuitiv.
                    Deshalb steht jetzt zusätzlich auf meiner Todo-Liste, die Terminalfunktion später auf eine echte Live-Terminal-Lösung umzubauen. Die bestehende Job-/PTY-Struktur ist dafür bereits gut vorbereitet. Ziel wäre eine direkte, flüssige Terminalansicht im Browser mit sauberer Ein-/Ausgabe und Reconnect, ohne dass der eigentliche Updatejob vom Browser abhängig wird.
                    Für den Moment bleibt aber erstmal die aktuelle Polling-Lösung bestehen. Die grundlegende Funktion ist da – ich möchte nur nicht viel Kosmetik in einen Ansatz stecken, den ich später ohnehin durch eine richtige Terminallösung ersetzen möchte.
                    Danke weiterhin für die Rückmeldungen und Tests hier. Die haben in den letzten Tagen tatsächlich einige Stellen aufgedeckt, die ich selbst so wahrscheinlich nicht erwischt hätte. 👍🏻

                    Proxmox Cluster

                    • NUC7i5 ioBroker Server (Proxmox)
                    • HP Elitedesk 800 G4 SSF (Proxmox)
                    • HP Elitedesk 800 G2 DM (Proxmox)
                      mein Proxmox-Updater für euch

                    KNX / Broadlink / Tasmota ESPs / Zigbee
                    Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                    T 1 Antwort Letzte Antwort
                    2
                    • Bass-TB Bass-T

                      Kurzer Zwischenstand von meiner Seite 👌🏻
                      Aus den letzten Rückmeldungen hier sind inzwischen einige Punkte direkt in develop gelandet:

                      • der Fehler beim Kopieren des Welcome-Screens nach der Installation ist behoben
                      • die Live-Ausgabe bei Checks und Updates wurde verbessert, sodass Fehler wie apt-get update jetzt direkt sichtbar sind
                      • Einzelchecks zeigen keine irreführende globale Filterauswahl mehr
                      • fehlgeschlagene Checks blockieren ein Update nicht mehr komplett – es gibt jetzt stattdessen eine Warnung und man kann bewusst trotzdem fortfahren
                      • die Status-Mail ist übersichtlicher geworden und trennt Security-/Normal-Updates klarer
                      • interaktive Updatejobs laufen serverseitig unabhängig vom Browser weiter und können grundsätzlich Eingaben an den laufenden Prozess zurückgeben
                      • ein Frontend-Freeze bei laufenden interaktiven Jobs wurde gefunden und behoben
                      • zusätzlich wurde der Interactive-Job-Lookup in der WebUI beschleunigt, damit Attach/Input nicht mehr durch die globale Jobliste ausgebremst werden

                      Gerade die interaktiven Tests haben aber auch gezeigt, dass die aktuelle Terminal-/Live-Log-Lösung in der WebUI zwar technisch funktioniert, sich bei echten Rückfragen von apt/dpkg/Whiptail aber noch nicht gut bedienen lässt.
                      Aktuell wird die Ausgabe noch per Polling nachgeladen. Dadurch springt die Ansicht beim Aktualisieren, Terminal-Steuerzeichen werden nicht sauber dargestellt und eine Rückmeldung ist zwar möglich, aber ziemlich unintuitiv.
                      Deshalb steht jetzt zusätzlich auf meiner Todo-Liste, die Terminalfunktion später auf eine echte Live-Terminal-Lösung umzubauen. Die bestehende Job-/PTY-Struktur ist dafür bereits gut vorbereitet. Ziel wäre eine direkte, flüssige Terminalansicht im Browser mit sauberer Ein-/Ausgabe und Reconnect, ohne dass der eigentliche Updatejob vom Browser abhängig wird.
                      Für den Moment bleibt aber erstmal die aktuelle Polling-Lösung bestehen. Die grundlegende Funktion ist da – ich möchte nur nicht viel Kosmetik in einen Ansatz stecken, den ich später ohnehin durch eine richtige Terminallösung ersetzen möchte.
                      Danke weiterhin für die Rückmeldungen und Tests hier. Die haben in den letzten Tagen tatsächlich einige Stellen aufgedeckt, die ich selbst so wahrscheinlich nicht erwischt hätte. 👍🏻

                      T
                      T
                      TT-Tom
                      schrieb am zuletzt editiert von TT-Tom
                      #103

                      @Bass-T

                      kannst du in der webUI beim Schedule auch die Auswahl von Tagen verfügbar machen, ich lasse gerne am 1. und 15. des Monats einen Check machen. Das ist für mich ausreichend.

                      Alternativ wäre auch der Mail-Versand nur an den Tagen.

                      Gruß Tom

                      Wenn meine Hilfe erfolgreich war, benutze bitte das Voting unten rechts im Beitrag

                      Public Transport
                      https://github.com/tt-tom17/ioBroker.public-transport
                      https://github.com/tt-tom17/ioBroker.public-transport/wiki

                      NSPanel Adapter Wiki
                      https://github.com/ticaki/ioBroker.nspanel-lovelace-ui/wiki

                      1 Antwort Letzte Antwort
                      0
                      • Bass-TB
                        Bass-TB
                        Bass-T
                        schrieb am zuletzt editiert von Bass-T
                        #104

                        Kurzer Zwischenstand 👌🏻
                        Die letzten Rückmeldungen sind inzwischen in develop eingeflossen und der Stand ist jetzt auch als 5.1.3 Beta 1 auf beta verfügbar.
                        Dabei wurden u. a. Live-Ausgabe, Soft-Gate nach fehlgeschlagenem Check, Mail-Übersicht, Node-Checks und die interaktiven Jobs weiter verbessert. Auch der Fall mit aktiviertem QEMU Guest Agent, aber gesperrtem guest-exec, wird jetzt sauber erkannt.
                        Die interaktive Rückmeldung in der WebUI funktioniert technisch inzwischen auch. Allerdings hat der Test sehr deutlich gezeigt, dass die aktuelle Polling-/Logdarstellung dafür einfach zu unintuitiv ist – gerade bei apt/dpkg/Whiptail-Rückfragen.
                        Deshalb steht als nächster größerer Punkt ein Umbau auf eine echte Live-Terminal-Lösung auf der Todo-Liste. Bis dahin bleibt die aktuelle Lösung erstmal bestehen.
                        Wer testen möchte:
                        update beta -up
                        Danke euch weiterhin fürs Testen und die Rückmeldungen. 👍🏻

                        @tt-tom kein ding bau ich in dem nächsten develop block mit ein ;)
                        Email versand erfolgt sowieso nur nach einem Run. Nie einfach so ;)

                        EDIT:
                        3ea56f88-cdbb-4197-893a-5d5a6e7fe2bf-image.jpeg

                        EDIT2:
                        ist in beta integriert ;)
                        Beta werde ich jetzt erstmal stabil halten und den terminal umbau unter develop machen.
                        Beta kann gerne getestet werden :)

                        Proxmox Cluster

                        • NUC7i5 ioBroker Server (Proxmox)
                        • HP Elitedesk 800 G4 SSF (Proxmox)
                        • HP Elitedesk 800 G2 DM (Proxmox)
                          mein Proxmox-Updater für euch

                        KNX / Broadlink / Tasmota ESPs / Zigbee
                        Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                        NegaleinN 1 Antwort Letzte Antwort
                        2
                        • Bass-TB Bass-T

                          Kurzer Zwischenstand 👌🏻
                          Die letzten Rückmeldungen sind inzwischen in develop eingeflossen und der Stand ist jetzt auch als 5.1.3 Beta 1 auf beta verfügbar.
                          Dabei wurden u. a. Live-Ausgabe, Soft-Gate nach fehlgeschlagenem Check, Mail-Übersicht, Node-Checks und die interaktiven Jobs weiter verbessert. Auch der Fall mit aktiviertem QEMU Guest Agent, aber gesperrtem guest-exec, wird jetzt sauber erkannt.
                          Die interaktive Rückmeldung in der WebUI funktioniert technisch inzwischen auch. Allerdings hat der Test sehr deutlich gezeigt, dass die aktuelle Polling-/Logdarstellung dafür einfach zu unintuitiv ist – gerade bei apt/dpkg/Whiptail-Rückfragen.
                          Deshalb steht als nächster größerer Punkt ein Umbau auf eine echte Live-Terminal-Lösung auf der Todo-Liste. Bis dahin bleibt die aktuelle Lösung erstmal bestehen.
                          Wer testen möchte:
                          update beta -up
                          Danke euch weiterhin fürs Testen und die Rückmeldungen. 👍🏻

                          @tt-tom kein ding bau ich in dem nächsten develop block mit ein ;)
                          Email versand erfolgt sowieso nur nach einem Run. Nie einfach so ;)

                          EDIT:
                          3ea56f88-cdbb-4197-893a-5d5a6e7fe2bf-image.jpeg

                          EDIT2:
                          ist in beta integriert ;)
                          Beta werde ich jetzt erstmal stabil halten und den terminal umbau unter develop machen.
                          Beta kann gerne getestet werden :)

                          NegaleinN
                          NegaleinN
                          Negalein
                          schrieb am zuletzt editiert von
                          #105

                          @Bass-T sagte:

                          Beta kann gerne getestet werden :)

                          gesagt......getan :)

                          lief 1A durch

                          ° Node.js & System Update ---> sudo apt update, iob stop, sudo apt full-upgrade
                          ° Node.js Fixer ---> iob nodejs-update
                          ° Fixer ---> iob fix

                          1 Antwort Letzte Antwort
                          1
                          • Bass-TB
                            Bass-TB
                            Bass-T
                            schrieb am zuletzt editiert von
                            #106

                            Das "alte" Terminal ging mir auf den geist.
                            Rückgabe an Terminal nicht wirklich gut umsetzbar.
                            Daher ist jetzt ein neues Live Terminal in den Startlöchern.
                            Erste Tests sind sehr vielversprechend :)
                            PS; dass ist sogar am Handy ordentlich nutzbar - Mobile Ansicht

                            image.jpeg

                            Proxmox Cluster

                            • NUC7i5 ioBroker Server (Proxmox)
                            • HP Elitedesk 800 G4 SSF (Proxmox)
                            • HP Elitedesk 800 G2 DM (Proxmox)
                              mein Proxmox-Updater für euch

                            KNX / Broadlink / Tasmota ESPs / Zigbee
                            Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                            NegaleinN 1 Antwort Letzte Antwort
                            1
                            • Bass-TB Bass-T

                              Das "alte" Terminal ging mir auf den geist.
                              Rückgabe an Terminal nicht wirklich gut umsetzbar.
                              Daher ist jetzt ein neues Live Terminal in den Startlöchern.
                              Erste Tests sind sehr vielversprechend :)
                              PS; dass ist sogar am Handy ordentlich nutzbar - Mobile Ansicht

                              image.jpeg

                              NegaleinN
                              NegaleinN
                              Negalein
                              schrieb am zuletzt editiert von
                              #107

                              @Bass-T

                              ein Reboot-Button übers Web Interface wäre noch super

                              ° Node.js & System Update ---> sudo apt update, iob stop, sudo apt full-upgrade
                              ° Node.js Fixer ---> iob nodejs-update
                              ° Fixer ---> iob fix

                              Bass-TB 1 Antwort Letzte Antwort
                              1
                              • NegaleinN Negalein

                                @Bass-T

                                ein Reboot-Button übers Web Interface wäre noch super

                                Bass-TB
                                Bass-TB
                                Bass-T
                                schrieb am zuletzt editiert von
                                #108

                                @Negalein sagte:
                                ein Reboot-Button übers Web Interface wäre noch super

                                find ich gut. Würde es aber nur in "Detail" Ansicht von node/VM reinmachen, wenn reboot required = yes
                                Es soll ja kein admin tool für Proxmox werden :D

                                Proxmox Cluster

                                • NUC7i5 ioBroker Server (Proxmox)
                                • HP Elitedesk 800 G4 SSF (Proxmox)
                                • HP Elitedesk 800 G2 DM (Proxmox)
                                  mein Proxmox-Updater für euch

                                KNX / Broadlink / Tasmota ESPs / Zigbee
                                Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                                NegaleinN 1 Antwort Letzte Antwort
                                0
                                • T
                                  T
                                  TT-Tom
                                  schrieb am zuletzt editiert von
                                  #109

                                  @bass-t

                                  Beta läuft bei mir auch, habe noch keine Probleme.
                                  Ein Problem oder Feature weiss nicht. ich bekomme immer zwei E-Mails mit unterschiedlichen Betreff und Inhalt, ist das normal? Beide auch zur gleichen Zeit.

                                  Betreff Ultimate Updater
                                  Bildschirmfoto 2026-09-09 um 09.39.32.png

                                  Betreff Ultimate Updater summary - proxmox1
                                  Bildschirmfoto 2026-09-09 um 09.40.04.png

                                  Gruß Tom

                                  Wenn meine Hilfe erfolgreich war, benutze bitte das Voting unten rechts im Beitrag

                                  Public Transport
                                  https://github.com/tt-tom17/ioBroker.public-transport
                                  https://github.com/tt-tom17/ioBroker.public-transport/wiki

                                  NSPanel Adapter Wiki
                                  https://github.com/ticaki/ioBroker.nspanel-lovelace-ui/wiki

                                  Bass-TB 1 Antwort Letzte Antwort
                                  0
                                  • T TT-Tom

                                    @bass-t

                                    Beta läuft bei mir auch, habe noch keine Probleme.
                                    Ein Problem oder Feature weiss nicht. ich bekomme immer zwei E-Mails mit unterschiedlichen Betreff und Inhalt, ist das normal? Beide auch zur gleichen Zeit.

                                    Betreff Ultimate Updater
                                    Bildschirmfoto 2026-09-09 um 09.39.32.png

                                    Betreff Ultimate Updater summary - proxmox1
                                    Bildschirmfoto 2026-09-09 um 09.40.04.png

                                    Bass-TB
                                    Bass-TB
                                    Bass-T
                                    schrieb am zuletzt editiert von
                                    #110

                                    @TT-Tom
                                    schau bitte mal in der ui. welche version und git short tag steht bei dir?

                                    so sollte es mit beta aussehen:

                                    image.jpeg

                                    Proxmox Cluster

                                    • NUC7i5 ioBroker Server (Proxmox)
                                    • HP Elitedesk 800 G4 SSF (Proxmox)
                                    • HP Elitedesk 800 G2 DM (Proxmox)
                                      mein Proxmox-Updater für euch

                                    KNX / Broadlink / Tasmota ESPs / Zigbee
                                    Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                                    T 1 Antwort Letzte Antwort
                                    0
                                    • Bass-TB Bass-T

                                      @Negalein sagte:
                                      ein Reboot-Button übers Web Interface wäre noch super

                                      find ich gut. Würde es aber nur in "Detail" Ansicht von node/VM reinmachen, wenn reboot required = yes
                                      Es soll ja kein admin tool für Proxmox werden :D

                                      NegaleinN
                                      NegaleinN
                                      Negalein
                                      schrieb am zuletzt editiert von
                                      #111

                                      @Bass-T sagte:

                                      find ich gut. Würde es aber nur in "Detail" Ansicht von node/VM reinmachen, wenn reboot required = yes

                                      ja, da wäre er gut aufgehoben

                                      ° Node.js & System Update ---> sudo apt update, iob stop, sudo apt full-upgrade
                                      ° Node.js Fixer ---> iob nodejs-update
                                      ° Fixer ---> iob fix

                                      1 Antwort Letzte Antwort
                                      1
                                      • Bass-TB Bass-T

                                        @TT-Tom
                                        schau bitte mal in der ui. welche version und git short tag steht bei dir?

                                        so sollte es mit beta aussehen:

                                        image.jpeg

                                        T
                                        T
                                        TT-Tom
                                        schrieb am zuletzt editiert von
                                        #112

                                        @Bass-T
                                        die mails waren noch vor der beta Installation.

                                        das ist mein aktueller Stand.
                                        Bildschirmfoto 2026-09-09 um 12.12.01.png

                                        habe nochmal den Check laufen lassen, passt jetzt mit deiner Übersicht. Danke

                                        Gruß Tom

                                        Wenn meine Hilfe erfolgreich war, benutze bitte das Voting unten rechts im Beitrag

                                        Public Transport
                                        https://github.com/tt-tom17/ioBroker.public-transport
                                        https://github.com/tt-tom17/ioBroker.public-transport/wiki

                                        NSPanel Adapter Wiki
                                        https://github.com/ticaki/ioBroker.nspanel-lovelace-ui/wiki

                                        1 Antwort Letzte Antwort
                                        1
                                        • Bass-TB
                                          Bass-TB
                                          Bass-T
                                          schrieb am zuletzt editiert von
                                          #113

                                          @negalein
                                          image.jpeg

                                          Proxmox Cluster

                                          • NUC7i5 ioBroker Server (Proxmox)
                                          • HP Elitedesk 800 G4 SSF (Proxmox)
                                          • HP Elitedesk 800 G2 DM (Proxmox)
                                            mein Proxmox-Updater für euch

                                          KNX / Broadlink / Tasmota ESPs / Zigbee
                                          Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                                          NegaleinN 1 Antwort Letzte Antwort
                                          1

                                          Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                                          Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                                          Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                                          Registrieren Anmelden
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          471

                                          Online

                                          33.1k

                                          Benutzende

                                          84.0k

                                          Themen

                                          1.4m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2026
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe