diff --git a/.github/scripts/tests/test_pbs_cli_localization.py b/.github/scripts/tests/test_pbs_cli_localization.py index bda1064b..618395e2 100644 --- a/.github/scripts/tests/test_pbs_cli_localization.py +++ b/.github/scripts/tests/test_pbs_cli_localization.py @@ -167,7 +167,7 @@ class UpdateCliTests(unittest.TestCase): with tempfile.TemporaryDirectory(prefix='pbs-update-i18n-') as folder: body=function('scripts/global/update-pbs.sh','update_pbs') for old,new in [('/var/log/',folder+'/log/'),('/tmp/proxmenux_screen_capture_',folder+'/tmp/proxmenux_screen_capture_'),('/var/run/',folder+'/run/'),('/boot/',folder+'/boot/')]: body=body.replace(old,new) - script=UPDATE_HARNESS+function('scripts/utils.sh','translate')+body+'\nupdate_pbs\n' + script=UPDATE_HARNESS+function('scripts/utils.sh','translate')+function('scripts/global/common-functions.sh','pmx_confirm_update')+body+'\nupdate_pbs\n' result=subprocess.run(['bash','-c',script],capture_output=True,text=True,timeout=15, env={**os.environ,'REPO':str(ROOT),'FIXTURE':folder,'CASE':case,'LANGUAGE':language,'SUITE':suite}) files={str(p.relative_to(folder)):p.read_text() for p in Path(folder).rglob('*') if p.is_file()} diff --git a/AppImage/ProxMenux-1.2.7.AppImage b/AppImage/ProxMenux-1.2.7.AppImage index 17ff55ad..48d0bc83 100755 Binary files a/AppImage/ProxMenux-1.2.7.AppImage and b/AppImage/ProxMenux-1.2.7.AppImage differ diff --git a/AppImage/ProxMenux-Monitor.AppImage.sha256 b/AppImage/ProxMenux-Monitor.AppImage.sha256 index 5d0e6c81..7eb5d949 100644 --- a/AppImage/ProxMenux-Monitor.AppImage.sha256 +++ b/AppImage/ProxMenux-Monitor.AppImage.sha256 @@ -1 +1 @@ -672e5c8aa1a160164edafe878c767d78ea2a4073d9b594340658fdf9847f9e48 ProxMenux-1.2.7.AppImage +be3f0a0813edc04971a2bbaada9a7f5a54b7232fca66d1144bc30687cad5b710 ProxMenux-1.2.7.AppImage diff --git a/AppImage/components/health-thresholds.tsx b/AppImage/components/health-thresholds.tsx index e82ecfc0..9de79744 100644 --- a/AppImage/components/health-thresholds.tsx +++ b/AppImage/components/health-thresholds.tsx @@ -19,6 +19,7 @@ import { FolderOpen, Database, Waves, + Monitor, } from "lucide-react" import { getApiUrl, getAuthToken } from "../lib/api-config" import { useT } from "../lib/i18n/provider" @@ -85,6 +86,7 @@ interface ThresholdsTree { memory: { warning: ThresholdLeaf; critical: ThresholdLeaf; swap_high: ThresholdLeaf; available_min: ThresholdLeaf } host_storage: { warning: ThresholdLeaf; critical: ThresholdLeaf } lxc_rootfs: { warning: ThresholdLeaf; critical: ThresholdLeaf } + vm_disk: { warning: ThresholdLeaf; critical: ThresholdLeaf } cpu_temperature: { warning: ThresholdLeaf; critical: ThresholdLeaf } disk_temperature: { hdd: { warning: ThresholdLeaf; critical: ThresholdLeaf } @@ -202,6 +204,17 @@ const SECTIONS: SectionDef[] = [ { path: ["lxc_rootfs", "critical"], label: "Critical" }, ], }, + { + id: "vm_disk", + title: "Disk space — VMs", + icon: Monitor, + description: + "Filesystems of running VMs as the QEMU guest agent reports them, passthrough drives included. VMs without the guest agent are not evaluated.", + fields: [ + { path: ["vm_disk", "warning"], label: "Warning" }, + { path: ["vm_disk", "critical"], label: "Critical" }, + ], + }, { id: "lxc_mount", title: "LXC mount points", @@ -285,7 +298,7 @@ function computeVisualRange( // ─── Component ─────────────────────────────────────────────────────────────── // Guests and PVE storages do not exist on a Proxmox Backup Server. -const PVE_ONLY_SECTIONS = new Set(["lxc_rootfs", "lxc_mount", "pve_storage"]) +const PVE_ONLY_SECTIONS = new Set(["lxc_rootfs", "vm_disk", "lxc_mount", "pve_storage"]) export function HealthThresholds() { const t = useT() diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index b319a539..8fc8a742 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -2576,6 +2576,10 @@ "title": "Speicherplatz – LXC rootfs", "description": "Root-Laufwerk pro Container, bewertet anhand der Rootfs-Größe von PVE." }, + "vm_disk": { + "title": "Speicherplatz - VMs", + "description": "Dateisysteme laufender VMs, wie der QEMU-Gastagent sie meldet, einschließlich durchgereichter Laufwerke. VMs ohne Gastagent werden nicht ausgewertet." + }, "lxc_mount": { "title": "LXC-Montagepunkte", "description": "Kapazität der Mountpunkte innerhalb laufender CTs (mp0, mp1, NFS, Bind-Mounts). Schließt die Rootfs aus; das steht oben." diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index 5c4d153c..2c965461 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -2703,6 +2703,10 @@ "title": "Disk space - LXC rootfs", "description": "Per-container root disk, evaluated against the rootfs size from PVE." }, + "vm_disk": { + "title": "Disk space - VMs", + "description": "Filesystems of running VMs as the QEMU guest agent reports them, passthrough drives included. VMs without the guest agent are not evaluated." + }, "lxc_mount": { "title": "LXC mount points", "description": "Capacity of mountpoints inside running CTs (mp0, mp1, NFS, bind mounts). Excludes the rootfs; that is covered above." diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index 66553422..73f83291 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -2729,6 +2729,10 @@ "title": "Espacio en disco - rootfs LXC", "description": "Disco raíz por contenedor, evaluado con respecto al tamaño de rootfs de PVE." }, + "vm_disk": { + "title": "Espacio en disco - VMs", + "description": "Sistemas de archivos de las VM en marcha según los informa el agente invitado de QEMU, incluidos los discos en passthrough. Las VM sin agente invitado no se evalúan." + }, "lxc_mount": { "title": "Puntos de montaje LXC", "description": "Capacidad de puntos de montaje dentro de CT en ejecución (mp0, mp1, NFS, montajes vinculados). Excluye los rootfs; que se trata arriba." diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index 621d96f2..2568f206 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -2576,6 +2576,10 @@ "title": "Espace disque - rootfs LXC", "description": "Disque racine par conteneur, évalué par rapport à la taille rootfs de PVE." }, + "vm_disk": { + "title": "Espace disque - VM", + "description": "Systèmes de fichiers des VM en cours d'exécution, tels que les signale l'agent invité QEMU, disques en passthrough compris. Les VM sans agent invité ne sont pas évaluées." + }, "lxc_mount": { "title": "Points de montage LXC", "description": "Capacité des points de montage à l’intérieur des CT en cours d’exécution (mp0, mp1, NFS, montages de liaison). Exclut les rootfs ; qui est couvert ci-dessus." diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 01c7c3d3..d806845d 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -2576,6 +2576,10 @@ "title": "Spazio disco: rootfs LXC", "description": "Disco root di ciascun container, valutato rispetto alla dimensione rootfs indicata da PVE." }, + "vm_disk": { + "title": "Spazio su disco - VM", + "description": "File system delle VM in esecuzione come li riporta l'agente guest QEMU, inclusi i dischi in passthrough. Le VM senza agente guest non vengono valutate." + }, "lxc_mount": { "title": "Punti di montaggio LXC", "description": "Capacità dei punti di montaggio nei CT in esecuzione (mp0, mp1, NFS, bind mount). Esclude il rootfs, trattato sopra." diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index eb08496c..b3dab814 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -2576,6 +2576,10 @@ "title": "Espaço em disco - rootfs LXC", "description": "Disco raiz por contêiner, avaliado em relação ao tamanho do rootfs do PVE." }, + "vm_disk": { + "title": "Espaço em disco - VMs", + "description": "Sistemas de ficheiros das VMs em execução, conforme os reporta o agente convidado do QEMU, incluindo discos em passthrough. As VMs sem agente convidado não são avaliadas." + }, "lxc_mount": { "title": "Pontos de montagem LXC", "description": "Capacidade de pontos de montagem dentro de CTs em execução (mp0, mp1, NFS, montagens de ligação). Exclui os rootfs; que é abordado acima." diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 6d2f0191..38bfc2f3 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -2575,6 +2575,10 @@ "title": "Miesto na disku - LXC rootfs", "description": "Koreňový disk každého kontajnera, hodnotený podľa veľkosti rootfs z PVE." }, + "vm_disk": { + "title": "Miesto na disku - VM", + "description": "Súborové systémy spustených VM podľa hlásenia hosťovského agenta QEMU vrátane diskov v passthrough. VM bez hosťovského agenta sa nevyhodnocujú." + }, "lxc_mount": { "title": "Prípojné body LXC", "description": "Kapacita prípojných bodov v bežiacich CT (mp0, mp1, NFS, bind mounty). Rootfs je riešený vyššie." diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index f3887cad..8d312688 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -2576,6 +2576,10 @@ "title": "Diskutrymme - LXC rootfs", "description": "Rotskiva per behållare, utvärderad mot rootfs-storleken från PVE." }, + "vm_disk": { + "title": "Diskutrymme - VM", + "description": "Filsystem i körande VM så som QEMU-gästagenten rapporterar dem, inklusive genomsläppta diskar. VM utan gästagent utvärderas inte." + }, "lxc_mount": { "title": "LXC fästpunkter", "description": "Kapacitet för monteringspunkter inuti körda CT: er (mp0, mp1, NFS, bind mounts). Utesluter rootfs; som täcks ovan." diff --git a/lang/de.json b/lang/de.json index 84917c9d..e85e2dc1 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1960,6 +1960,7 @@ "Discard changes": "Änderungen verwerfen", "Discard unapplied changes?": "Noch nicht angewendete Änderungen verwerfen?", "Discover NFS exports on a server": "NFS-Exporte eines Servers ermitteln", + "Discover and monitor devices on your local network.": "Geräte im lokalen Netzwerk erkennen und überwachen.", "Discover targets on portal:": "Entdecken Sie Ziele auf dem Portal:", "Discovering iSCSI targets...": "iSCSI-Ziele werden ermittelt...", "Discovery successful": "Entdeckung erfolgreich", @@ -6003,6 +6004,7 @@ "Script Information": "Skriptinformationen", "Script not found:": "Skript nicht gefunden:", "Scripts in": "Skripte in", + "Scrypted Web UI": "Scrypted-Weboberfläche", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM ist ein Programm, mit dem Sie bestimmte klassische grafische Abenteuer- und Rollenspiele ausführen können, sofern Sie bereits über deren Datendateien verfügen. Der clevere Teil dabei: ScummVM ersetzt nur die ausführbaren Dateien, die mit den Spielen ausgeliefert werden, so dass Sie sie auf Systemen spielen können, für die sie nie entwickelt wurden! ScummVM ist ein komplettes Umschreiben der ausführbaren Dateien dieser Spiele und kein Emulator.", "SeaBIOS (Legacy)": "SeaBIOS (Legacy)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin ist eine selbst gehostete Client-Server-Plattform, mit der Benutzer leistungsstarke, containerisierte Desktop-Anwendungen ausführen können, die direkt in einen Webbrowser gestreamt werden. Es verwendet eine Browsererweiterung, um Benutzeraktionen abzufangen - wie das Klicken auf einen Link oder das Herunterladen einer Datei und leitet sie in eine sichere, isolierte Anwendungsumgebung um, die auf einem entfernten Server ausgeführt wird.", @@ -6247,6 +6249,7 @@ "Self-hosted remote device management.": "Selbst gehostete Fernverwaltung von Geräten.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "Selbst gehostete Terminplanungs- und Buchungsanwendung mit integrierter Administrationsoberfläche und SQLite-Datenbank.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Selbst gehostete Verwaltung gemeinsamer Ausgaben und Gruppenbudgets, um Käufe zu erfassen und abzurechnen, wer wem etwas schuldet.", + "Self-hosted smart home video platform.": "Selbst gehostete Video-Plattform für das Smart Home.", "Self-hosted software development service": "Entwicklung von selbst gehosteter Software", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Selbst gehosteter Krafttrainings-Tracker mit Trainingsprotokollen, Programmen, Fortschrittsfotos und mobiler PWA.", "Self-hosted task and project management.": "Selbst gehostete Aufgaben- und Projektverwaltung.", @@ -8600,6 +8603,7 @@ "missing": "fehlt", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs nicht gefunden. Installieren Sie btrfs-progs und versuchen Sie es erneut.", "more": "mehr", + "more packages; the full list is in the update log": "weitere Pakete; die vollständige Liste steht im Update-Protokoll", "motionEye web interface": "motionEye Web-Schnittstelle", "mount.cifs command not found after installation.": "Der Befehl mount.cifs wurde nach der Installation nicht gefunden.", "mount.nfs command not found after installation.": "Der Befehl mount.nfs wurde nach der Installation nicht gefunden.", diff --git a/lang/es.json b/lang/es.json index 79607210..2ce4fb48 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1958,6 +1958,7 @@ "Discard changes": "Descartar cambios", "Discard unapplied changes?": "¿Descartar los cambios sin aplicar?", "Discover NFS exports on a server": "Descubrir los exports NFS de un servidor", + "Discover and monitor devices on your local network.": "Descubre y supervisa los dispositivos de tu red local.", "Discover targets on portal:": "Detectar destinos en el portal:", "Discovering iSCSI targets...": "Descubriendo destinos iSCSI...", "Discovery successful": "Detección correcta", @@ -5987,6 +5988,7 @@ "Script Information": "Información del script", "Script not found:": "Script no encontrado:", "Scripts in": "Scripts en", + "Scrypted Web UI": "Interfaz web de Scrypted", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM es un programa que permite ejecutar ciertas aventuras gráficas y juegos de rol clásicos, siempre que ya tengas sus archivos de datos. Lo ingenioso es que ScummVM sustituye los ejecutables de los juegos, de modo que puedes jugarlos en sistemas para los que nunca se diseñaron. ScummVM es una reescritura completa de esos ejecutables y no es un emulador.", "SeaBIOS (Legacy)": "SeaBIOS (heredado)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin es una plataforma cliente-servidor autoalojada que permite ejecutar potentes aplicaciones de escritorio en contenedores transmitidas directamente al navegador. Usa una extensión del navegador para interceptar acciones del usuario, como hacer clic en un enlace o descargar un archivo, y redirigirlas a un entorno de aplicación seguro y aislado en un servidor remoto.", @@ -6231,6 +6233,7 @@ "Self-hosted remote device management.": "Gestión remota de dispositivos autoalojada.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "Aplicación autoalojada de programación y reservas, con interfaz de administración integrada y base de datos SQLite.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Gestor autoalojado de gastos compartidos y presupuestos de grupo para registrar compras y saldar deudas entre sus miembros.", + "Self-hosted smart home video platform.": "Plataforma de vídeo para el hogar inteligente alojada en tu propio servidor.", "Self-hosted software development service": "Servicio autoalojado de desarrollo de software", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Seguimiento autoalojado de entrenamiento de fuerza, con registros de sesiones, programas, fotos de progreso y una PWA para móviles.", "Self-hosted task and project management.": "Gestión autoalojada de tareas y proyectos.", @@ -8573,6 +8576,7 @@ "missing": "faltan", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs no encontrado. Instala btrfs-progs y vuelve a intentarlo.", "more": "más", + "more packages; the full list is in the update log": "paquetes más; la lista completa está en el registro de la actualización", "motionEye web interface": "Interfaz web de motionEye", "mount.cifs command not found after installation.": "El comando mount.cifs no se encuentra después de la instalación.", "mount.nfs command not found after installation.": "El comando mount.nfs no se encuentra después de la instalación.", diff --git a/lang/fr.json b/lang/fr.json index a34d90ea..49bf1fd0 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -1960,6 +1960,7 @@ "Discard changes": "Abandonner les modifications", "Discard unapplied changes?": "Abandonner les modifications non appliquées ?", "Discover NFS exports on a server": "Découvrir les exports NFS d’un serveur", + "Discover and monitor devices on your local network.": "Découvrez et surveillez les appareils de votre réseau local.", "Discover targets on portal:": "Découvrez les cibles sur le portail :", "Discovering iSCSI targets...": "Découverte des cibles iSCSI...", "Discovery successful": "Découverte réussie", @@ -6003,6 +6004,7 @@ "Script Information": "Informations sur le script", "Script not found:": "Script introuvable :", "Scripts in": "Scripts dans", + "Scrypted Web UI": "Interface web de Scrypted", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM est un programme qui vous permet d'exécuter certaines aventures graphiques classiques et jeux de rôle, à condition que vous ayez déjà leurs fichiers de données. La partie intelligente à ce sujet: ScummVM remplace les exécutables livrés avec les jeux, vous permettant de les jouer sur des systèmes pour lesquels ils n'ont jamais été conçus! ScummVM est une réécriture complète des exécutables de ces jeux et n'est pas un émulateur.", "SeaBIOS (Legacy)": "SeaBIOS (hérité)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin est une plate-forme client-serveur auto-organisé qui permet aux utilisateurs d'exécuter des applications de bureau puissantes et containerizzato diffusées directement sur un navigateur Web. Il utilise une extension de navigateur pour intercepter les actions de l'utilisateur – comme cliquer sur un lien ou télécharger un fichier et les rediriger vers un environnement d'application sécurisé et isolé fonctionnant sur un serveur distant.", @@ -6247,6 +6249,7 @@ "Self-hosted remote device management.": "Outil auto-hébergé de gestion à distance des appareils.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "Application auto-hébergée de planification et de réservation avec interface d'administration intégrée et base de données SQLite.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Gestionnaire auto-hébergé des dépenses partagées et des budgets de groupe pour suivre les achats et calculer les remboursements entre participants.", + "Self-hosted smart home video platform.": "Plateforme vidéo domotique auto-hébergée.", "Self-hosted software development service": "Service de développement de logiciels autonomes", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Suivi auto-hébergé de la musculation avec journaux d'entraînement, programmes, photos de progression et PWA mobile.", "Self-hosted task and project management.": "Gestion des tâches et des projets auto-hébergée.", @@ -8599,6 +8602,7 @@ "missing": "manquant", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs introuvable. Installez btrfs-progs et réessayez.", "more": "plus", + "more packages; the full list is in the update log": "paquets de plus ; la liste complète se trouve dans le journal de la mise à jour", "motionEye web interface": "Interface web motionEye", "mount.cifs command not found after installation.": "Commande mount.cifs introuvable après l'installation.", "mount.nfs command not found after installation.": "Commande mount.nfs introuvable après l'installation.", diff --git a/lang/it.json b/lang/it.json index 04d6a7df..2e625153 100644 --- a/lang/it.json +++ b/lang/it.json @@ -1960,6 +1960,7 @@ "Discard changes": "Scarta le modifiche", "Discard unapplied changes?": "Scartare le modifiche non applicate?", "Discover NFS exports on a server": "Individua le esportazioni NFS di un server", + "Discover and monitor devices on your local network.": "Rileva e monitora i dispositivi della tua rete locale.", "Discover targets on portal:": "Scopri target sul portale:", "Discovering iSCSI targets...": "Individuazione dei target iSCSI...", "Discovery successful": "Scoperta riuscita", @@ -6003,6 +6004,7 @@ "Script Information": "Informazioni script", "Script not found:": "Script non trovato:", "Scripts in": "Script in", + "Scrypted Web UI": "Interfaccia web di Scrypted", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM consente di eseguire alcuni classici giochi di avventura grafica e di ruolo, purché tu ne abbia già i file di dati. Sostituisce gli eseguibili originali dei giochi, così puoi usarli su sistemi per i quali non erano stati progettati. Gli eseguibili vengono riscritti completamente: ScummVM non è un emulatore.", "SeaBIOS (Legacy)": "SeaBIOS (Legacy)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin è una piattaforma client-server self-hosted che consente di eseguire applicazioni desktop containerizzate e trasmetterle direttamente a un browser web. Usa un'estensione del browser per intercettare le azioni degli utenti, come cliccare su un link o scaricare un file, e reindirizzarle verso un ambiente applicativo sicuro e isolato su un server remoto.", @@ -6247,6 +6249,7 @@ "Self-hosted remote device management.": "Gestione self-hosted dei dispositivi remoti.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "App di pianificazione e prenotazione da ospitare sul proprio server, con interfaccia di amministrazione integrata e database SQLite.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Gestore di spese condivise e budget di gruppo da ospitare sul proprio server, per registrare gli acquisti e calcolare chi deve cosa a chi.", + "Self-hosted smart home video platform.": "Piattaforma video per la smart home ospitata in autonomia.", "Self-hosted software development service": "Servizio di sviluppo software ospitabile sul proprio server", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Tracker per l'allenamento della forza da ospitare sul proprio server, con registri degli allenamenti, programmi, foto dei progressi e PWA per dispositivi mobili.", "Self-hosted task and project management.": "Gestione di attività e progetti sul proprio server.", @@ -8600,6 +8603,7 @@ "missing": "mancanti", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs non trovato. Installare btrfs-progs e riprovare.", "more": "in più", + "more packages; the full list is in the update log": "pacchetti in più; l'elenco completo è nel log dell'aggiornamento", "motionEye web interface": "Interfaccia web motionEye", "mount.cifs command not found after installation.": "Comando mount.cifs non trovato dopo l'installazione.", "mount.nfs command not found after installation.": "Comando mount.nfs non trovato dopo l'installazione.", diff --git a/lang/pt.json b/lang/pt.json index da964694..588f0f86 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -1960,6 +1960,7 @@ "Discard changes": "Descartar alterações", "Discard unapplied changes?": "Descartar as alterações por aplicar?", "Discover NFS exports on a server": "Descobrir as exportações NFS de um servidor", + "Discover and monitor devices on your local network.": "Descubra e monitorize os dispositivos da sua rede local.", "Discover targets on portal:": "Descubra alvos no portal:", "Discovering iSCSI targets...": "Descobrindo alvos iSCSI...", "Discovery successful": "Descoberta bem-sucedida", @@ -6003,6 +6004,7 @@ "Script Information": "Informações do roteiro", "Script not found:": "Script não encontrado:", "Scripts in": "Scripts em", + "Scrypted Web UI": "Interface web do Scrypted", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM é um programa que permite que você execute certos jogos clássicos de aventura gráfica e RPG, desde que você já tenha seus arquivos de dados. A parte inteligente sobre isso: ScummVM apenas substitui os executáveis enviados com os jogos, permitindo que você jogá-los em sistemas para os quais eles nunca foram projetados! ScummVM é uma reescrita completa dos executáveis desses jogos e não é um emulador.", "SeaBIOS (Legacy)": "SeaBIOS (Legado)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin é uma plataforma auto-hospedada, cliente-servidor que permite aos usuários executar aplicativos de desktop poderosos e containerizados transmitidos diretamente para um navegador web. Ele usa uma extensão do navegador para interceptar ações do usuário, como clicar em um link ou baixar um arquivo e redirecioná-los para um ambiente de aplicação seguro e isolado rodando em um servidor remoto.", @@ -6247,6 +6249,7 @@ "Self-hosted remote device management.": "Gerenciamento remoto de dispositivos self-hosted.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "Aplicativo auto-hospedado de agendamento e reservas, com painel administrativo integrado e banco de dados SQLite.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Gerenciador auto-hospedado de despesas compartilhadas e orçamentos em grupo para registrar compras e acertar quem deve a quem.", + "Self-hosted smart home video platform.": "Plataforma de vídeo para casa inteligente autoalojada.", "Self-hosted software development service": "Serviço de desenvolvimento de software self-hosted", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Rastreador auto-hospedado de treinamento de força, com registros de exercícios, programas, fotos de progresso e um PWA para dispositivos móveis.", "Self-hosted task and project management.": "Gestão de tarefas e projetos autoalojada.", @@ -8600,6 +8603,7 @@ "missing": "faltando", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs não encontrado. Instale o btrfs-progs e tente novamente.", "more": "mais", + "more packages; the full list is in the update log": "pacotes a mais; a lista completa está no registo da atualização", "motionEye web interface": "Interface Web motionEye", "mount.cifs command not found after installation.": "Comando mount.cifs não encontrado após a instalação.", "mount.nfs command not found after installation.": "Comando mount.nfs não encontrado após a instalação.", diff --git a/lang/sk.json b/lang/sk.json index c71d30da..ed0a8955 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -1960,6 +1960,7 @@ "Discard changes": "Zahodiť zmeny", "Discard unapplied changes?": "Zahodiť nepoužité zmeny?", "Discover NFS exports on a server": "Zistiť NFS exporty servera", + "Discover and monitor devices on your local network.": "Objavujte a monitorujte zariadenia vo svojej lokálnej sieti.", "Discover targets on portal:": "Vyhľadať targety na portáli:", "Discovering iSCSI targets...": "Vyhľadávam iSCSI targety...", "Discovery successful": "Vyhľadanie bolo úspešné", @@ -6005,6 +6006,7 @@ "Script Information": "Informácie o skripte", "Script not found:": "Skript sa nenašiel:", "Scripts in": "Skripty v kategórii", + "Scrypted Web UI": "Webové rozhranie Scrypted", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM umožňuje spúšťať vybrané klasické grafické adventúry a hry na hrdinov, ak už máte ich dátové súbory. Nahrádza pôvodné spustiteľné súbory hier, takže ich možno hrať aj na systémoch, pre ktoré neboli navrhnuté. Je úplným prepisom ich herných programov, nie emulátorom.", "SeaBIOS (Legacy)": "SeaBIOS (starší režim BIOS)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin je self-hosted, klient-server platforma, ktorá umožňuje užívateľom spustiť výkonné, kontajnerové desktopové aplikácie streamované priamo do webového prehliadača. Používa rozšírenie prehliadača na zachytenie užívateľských akcií, ako je kliknutie na odkaz alebo stiahnutie súboru a presmeruje ich na bezpečné, izolované aplikačné prostredie bežiace na vzdialenom serveri.", @@ -6249,6 +6251,7 @@ "Self-hosted remote device management.": "Vlastná služba na vzdialenú správu zariadení.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "Aplikácia na plánovanie a rezervácie prevádzkovaná na vlastnom serveri so vstavaným rozhraním správcu a databázou SQLite.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Správca spoločných výdavkov a skupinových rozpočtov prevádzkovaný na vlastnom serveri na evidenciu nákupov a vyrovnanie dlhov medzi členmi skupiny.", + "Self-hosted smart home video platform.": "Samohostovaná video platforma pre inteligentnú domácnosť.", "Self-hosted software development service": "Služba na vývoj softvéru s vlastným hostovaním", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Prehľad silového tréningu prevádzkovaný na vlastnom serveri so záznamami tréningov, programami, fotografiami pokroku a mobilnou PWA.", "Self-hosted task and project management.": "Správa úloh a projektov na vlastnom serveri.", @@ -8601,6 +8604,7 @@ "missing": "chýba", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs sa nenašiel. Nainštalujte btrfs-progs a skúste to znova.", "more": "ďalších", + "more packages; the full list is in the update log": "ďalších balíkov; úplný zoznam je v zázname aktualizácie", "motionEye web interface": "Webové rozhranie motionEye", "mount.cifs command not found after installation.": "Príkaz mount.cifs sa po inštalácii nenašiel.", "mount.nfs command not found after installation.": "Príkaz mount.nfs sa po inštalácii nenašiel.", diff --git a/lang/sv.json b/lang/sv.json index ede4a768..bfbcf60b 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -1960,6 +1960,7 @@ "Discard changes": "Kasta ändringar", "Discard unapplied changes?": "Kasta ändringar som inte har tillämpats?", "Discover NFS exports on a server": "Hitta NFS-exporter på en server", + "Discover and monitor devices on your local network.": "Hitta och övervaka enheter i ditt lokala nätverk.", "Discover targets on portal:": "Upptäck mål på portalen:", "Discovering iSCSI targets...": "Upptäcker iSCSI-mål...", "Discovery successful": "Upptäckten lyckades", @@ -6003,6 +6004,7 @@ "Script Information": "Skriptinformation", "Script not found:": "Skriptet hittades inte:", "Scripts in": "Skript i", + "Scrypted Web UI": "Scrypted webbgränssnitt", "ScummVM is a program which allows you to run certain classic graphical adventure and role-playing games, provided you already have their data files. The clever part about this: ScummVM just replaces the executables shipped with the games, allowing you to play them on systems for which they were never designed! ScummVM is a complete rewrite of these games' executables and is not an emulator.": "ScummVM är ett program som låter dig köra vissa klassiska grafiska äventyr och rollspel, förutsatt att du redan har sina datafiler. Den smarta delen om detta: ScummVM ersätter just de körbara som levereras med spelen, så att du kan spela dem på system för vilka de aldrig designades! ScummVM är en komplett omskrivning av dessa spel körbara och är inte en emulator.", "SeaBIOS (Legacy)": "SeaBIOS (Legacy)", "Sealskin is a self-hosted, client-server platform that enables users to run powerful, containerized desktop applications streamed directly to a web browser. It uses a browser extension to intercept user actions—such as clicking a link or downloading a file and redirects them to a secure, isolated application environment running on a remote server.": "Sealskin är en självhäftad, klient-server plattform som gör det möjligt för användare att köra kraftfulla, containeriserade stationära program strömmade direkt till en webbläsare. Den använder en webbläsartillägg för att avlyssna användaråtgärder - till exempel att klicka på en länk eller ladda ner en fil och omdirigerar dem till en säker, isolerad applikationsmiljö som körs på en fjärrserver.", @@ -6247,6 +6249,7 @@ "Self-hosted remote device management.": "Självhostad fjärrhantering av enheter.", "Self-hosted scheduling and booking app with a built-in admin interface and SQLite database.": "Självhostad app för schemaläggning och bokning med inbyggt administratörsgränssnitt och SQLite-databas.", "Self-hosted shared-expense and group-budget manager for tracking purchases and settling who owes whom.": "Självhostad hantering av gemensamma utgifter och gruppbudgetar för att registrera köp och reda ut vem som är skyldig vem.", + "Self-hosted smart home video platform.": "Självhostad videoplattform för det smarta hemmet.", "Self-hosted software development service": "Självvärd programvara utvecklingstjänst", "Self-hosted strength-training tracker with workout logs, programs, progress photos, and a mobile PWA.": "Självhostad styrketräningslogg med träningspass, program, framstegsbilder och en mobil PWA.", "Self-hosted task and project management.": "Självhostad hantering av uppgifter och projekt.", @@ -8600,6 +8603,7 @@ "missing": "saknad", "mkfs.btrfs not found. Install btrfs-progs and retry.": "mkfs.btrfs hittades inte. Installera btrfs-progs och försök igen.", "more": "mer", + "more packages; the full list is in the update log": "paket till; hela listan finns i uppdateringsloggen", "motionEye web interface": "motionEye webbgränssnitt", "mount.cifs command not found after installation.": "mount.cifs-kommandot hittades inte efter installationen.", "mount.nfs command not found after installation.": "mount.nfs-kommandot hittades inte efter installationen.", diff --git a/oci/VALIDATION.md b/oci/VALIDATION.md index 528773e6..436bdcb9 100644 --- a/oci/VALIDATION.md +++ b/oci/VALIDATION.md @@ -2,7 +2,7 @@ -43 of 527 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 2 by the community. The OCI installer shows them as verified, with a ✓ in the lists. +43 of 529 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 2 by the community. The OCI installer shows them as verified, with a ✓ in the lists. Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it. diff --git a/oci/catalog/apps/netalertx.json b/oci/catalog/apps/netalertx.json new file mode 100644 index 00000000..fc14d905 --- /dev/null +++ b/oci/catalog/apps/netalertx.json @@ -0,0 +1,379 @@ +{ + "schema_version": "0.5.0", + "kind": "proxmenux.oci-template", + "id": "image-netalertx", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "NetAlertX" + }, + "tagline": { + "en_US": "Discover and monitor devices on your local network." + }, + "description": { + "en_US": "NetAlertX discovers devices on your local network and tracks its device inventory in a self-hosted web interface." + }, + "category": "monitoring", + "category_label": "Monitoring & Analytics", + "author": "NetAlertX", + "developer": "NetAlertX", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/netalertx.webp", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64", + "arm64" + ], + "launch": { + "scheme": "http", + "port": 20211, + "path": "/" + }, + "website": "https://netalertx.com/", + "documentation": "https://docs.netalertx.com/DOCKER_INSTALLATION/", + "repository": "https://github.com/netalertx/NetAlertX", + "tips": [ + "The web interface listens on port 20211. GraphQL listens on port 20212.", + "The upstream Docker Compose baseline requests a read-only root filesystem, drops all capabilities before adding a small allow-list, and uses a temporary filesystem for /tmp. This native OCI-on-LXC profile does not reproduce those Docker Engine controls: ProxMenux validates requested cap_add values against the LXC user namespace and maps only the supported network sysctls. Docker capability bounding and read-only-rootfs behavior are not enforced.", + "Compose network_mode: host maps to this application LXC network namespace, never the Proxmox host. Layer 2 discovery may still depend on the selected bridge and network; the installation smoke test only confirms startup and the web interface.", + "The initial Devices page reported no devices yet and advises checking SCAN_SUBNETS. Configure scanning for the local subnet as needed; this smoke test did not verify discovery.", + "This profile does not configure a login. The web interface opened directly to the Devices page during the smoke test." + ], + "mini_changelog": [], + "display_version": null, + "updated_at": null + }, + "source": { + "provider": "NetAlertX", + "repository": "https://github.com/netalertx/NetAlertX", + "revision": "6f3cb9bd43855596da3e8c6185490abd5784f1dd", + "image_repository_url": "https://github.com/netalertx/NetAlertX/pkgs/container/netalertx", + "readme_pushed_at": "", + "compose_sha256": "1e5265637e13cc4c61901a685e4fe9cd015f8e1c52ad419de2392f2791795135", + "generated_at": "2026-10-27T20:39:28+00:00", + "readme_raw_url": "https://raw.githubusercontent.com/netalertx/NetAlertX/6f3cb9bd43855596da3e8c6185490abd5784f1dd/docs/DOCKER_COMPOSE.md" + }, + "container_contract": { + "service_name": "netalertx", + "container_name": "netalertx", + "image": { + "reference": "ghcr.io/netalertx/netalertx:latest", + "registry": "ghcr.io", + "repository": "ghcr.io/netalertx/netalertx", + "tag": "latest", + "digest": null, + "pull_policy": "resolve-selected-tag-to-architecture-digest-at-install" + }, + "environment": [ + { + "name": "LISTEN_ADDR", + "example": "0.0.0.0", + "required": true, + "sensitive": false, + "source": "docker-compose" + }, + { + "name": "PORT", + "example": "20211", + "required": true, + "sensitive": false, + "source": "docker-compose" + }, + { + "name": "GRAPHQL_PORT", + "example": "20212", + "required": true, + "sensitive": false, + "source": "docker-compose" + } + ], + "volumes": [ + { + "id": "volume-0", + "container_path": "/data", + "compose_source_example": "netalertx-data", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 8 + } + } + ], + "ports": [], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "services:\n netalertx:\n #use an environmental variable to set host networking mode if needed\n container_name: netalertx # The name when you docker contiainer ls\n image: ghcr.io/netalertx/netalertx:latest\n network_mode: ${NETALERTX_NETWORK_MODE:-host} # Use host networking for ARP scanning and other services\n\n read_only: true # Make the container filesystem read-only\n cap_drop: # Drop all capabilities for enhanced security\n - ALL\n cap_add: # Add only the necessary capabilities\n - NET_ADMIN # Required for ARP scanning\n - NET_RAW # Required for raw socket operations\n - NET_BIND_SERVICE # Required to bind to privileged ports (nbtscan)\n - CHOWN # Required for root-entrypoint to chown /data + /tmp before dropping privileges\n - SETUID # Required for root-entrypoint to switch to non-root user\n - SETGID # Required for root-entrypoint to switch to non-root group\n # --- ARP FLUX MITIGATION ---\n # Note: When using `network_mode: host`, these sysctls require the\n # NET_ADMIN capability to be applied to the host namespace.\n # \n # If your environment restricts capabilities, or you prefer to configure\n # them on the Host OS, REMOVE the sysctls block below and apply via:\n # sudo sysctl -w net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2\n # ---------------------------\n sysctls: # ARP flux mitigation (reduces duplicate/ambiguous ARP behavior on host networking)\n net.ipv4.conf.all.arp_ignore: 1\n net.ipv4.conf.all.arp_announce: 2\n\n volumes:\n - type: volume # Persistent Docker-managed named volume for config + database\n source: netalertx_data\n target: /data # `/data/config` and `/data/db` live inside this mount\n read_only: false\n\n # Example custom local folder called /home/user/netalertx_data\n # - type: bind\n # source: /home/user/netalertx_data\n # target: /data\n # read_only: false\n # ... or use the alternative format\n # - /home/user/netalertx_data:/data:rw\n\n - type: bind # Bind mount for timezone consistency\n source: /etc/localtime\n target: /etc/localtime\n read_only: true\n\n # Mount your DHCP server file into NetAlertX for a plugin to access\n # - path/on/host/to/dhcp.file:/resources/dhcp.file\n\n # tmpfs mount consolidates writable state for a read-only container and improves performance\n # uid/gid default to the service user (NETALERTX_UID/GID, default 20211)\n # mode=1700 grants rwx------ permissions to the runtime user only\n tmpfs:\n # Comment out to retain logs between container restarts - this has a server performance impact.\n - \"/tmp:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n\n # Retain logs - comment out tmpfs /tmp if you want to retain logs between container restarts\n # Please note if you remove the /tmp mount, you must create and maintain sub-folder mounts.\n # - /path/on/host/log:/tmp/log\n # - \"/tmp/api:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n # - \"/tmp/nginx:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n # - \"/tmp/run:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n\n environment:\n LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} # Listen for connections on all interfaces\n PORT: ${PORT:-20211} # Application port\n GRAPHQL_PORT: ${GRAPHQL_PORT:-20212} # GraphQL API port (passed into APP_CONF_OVERRIDE at runtime)\n # NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0} # 0=kill all services and restart if any dies. 1 keeps running dead services.\n # PUID: 20211 # Runtime PUID override, set to 0 to run as root\n # PGID: 20211 # Runtime PGID override\n\n # Resource limits to prevent resource exhaustion\n mem_limit: 2048m # Maximum memory usage\n mem_reservation: 1024m # Soft memory limit\n cpu_shares: 512 # Relative CPU weight for CPU contention scenarios\n pids_limit: 512 # Limit the number of processes/threads to prevent fork bombs\n logging:\n options:\n max-size: \"10m\" # Rotate log files after they reach 10MB\n max-file: \"3\" # Keep a maximum of 3 log files\n\n # Always restart the container unless explicitly stopped\n restart: unless-stopped\n\nvolumes: # Persistent volume for configuration and database storage\n netalertx_data:\n" + }, + "compose_stack": { + "project_name": "netalertx", + "deployment_model": "one-native-oci-lxc-per-compose-service", + "user_experience": "single-application-install", + "main_service": "netalertx", + "service_count": 1, + "services": [ + { + "name": "netalertx", + "image": "ghcr.io/netalertx/netalertx:latest", + "is_main": true, + "role": "frontend", + "vmid_offset": 0, + "depends_on": [], + "frontend_network": true, + "private_network": false, + "compose": { + "image": "ghcr.io/netalertx/netalertx:latest", + "container_name": "netalertx", + "network_mode": "host", + "cap_add": [ + "NET_ADMIN", + "NET_RAW", + "NET_BIND_SERVICE", + "CHOWN", + "SETUID", + "SETGID" + ], + "sysctls": { + "net.ipv4.conf.all.arp_ignore": "1", + "net.ipv4.conf.all.arp_announce": "2" + }, + "restart": "unless-stopped", + "environment": { + "LISTEN_ADDR": "0.0.0.0", + "PORT": "20211", + "GRAPHQL_PORT": "20212" + }, + "volumes": [ + "netalertx-data:/data" + ], + "mem_limit": "2048m" + } + } + ], + "top_level": { + "name": "netalertx", + "volumes": { + "netalertx-data": {} + } + }, + "networking": { + "frontend": "selected-proxmox-bridge", + "private_required": false, + "private_creation": "automatic-create-if-missing", + "private_address_allocation": "automatic-static-address-per-service", + "service_discovery": "private-addresses-with-compose-service-host-aliases", + "dependency_external_access": "disabled-unless-service-publishes-ports", + "prompt_user_for_private_network": false + }, + "storage": [ + { + "id": "netalertx-volume-0", + "service": "netalertx", + "container_path": "/data", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + } + ], + "orchestration": { + "reserve_vmids_atomically": 1, + "start_order": [ + "netalertx" + ], + "stop_order": [ + "netalertx" + ], + "dependency_readiness": "compose-healthcheck-then-port-or-process-fallback", + "rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes" + }, + "installer_inputs": { + "prompted": [ + "stack_name", + "base_vmid", + "rootfs_storage", + "persistent_data_destinations", + "frontend_bridge", + "frontend_ipv4_mode" + ], + "automatic": [ + "dependent_vmids", + "private_bridge", + "private_subnet", + "private_service_addresses", + "compose_service_aliases", + "generated_secrets", + "dependency_start_and_stop_order" + ], + "generated_secrets": [] + } + }, + "first_run": { + "endpoints": [ + { + "label": "Web UI", + "scheme": "http", + "port": 20211, + "path": "/", + "source": "compose-metadata" + } + ], + "credentials": [] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 1024, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 30 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "installer_profile": { + "resources": { + "memory_default_mb": 2048 + }, + "network": { + "compose_mode": "host" + }, + "security": { + "required_capabilities": [ + "NET_ADMIN", + "NET_RAW", + "NET_BIND_SERVICE", + "CHOWN", + "SETUID", + "SETGID" + ], + "sysctls": [ + { + "name": "net.ipv4.conf.all.arp_ignore", + "value": "1" + }, + { + "name": "net.ipv4.conf.all.arp_announce", + "value": "2" + } + ] + } + }, + "adaptations": [ + { + "id": "netalertx-native-network", + "upstream_behavior": "The official Compose profile uses host networking for layer 2 discovery and adds NET_ADMIN, NET_RAW and NET_BIND_SERVICE capabilities.", + "native_lxc_behavior": "ProxMenux validates the declared cap_add names against the LXC user namespace and maps the documented network sysctls into the LXC configuration.", + "reason": "A dedicated LXC already has its own address and network namespace, so Docker network plumbing is not needed.", + "behavioral_impact": "Host mode refers to the NetAlertX LXC, not the Proxmox host. Compose cap_add values are validated but are not applied as an OCI capability bounding set; actual discovery may depend on the selected bridge and network.", + "validation": "startup-and-web-smoke-tested-2026-10-27" + }, + { + "id": "netalertx-docker-hardening", + "upstream_behavior": "The official Docker baseline sets read_only, cap_drop: ALL and a tmpfs for /tmp before adding its documented capability allow-list.", + "native_lxc_behavior": "ProxMenux validates declared cap_add names against the LXC user namespace and maps supported sysctls, but does not translate read_only, cap_drop or tmpfs into equivalent native OCI-on-LXC controls.", + "reason": "The OCI-on-LXC engine maps a supported subset of Compose to Proxmox resources; Docker Engine confinement options cannot be asserted as equivalent without a native mapping.", + "behavioral_impact": "This profile does not reproduce Docker capability bounding, dropped-capability or read-only-rootfs behavior. The startup and web UI smoke test did not validate network scanning or scanner permissions.", + "validation": "reviewed-from-upstream-compose" + }, + { + "id": "netalertx-persistent-data", + "upstream_behavior": "The official Compose profile stores configuration and the device database in /data.", + "native_lxc_behavior": "ProxMenux stores /data in a selectable managed volume or host bind directory.", + "reason": "The app database and configuration need to survive OCI image replacement.", + "behavioral_impact": "Persistent /data is included in Proxmox backups when a managed volume is selected.", + "validation": "not-tested-in-installation-smoke" + } + ] + }, + "compatibility": { + "automatic_install_candidate": true, + "validated": false, + "supported_compose_keys": [ + "cap_add", + "command", + "container_name", + "cpu_shares", + "deploy", + "devices", + "entrypoint", + "environment", + "extra_hosts", + "group_add", + "healthcheck", + "hostname", + "image", + "init", + "ipc", + "labels", + "logging", + "mac_address", + "mem_limit", + "network_mode", + "networks", + "ports", + "privileged", + "restart", + "runtime", + "security_opt", + "shm_size", + "stdin_open", + "stop_grace_period", + "sysctls", + "tty", + "ulimits", + "user", + "volumes", + "working_dir" + ], + "untranslated_blockers": [], + "policy": "Single-image definitions are installable when every declared Compose option has a native Proxmox translation. Multi-image and unsupported runtime features remain blocked until their orchestrator or mapping is available." + }, + "validation": { + "schema": "passed-at-generation", + "clean_install": "pending", + "service_health": "pending", + "restart_persistence": "pending", + "backup_restore": "pending", + "update_preserves_data": "pending" + }, + "lifecycle": { + "update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update", + "registry_state": { + "resolved_architecture": null, + "resolved_digest": null, + "image_version_label": null, + "image_created": null + }, + "change_detection": "compare-compose-sha256-and-resolved-latest-image-digest", + "automatic_unattended_updates": false + } +} diff --git a/oci/catalog/apps/scrypted.json b/oci/catalog/apps/scrypted.json new file mode 100644 index 00000000..4884430a --- /dev/null +++ b/oci/catalog/apps/scrypted.json @@ -0,0 +1,454 @@ +{ + "schema_version": "0.5.0", + "kind": "proxmenux.oci-template", + "id": "image-scrypted", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "Scrypted" + }, + "tagline": { + "en_US": "Self-hosted smart home video platform." + }, + "description": { + "en_US": "Manage cameras and integrate video devices with Apple HomeKit, Google Home, Alexa and Home Assistant from one self-hosted platform." + }, + "category": "smarthome", + "category_label": "IoT & Smart Home", + "author": "Scrypted", + "developer": "Scrypted", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/scrypted.webp", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64", + "arm64" + ], + "launch": { + "scheme": "https", + "port": 10443, + "path": "/" + }, + "website": "https://scrypted.app/", + "documentation": "https://docs.scrypted.app/install/linux-docker.html", + "repository": "https://github.com/koush/scrypted", + "tips": [ + "This profile uses the official single-service image and persists Scrypted state in /server/volume.", + "The official Compose file includes an optional Watchtower service for Docker Engine updates. It is omitted because ProxMenux manages OCI image updates.", + "Camera, NVR, mDNS/Avahi, GPU and USB integrations are not configured by this minimal profile; add required devices and storage separately." + ], + "mini_changelog": [], + "display_version": null, + "updated_at": null + }, + "source": { + "provider": "official", + "repository": "https://github.com/koush/scrypted", + "revision": "78320e94ad977a77fa2bb201346b98630b8b1d47", + "image_repository_url": "https://github.com/koush/scrypted/pkgs/container/scrypted", + "readme_pushed_at": "2026-10-18T02:15:15Z", + "compose_sha256": "a34bc5941c67a43e0e328f216c82f3dcf31c213c046b6920e08500af17d91727", + "generated_at": "2026-10-27T20:07:09+00:00" + }, + "container_contract": { + "service_name": "scrypted", + "container_name": "scrypted", + "image": { + "reference": "ghcr.io/koush/scrypted:latest", + "registry": "ghcr.io", + "repository": "ghcr.io/koush/scrypted", + "tag": "latest", + "digest": null, + "pull_policy": "resolve-selected-tag-to-architecture-digest-at-install" + }, + "environment": [], + "volumes": [ + { + "id": "volume-0", + "container_path": "/server/volume", + "compose_source_example": "./volume", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 8 + } + } + ], + "ports": [ + { + "container_port": 10443, + "published_example": 10443, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + } + ], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "name: scrypted\nservices:\n scrypted:\n image: ghcr.io/koush/scrypted:latest\n restart: unless-stopped\n network_mode: host\n volumes:\n - ./volume:/server/volume\n" + }, + "compose_stack": { + "project_name": "scrypted", + "deployment_model": "one-native-oci-lxc-per-compose-service", + "user_experience": "single-application-install", + "main_service": "scrypted", + "service_count": 1, + "services": [ + { + "name": "scrypted", + "image": "ghcr.io/koush/scrypted:latest", + "is_main": true, + "role": "frontend", + "vmid_offset": 0, + "depends_on": [], + "frontend_network": true, + "private_network": false, + "compose": { + "image": "ghcr.io/koush/scrypted:latest", + "restart": "unless-stopped", + "network_mode": "host", + "volumes": [ + "./volume:/server/volume" + ] + } + } + ], + "top_level": { + "name": "scrypted" + }, + "networking": { + "frontend": "selected-proxmox-bridge", + "private_required": false, + "private_creation": "automatic-create-if-missing", + "private_address_allocation": "automatic-static-address-per-service", + "service_discovery": "private-addresses-with-compose-service-host-aliases", + "dependency_external_access": "disabled-unless-service-publishes-ports", + "prompt_user_for_private_network": false + }, + "storage": [ + { + "id": "scrypted-volume-0", + "service": "scrypted", + "container_path": "/server/volume", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + } + ], + "orchestration": { + "reserve_vmids_atomically": 1, + "start_order": [ + "scrypted" + ], + "stop_order": [ + "scrypted" + ], + "dependency_readiness": "compose-healthcheck-then-port-or-process-fallback", + "rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes" + }, + "installer_inputs": { + "prompted": [ + "stack_name", + "base_vmid", + "rootfs_storage", + "persistent_data_destinations", + "frontend_bridge", + "frontend_ipv4_mode" + ], + "automatic": [ + "dependent_vmids", + "private_bridge", + "private_subnet", + "private_service_addresses", + "compose_service_aliases", + "generated_secrets", + "dependency_start_and_stop_order" + ], + "generated_secrets": [] + } + }, + "first_run": { + "endpoints": [ + { + "label": "Scrypted Web UI", + "scheme": "https", + "port": 10443, + "path": "/", + "source": "official Scrypted documentation" + } + ], + "credentials": [] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 2048, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 30 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "installer_profile": { + "startup_healthcheck": { + "type": "http", + "scheme": "https", + "port": 10443, + "path": "/", + "timeout_seconds": 300, + "request_timeout_seconds": 10, + "stability_seconds": 4, + "verify_tls": false, + "required": true, + "source": "official Scrypted web interface" + } + }, + "adaptations": [ + { + "id": "imported-compose-source", + "upstream_behavior": "The source definition deploys the complete Docker Compose application model.", + "native_lxc_behavior": "The source model is preserved and remains blocked until every service option has a reviewed native Proxmox mapping.", + "reason": "Catalog import must not imply runtime compatibility.", + "behavioral_impact": "No automatic installation before review.", + "validation": "pending-per-application" + }, + { + "id": "rolling-latest-image", + "upstream_behavior": "A discovered Compose may pin a release tag or digest.", + "native_lxc_behavior": "ProxMenux selects the same image repository with the latest tag for catalog installations.", + "reason": "The automatic catalog intentionally offers rolling latest images; pinned versions belong to the future manual installer.", + "behavioral_impact": "The installed release can be newer than the discovered Compose revision.", + "validation": "pending-per-application" + }, + { + "id": "dedicated-lxc-network", + "upstream_behavior": "Docker publishes selected container ports on the Docker host.", + "native_lxc_behavior": "A reviewed native application will listen on its original container ports at a dedicated LXC address.", + "reason": "A native LXC has its own address and does not require Docker port NAT.", + "behavioral_impact": "Published ports are metadata; ProxMenux URLs use the matching container target port.", + "validation": "pending-per-application" + }, + { + "id": "compose-shm-size", + "upstream_behavior": "Compose sets the size of the container /dev/shm tmpfs.", + "native_lxc_behavior": "ProxMenux mounts a native LXC tmpfs at /dev/shm with the same requested capacity.", + "reason": "The OCI image runs directly as an LXC and therefore needs the equivalent Proxmox mount entry.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-command", + "upstream_behavior": "Compose replaces the image Cmd while retaining its Entrypoint.", + "native_lxc_behavior": "ProxMenux reads the official OCI Entrypoint and combines it with the Compose command as the native LXC init command.", + "reason": "Proxmox stores the effective OCI process as one entrypoint string.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-privileged-mode", + "upstream_behavior": "Compose selects whether the container runs in privileged mode.", + "native_lxc_behavior": "ProxMenux keeps the LXC unprivileged by default and exposes the broad Compose privilege request only as an explicit compatibility option.", + "reason": "A Compose privilege request is source metadata, not proof that the image technically requires a privileged LXC.", + "behavioral_impact": "A privileged LXC has weaker isolation from the Proxmox host.", + "validation": "native-equivalent" + }, + { + "id": "compose-process-runtime", + "upstream_behavior": "Compose can replace Entrypoint, User and WorkingDir and request an init process or interactive terminal.", + "native_lxc_behavior": "ProxMenux applies the process overrides through native LXC init directives; lxc-init provides PID 1 supervision and the CT console provides terminal access.", + "reason": "The OCI process must start with the same identity, command and working directory without Docker.", + "behavioral_impact": "Compose stdin_open and tty become access through the Proxmox LXC console.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-healthcheck", + "upstream_behavior": "Docker periodically executes the declared container healthcheck.", + "native_lxc_behavior": "For a single-service LXC, ProxMenux translates HTTP localhost checks into a mandatory first-start service check.", + "reason": "Proxmox has no persistent Docker health state, while the installer still must detect a failed first boot.", + "behavioral_impact": "The check runs during installation rather than continuously after installation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-cpu-priority", + "upstream_behavior": "Docker cpu_shares sets a relative scheduling weight with 1024 as its neutral value.", + "native_lxc_behavior": "ProxMenux converts the relative weight to Proxmox cpuunits with 100 as its neutral value and lets the user review it.", + "reason": "Both settings express relative CPU priority on different scales.", + "behavioral_impact": "Rounding and Proxmox minimum limits can slightly change very low weights.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-resource-limits", + "upstream_behavior": "mem_limit sets the memory ceiling; ulimits sets process soft/hard resource limits.", + "native_lxc_behavior": "mem_limit supplies the editable Proxmox memory default, rounded up to MiB; ulimits maps to lxc.prlimit with -1 represented as unlimited.", + "reason": "Use native Proxmox memory and LXC prlimits, without a wrapper or changing the host kernel configuration.", + "behavioral_impact": "User-selected memory overrides Compose. Swap is a separate choice. Kernel restrictions still apply; nproc counts processes by real UID, not by container.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-identity", + "upstream_behavior": "Compose can set hostname, MAC address, extra hosts and attach a service to Docker networks.", + "native_lxc_behavior": "ProxMenux applies hostname and MAC to net0, writes additional host aliases into the LXC and uses its dedicated bridge connection for single-service networks.", + "reason": "A dedicated LXC has its own network namespace and does not need a Docker bridge per service.", + "behavioral_impact": "host-gateway resolves to the IPv4 address of the selected Proxmox bridge.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-mode", + "upstream_behavior": "Docker host mode removes Docker network isolation; bridge and default use a Docker-managed network.", + "native_lxc_behavior": "The OCI process uses the dedicated LXC network namespace directly, so host, bridge and default all listen on the LXC address without Docker NAT.", + "reason": "The LXC is the application host and already has its own address and port namespace.", + "behavioral_impact": "host means the LXC host, never the Proxmox host; this preserves Proxmox network isolation.", + "validation": "native-equivalent" + }, + { + "id": "compose-capabilities-and-sysctls", + "upstream_behavior": "Compose can add Linux capabilities and set kernel parameters in the container network namespace.", + "native_lxc_behavior": "ProxMenux validates requested capabilities against the native LXC capability set and writes namespaced network settings as lxc.sysctl directives.", + "reason": "A native OCI-LXC already starts with the namespaced capability set; lxc.cap.keep would incorrectly discard unrelated required capabilities.", + "behavioral_impact": "Host-global capabilities such as SYS_MODULE remain blocked until their host prerequisite is explicitly adapted.", + "validation": "not-requested-by-compose" + }, + { + "id": "docker-engine-metadata", + "upstream_behavior": "Compose labels annotate Docker objects and the json-file logging driver rotates Docker-managed logs.", + "native_lxc_behavior": "Labels remain source metadata; Docker json-file settings are not applied because the OCI process runs directly under LXC.", + "reason": "There is no Docker object or Docker json-file log behind a native OCI-LXC application.", + "behavioral_impact": "Docker-only label consumers and Docker log-driver rotation do not exist in the native deployment.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-device-passthrough", + "upstream_behavior": "Compose passes host character devices or requests an NVIDIA runtime GPU.", + "native_lxc_behavior": "ProxMenux converts recognized device declarations to native Proxmox dev resources; NVIDIA profiles also inject compatible host driver libraries read-only.", + "reason": "Native OCI-LXC does not execute Docker device or NVIDIA runtime hooks.", + "behavioral_impact": "Hardware is exposed only after explicit user confirmation and host-path validation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-host-ipc", + "upstream_behavior": "ipc: host shares the Docker host IPC namespace, commonly to avoid Docker's small default shared-memory allocation.", + "native_lxc_behavior": "The application keeps the LXC IPC namespace and receives a configurable 1 GiB /dev/shm instead of sharing Proxmox host IPC.", + "reason": "Processes in a single native LXC already share one IPC namespace; retaining isolation is safer than exposing host IPC.", + "behavioral_impact": "The application cannot exchange IPC objects with processes on the Proxmox host.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-watchtower", + "upstream_behavior": "The official Compose file offers a Watchtower sidecar to trigger Docker Engine image updates.", + "native_lxc_behavior": "The sidecar and Docker socket mount are omitted; ProxMenux performs native OCI image updates from Manage.", + "reason": "The native OCI deployment has no Docker daemon or Docker socket.", + "behavioral_impact": "Use Manage installed OCI applications to check and apply image updates.", + "validation": "native-equivalent" + }, + { + "id": "compose-dns", + "upstream_behavior": "The official Compose file can use public DNS resolvers for the application container.", + "native_lxc_behavior": "The OCI service uses the resolver configured in its dedicated LXC.", + "reason": "The native OCI process runs in the LXC network namespace.", + "behavioral_impact": "DNS follows the selected LXC network configuration.", + "validation": "native-equivalent" + }, + { + "id": "scrypted-optional-hardware-and-nvr", + "upstream_behavior": "The official Compose file documents optional camera storage, mDNS/Avahi, GPU and USB/serial device configuration.", + "native_lxc_behavior": "This single-image profile leaves optional storage and device access disabled; ProxMenux OCI Modify can add supported devices and mounts.", + "reason": "The minimal profile is intended to verify the core service without assuming the user has a camera, NVR volume or specific accelerator.", + "behavioral_impact": "Camera discovery, plugins, NVR recording, mDNS/Avahi and hardware acceleration require separate setup and are not checked by the initial health test.", + "validation": "not-configured-by-profile" + } + ] + }, + "compatibility": { + "automatic_install_candidate": true, + "validated": false, + "supported_compose_keys": [ + "cap_add", + "command", + "container_name", + "cpu_shares", + "deploy", + "devices", + "entrypoint", + "environment", + "extra_hosts", + "group_add", + "healthcheck", + "hostname", + "image", + "init", + "ipc", + "labels", + "logging", + "mac_address", + "mem_limit", + "network_mode", + "networks", + "ports", + "privileged", + "restart", + "runtime", + "security_opt", + "shm_size", + "stdin_open", + "stop_grace_period", + "sysctls", + "tty", + "ulimits", + "user", + "volumes", + "working_dir" + ], + "untranslated_blockers": [], + "policy": "Single-image definitions are installable when every declared Compose option has a native Proxmox translation. Multi-image and unsupported runtime features remain blocked until their orchestrator or mapping is available." + }, + "validation": { + "schema": "passed-at-generation", + "clean_install": "pending", + "service_health": "pending", + "restart_persistence": "pending", + "backup_restore": "pending", + "update_preserves_data": "pending" + }, + "lifecycle": { + "update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update", + "registry_state": { + "resolved_architecture": null, + "resolved_digest": null, + "image_version_label": null, + "image_created": null + }, + "change_detection": "compare-compose-sha256-and-resolved-latest-image-digest", + "automatic_unattended_updates": false + } +} diff --git a/oci/catalog/categories.json b/oci/catalog/categories.json index 819818aa..1e91ef33 100644 --- a/oci/catalog/categories.json +++ b/oci/catalog/categories.json @@ -430,6 +430,7 @@ "bytebase": "databases", "metabase": "monitoring", "kopia": "backup", - "restic-rest-server": "backup" + "restic-rest-server": "backup", + "netalertx": "monitoring" } } diff --git a/oci/catalog/curated/netalertx.json b/oci/catalog/curated/netalertx.json new file mode 100644 index 00000000..fc14d905 --- /dev/null +++ b/oci/catalog/curated/netalertx.json @@ -0,0 +1,379 @@ +{ + "schema_version": "0.5.0", + "kind": "proxmenux.oci-template", + "id": "image-netalertx", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "NetAlertX" + }, + "tagline": { + "en_US": "Discover and monitor devices on your local network." + }, + "description": { + "en_US": "NetAlertX discovers devices on your local network and tracks its device inventory in a self-hosted web interface." + }, + "category": "monitoring", + "category_label": "Monitoring & Analytics", + "author": "NetAlertX", + "developer": "NetAlertX", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/netalertx.webp", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64", + "arm64" + ], + "launch": { + "scheme": "http", + "port": 20211, + "path": "/" + }, + "website": "https://netalertx.com/", + "documentation": "https://docs.netalertx.com/DOCKER_INSTALLATION/", + "repository": "https://github.com/netalertx/NetAlertX", + "tips": [ + "The web interface listens on port 20211. GraphQL listens on port 20212.", + "The upstream Docker Compose baseline requests a read-only root filesystem, drops all capabilities before adding a small allow-list, and uses a temporary filesystem for /tmp. This native OCI-on-LXC profile does not reproduce those Docker Engine controls: ProxMenux validates requested cap_add values against the LXC user namespace and maps only the supported network sysctls. Docker capability bounding and read-only-rootfs behavior are not enforced.", + "Compose network_mode: host maps to this application LXC network namespace, never the Proxmox host. Layer 2 discovery may still depend on the selected bridge and network; the installation smoke test only confirms startup and the web interface.", + "The initial Devices page reported no devices yet and advises checking SCAN_SUBNETS. Configure scanning for the local subnet as needed; this smoke test did not verify discovery.", + "This profile does not configure a login. The web interface opened directly to the Devices page during the smoke test." + ], + "mini_changelog": [], + "display_version": null, + "updated_at": null + }, + "source": { + "provider": "NetAlertX", + "repository": "https://github.com/netalertx/NetAlertX", + "revision": "6f3cb9bd43855596da3e8c6185490abd5784f1dd", + "image_repository_url": "https://github.com/netalertx/NetAlertX/pkgs/container/netalertx", + "readme_pushed_at": "", + "compose_sha256": "1e5265637e13cc4c61901a685e4fe9cd015f8e1c52ad419de2392f2791795135", + "generated_at": "2026-10-27T20:39:28+00:00", + "readme_raw_url": "https://raw.githubusercontent.com/netalertx/NetAlertX/6f3cb9bd43855596da3e8c6185490abd5784f1dd/docs/DOCKER_COMPOSE.md" + }, + "container_contract": { + "service_name": "netalertx", + "container_name": "netalertx", + "image": { + "reference": "ghcr.io/netalertx/netalertx:latest", + "registry": "ghcr.io", + "repository": "ghcr.io/netalertx/netalertx", + "tag": "latest", + "digest": null, + "pull_policy": "resolve-selected-tag-to-architecture-digest-at-install" + }, + "environment": [ + { + "name": "LISTEN_ADDR", + "example": "0.0.0.0", + "required": true, + "sensitive": false, + "source": "docker-compose" + }, + { + "name": "PORT", + "example": "20211", + "required": true, + "sensitive": false, + "source": "docker-compose" + }, + { + "name": "GRAPHQL_PORT", + "example": "20212", + "required": true, + "sensitive": false, + "source": "docker-compose" + } + ], + "volumes": [ + { + "id": "volume-0", + "container_path": "/data", + "compose_source_example": "netalertx-data", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 8 + } + } + ], + "ports": [], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "services:\n netalertx:\n #use an environmental variable to set host networking mode if needed\n container_name: netalertx # The name when you docker contiainer ls\n image: ghcr.io/netalertx/netalertx:latest\n network_mode: ${NETALERTX_NETWORK_MODE:-host} # Use host networking for ARP scanning and other services\n\n read_only: true # Make the container filesystem read-only\n cap_drop: # Drop all capabilities for enhanced security\n - ALL\n cap_add: # Add only the necessary capabilities\n - NET_ADMIN # Required for ARP scanning\n - NET_RAW # Required for raw socket operations\n - NET_BIND_SERVICE # Required to bind to privileged ports (nbtscan)\n - CHOWN # Required for root-entrypoint to chown /data + /tmp before dropping privileges\n - SETUID # Required for root-entrypoint to switch to non-root user\n - SETGID # Required for root-entrypoint to switch to non-root group\n # --- ARP FLUX MITIGATION ---\n # Note: When using `network_mode: host`, these sysctls require the\n # NET_ADMIN capability to be applied to the host namespace.\n # \n # If your environment restricts capabilities, or you prefer to configure\n # them on the Host OS, REMOVE the sysctls block below and apply via:\n # sudo sysctl -w net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2\n # ---------------------------\n sysctls: # ARP flux mitigation (reduces duplicate/ambiguous ARP behavior on host networking)\n net.ipv4.conf.all.arp_ignore: 1\n net.ipv4.conf.all.arp_announce: 2\n\n volumes:\n - type: volume # Persistent Docker-managed named volume for config + database\n source: netalertx_data\n target: /data # `/data/config` and `/data/db` live inside this mount\n read_only: false\n\n # Example custom local folder called /home/user/netalertx_data\n # - type: bind\n # source: /home/user/netalertx_data\n # target: /data\n # read_only: false\n # ... or use the alternative format\n # - /home/user/netalertx_data:/data:rw\n\n - type: bind # Bind mount for timezone consistency\n source: /etc/localtime\n target: /etc/localtime\n read_only: true\n\n # Mount your DHCP server file into NetAlertX for a plugin to access\n # - path/on/host/to/dhcp.file:/resources/dhcp.file\n\n # tmpfs mount consolidates writable state for a read-only container and improves performance\n # uid/gid default to the service user (NETALERTX_UID/GID, default 20211)\n # mode=1700 grants rwx------ permissions to the runtime user only\n tmpfs:\n # Comment out to retain logs between container restarts - this has a server performance impact.\n - \"/tmp:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n\n # Retain logs - comment out tmpfs /tmp if you want to retain logs between container restarts\n # Please note if you remove the /tmp mount, you must create and maintain sub-folder mounts.\n # - /path/on/host/log:/tmp/log\n # - \"/tmp/api:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n # - \"/tmp/nginx:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n # - \"/tmp/run:uid=${NETALERTX_UID:-20211},gid=${NETALERTX_GID:-20211},mode=1700,rw,noexec,nosuid,nodev,async,noatime,nodiratime\"\n\n environment:\n LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} # Listen for connections on all interfaces\n PORT: ${PORT:-20211} # Application port\n GRAPHQL_PORT: ${GRAPHQL_PORT:-20212} # GraphQL API port (passed into APP_CONF_OVERRIDE at runtime)\n # NETALERTX_DEBUG: ${NETALERTX_DEBUG:-0} # 0=kill all services and restart if any dies. 1 keeps running dead services.\n # PUID: 20211 # Runtime PUID override, set to 0 to run as root\n # PGID: 20211 # Runtime PGID override\n\n # Resource limits to prevent resource exhaustion\n mem_limit: 2048m # Maximum memory usage\n mem_reservation: 1024m # Soft memory limit\n cpu_shares: 512 # Relative CPU weight for CPU contention scenarios\n pids_limit: 512 # Limit the number of processes/threads to prevent fork bombs\n logging:\n options:\n max-size: \"10m\" # Rotate log files after they reach 10MB\n max-file: \"3\" # Keep a maximum of 3 log files\n\n # Always restart the container unless explicitly stopped\n restart: unless-stopped\n\nvolumes: # Persistent volume for configuration and database storage\n netalertx_data:\n" + }, + "compose_stack": { + "project_name": "netalertx", + "deployment_model": "one-native-oci-lxc-per-compose-service", + "user_experience": "single-application-install", + "main_service": "netalertx", + "service_count": 1, + "services": [ + { + "name": "netalertx", + "image": "ghcr.io/netalertx/netalertx:latest", + "is_main": true, + "role": "frontend", + "vmid_offset": 0, + "depends_on": [], + "frontend_network": true, + "private_network": false, + "compose": { + "image": "ghcr.io/netalertx/netalertx:latest", + "container_name": "netalertx", + "network_mode": "host", + "cap_add": [ + "NET_ADMIN", + "NET_RAW", + "NET_BIND_SERVICE", + "CHOWN", + "SETUID", + "SETGID" + ], + "sysctls": { + "net.ipv4.conf.all.arp_ignore": "1", + "net.ipv4.conf.all.arp_announce": "2" + }, + "restart": "unless-stopped", + "environment": { + "LISTEN_ADDR": "0.0.0.0", + "PORT": "20211", + "GRAPHQL_PORT": "20212" + }, + "volumes": [ + "netalertx-data:/data" + ], + "mem_limit": "2048m" + } + } + ], + "top_level": { + "name": "netalertx", + "volumes": { + "netalertx-data": {} + } + }, + "networking": { + "frontend": "selected-proxmox-bridge", + "private_required": false, + "private_creation": "automatic-create-if-missing", + "private_address_allocation": "automatic-static-address-per-service", + "service_discovery": "private-addresses-with-compose-service-host-aliases", + "dependency_external_access": "disabled-unless-service-publishes-ports", + "prompt_user_for_private_network": false + }, + "storage": [ + { + "id": "netalertx-volume-0", + "service": "netalertx", + "container_path": "/data", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + } + ], + "orchestration": { + "reserve_vmids_atomically": 1, + "start_order": [ + "netalertx" + ], + "stop_order": [ + "netalertx" + ], + "dependency_readiness": "compose-healthcheck-then-port-or-process-fallback", + "rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes" + }, + "installer_inputs": { + "prompted": [ + "stack_name", + "base_vmid", + "rootfs_storage", + "persistent_data_destinations", + "frontend_bridge", + "frontend_ipv4_mode" + ], + "automatic": [ + "dependent_vmids", + "private_bridge", + "private_subnet", + "private_service_addresses", + "compose_service_aliases", + "generated_secrets", + "dependency_start_and_stop_order" + ], + "generated_secrets": [] + } + }, + "first_run": { + "endpoints": [ + { + "label": "Web UI", + "scheme": "http", + "port": 20211, + "path": "/", + "source": "compose-metadata" + } + ], + "credentials": [] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 1024, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 30 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "installer_profile": { + "resources": { + "memory_default_mb": 2048 + }, + "network": { + "compose_mode": "host" + }, + "security": { + "required_capabilities": [ + "NET_ADMIN", + "NET_RAW", + "NET_BIND_SERVICE", + "CHOWN", + "SETUID", + "SETGID" + ], + "sysctls": [ + { + "name": "net.ipv4.conf.all.arp_ignore", + "value": "1" + }, + { + "name": "net.ipv4.conf.all.arp_announce", + "value": "2" + } + ] + } + }, + "adaptations": [ + { + "id": "netalertx-native-network", + "upstream_behavior": "The official Compose profile uses host networking for layer 2 discovery and adds NET_ADMIN, NET_RAW and NET_BIND_SERVICE capabilities.", + "native_lxc_behavior": "ProxMenux validates the declared cap_add names against the LXC user namespace and maps the documented network sysctls into the LXC configuration.", + "reason": "A dedicated LXC already has its own address and network namespace, so Docker network plumbing is not needed.", + "behavioral_impact": "Host mode refers to the NetAlertX LXC, not the Proxmox host. Compose cap_add values are validated but are not applied as an OCI capability bounding set; actual discovery may depend on the selected bridge and network.", + "validation": "startup-and-web-smoke-tested-2026-10-27" + }, + { + "id": "netalertx-docker-hardening", + "upstream_behavior": "The official Docker baseline sets read_only, cap_drop: ALL and a tmpfs for /tmp before adding its documented capability allow-list.", + "native_lxc_behavior": "ProxMenux validates declared cap_add names against the LXC user namespace and maps supported sysctls, but does not translate read_only, cap_drop or tmpfs into equivalent native OCI-on-LXC controls.", + "reason": "The OCI-on-LXC engine maps a supported subset of Compose to Proxmox resources; Docker Engine confinement options cannot be asserted as equivalent without a native mapping.", + "behavioral_impact": "This profile does not reproduce Docker capability bounding, dropped-capability or read-only-rootfs behavior. The startup and web UI smoke test did not validate network scanning or scanner permissions.", + "validation": "reviewed-from-upstream-compose" + }, + { + "id": "netalertx-persistent-data", + "upstream_behavior": "The official Compose profile stores configuration and the device database in /data.", + "native_lxc_behavior": "ProxMenux stores /data in a selectable managed volume or host bind directory.", + "reason": "The app database and configuration need to survive OCI image replacement.", + "behavioral_impact": "Persistent /data is included in Proxmox backups when a managed volume is selected.", + "validation": "not-tested-in-installation-smoke" + } + ] + }, + "compatibility": { + "automatic_install_candidate": true, + "validated": false, + "supported_compose_keys": [ + "cap_add", + "command", + "container_name", + "cpu_shares", + "deploy", + "devices", + "entrypoint", + "environment", + "extra_hosts", + "group_add", + "healthcheck", + "hostname", + "image", + "init", + "ipc", + "labels", + "logging", + "mac_address", + "mem_limit", + "network_mode", + "networks", + "ports", + "privileged", + "restart", + "runtime", + "security_opt", + "shm_size", + "stdin_open", + "stop_grace_period", + "sysctls", + "tty", + "ulimits", + "user", + "volumes", + "working_dir" + ], + "untranslated_blockers": [], + "policy": "Single-image definitions are installable when every declared Compose option has a native Proxmox translation. Multi-image and unsupported runtime features remain blocked until their orchestrator or mapping is available." + }, + "validation": { + "schema": "passed-at-generation", + "clean_install": "pending", + "service_health": "pending", + "restart_persistence": "pending", + "backup_restore": "pending", + "update_preserves_data": "pending" + }, + "lifecycle": { + "update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update", + "registry_state": { + "resolved_architecture": null, + "resolved_digest": null, + "image_version_label": null, + "image_created": null + }, + "change_detection": "compare-compose-sha256-and-resolved-latest-image-digest", + "automatic_unattended_updates": false + } +} diff --git a/oci/catalog/curated/scrypted.json b/oci/catalog/curated/scrypted.json new file mode 100644 index 00000000..4884430a --- /dev/null +++ b/oci/catalog/curated/scrypted.json @@ -0,0 +1,454 @@ +{ + "schema_version": "0.5.0", + "kind": "proxmenux.oci-template", + "id": "image-scrypted", + "status": "generated-unvalidated", + "catalog_ui": { + "title": { + "en_US": "Scrypted" + }, + "tagline": { + "en_US": "Self-hosted smart home video platform." + }, + "description": { + "en_US": "Manage cameras and integrate video devices with Apple HomeKit, Google Home, Alexa and Home Assistant from one self-hosted platform." + }, + "category": "smarthome", + "category_label": "IoT & Smart Home", + "author": "Scrypted", + "developer": "Scrypted", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/scrypted.webp", + "thumbnail": null, + "screenshots": [], + "architectures": [ + "amd64", + "arm64" + ], + "launch": { + "scheme": "https", + "port": 10443, + "path": "/" + }, + "website": "https://scrypted.app/", + "documentation": "https://docs.scrypted.app/install/linux-docker.html", + "repository": "https://github.com/koush/scrypted", + "tips": [ + "This profile uses the official single-service image and persists Scrypted state in /server/volume.", + "The official Compose file includes an optional Watchtower service for Docker Engine updates. It is omitted because ProxMenux manages OCI image updates.", + "Camera, NVR, mDNS/Avahi, GPU and USB integrations are not configured by this minimal profile; add required devices and storage separately." + ], + "mini_changelog": [], + "display_version": null, + "updated_at": null + }, + "source": { + "provider": "official", + "repository": "https://github.com/koush/scrypted", + "revision": "78320e94ad977a77fa2bb201346b98630b8b1d47", + "image_repository_url": "https://github.com/koush/scrypted/pkgs/container/scrypted", + "readme_pushed_at": "2026-10-18T02:15:15Z", + "compose_sha256": "a34bc5941c67a43e0e328f216c82f3dcf31c213c046b6920e08500af17d91727", + "generated_at": "2026-10-27T20:07:09+00:00" + }, + "container_contract": { + "service_name": "scrypted", + "container_name": "scrypted", + "image": { + "reference": "ghcr.io/koush/scrypted:latest", + "registry": "ghcr.io", + "repository": "ghcr.io/koush/scrypted", + "tag": "latest", + "digest": null, + "pull_policy": "resolve-selected-tag-to-architecture-digest-at-install" + }, + "environment": [], + "volumes": [ + { + "id": "volume-0", + "container_path": "/server/volume", + "compose_source_example": "./volume", + "read_only": false, + "required": true, + "installation_choice": [ + "managed-volume", + "host-bind" + ], + "default": "managed-volume", + "managed_volume": { + "backup": true, + "default_size_gb": 8 + } + } + ], + "ports": [ + { + "container_port": 10443, + "published_example": 10443, + "protocol": "tcp", + "required": true, + "proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat" + } + ], + "related_services": [], + "restart": "unless-stopped", + "stop_grace_period": null, + "original_compose": "name: scrypted\nservices:\n scrypted:\n image: ghcr.io/koush/scrypted:latest\n restart: unless-stopped\n network_mode: host\n volumes:\n - ./volume:/server/volume\n" + }, + "compose_stack": { + "project_name": "scrypted", + "deployment_model": "one-native-oci-lxc-per-compose-service", + "user_experience": "single-application-install", + "main_service": "scrypted", + "service_count": 1, + "services": [ + { + "name": "scrypted", + "image": "ghcr.io/koush/scrypted:latest", + "is_main": true, + "role": "frontend", + "vmid_offset": 0, + "depends_on": [], + "frontend_network": true, + "private_network": false, + "compose": { + "image": "ghcr.io/koush/scrypted:latest", + "restart": "unless-stopped", + "network_mode": "host", + "volumes": [ + "./volume:/server/volume" + ] + } + } + ], + "top_level": { + "name": "scrypted" + }, + "networking": { + "frontend": "selected-proxmox-bridge", + "private_required": false, + "private_creation": "automatic-create-if-missing", + "private_address_allocation": "automatic-static-address-per-service", + "service_discovery": "private-addresses-with-compose-service-host-aliases", + "dependency_external_access": "disabled-unless-service-publishes-ports", + "prompt_user_for_private_network": false + }, + "storage": [ + { + "id": "scrypted-volume-0", + "service": "scrypted", + "container_path": "/server/volume", + "mode": "managed-volume", + "user_selectable": false, + "backup": true, + "shared_with_other_lxc": false, + "source_path": null, + "source_path_prompt": null + } + ], + "orchestration": { + "reserve_vmids_atomically": 1, + "start_order": [ + "scrypted" + ], + "stop_order": [ + "scrypted" + ], + "dependency_readiness": "compose-healthcheck-then-port-or-process-fallback", + "rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes" + }, + "installer_inputs": { + "prompted": [ + "stack_name", + "base_vmid", + "rootfs_storage", + "persistent_data_destinations", + "frontend_bridge", + "frontend_ipv4_mode" + ], + "automatic": [ + "dependent_vmids", + "private_bridge", + "private_subnet", + "private_service_addresses", + "compose_service_aliases", + "generated_secrets", + "dependency_start_and_stop_order" + ], + "generated_secrets": [] + } + }, + "first_run": { + "endpoints": [ + { + "label": "Scrypted Web UI", + "scheme": "https", + "port": 10443, + "path": "/", + "source": "official Scrypted documentation" + } + ], + "credentials": [] + }, + "proxmox": { + "runtime": "native-oci-lxc", + "technology_status": "proxmox-technology-preview", + "defaults": { + "unprivileged": true, + "ostype": "auto-from-image", + "cores": 2, + "memory_mb": 2048, + "swap_mb": 512, + "rootfs_size_gb": 8, + "rootfs_storage": "local-lvm", + "volume_storage": "local-lvm", + "template_storage": "local", + "bridge": "vmbr0", + "ipv4": "dhcp", + "firewall": true, + "host_managed_network": true, + "onboot": false, + "features": [ + "nesting=1" + ], + "shutdown_timeout_seconds": 30 + }, + "image_metadata_policy": { + "entrypoint": "import-from-oci-image", + "cmd": "import-from-oci-image", + "environment": "import-image-env-then-apply-compose-overrides", + "user": "import-from-oci-image", + "working_dir": "import-from-oci-image", + "stop_signal": "import-from-oci-image" + }, + "installer_profile": { + "startup_healthcheck": { + "type": "http", + "scheme": "https", + "port": 10443, + "path": "/", + "timeout_seconds": 300, + "request_timeout_seconds": 10, + "stability_seconds": 4, + "verify_tls": false, + "required": true, + "source": "official Scrypted web interface" + } + }, + "adaptations": [ + { + "id": "imported-compose-source", + "upstream_behavior": "The source definition deploys the complete Docker Compose application model.", + "native_lxc_behavior": "The source model is preserved and remains blocked until every service option has a reviewed native Proxmox mapping.", + "reason": "Catalog import must not imply runtime compatibility.", + "behavioral_impact": "No automatic installation before review.", + "validation": "pending-per-application" + }, + { + "id": "rolling-latest-image", + "upstream_behavior": "A discovered Compose may pin a release tag or digest.", + "native_lxc_behavior": "ProxMenux selects the same image repository with the latest tag for catalog installations.", + "reason": "The automatic catalog intentionally offers rolling latest images; pinned versions belong to the future manual installer.", + "behavioral_impact": "The installed release can be newer than the discovered Compose revision.", + "validation": "pending-per-application" + }, + { + "id": "dedicated-lxc-network", + "upstream_behavior": "Docker publishes selected container ports on the Docker host.", + "native_lxc_behavior": "A reviewed native application will listen on its original container ports at a dedicated LXC address.", + "reason": "A native LXC has its own address and does not require Docker port NAT.", + "behavioral_impact": "Published ports are metadata; ProxMenux URLs use the matching container target port.", + "validation": "pending-per-application" + }, + { + "id": "compose-shm-size", + "upstream_behavior": "Compose sets the size of the container /dev/shm tmpfs.", + "native_lxc_behavior": "ProxMenux mounts a native LXC tmpfs at /dev/shm with the same requested capacity.", + "reason": "The OCI image runs directly as an LXC and therefore needs the equivalent Proxmox mount entry.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-command", + "upstream_behavior": "Compose replaces the image Cmd while retaining its Entrypoint.", + "native_lxc_behavior": "ProxMenux reads the official OCI Entrypoint and combines it with the Compose command as the native LXC init command.", + "reason": "Proxmox stores the effective OCI process as one entrypoint string.", + "behavioral_impact": "None expected.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-privileged-mode", + "upstream_behavior": "Compose selects whether the container runs in privileged mode.", + "native_lxc_behavior": "ProxMenux keeps the LXC unprivileged by default and exposes the broad Compose privilege request only as an explicit compatibility option.", + "reason": "A Compose privilege request is source metadata, not proof that the image technically requires a privileged LXC.", + "behavioral_impact": "A privileged LXC has weaker isolation from the Proxmox host.", + "validation": "native-equivalent" + }, + { + "id": "compose-process-runtime", + "upstream_behavior": "Compose can replace Entrypoint, User and WorkingDir and request an init process or interactive terminal.", + "native_lxc_behavior": "ProxMenux applies the process overrides through native LXC init directives; lxc-init provides PID 1 supervision and the CT console provides terminal access.", + "reason": "The OCI process must start with the same identity, command and working directory without Docker.", + "behavioral_impact": "Compose stdin_open and tty become access through the Proxmox LXC console.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-healthcheck", + "upstream_behavior": "Docker periodically executes the declared container healthcheck.", + "native_lxc_behavior": "For a single-service LXC, ProxMenux translates HTTP localhost checks into a mandatory first-start service check.", + "reason": "Proxmox has no persistent Docker health state, while the installer still must detect a failed first boot.", + "behavioral_impact": "The check runs during installation rather than continuously after installation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-cpu-priority", + "upstream_behavior": "Docker cpu_shares sets a relative scheduling weight with 1024 as its neutral value.", + "native_lxc_behavior": "ProxMenux converts the relative weight to Proxmox cpuunits with 100 as its neutral value and lets the user review it.", + "reason": "Both settings express relative CPU priority on different scales.", + "behavioral_impact": "Rounding and Proxmox minimum limits can slightly change very low weights.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-resource-limits", + "upstream_behavior": "mem_limit sets the memory ceiling; ulimits sets process soft/hard resource limits.", + "native_lxc_behavior": "mem_limit supplies the editable Proxmox memory default, rounded up to MiB; ulimits maps to lxc.prlimit with -1 represented as unlimited.", + "reason": "Use native Proxmox memory and LXC prlimits, without a wrapper or changing the host kernel configuration.", + "behavioral_impact": "User-selected memory overrides Compose. Swap is a separate choice. Kernel restrictions still apply; nproc counts processes by real UID, not by container.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-identity", + "upstream_behavior": "Compose can set hostname, MAC address, extra hosts and attach a service to Docker networks.", + "native_lxc_behavior": "ProxMenux applies hostname and MAC to net0, writes additional host aliases into the LXC and uses its dedicated bridge connection for single-service networks.", + "reason": "A dedicated LXC has its own network namespace and does not need a Docker bridge per service.", + "behavioral_impact": "host-gateway resolves to the IPv4 address of the selected Proxmox bridge.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-network-mode", + "upstream_behavior": "Docker host mode removes Docker network isolation; bridge and default use a Docker-managed network.", + "native_lxc_behavior": "The OCI process uses the dedicated LXC network namespace directly, so host, bridge and default all listen on the LXC address without Docker NAT.", + "reason": "The LXC is the application host and already has its own address and port namespace.", + "behavioral_impact": "host means the LXC host, never the Proxmox host; this preserves Proxmox network isolation.", + "validation": "native-equivalent" + }, + { + "id": "compose-capabilities-and-sysctls", + "upstream_behavior": "Compose can add Linux capabilities and set kernel parameters in the container network namespace.", + "native_lxc_behavior": "ProxMenux validates requested capabilities against the native LXC capability set and writes namespaced network settings as lxc.sysctl directives.", + "reason": "A native OCI-LXC already starts with the namespaced capability set; lxc.cap.keep would incorrectly discard unrelated required capabilities.", + "behavioral_impact": "Host-global capabilities such as SYS_MODULE remain blocked until their host prerequisite is explicitly adapted.", + "validation": "not-requested-by-compose" + }, + { + "id": "docker-engine-metadata", + "upstream_behavior": "Compose labels annotate Docker objects and the json-file logging driver rotates Docker-managed logs.", + "native_lxc_behavior": "Labels remain source metadata; Docker json-file settings are not applied because the OCI process runs directly under LXC.", + "reason": "There is no Docker object or Docker json-file log behind a native OCI-LXC application.", + "behavioral_impact": "Docker-only label consumers and Docker log-driver rotation do not exist in the native deployment.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-device-passthrough", + "upstream_behavior": "Compose passes host character devices or requests an NVIDIA runtime GPU.", + "native_lxc_behavior": "ProxMenux converts recognized device declarations to native Proxmox dev resources; NVIDIA profiles also inject compatible host driver libraries read-only.", + "reason": "Native OCI-LXC does not execute Docker device or NVIDIA runtime hooks.", + "behavioral_impact": "Hardware is exposed only after explicit user confirmation and host-path validation.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-host-ipc", + "upstream_behavior": "ipc: host shares the Docker host IPC namespace, commonly to avoid Docker's small default shared-memory allocation.", + "native_lxc_behavior": "The application keeps the LXC IPC namespace and receives a configurable 1 GiB /dev/shm instead of sharing Proxmox host IPC.", + "reason": "Processes in a single native LXC already share one IPC namespace; retaining isolation is safer than exposing host IPC.", + "behavioral_impact": "The application cannot exchange IPC objects with processes on the Proxmox host.", + "validation": "not-requested-by-compose" + }, + { + "id": "compose-watchtower", + "upstream_behavior": "The official Compose file offers a Watchtower sidecar to trigger Docker Engine image updates.", + "native_lxc_behavior": "The sidecar and Docker socket mount are omitted; ProxMenux performs native OCI image updates from Manage.", + "reason": "The native OCI deployment has no Docker daemon or Docker socket.", + "behavioral_impact": "Use Manage installed OCI applications to check and apply image updates.", + "validation": "native-equivalent" + }, + { + "id": "compose-dns", + "upstream_behavior": "The official Compose file can use public DNS resolvers for the application container.", + "native_lxc_behavior": "The OCI service uses the resolver configured in its dedicated LXC.", + "reason": "The native OCI process runs in the LXC network namespace.", + "behavioral_impact": "DNS follows the selected LXC network configuration.", + "validation": "native-equivalent" + }, + { + "id": "scrypted-optional-hardware-and-nvr", + "upstream_behavior": "The official Compose file documents optional camera storage, mDNS/Avahi, GPU and USB/serial device configuration.", + "native_lxc_behavior": "This single-image profile leaves optional storage and device access disabled; ProxMenux OCI Modify can add supported devices and mounts.", + "reason": "The minimal profile is intended to verify the core service without assuming the user has a camera, NVR volume or specific accelerator.", + "behavioral_impact": "Camera discovery, plugins, NVR recording, mDNS/Avahi and hardware acceleration require separate setup and are not checked by the initial health test.", + "validation": "not-configured-by-profile" + } + ] + }, + "compatibility": { + "automatic_install_candidate": true, + "validated": false, + "supported_compose_keys": [ + "cap_add", + "command", + "container_name", + "cpu_shares", + "deploy", + "devices", + "entrypoint", + "environment", + "extra_hosts", + "group_add", + "healthcheck", + "hostname", + "image", + "init", + "ipc", + "labels", + "logging", + "mac_address", + "mem_limit", + "network_mode", + "networks", + "ports", + "privileged", + "restart", + "runtime", + "security_opt", + "shm_size", + "stdin_open", + "stop_grace_period", + "sysctls", + "tty", + "ulimits", + "user", + "volumes", + "working_dir" + ], + "untranslated_blockers": [], + "policy": "Single-image definitions are installable when every declared Compose option has a native Proxmox translation. Multi-image and unsupported runtime features remain blocked until their orchestrator or mapping is available." + }, + "validation": { + "schema": "passed-at-generation", + "clean_install": "pending", + "service_health": "pending", + "restart_persistence": "pending", + "backup_restore": "pending", + "update_preserves_data": "pending" + }, + "lifecycle": { + "update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update", + "registry_state": { + "resolved_architecture": null, + "resolved_digest": null, + "image_version_label": null, + "image_created": null + }, + "change_detection": "compare-compose-sha256-and-resolved-latest-image-digest", + "automatic_unattended_updates": false + } +} diff --git a/oci/catalog/index.json b/oci/catalog/index.json index f9b1db94..440b59bc 100644 --- a/oci/catalog/index.json +++ b/oci/catalog/index.json @@ -10340,6 +10340,39 @@ "hidden": false, "template_id": "image-navidrome" }, + { + "id": "netalertx", + "provider": "NetAlertX", + "template_family": "curated-profile", + "title": "NetAlertX", + "repository": "https://github.com/netalertx/NetAlertX", + "description": "NetAlertX discovers devices on your local network and tracks its device inventory in a self-hosted web interface.", + "website": "https://netalertx.com/", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/netalertx.webp", + "architectures": [ + "amd64", + "arm64" + ], + "updated_at": null, + "main_image": "ghcr.io/netalertx/netalertx:latest", + "category": "monitoring", + "category_label": "Monitoring & Analytics", + "replaces_discovered_ids": [], + "curated_path": "catalog/curated/netalertx.json", + "template": "apps/netalertx.json", + "template_status": "generated-unvalidated", + "hidden": false, + "automatic_install_candidate": true, + "untranslated_blockers": [], + "requires_privileged_lxc": false, + "optional_privileged_lxc": false, + "requires_host_pid_namespace": false, + "requires_relaxed_confinement": false, + "optional_relaxed_confinement": false, + "requires_security_confirmation": false, + "multi_container": false, + "template_id": "image-netalertx" + }, { "id": "netbird", "source_app_id": "netbird", @@ -14413,6 +14446,38 @@ "hidden": false, "template_id": "image-scrutiny" }, + { + "id": "scrypted", + "provider": "official", + "template_family": "curated-profile", + "title": "Scrypted", + "repository": "https://github.com/koush/scrypted", + "description": "Manage cameras and integrate video devices with Apple HomeKit, Google Home, Alexa and Home Assistant from one self-hosted platform.", + "website": "https://scrypted.app/", + "icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/scrypted.webp", + "architectures": [ + "amd64", + "arm64" + ], + "updated_at": null, + "main_image": "ghcr.io/koush/scrypted:latest", + "category": "smarthome", + "category_label": "IoT & Smart Home", + "replaces_discovered_ids": [], + "curated_path": "catalog/curated/scrypted.json", + "template": "apps/scrypted.json", + "template_status": "generated-unvalidated", + "automatic_install_candidate": true, + "untranslated_blockers": [], + "requires_privileged_lxc": false, + "optional_privileged_lxc": false, + "requires_host_pid_namespace": false, + "requires_relaxed_confinement": false, + "optional_relaxed_confinement": false, + "requires_security_confirmation": false, + "hidden": false, + "template_id": "image-scrypted" + }, { "id": "scummvm", "provider": "linuxserver.io", diff --git a/scripts/global/common-functions.sh b/scripts/global/common-functions.sh index dd95e9bf..da7a058d 100644 --- a/scripts/global/common-functions.sh +++ b/scripts/global/common-functions.sh @@ -445,19 +445,68 @@ cleanup_duplicate_repos() { # an operator reads the log days later to find out what moved. # Reads `apt list --upgradable` on stdin and writes one aligned -# `name old → new` line per package. Lines that do not carry the +# `name old → new` line per package, never wider than the dialog that +# shows it (70 columns by default): a package whose versions do not fit +# continues on indented lines. Lines that do not carry the # "[upgradable from: …]" part are passed through as they came. pmx_format_upgradable() { - awk 'NF == 0 { next } + awk -v width="${1:-70}" 'NF == 0 { next } $0 ~ /\[upgradable from: / { name = $1; sub(/\/.*/, "", name) from = $6; sub(/\]$/, "", from) - printf " %-30s %s → %s\n", name, from, $2 + # Widths are counted by hand: " → " is three columns but more + # bytes, and mawk measures bytes. + if (2 + 30 + 1 + length(from) + 3 + length($2) <= width && length(name) <= 30) + printf " %-30s %s → %s\n", name, from, $2 + else if (6 + length(from) + 3 + length($2) <= width) + printf " %s\n %s → %s\n", name, from, $2 + else + printf " %s\n %s\n → %s\n", name, from, $2 next } { print " " $0 }' } +# Asks whether to go ahead with an update, with Yes focused so Enter +# confirms. whiptail cannot focus a button while its text scrolls, so the +# box takes the height of the terminal and a list that does not fit ends +# with a line counting the packages left out; the update log keeps them all. +# +# pmx_confirm_update TITLE TEXT LIST QUESTION +pmx_confirm_update() { + local title="$1" text="$2" list="$3" question="$4" + local rows height room shown more + rows=$(tput lines 2>/dev/null) + [[ "$rows" =~ ^[0-9]+$ ]] || rows=24 + height=$(( rows > 50 ? 48 : rows - 2 )) + (( height < 16 )) && height=16 + # Frame, padding, the heading, the question and the buttons. + room=$(( height - 9 - $(printf '%b' "$text" | grep -c '') )) + (( room < 3 )) && room=3 + + # A package that spans several lines is shown whole or counted. + shown=$(printf '%s\n' "$list" | awk -v room="$room" ' + /^ [^ ]/ { package++ } + { lines[NR] = $0; owner[NR] = package } + END { + if (NR <= room) { for (i = 1; i <= NR; i++) print lines[i]; exit } + # One line is kept for the count of the packages left out. + cap = room - 1 + last = owner[cap] + if (owner[cap + 1] == last) last-- + for (i = 1; i <= NR && owner[i] <= last; i++) print lines[i] + printf "\n%d\n", package - last + }') + more="${shown##*$'\n'}" + if [[ "$shown" == *$'\n\n'"$more" && "$more" =~ ^[0-9]+$ ]]; then + shown="${shown%$'\n\n'"$more"}" + shown+="\n … ${more} $(translate "more packages; the full list is in the update log")" + fi + + whiptail --title "$title" --yesno \ + "${text}$(translate "Packages to be upgraded"):\n${shown}\n\n${question}" "$height" 78 +} + # Writes what dpkg actually did since `$1` (a "YYYY-MM-DD HH:MM:SS" # stamp taken before the upgrade). dpkg's own log is the authoritative # record of the transaction — it carries both versions of every package diff --git a/scripts/global/update-pbs.sh b/scripts/global/update-pbs.sh index 80200ba7..ac77c810 100644 --- a/scripts/global/update-pbs.sh +++ b/scripts/global/update-pbs.sh @@ -110,10 +110,9 @@ update_pbs() { return 0 fi - menu_text+="$(translate "Packages to be upgraded"):\n$upgradable_list\n\n" - menu_text+="$(translate "Do you want to proceed with the system update?")" local proceed=0 - whiptail --title "$(translate "Proxmox Backup Server Update")" --scrolltext --yesno "$menu_text" 24 78 || proceed=1 + pmx_confirm_update "$(translate "Proxmox Backup Server Update")" "$menu_text" "$upgradable_list" \ + "$(translate "Do you want to proceed with the system update?")" || proceed=1 clear show_proxmenux_logo diff --git a/scripts/global/update-pve-safe.sh b/scripts/global/update-pve-safe.sh index c9056fdd..3896c177 100644 --- a/scripts/global/update-pve-safe.sh +++ b/scripts/global/update-pve-safe.sh @@ -205,12 +205,9 @@ update_pve_safe() { fi # The package list rides in the same dialog as the summary, so the - # decision is taken knowing what is about to be replaced. Keep Yes focused - # while the list remains scrollable; --keep-tite restores the execution screen. - menu_text+="$(translate "Packages to be upgraded"):\n$upgradable_list\n\n" - menu_text+="$(translate "Do you want to proceed with the system update?")" - if ! dialog --keep-tite --title "$(translate "Proxmox Update")" \ - --default-button yes --no-collapse --scrollbar --yesno "$menu_text" 24 78; then + # decision is taken knowing what is about to be replaced. + if ! pmx_confirm_update "$(translate "Proxmox Update")" "$menu_text" "$upgradable_list" \ + "$(translate "Do you want to proceed with the system update?")"; then msg_info2 "$(translate "Update cancelled by user")" apt-get -y autoremove >/dev/null 2>&1 || true apt-get -y autoclean >/dev/null 2>&1 || true diff --git a/scripts/global/update-pve8.sh b/scripts/global/update-pve8.sh index 01503294..df47bd98 100644 --- a/scripts/global/update-pve8.sh +++ b/scripts/global/update-pve8.sh @@ -169,12 +169,8 @@ EOF else # The package list rides in the same dialog as the summary, so # the decision is taken knowing what is about to be replaced. - # Keep Yes focused while the full package list remains scrollable. - # --keep-tite restores the execution screen after confirmation. - menu_text+="$(translate "Packages to be upgraded"):\n$upgradable_list\n\n" - menu_text+="$(translate "Do you want to proceed with the system update?")" - if dialog --keep-tite --title "$(translate "Proxmox Update")" \ - --default-button yes --no-collapse --scrollbar --yesno "$menu_text" 24 78; then + if pmx_confirm_update "$(translate "Proxmox Update")" "$menu_text" "$upgradable_list" \ + "$(translate "Do you want to proceed with the system update?")"; then return 0 else return 1 diff --git a/scripts/global/update-pve9_2.sh b/scripts/global/update-pve9_2.sh index 6f01caff..2cd3e21f 100644 --- a/scripts/global/update-pve9_2.sh +++ b/scripts/global/update-pve9_2.sh @@ -272,12 +272,8 @@ EOF else # The package list rides in the same dialog as the summary, so # the decision is taken knowing what is about to be replaced. - # Keep Yes focused while the full package list remains scrollable. - # --keep-tite restores the execution screen after confirmation. - menu_text+="$(translate "Packages to be upgraded"):\n$upgradable_list\n\n" - menu_text+="$(translate "Do you want to proceed with the system update?")" - if dialog --keep-tite --title "$(translate "Proxmox Update")" \ - --default-button yes --no-collapse --scrollbar --yesno "$menu_text" 24 78; then + if pmx_confirm_update "$(translate "Proxmox Update")" "$menu_text" "$upgradable_list" \ + "$(translate "Do you want to proceed with the system update?")"; then return 0 else return 1 diff --git a/tests/test_update_confirmation.py b/tests/test_update_confirmation.py index 36281fd4..0acb3364 100644 --- a/tests/test_update_confirmation.py +++ b/tests/test_update_confirmation.py @@ -1,7 +1,7 @@ """Exercise only the update confirmation widget in a disposable terminal. -No update script is sourced or executed: the isolated UI command receives -synthetic package names. Requires dialog; skips on systems without it. +No update script is sourced or executed: the shared confirmation function +receives synthetic package names. Requires whiptail; skips without it. """ import errno @@ -27,17 +27,23 @@ UPDATERS = ( ) -def confirmation_command(name): - source = (ROOT / "scripts/global" / name).read_text() - match = re.search( - r'^\s*if (?:! )?((?:dialog|whiptail) --(?:keep-tite --)?title ' - r'"\$\(translate "Proxmox Update"\)"[^;]+); then', - source, - re.MULTILINE, - ) +COMMON = ROOT / "scripts/global/common-functions.sh" + + +def confirmation_function(): + match = re.search(r"^pmx_confirm_update\(\) \{\n.*?^\}\n", COMMON.read_text(), + re.MULTILINE | re.DOTALL) if not match: - raise AssertionError("Update confirmation not found: " + name) - return match.group(1) + raise AssertionError("pmx_confirm_update not found") + return match.group(0) + + +def confirmation_command(): + """The shared confirmation as an updater calls it; the package list comes + from UPDATE_CONFIRMATION_TEXT, formatted as pmx_format_upgradable writes it.""" + return (confirmation_function() + 'pmx_confirm_update "Proxmox Update" ' + '"System Update Information\\n\\n" "$UPDATE_CONFIRMATION_TEXT" ' + '"Do you want to proceed with the system update?"') def run_widget(command, text, keys): @@ -50,8 +56,7 @@ def run_widget(command, text, keys): for key in list(environment): if key.startswith("DIALOG_"): environment.pop(key) - script = ('translate() { printf "%s" "$1"; }\n' - 'menu_text=$UPDATE_CONFIRMATION_TEXT\n' + command) + script = 'translate() { printf "%s" "$1"; }\n' + command os.execve("/bin/bash", ["bash", "-c", script], environment) output = bytearray() @@ -90,36 +95,47 @@ def run_widget(command, text, keys): os.close(terminal) -@unittest.skipUnless(shutil.which("dialog"), "dialog is not installed") +def packages(count, long_every=0): + """Formatted list lines; every `long_every`-th package spans three lines.""" + lines = [] + for i in range(count): + if long_every and i % long_every == 0: + lines += [" libsamba-{:03d}".format(i), " 2:2.11.0+samba4.22.10+dfsg-0+deb13u1", + " → 2:2.11.0+samba4.22.11+dfsg-0+deb13u1"] + else: + lines.append(" package-{:03d} 1.0 → 2.0".format(i)) + return "\n".join(lines) + + +@unittest.skipUnless(shutil.which("whiptail"), "whiptail is not installed") class UpdateConfirmationTest(unittest.TestCase): def test_enter_confirms_yes_without_tab_for_short_and_long_lists(self): - for updater in UPDATERS: - for count in (1, 80): - with self.subTest(updater=updater, packages=count): - text = "System Update Information\\n\\n" + "\\n".join( - " package-{:02d} 1.0 -> 2.0".format(i) - for i in range(count) - ) + "\\n\\nDo you want to proceed with the system update?" - status, output = run_widget(confirmation_command(updater), text, b"\r") - self.assertEqual(status, 0, output) + for count in (1, 80, 200): + with self.subTest(packages=count): + status, output = run_widget(confirmation_command(), packages(count, 7), b"\r") + self.assertEqual(status, 0, output) def test_tab_can_select_no_and_escape_can_cancel(self): - for updater in UPDATERS: - for keys, expected in ((b"\t\r", 1), (b"\x1b", 255)): - with self.subTest(updater=updater, keys=keys): - status, output = run_widget(confirmation_command(updater), "Confirm update?", keys) - self.assertEqual(status, expected, output) + for keys, expected in ((b"\t\r", 1), (b"\x1b", 255)): + with self.subTest(keys=keys): + status, output = run_widget(confirmation_command(), packages(80), keys) + self.assertEqual(status, expected, output) - def test_full_list_remains_scrollable_with_yes_focused(self): - text = "\\n".join("package-{:03d}".format(i) for i in range(80)) - for updater in UPDATERS: + def test_a_long_list_fits_and_counts_the_packages_left_out(self): + status, output = run_widget(confirmation_command(), packages(200, 7), b"\r") + self.assertEqual(status, 0, output) + plain = re.sub(rb"\x1b(?:\[[0-?]*[ -/]*[@-~]|[()][A-Za-z0-9]|.)", b"", output) + self.assertIn(b"more packages; the full list is in the update log", plain) + # A package is shown whole or counted, never cut after its name. + self.assertNotRegex(plain, rb"libsamba-\d{3}\s+\.\.\.") + + def test_every_updater_asks_through_the_shared_confirmation(self): + for updater in UPDATERS + ("update-pbs.sh",): with self.subTest(updater=updater): - # G scrolls to the end of the yes/no text without moving focus. - status, output = run_widget(confirmation_command(updater), text, b"G\r") - self.assertEqual(status, 0, output) - # Curses may repaint only changed digits rather than emit a - # complete final line. Its 100% indicator confirms the end. - self.assertIn(b"100%", output) + source = (ROOT / "scripts/global" / updater).read_text() + self.assertIn("pmx_confirm_update ", source) + self.assertNotIn("--scrolltext --yesno", source) + self.assertNotIn("dialog --keep-tite", source) if __name__ == "__main__":