NEWS
Proxmox-Updater (Host/LXC/VM) auch ioBroker,piHole,etc
-
Ich finde ein händisch Updaten eigentlich ganz praktisch. Ich arbeite mich von weniger wichtigen zu wichtigen Instanzen auf meinem Proxmox-PVE vor, wenn da beim Update von "debian-test" etwas klemmt, kann man erstmal untersuchen, was es ist.
Ich finde das irgendwann eingeführte "mit nächsten LXC/VM weitermachen bei Fehler" etwas bedenklich. Ggfs macht das bequeme Script dann alle Container/VMs der Reihe nach kaputt wenn es ein systematisches Problem im Repository gibt...
Ich finde ein händisch Updaten eigentlich ganz praktisch. Ich arbeite mich von weniger wichtigen zu wichtigen Instanzen auf meinem Proxmox-PVE vor, wenn da beim Update von "debian-test" etwas klemmt, kann man erstmal untersuchen, was es ist.
Ich finde das irgendwann eingeführte "mit nächsten LXC/VM weitermachen bei Fehler" etwas bedenklich. Ggfs macht das bequeme Script dann alle Container/VMs der Reihe nach kaputt wenn es ein systematisches Problem im Repository gibt...
Vielen Dank erstmal für dein input. Und ich muss dir recht geben. Unter bestimmten kriterien und bei kritischen Systemen bin ich voll bei dir.
Daher gibt es unter Settings die Option "continue after errors".
Nach belieben einfach deaktivieren ;)

-
ps aux | grep -E 'apt|apt-get|dpkg'root@proxmox2:~# ps aux | grep -E 'apt|apt-get|dpkg' root 436538 0.0 0.0 6528 2376 pts/0 S+ 10:47 0:00 grep -E apt|apt-get|dpkg root 1448924 0.0 0.0 6652 3632 ? S Sep06 0:01 lxc-attach -n 108 --keep-env -- bash -c apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade -y 100000 1449016 0.1 0.9 90888 71624 ? S Sep06 1:49 apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade -y 100000 1455020 0.0 0.0 10612 5012 ? Ss+ Sep06 0:00 /usr/bin/dpkg --force-confdef --force-confold --status-fd 24 --configure --pending 100000 1457403 0.0 0.2 22936 17592 ? S+ Sep06 0:00 /usr/bin/perl /usr/share/debconf/frontend /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 100000 1457519 0.0 0.0 2680 1880 ? S+ Sep06 0:00 /bin/sh /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7ps -ef --forest | grep -A20 -B5 -E 'apt|apt-get|dpkg'root@proxmox2:~# ps -ef --forest | grep -A20 -B5 -E 'apt|apt-get|dpkg' root 930 1 0 Aug25 ? 00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups root 414595 930 0 10:42 ? 00:00:00 \_ sshd-session: root [priv] root 414633 414595 0 10:42 ? 00:00:00 | \_ sshd-session: root@pts/0 root 414636 414633 0 10:42 pts/0 00:00:00 | \_ -bash root 440215 414636 0 10:48 pts/0 00:00:00 | \_ ps -ef --forest root 440216 414636 0 10:48 pts/0 00:00:00 | \_ grep -A20 -B5 -E apt|apt-get|dpkg root 414600 930 0 10:42 ? 00:00:00 \_ sshd-session: root [priv] root 414635 414600 0 10:42 ? 00:00:00 \_ sshd-session: root@notty root 414637 414635 0 10:42 ? 00:00:00 \_ /usr/lib/openssh/sftp-server root 941 1 0 Aug25 tty1 00:00:00 /sbin/agetty -o -- \u --noreset --noclear - linux root 1075 1 0 Aug25 ? 00:00:03 /usr/sbin/cron -f root 1076 1 0 Aug25 ? 00:03:26 /usr/libexec/proxmox/proxmox-firewall start root 1097 1 0 Aug25 ? 01:46:56 pve-firewall root 1104 1 2 Aug25 ? 06:17:25 pvestatd root 1128 1 0 Aug25 ? 00:00:34 pvedaemon root 3834705 1128 0 05:37 ? 00:00:33 \_ pvedaemon worker root 3854893 1128 0 06:37 ? 00:00:31 \_ pvedaemon worker root 4479 1128 0 08:59 ? 00:00:11 \_ pvedaemon worker www-data 1141 1 0 Aug25 ? 00:01:05 pveproxy www-data 161013 1141 0 09:38 ? 00:00:04 \_ pveproxy worker www-data 173411 1141 0 09:42 ? 00:00:04 \_ pveproxy worker www-data 197959 1141 0 09:48 ? 00:00:03 \_ pveproxy worker www-data 1152 1 0 Aug25 ? 00:00:40 spiceproxy www-data 3433683 1152 0 00:04 ? 00:00:02 \_ spiceproxy worker root 1178 1 0 Aug25 ? 00:02:32 /usr/bin/lxc-start -F -n 108 100000 1248 1178 0 Aug25 ? 00:04:47 \_ /usr/lib/systemd/systemd --system --deserialize=34 -- root 1392096 1 0 Sep06 ? 00:02:44 /usr/bin/python3 /etc/ultimate-updater/web-ui/server.py --bind 0.0.0.0 --port 8765 root 1408660 1 0 Sep06 ? 00:00:00 /bin/bash /etc/ultimate-updater/job-runner.sh run-global ultimate-updater-update-all-systems-20260906-153017-380501623-1408489 /etc/ultimate-updater/global-update.sh root 1408698 1408660 0 Sep06 ? 00:00:00 \_ /bin/bash /etc/ultimate-updater/global-update.sh root 1408709 1408698 0 Sep06 ? 00:00:00 \_ /bin/bash /etc/ultimate-updater/update.sh root 1408811 1408709 0 Sep06 ? 00:00:00 \_ tee /var/log/ultimate-updater.log root 1448924 1408709 0 Sep06 ? 00:00:01 \_ lxc-attach -n 108 --keep-env -- bash -c apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade -y 100000 1449016 1448924 0 Sep06 ? 00:01:49 \_ apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade -y 100000 1455020 1449016 0 Sep06 ? 00:00:00 \_ /usr/bin/dpkg --force-confdef --force-confold --status-fd 24 --configure --pending 100000 1457403 1455020 0 Sep06 ? 00:00:00 \_ /usr/bin/perl /usr/share/debconf/frontend /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 100000 1457519 1457403 0 Sep06 ? 00:00:00 \_ /bin/sh /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 100000 1457533 1457519 0 Sep06 ? 00:00:00 | \_ /bin/sh /usr/bin/ucf --three-way --debconf-ok --sum-file /usr/share/openssh/sshd_config.md5sum /tmp/tmp.wUhDAEjyQn /etc/ssh/sshd_config 100000 1457635 1457403 0 Sep06 ? 00:00:00 \_ whiptail --backtitle Package configuration --title Configuring openssh-server --output-fd 11 --nocancel --default-item keep the local version currently installed --menu A new version (/tmp/tmp.wUhDAEjyQn) of configuration file /etc/ssh/sshd_config is available, but the version installed currently has been locally modified. What do you want to do about modified configuration file sshd_config? 19 76 7 -- install the package maintainer's version keep the local version currently installed show the differences between the versions show a side-by-side difference between the versions show a 3-way difference between available versions do a 3-way merge between available versions start a new shell to examine the situation _chrony 1429618 1 0 Sep06 ? 00:00:00 /usr/sbin/chronyd -n -F 1 _chrony 1429623 1429618 0 Sep06 ? 00:00:00 \_ /usr/sbin/chronyd -n -F 1 root 1431494 1 0 Sep06 ? 00:00:00 /usr/sbin/zed -F root 1432298 1 0 Sep06 ? 00:01:39 /usr/bin/pmxcfs root 1433736 1 0 Sep06 ? 00:00:00 /usr/sbin/qmeventd /var/run/qmeventd.sock root 1441793 1 0 Sep06 ? 00:00:00 /usr/sbin/rsyslogd -n -iNONE root 1441927 1 0 Sep06 ? 00:00:00 /usr/lib/postfix/sbin/master -w postfix 1441929 1441927 0 Sep06 ? 00:00:00 \_ qmgr -l -t unix -u postfix 299199 1441927 0 10:13 ? 00:00:00 \_ pickup -l -t unix -u -c root 1442345 1 0 Sep06 ? 00:00:16 pve-ha-lrm root 1442740 1 0 Sep06 ? 00:00:10 pve-ha-crm root 3433677 1 0 00:04 ? 00:00:10 /usr/sbin/pvefw-logger root 414602 1 0 10:42 ? 00:00:00 /usr/lib/systemd/systemd --user root 414604 414602 0 10:42 ? 00:00:00 \_ (sd-pam)dpkg --auditroot@proxmox2:~# dpkg --audit root@proxmox2:~#systemctl list-units --type=service | grep ultimate-updaterroot@proxmox2:~# systemctl list-units --type=service | grep ultimate-updater ● ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service loaded activating start start Ultimate Updater update for all systems ultimate-updater-web.service loaded active running Ultimate Updater web UI@Negalein danke, damit ist jetzt ziemlich eindeutig zu sehen, wo der Job hängt. 👍
Der Proxmox-Host selbst hängt nicht in
apt/dpkg.Der Updatejob befindet sich aktuell in LXC 108 und wartet dort auf eine interaktive Frage von
openssh-server:apt-get └─ dpkg └─ openssh-server.postinst └─ ucf └─ whiptailKonkret wartet dieser Dialog auf eine Entscheidung zu:
/etc/ssh/sshd_configDie Auswahl ist momentan sogar bereits auf
keep the local version currently installedvoreingestellt.
Das bestätigt genau die inzwischen gefundene Lücke:
Der Ultimate-Updater-Job läuft korrekt serverseitig weiter, kann die interaktivewhiptail-Abfrage derzeit aber noch nicht zurück an die WebUI/CLI geben.Bitte weiterhin nicht rebooten und auch nicht die komplette Ultimate-Updater-Service-Unit abschießen.
Dein
dpkg --auditwar übrigens auf dem Proxmox-Host. Relevant ist jetzt CT 108.Bitte zunächst einmal prüfen:
pct exec 108 -- dpkg --auditund:
pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'Wenn dort weiterhin der
whiptail-Prozess wartet, würde ich diesen einzelnen Dialog kontrolliert beenden und nichtaptoderdpkgselbst killen.Auf deinem bisherigen Prozessbaum wäre das PID
1457635, bitte die aktuelle PID vorher nochmals prüfen:pgrep -a whiptailDann nur diesen Prozess mit
TERMbeenden:kill -TERM <WHIPTAIL-PID>Danach bitte kurz warten und kontrollieren:
ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail'Sobald die laufende
apt/dpkg-Kette beendet ist, direkt interaktiv in den Container:pct enter 108Dort:
dpkg --auditund anschließend:
dpkg --configure -aJetzt läuft die Paketkonfiguration in einem echten Terminal und die Frage zu
sshd_configsollte sichtbar und beantwortbar sein.In deinem Fall würde ich bei einer bewusst angepassten SSH-Konfiguration in der Regel:
keep the local version currently installedwählen.
Danach nochmals:
dpkg --auditWenn das ohne Ausgabe zurückkommt, ist der dpkg-Zustand wieder sauber.
Bitte poste danach noch kurz die Ausgabe bzw. ob
dpkg --configure -asauber durchgelaufen ist.Der Fall ist für die Weiterentwicklung übrigens sehr hilfreich: Wir haben inzwischen auf dem Testcluster bereits erfolgreich nachgewiesen, dass sich genau solche Paketdialoge über einen persistenten PTY-/Reconnect-Kanal lösen lassen. Das wird jetzt sauber in den Ultimate Updater integriert.
-
@Negalein danke, damit ist jetzt ziemlich eindeutig zu sehen, wo der Job hängt. 👍
Der Proxmox-Host selbst hängt nicht in
apt/dpkg.Der Updatejob befindet sich aktuell in LXC 108 und wartet dort auf eine interaktive Frage von
openssh-server:apt-get └─ dpkg └─ openssh-server.postinst └─ ucf └─ whiptailKonkret wartet dieser Dialog auf eine Entscheidung zu:
/etc/ssh/sshd_configDie Auswahl ist momentan sogar bereits auf
keep the local version currently installedvoreingestellt.
Das bestätigt genau die inzwischen gefundene Lücke:
Der Ultimate-Updater-Job läuft korrekt serverseitig weiter, kann die interaktivewhiptail-Abfrage derzeit aber noch nicht zurück an die WebUI/CLI geben.Bitte weiterhin nicht rebooten und auch nicht die komplette Ultimate-Updater-Service-Unit abschießen.
Dein
dpkg --auditwar übrigens auf dem Proxmox-Host. Relevant ist jetzt CT 108.Bitte zunächst einmal prüfen:
pct exec 108 -- dpkg --auditund:
pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'Wenn dort weiterhin der
whiptail-Prozess wartet, würde ich diesen einzelnen Dialog kontrolliert beenden und nichtaptoderdpkgselbst killen.Auf deinem bisherigen Prozessbaum wäre das PID
1457635, bitte die aktuelle PID vorher nochmals prüfen:pgrep -a whiptailDann nur diesen Prozess mit
TERMbeenden:kill -TERM <WHIPTAIL-PID>Danach bitte kurz warten und kontrollieren:
ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail'Sobald die laufende
apt/dpkg-Kette beendet ist, direkt interaktiv in den Container:pct enter 108Dort:
dpkg --auditund anschließend:
dpkg --configure -aJetzt läuft die Paketkonfiguration in einem echten Terminal und die Frage zu
sshd_configsollte sichtbar und beantwortbar sein.In deinem Fall würde ich bei einer bewusst angepassten SSH-Konfiguration in der Regel:
keep the local version currently installedwählen.
Danach nochmals:
dpkg --auditWenn das ohne Ausgabe zurückkommt, ist der dpkg-Zustand wieder sauber.
Bitte poste danach noch kurz die Ausgabe bzw. ob
dpkg --configure -asauber durchgelaufen ist.Der Fall ist für die Weiterentwicklung übrigens sehr hilfreich: Wir haben inzwischen auf dem Testcluster bereits erfolgreich nachgewiesen, dass sich genau solche Paketdialoge über einen persistenten PTY-/Reconnect-Kanal lösen lassen. Das wird jetzt sauber in den Ultimate Updater integriert.
pct exec 108 -- dpkg --auditroot@proxmox2:~# pct exec 108 -- dpkg --audit Another process has locked the database for writing, and might currently be modifying it, some of the following problems might just be due to that. The following packages have been unpacked but not yet configured. They must be configured using dpkg --configure or the configure menu option in dselect for them to work: bind9-dnsutils Clients provided with BIND 9 python3.13-dev Header files and a static library for Python (v3.13) ssh secure shell client and server (metapackage) The following packages are only half configured, probably due to problems configuring them the first time. The configuration should be retried using dpkg --configure <package> or the configure menu option in dselect: openssh-server secure shell (SSH) server, for secure access from remote The following packages have been triggered, but the trigger processing has not yet been done. Trigger processing can be requested using dselect or dpkg --configure --pending (or dpkg --triggers-only): dbus simple interprocess messaging system (system message bus) debianutils Miscellaneous utilities specific to Debian libc-bin GNU C Library: Binaries mailcap Debian's mailcap system, and support programs man-db tools for reading manual pages postfix High-performance mail transport agent procps /proc file system utilities shared-mime-info FreeDesktop.org shared MIME database and specpct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'root@proxmox2:~# pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail' root 157822 0 0 Sep06 ? 00:01:52 apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade -y root 160812 157822 0 Sep06 pts/3 00:00:00 /usr/bin/dpkg --force-confdef --force-confold --status-fd 24 --configure --pending root 161796 160812 0 Sep06 pts/3 00:00:00 /usr/bin/perl /usr/share/debconf/frontend /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 root 161809 161796 0 Sep06 pts/3 00:00:00 /bin/sh /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 root 161814 161809 0 Sep06 pts/3 00:00:00 /bin/sh /usr/bin/ucf --three-way --debconf-ok --sum-file /usr/share/openssh/sshd_config.md5sum /tmp/tmp.wUhDAEjyQn /etc/ssh/sshd_config root 161852 161796 0 Sep06 pts/3 00:00:00 whiptail --backtitle Package configuration --title Configuring openssh-server --output-fd 11 --nocancel --default-item keep the local version currently installed --menu A new version (/tmp/tmp.wUhDAEjyQn) of configuration file /etc/ssh/sshd_config is available, but the version installed currently has been locally modified. What do you want to do about modified configuration file sshd_config? 19 76 7 -- install the package maintainer's version keep the local version currently installed show the differences between the versions show a side-by-side difference between the versions show a 3-way difference between available versions do a 3-way merge between available versions start a new shell to examine the situationpgrep -a whiptailroot@proxmox2:~# pgrep -a whiptail 1457635 whiptail --backtitle Package configuration --title Configuring openssh-server --output-fd 11 --nocancel --default-item keep the local version currently installed --menu A new version (/tmp/tmp.wUhDAEjyQn) of configuration file /etc/ssh/sshd_config is available, but the version installed currently has been locally modified. What do you want to do about modified configuration file sshd_config? 19 76 7 -- install the package maintainer's version keep the local version currently installed show the differences between the versions show a side-by-side difference between the versions show a 3-way difference between available versions do a 3-way merge between available versions start a new shell to examine the situationkill -TERM 1457635root@proxmox2:~# kill -TERM 1457635 root@proxmox2:~#ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail'root@proxmox2:~# ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail' 100000 570046 0.3 0.1 17788 14536 ? S 11:20 0:00 bash -c #!/usr/bin/env bash _CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main" _cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func" source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func") # Copyright (c) 2021-2026 tteck # Author: tteck (tteckster) # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://www.home-assistant.io/ APP="Home Assistant" var_tags="${var_tags:-automation;smarthome;docker}" var_cpu="${var_cpu:-2}" var_ram="${var_ram:-2048}" var_disk="${var_disk:-16}" var_os="${var_os:-debian}" var_version="${var_version:-13}" var_arm64="${var_arm64:-yes}" var_unprivileged="${var_unprivileged:-1}" header_info "$APP" variables color catch_errors function update_script() { header_info check_container_storage check_container_resources if [[ ! -d /var/lib/docker/volumes/hass_config/_data ]]; then msg_error "No ${APP} Installation Found!" exit fi UPD=$(msg_menu "Home Assistant Update Options" \ "1" "Update ALL Containers" \ "2" "Remove ALL Unused Images" \ "3" "Install HACS" \ "4" "Install FileBrowser") if [ "$UPD" == "1" ]; then msg_info "Updating All Containers" CONTAINER_LIST="${1:-$(docker ps -q)}" for container in ${CONTAINER_LIST}; do CONTAINER_IMAGE="$(docker inspect --format "{{.Config.Image}}" --type container "${container}")" RUNNING_IMAGE="$(docker inspect --format "{{.Image}}" --type container "${container}")" docker pull "${CONTAINER_IMAGE}" LATEST_IMAGE="$(docker inspect --format "{{.Id}}" --type image "${CONTAINER_IMAGE}")" if [[ "${RUNNING_IMAGE}" != "${LATEST_IMAGE}" ]]; then pip install -U runlike echo "Updating ${container} image ${CONTAINER_IMAGE}" DOCKER_COMMAND="$(runlike --use-volume-id "${container}")" docker rm --force "${container}" eval "${DOCKER_COMMAND}" fi done msg_ok "Updated All Containers" exit fi if [ "$UPD" == "2" ]; then msg_info "Removing ALL Unused Images" docker image prune -af msg_ok "Removed ALL Unused Images" exit fi if [ "$UPD" == "3" ]; then msg_info "Installing Home Assistant Community Store (HACS)" $STD apt update cd /var/lib/docker/volumes/hass_config/_data $STD bash <(curl -fsSL https://get.hacs.xyz) msg_ok "Installed Home Assistant Community Store (HACS)" echo -e "\n Reboot Home Assistant and clear browser cache then Add HACS integration.\n" exit fi if [ "$UPD" == "4" ]; then msg_info "Installing FileBrowser" RELEASE=$(curl -fsSL https://api.github.com/repos/filebrowser/filebrowser/releases/latest | grep -o '"tag_name": ".*"' | sed 's/"//g' | sed 's/tag_name: //g') $STD curl -fsSL https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-$(arch_resolve)-filebrowser.tar.gz | tar -xzv -C /usr/local/bin $STD filebrowser config init -a '0.0.0.0' $STD filebrowser config set -a '0.0.0.0' $STD filebrowser users add admin community-scripts.org --perm.admin msg_ok "Installed FileBrowser" msg_info "Creating Service" service_path="/etc/systemd/system/filebrowser.service" echo "[Unit] Description=Filebrowser After=network-online.target [Service] User=root WorkingDirectory=/root/ ExecStart=/usr/local/bin/filebrowser -r / [Install] WantedBy=default.target" >$service_path $STD systemctl enable --now filebrowser msg_ok "Created Service" msg_ok "Completed successfully!\n" echo -e "FileBrowser should be reachable by going to the following URL. ${BL}http://$LOCAL_IP:8080${CL} admin|community-scripts.org\n" exit fi } start build_container description msg_ok "Completed successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${INFO}${YW}Access it using the following URL:${CL}" echo -e "${GATEWAY}${BGN}HA: http://${IP}:8123${CL}" echo -e "${GATEWAY}${BGN}Portainer: https://${IP}:9443${CL}" root 574939 0.0 0.0 6660 2352 pts/0 S+ 11:21 0:00 grep -E apt|apt-get|dpkg|ucf|whiptailjetzt hier weiter?
Sobald die laufende apt/dpkg-Kette beendet ist, direkt interaktiv in den Container:
Copy to Clipboard
pct enter 108
Dort:
Copy to Clipboard
dpkg --audit
und anschließend:
Copy to Clipboard
dpkg --configure -a
-
pct exec 108 -- dpkg --auditroot@proxmox2:~# pct exec 108 -- dpkg --audit Another process has locked the database for writing, and might currently be modifying it, some of the following problems might just be due to that. The following packages have been unpacked but not yet configured. They must be configured using dpkg --configure or the configure menu option in dselect for them to work: bind9-dnsutils Clients provided with BIND 9 python3.13-dev Header files and a static library for Python (v3.13) ssh secure shell client and server (metapackage) The following packages are only half configured, probably due to problems configuring them the first time. The configuration should be retried using dpkg --configure <package> or the configure menu option in dselect: openssh-server secure shell (SSH) server, for secure access from remote The following packages have been triggered, but the trigger processing has not yet been done. Trigger processing can be requested using dselect or dpkg --configure --pending (or dpkg --triggers-only): dbus simple interprocess messaging system (system message bus) debianutils Miscellaneous utilities specific to Debian libc-bin GNU C Library: Binaries mailcap Debian's mailcap system, and support programs man-db tools for reading manual pages postfix High-performance mail transport agent procps /proc file system utilities shared-mime-info FreeDesktop.org shared MIME database and specpct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'root@proxmox2:~# pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail' root 157822 0 0 Sep06 ? 00:01:52 apt-get -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade -y root 160812 157822 0 Sep06 pts/3 00:00:00 /usr/bin/dpkg --force-confdef --force-confold --status-fd 24 --configure --pending root 161796 160812 0 Sep06 pts/3 00:00:00 /usr/bin/perl /usr/share/debconf/frontend /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 root 161809 161796 0 Sep06 pts/3 00:00:00 /bin/sh /var/lib/dpkg/info/openssh-server.postinst configure 1:10.0p1-7 root 161814 161809 0 Sep06 pts/3 00:00:00 /bin/sh /usr/bin/ucf --three-way --debconf-ok --sum-file /usr/share/openssh/sshd_config.md5sum /tmp/tmp.wUhDAEjyQn /etc/ssh/sshd_config root 161852 161796 0 Sep06 pts/3 00:00:00 whiptail --backtitle Package configuration --title Configuring openssh-server --output-fd 11 --nocancel --default-item keep the local version currently installed --menu A new version (/tmp/tmp.wUhDAEjyQn) of configuration file /etc/ssh/sshd_config is available, but the version installed currently has been locally modified. What do you want to do about modified configuration file sshd_config? 19 76 7 -- install the package maintainer's version keep the local version currently installed show the differences between the versions show a side-by-side difference between the versions show a 3-way difference between available versions do a 3-way merge between available versions start a new shell to examine the situationpgrep -a whiptailroot@proxmox2:~# pgrep -a whiptail 1457635 whiptail --backtitle Package configuration --title Configuring openssh-server --output-fd 11 --nocancel --default-item keep the local version currently installed --menu A new version (/tmp/tmp.wUhDAEjyQn) of configuration file /etc/ssh/sshd_config is available, but the version installed currently has been locally modified. What do you want to do about modified configuration file sshd_config? 19 76 7 -- install the package maintainer's version keep the local version currently installed show the differences between the versions show a side-by-side difference between the versions show a 3-way difference between available versions do a 3-way merge between available versions start a new shell to examine the situationkill -TERM 1457635root@proxmox2:~# kill -TERM 1457635 root@proxmox2:~#ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail'root@proxmox2:~# ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail' 100000 570046 0.3 0.1 17788 14536 ? S 11:20 0:00 bash -c #!/usr/bin/env bash _CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main" _cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func" source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func") # Copyright (c) 2021-2026 tteck # Author: tteck (tteckster) # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://www.home-assistant.io/ APP="Home Assistant" var_tags="${var_tags:-automation;smarthome;docker}" var_cpu="${var_cpu:-2}" var_ram="${var_ram:-2048}" var_disk="${var_disk:-16}" var_os="${var_os:-debian}" var_version="${var_version:-13}" var_arm64="${var_arm64:-yes}" var_unprivileged="${var_unprivileged:-1}" header_info "$APP" variables color catch_errors function update_script() { header_info check_container_storage check_container_resources if [[ ! -d /var/lib/docker/volumes/hass_config/_data ]]; then msg_error "No ${APP} Installation Found!" exit fi UPD=$(msg_menu "Home Assistant Update Options" \ "1" "Update ALL Containers" \ "2" "Remove ALL Unused Images" \ "3" "Install HACS" \ "4" "Install FileBrowser") if [ "$UPD" == "1" ]; then msg_info "Updating All Containers" CONTAINER_LIST="${1:-$(docker ps -q)}" for container in ${CONTAINER_LIST}; do CONTAINER_IMAGE="$(docker inspect --format "{{.Config.Image}}" --type container "${container}")" RUNNING_IMAGE="$(docker inspect --format "{{.Image}}" --type container "${container}")" docker pull "${CONTAINER_IMAGE}" LATEST_IMAGE="$(docker inspect --format "{{.Id}}" --type image "${CONTAINER_IMAGE}")" if [[ "${RUNNING_IMAGE}" != "${LATEST_IMAGE}" ]]; then pip install -U runlike echo "Updating ${container} image ${CONTAINER_IMAGE}" DOCKER_COMMAND="$(runlike --use-volume-id "${container}")" docker rm --force "${container}" eval "${DOCKER_COMMAND}" fi done msg_ok "Updated All Containers" exit fi if [ "$UPD" == "2" ]; then msg_info "Removing ALL Unused Images" docker image prune -af msg_ok "Removed ALL Unused Images" exit fi if [ "$UPD" == "3" ]; then msg_info "Installing Home Assistant Community Store (HACS)" $STD apt update cd /var/lib/docker/volumes/hass_config/_data $STD bash <(curl -fsSL https://get.hacs.xyz) msg_ok "Installed Home Assistant Community Store (HACS)" echo -e "\n Reboot Home Assistant and clear browser cache then Add HACS integration.\n" exit fi if [ "$UPD" == "4" ]; then msg_info "Installing FileBrowser" RELEASE=$(curl -fsSL https://api.github.com/repos/filebrowser/filebrowser/releases/latest | grep -o '"tag_name": ".*"' | sed 's/"//g' | sed 's/tag_name: //g') $STD curl -fsSL https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-$(arch_resolve)-filebrowser.tar.gz | tar -xzv -C /usr/local/bin $STD filebrowser config init -a '0.0.0.0' $STD filebrowser config set -a '0.0.0.0' $STD filebrowser users add admin community-scripts.org --perm.admin msg_ok "Installed FileBrowser" msg_info "Creating Service" service_path="/etc/systemd/system/filebrowser.service" echo "[Unit] Description=Filebrowser After=network-online.target [Service] User=root WorkingDirectory=/root/ ExecStart=/usr/local/bin/filebrowser -r / [Install] WantedBy=default.target" >$service_path $STD systemctl enable --now filebrowser msg_ok "Created Service" msg_ok "Completed successfully!\n" echo -e "FileBrowser should be reachable by going to the following URL. ${BL}http://$LOCAL_IP:8080${CL} admin|community-scripts.org\n" exit fi } start build_container description msg_ok "Completed successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${INFO}${YW}Access it using the following URL:${CL}" echo -e "${GATEWAY}${BGN}HA: http://${IP}:8123${CL}" echo -e "${GATEWAY}${BGN}Portainer: https://${IP}:9443${CL}" root 574939 0.0 0.0 6660 2352 pts/0 S+ 11:21 0:00 grep -E apt|apt-get|dpkg|ucf|whiptailjetzt hier weiter?
Sobald die laufende apt/dpkg-Kette beendet ist, direkt interaktiv in den Container:
Copy to Clipboard
pct enter 108
Dort:
Copy to Clipboard
dpkg --audit
und anschließend:
Copy to Clipboard
dpkg --configure -a
@Negalein ja, grundsätzlich wäre
dpkg --configure -ader nächste Reparaturschritt – aber bitte noch einen Moment warten und nicht direkt ausführen. 👍Das Beenden von
whiptailhat funktioniert:
Die vorher festhängende Ketteapt-get → dpkg → openssh-server.postinst → ucf → whiptailist jetzt nicht mehr in der Prozessliste.
Allerdings sieht man inzwischen einen neuen Community-Scripts-Prozess. Das heißt sehr wahrscheinlich, dass der globale Ultimate-Updater-Job nach dem abgebrochenen Paketdialog bereits weitergelaufen ist.
Bevor wir jetzt manuell
dpkg --configure -ain CT 108 starten, müssen wir deshalb kurz sicherstellen, dass dort nicht erneut bzw. parallel etwas arbeitet.Bitte einmal:
pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'und:
pct exec 108 -- dpkg --auditZusätzlich bitte den aktuellen Ultimate-Updater-Status:
ultimate-updater statusund den aktuellen Stand des laufenden Jobs:
systemctl status ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service --no-pagersowie die letzten Zeilen:
journalctl -u ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service -n 80 --no-pagerWenn in CT 108 kein
apt/dpkgmehr läuft, können wir anschließend dort sauber interaktiv fortsetzen mit:pct enter 108und dann:
dpkg --configure -aBei der Frage zu
/etc/ssh/sshd_configwürde ich bei einer bewusst angepassten SSH-Konfiguration weiterhinkeep the local version currently installedwählen.
Aber erst die obigen Ausgaben – ich möchte vermeiden, dass wir jetzt einen zweiten
dpkgstarten, während der Ultimate Updater noch irgendwo im selben Container arbeitet. -
@Negalein danke, damit ist jetzt ziemlich eindeutig zu sehen, wo der Job hängt. 👍
Der Proxmox-Host selbst hängt nicht in
apt/dpkg.Der Updatejob befindet sich aktuell in LXC 108 und wartet dort auf eine interaktive Frage von
openssh-server:apt-get └─ dpkg └─ openssh-server.postinst └─ ucf └─ whiptailKonkret wartet dieser Dialog auf eine Entscheidung zu:
/etc/ssh/sshd_configDie Auswahl ist momentan sogar bereits auf
keep the local version currently installedvoreingestellt.
Das bestätigt genau die inzwischen gefundene Lücke:
Der Ultimate-Updater-Job läuft korrekt serverseitig weiter, kann die interaktivewhiptail-Abfrage derzeit aber noch nicht zurück an die WebUI/CLI geben.Bitte weiterhin nicht rebooten und auch nicht die komplette Ultimate-Updater-Service-Unit abschießen.
Dein
dpkg --auditwar übrigens auf dem Proxmox-Host. Relevant ist jetzt CT 108.Bitte zunächst einmal prüfen:
pct exec 108 -- dpkg --auditund:
pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'Wenn dort weiterhin der
whiptail-Prozess wartet, würde ich diesen einzelnen Dialog kontrolliert beenden und nichtaptoderdpkgselbst killen.Auf deinem bisherigen Prozessbaum wäre das PID
1457635, bitte die aktuelle PID vorher nochmals prüfen:pgrep -a whiptailDann nur diesen Prozess mit
TERMbeenden:kill -TERM <WHIPTAIL-PID>Danach bitte kurz warten und kontrollieren:
ps aux | grep -E 'apt|apt-get|dpkg|ucf|whiptail'Sobald die laufende
apt/dpkg-Kette beendet ist, direkt interaktiv in den Container:pct enter 108Dort:
dpkg --auditund anschließend:
dpkg --configure -aJetzt läuft die Paketkonfiguration in einem echten Terminal und die Frage zu
sshd_configsollte sichtbar und beantwortbar sein.In deinem Fall würde ich bei einer bewusst angepassten SSH-Konfiguration in der Regel:
keep the local version currently installedwählen.
Danach nochmals:
dpkg --auditWenn das ohne Ausgabe zurückkommt, ist der dpkg-Zustand wieder sauber.
Bitte poste danach noch kurz die Ausgabe bzw. ob
dpkg --configure -asauber durchgelaufen ist.Der Fall ist für die Weiterentwicklung übrigens sehr hilfreich: Wir haben inzwischen auf dem Testcluster bereits erfolgreich nachgewiesen, dass sich genau solche Paketdialoge über einen persistenten PTY-/Reconnect-Kanal lösen lassen. Das wird jetzt sauber in den Ultimate Updater integriert.
-
@Negalein ja, grundsätzlich wäre
dpkg --configure -ader nächste Reparaturschritt – aber bitte noch einen Moment warten und nicht direkt ausführen. 👍Das Beenden von
whiptailhat funktioniert:
Die vorher festhängende Ketteapt-get → dpkg → openssh-server.postinst → ucf → whiptailist jetzt nicht mehr in der Prozessliste.
Allerdings sieht man inzwischen einen neuen Community-Scripts-Prozess. Das heißt sehr wahrscheinlich, dass der globale Ultimate-Updater-Job nach dem abgebrochenen Paketdialog bereits weitergelaufen ist.
Bevor wir jetzt manuell
dpkg --configure -ain CT 108 starten, müssen wir deshalb kurz sicherstellen, dass dort nicht erneut bzw. parallel etwas arbeitet.Bitte einmal:
pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'und:
pct exec 108 -- dpkg --auditZusätzlich bitte den aktuellen Ultimate-Updater-Status:
ultimate-updater statusund den aktuellen Stand des laufenden Jobs:
systemctl status ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service --no-pagersowie die letzten Zeilen:
journalctl -u ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service -n 80 --no-pagerWenn in CT 108 kein
apt/dpkgmehr läuft, können wir anschließend dort sauber interaktiv fortsetzen mit:pct enter 108und dann:
dpkg --configure -aBei der Frage zu
/etc/ssh/sshd_configwürde ich bei einer bewusst angepassten SSH-Konfiguration weiterhinkeep the local version currently installedwählen.
Aber erst die obigen Ausgaben – ich möchte vermeiden, dass wir jetzt einen zweiten
dpkgstarten, während der Ultimate Updater noch irgendwo im selben Container arbeitet.pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'root@proxmox2:~# pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail' root@proxmox2:~#pct exec 108 -- dpkg --auditroot@proxmox2:~# pct exec 108 -- dpkg --audit root@proxmox2:~#ultimate-updater statusroot@proxmox2:~# ultimate-updater status Status schema 1 Generated: 2026-09-07T09:23:33Z Target / friendly name Reachable Updates Status Last check ---------------------- --------- ------- ------ ---------- proxmox2 yes 130 updates_ava... 2026-09-06T15:29:41Z 108 · spoolman yes 106 updates_ava... 2026-09-06T15:29:41Z 109 · homeassistant yes 73 updates_ava... 2026-09-06T15:29:41Z 110 · agentdvr yes 96 updates_ava... 2026-09-06T15:29:41Z Jobs Type Target / friendly name State Exit JOB all-systems completed 0 CHECK all-systems completed 0 UPDATE all-systems completed 0 JOB selfupdate completed 0systemctl status ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service --no-pagerroot@proxmox2:~# systemctl status ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service --no-pager Unit ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service could not be found.journalctl -u ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service -n 80 --no-pagerSep 07 11:30:52 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:03 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:03 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:14 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:14 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:25 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:25 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:33 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:36 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:36 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:47 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:47 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:58 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:58 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:09 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:09 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:20 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:20 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory root@proxmox -
pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail'root@proxmox2:~# pct exec 108 -- ps -ef | grep -E 'apt|apt-get|dpkg|ucf|whiptail' root@proxmox2:~#pct exec 108 -- dpkg --auditroot@proxmox2:~# pct exec 108 -- dpkg --audit root@proxmox2:~#ultimate-updater statusroot@proxmox2:~# ultimate-updater status Status schema 1 Generated: 2026-09-07T09:23:33Z Target / friendly name Reachable Updates Status Last check ---------------------- --------- ------- ------ ---------- proxmox2 yes 130 updates_ava... 2026-09-06T15:29:41Z 108 · spoolman yes 106 updates_ava... 2026-09-06T15:29:41Z 109 · homeassistant yes 73 updates_ava... 2026-09-06T15:29:41Z 110 · agentdvr yes 96 updates_ava... 2026-09-06T15:29:41Z Jobs Type Target / friendly name State Exit JOB all-systems completed 0 CHECK all-systems completed 0 UPDATE all-systems completed 0 JOB selfupdate completed 0systemctl status ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service --no-pagerroot@proxmox2:~# systemctl status ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service --no-pager Unit ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service could not be found.journalctl -u ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service -n 80 --no-pagerSep 07 11:30:52 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:03 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:03 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:14 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:14 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:25 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:25 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:33 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:36 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:36 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:47 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:47 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:58 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:31:58 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:09 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:09 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:20 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory Sep 07 11:32:20 proxmox2 systemd[1]: ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: Failed to open /run/systemd/transient/ultimate-updater-update-all-systems-20260906-153017-380501623-1408489.service: No such file or directory root@proxmox@Negalein perfekt – jetzt sieht alles wieder sauber aus. 👍
Du musst nicht mehr mit
pct enter 108unddpkg --configure -aweitermachen.Die wichtigen Punkte sind:
pct exec 108 -- ps ...liefert keinen laufenden
apt-/dpkg-/ucf-/whiptail-Prozess mehr.Und:
pct exec 108 -- dpkg --auditliefert ebenfalls keine Ausgabe.
Damit ist der Paketstatus in CT 108 wieder sauber. ✅
Auch der Ultimate Updater selbst hat den Lauf inzwischen beendet:
JOB all-systems completed 0 CHECK all-systems completed 0 UPDATE all-systems completed 0Der Job ist also nicht mehr aktiv und hat Exitcode
0.Dass
Unit ultimate-updater-update-all-systems-....service could not be foundkommt, ist an dieser Stelle ebenfalls normal:
Ultimate Updater verwendet dafür eine transiente systemd-Unit. Sobald der Job beendet und aufgeräumt wurde, existiert diese Unit unter
/run/systemd/transient/nicht mehr.Die nachfolgenden Meldungen im Journal
Failed to open /run/systemd/transient/...entstehen nur dadurch, dass nachträglich nach dieser bereits entfernten Unit gefragt wurde. Das ist jetzt kein weiterer Fehler des Updates.
Was jetzt noch sinnvoll ist
Die Updatezahlen in
ultimate-updater statusstammen noch vom vorherigen Check:
Last check: 2026-09-06T15:29:41ZDeshalb würde ich jetzt einfach nochmal einen normalen Check aller Systeme über die WebUI starten.
Noch kein weiteres Update, nur Check.
Dann sollten die Updatezahlen und Statuswerte wieder auf dem aktuellen Stand sein.
Der Fall war übrigens sehr hilfreich. 🙂
Damit ist jetzt eindeutig bestätigt, dass der lange Lauf durch den interaktiven
openssh-server-/sshd_config-Dialog in LXC 108 verursacht wurde und nicht durch einen Defekt der Jobverwaltung.Genau diese Lücke – interaktive Paketabfragen bei serverseitig weiterlaufenden Jobs – wird gerade auf
developbearbeitet.Auf dem Testcluster konnte bereits erfolgreich nachgewiesen werden, dass ein solcher
dpkg-/whiptail-Prompt auch nach Disconnect über einen persistenten PTY-Kanal wieder verbunden und beantwortet werden kann.Danke fürs Durchhalten und für die ganzen Ausgaben. 👍
-
@Negalein perfekt – jetzt sieht alles wieder sauber aus. 👍
Du musst nicht mehr mit
pct enter 108unddpkg --configure -aweitermachen.Die wichtigen Punkte sind:
pct exec 108 -- ps ...liefert keinen laufenden
apt-/dpkg-/ucf-/whiptail-Prozess mehr.Und:
pct exec 108 -- dpkg --auditliefert ebenfalls keine Ausgabe.
Damit ist der Paketstatus in CT 108 wieder sauber. ✅
Auch der Ultimate Updater selbst hat den Lauf inzwischen beendet:
JOB all-systems completed 0 CHECK all-systems completed 0 UPDATE all-systems completed 0Der Job ist also nicht mehr aktiv und hat Exitcode
0.Dass
Unit ultimate-updater-update-all-systems-....service could not be foundkommt, ist an dieser Stelle ebenfalls normal:
Ultimate Updater verwendet dafür eine transiente systemd-Unit. Sobald der Job beendet und aufgeräumt wurde, existiert diese Unit unter
/run/systemd/transient/nicht mehr.Die nachfolgenden Meldungen im Journal
Failed to open /run/systemd/transient/...entstehen nur dadurch, dass nachträglich nach dieser bereits entfernten Unit gefragt wurde. Das ist jetzt kein weiterer Fehler des Updates.
Was jetzt noch sinnvoll ist
Die Updatezahlen in
ultimate-updater statusstammen noch vom vorherigen Check:
Last check: 2026-09-06T15:29:41ZDeshalb würde ich jetzt einfach nochmal einen normalen Check aller Systeme über die WebUI starten.
Noch kein weiteres Update, nur Check.
Dann sollten die Updatezahlen und Statuswerte wieder auf dem aktuellen Stand sein.
Der Fall war übrigens sehr hilfreich. 🙂
Damit ist jetzt eindeutig bestätigt, dass der lange Lauf durch den interaktiven
openssh-server-/sshd_config-Dialog in LXC 108 verursacht wurde und nicht durch einen Defekt der Jobverwaltung.Genau diese Lücke – interaktive Paketabfragen bei serverseitig weiterlaufenden Jobs – wird gerade auf
developbearbeitet.Auf dem Testcluster konnte bereits erfolgreich nachgewiesen werden, dass ein solcher
dpkg-/whiptail-Prompt auch nach Disconnect über einen persistenten PTY-Kanal wieder verbunden und beantwortet werden kann.Danke fürs Durchhalten und für die ganzen Ausgaben. 👍
-
Noch kein weiteres Update, nur Check.

Danke fürs Durchhalten und für die ganzen Ausgaben. 👍
gerne! nur gemeinsam kommt man ans Ziel :)
den Host kann ich jetzt rebooten?

@Negalein jetzt spricht aus Sicht des Ultimate Updaters nichts mehr gegen einen Reboot. 👍
Der problematische Paketlauf ist beendet:
- kein
apt/dpkgmehr aktiv dpkg --auditsauber- Ultimate-Updater-Job
completed 0 - abschließender Check ebenfalls erfolgreich
Damit besteht keine Gefahr mehr, dass du durch den Reboot einen laufenden Paketmanager abschießt.
Die
pveproxy-/pve-ssl.key-Meldungen sind ein separates Proxmox-Thema. Bevor du rebootest, würde ich deshalb nur noch kurz prüfen:systemctl --failedund:
systemctl is-active pveproxy pvedaemon pvestatdFalls der Host Teil eines Clusters ist zusätzlich:
pvecm statusWenn da nichts Kritisches auffällt, kannst du den Host rebooten.
Vielen Dank nochmal - gefällt mir deine Einstellung

- kein
-
@Negalein jetzt spricht aus Sicht des Ultimate Updaters nichts mehr gegen einen Reboot. 👍
Der problematische Paketlauf ist beendet:
- kein
apt/dpkgmehr aktiv dpkg --auditsauber- Ultimate-Updater-Job
completed 0 - abschließender Check ebenfalls erfolgreich
Damit besteht keine Gefahr mehr, dass du durch den Reboot einen laufenden Paketmanager abschießt.
Die
pveproxy-/pve-ssl.key-Meldungen sind ein separates Proxmox-Thema. Bevor du rebootest, würde ich deshalb nur noch kurz prüfen:systemctl --failedund:
systemctl is-active pveproxy pvedaemon pvestatdFalls der Host Teil eines Clusters ist zusätzlich:
pvecm statusWenn da nichts Kritisches auffällt, kannst du den Host rebooten.
Vielen Dank nochmal - gefällt mir deine Einstellung

systemctl --failed
root@proxmox2:~# systemctl --failed UNIT LOAD ACTIVE SUB DESCRIPTION 0 loaded units listed.systemctl is-active pveproxy pvedaemon pvestatd
root@proxmox2:~# systemctl is-active pveproxy pvedaemon pvestatd active active activeschaut alles gut aus.
Dann reboote ich mal. - kein
-
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.@TT-Tom ja, genau derselbe Grund.
Das Problem ist inzwischen eindeutig identifiziert und der technische Fix ist auf
developbereits 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. -
@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
-
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); - Kompletter Status aller Targets (Host, VMs, CTs) als Datenpunkte, automatisch je
-
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); - Kompletter Status aller Targets (Host, VMs, CTs) als Datenpunkte, automatisch je
-
-
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. 👍🏻 -
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. 👍🏻
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