Merge develop into feature/oci-artcraft-suite

Resolve the catalog conflicts and regenerate VALIDATION.md.
This commit is contained in:
MacRimi
2026-11-03 18:43:52 +01:00
48 changed files with 8612 additions and 37 deletions
+5
View File
@@ -25,6 +25,11 @@ on:
permissions:
contents: read
# A newer push to the same branch or pull request replaces the run in progress.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
+5
View File
@@ -112,6 +112,11 @@ on:
permissions:
contents: read
# A newer push to the same branch or pull request replaces the run in progress.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
apprise-services:
runs-on: ubuntu-latest
+5
View File
@@ -51,6 +51,11 @@ on:
permissions:
contents: read
# A newer push to the same branch or pull request replaces the run in progress.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# Preserve this check ID for existing branch protection rules. This suite
# includes menu, download and scheduler regressions as well as i18n tests.
@@ -57,6 +57,11 @@ on:
permissions:
contents: read
# A newer push to the same branch or pull request replaces the run in progress.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
offline-python:
runs-on: ubuntu-latest
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
a251d5fd47ab52ac5709aae67dae426f22165466fdb19342be6cfd4e7ad0a305 ProxMenux-1.2.8.AppImage
100ea32cfbbf9dadd72754cebf17f39def33002bd562d38952cf2dc9151f6fad ProxMenux-1.2.8.AppImage
+14
View File
@@ -256,6 +256,7 @@
"Acceleration for llama.cpp": "Beschleunigung für llama.cpp",
"Accept routes from other nodes?": "Routen von anderen Knoten akzeptieren?",
"Accept this host monitoring profile?": "Akzeptieren Sie dieses Host Monitoring Profil?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Zugriff auf die Movistar- und O2-Cloud über WebDAV-Clients und Backup-Werkzeuge.",
"Access Scope:": "Zugriffsbereich:",
"Access bridge": "Zugangsbrücke",
"Access bridge for Immich": "Access Bridge für Immich",
@@ -410,6 +411,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Nach dem Neustart können Sie nur noch auf den Proxmox-Host zugreifen über:",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Starten Sie nach diesem Wechsel von LXC → VM den Host neu, damit der neue Bindungsstatus sauber angewendet wird.",
"Agentless monitoring and observability platform.": "Agentenlose Plattform für Monitoring und Observability.",
"AirDC++ administrator": "AirDC++-Administrator",
"Airsonic Advanced web interface": "Airsonic Advanced Web-Schnittstelle",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced ist ein kostenloser, webbasierter Medienstreamer, der ubiquitious Zugang zu Ihrer Musik bietet. Verwenden Sie es, um Ihre Musik mit Freunden zu teilen oder Ihre eigene Musik während der Arbeit zu hören. Sie können zu mehreren Spielern gleichzeitig streamen, zum Beispiel zu einem Spieler in Ihrer Küche und einem anderen in Ihrem Wohnzimmer.",
"Aliases added to .bashrc": "Aliase wurden zu .bashrc hinzugefügt",
@@ -603,6 +605,9 @@
"Audit socket disabled or not required": "Audit-Socket deaktiviert oder nicht erforderlich",
"Auth key is required.": "Authentifizierungsschlüssel ist erforderlich.",
"Auth:": "Authentifizierung:",
"Authelia Web UI": "Authelia Web-Benutzeroberfläche",
"Authelia administrator": "Authelia-Administrator",
"Authelia session cookie domain for this portal (for example example.com)": "Authelia Sitzungscookie-Domäne für dieses Portal (z. B. example.com)",
"Authentication": "Authentifizierung",
"Authentication & Security": "Authentifizierung und Sicherheit",
"Authentication Error": "Authentifizierungsfehler",
@@ -1927,6 +1932,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Unterschied: aktuelles System vs. Backup (--- System +++ Backup)",
"Different host. Backup from:": "Anderer Gastgeber. Backup von:",
"Direct Connect file sharing with a web interface": "Direct-Connect-Dateifreigabe mit Weboberfläche",
"Direct conversion completed for container": "Direkte Konvertierung für Container abgeschlossen",
"Directory Exists": "Verzeichnis existiert",
"Directory Not Empty": "Verzeichnis nicht leer",
@@ -4897,6 +4903,7 @@
"Open the page of an application in the menu": "Die Seite einer Anwendung im Menü öffnen",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Öffne die Webadresse und erstelle das erste Konto: Der Daemon registriert sich selbst und startet den ersten Scan.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "Öffnen Sie die Webadresse und erstellen Sie im ersten Setup das Passwort des akadmin-Benutzers.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Öffnen Sie das Web-Dashboard und verknüpfen Sie Ihr Movistar/O2-Konto über die SMS-Bestätigung.",
"Open-source AI-powered coding assistant": "Open-Source AI-powered Coding Assistant",
"Open-source UI for building and debugging multi-agent and RAG applications": "Open-Source-Benutzeroberfläche zum Erstellen und Debuggen von Multi-Agenten- und RAG-Anwendungen",
"Open-source in-memory data store.": "Open-Source-Datenspeicher im Arbeitsspeicher.",
@@ -5396,6 +5403,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Proxmox-Weboberfläche: Datencenter > Speicher > Hinzufügen > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Proxmox-Weboberfläche: Rechenzentrum > Speicher > Hinzufügen > iSCSI",
"Proxy address to reach the API (optional)": "Proxy-Adresse zum Erreichen der API (optional)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "Öffentliche HTTPS-URL, die zum Öffnen dieses Authelia-Portals verwendet wird (zum Beispiel https://auth.example.com)",
"Public UDP port clients connect to": "Öffentliche UDP Port Clients verbinden sich mit",
"Public UDP port peers connect to": "Öffentliche UDP-Port Peers verbinden sich mit",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "Öffentliche URL von phpMyAdmin, wenn sie hinter einem Reverse-Proxy bereitgestellt wird",
@@ -6453,6 +6461,7 @@
"Simple and easy to use DDNS": "Einfach und einfach zu bedienen DDNS",
"Simple self-hosted file sharing.": "Einfacher, selbst gehosteter Dateiaustausch.",
"Single GPU Warning": "Warnung vor einer einzelnen GPU",
"Single sign-on and multi-factor authentication": "Single Sign-On und Multi-Faktor-Authentifizierung",
"Single target found — selected automatically:": "Einzelnes Ziel gefunden – automatisch ausgewählt:",
"Single-node log storage and query UI.": "Logspeicher mit Abfrageoberfläche für einen einzelnen Knoten.",
"Single-node observability database for metrics, logs and traces.": "Einzelknoten-Datenbank für Metriken, Protokolle und Traces.",
@@ -8202,12 +8211,17 @@
"Web access": "Webzugang",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webadresse des AdGuard Home, der die Einstellungen erhält (z. B. http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webadresse des AdGuard Home, dessen Einstellungen kopiert sind (z. B. http://192.168.1.2)",
"Web dashboard": "Web-Dashboard",
"Web dashboard and WebDAV": "Web-Dashboard und WebDAV",
"Web interface": "Weboberfläche",
"Web interface for Borg Backup repositories and jobs.": "Weboberfläche für Borg-Backup-Repositories und -Jobs.",
"Web interface for managing 3D printers.": "Weboberfläche zur Verwaltung von 3D-Druckern.",
"Web interface to manage devices running Tasmota firmware.": "Web-Schnittstelle zur Verwaltung von Geräten mit Tasmota-Firmware.",
"Web reverse proxy with an administration interface.": "Web-Reverse-Proxy mit Administrationsoberfläche.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord kann als ein Paket von Sicherheits- und Datenschutz-Härtungen, Discord-Features-Reimplementierungen, Electron / Chromium / Discord-Bugs-Workarounds, Stylesheets, interne Seiten und verpackte https://discord.com-Seite zusammengefasst werden, die so weit wie möglich mit ToS konform sind (oder die Änderungen, die es verletzen könnten, vor Discords Augen verbergen).",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Passwort für WebDAV und Dashboard (leer = generiert)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV ist auf Port 8080 des Containers verfügbar. Vor Dateioperationen in der Cloud muss das Konto verknüpft werden.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus ist ein multi-site inkrementeller xmltv epg grabber. Es sammelt tv-programm-guide-daten von ausgewählten tvguide-seiten für ihre lieblingskanäle.",
"Webservers & Proxies": "Webserver und Proxys",
"Website": "Webseite",
+14
View File
@@ -255,6 +255,7 @@
"Acceleration for llama.cpp": "Aceleración para llama.cpp",
"Accept routes from other nodes?": "¿Aceptar rutas de otros nodos?",
"Accept this host monitoring profile?": "¿Aceptar este perfil de monitorización del host?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Accede a la nube de Movistar y O2 desde clientes WebDAV y herramientas de backup.",
"Access Scope:": "Ámbito de acceso:",
"Access bridge": "Puente de acceso",
"Access bridge for Immich": "Puente de acceso para Immich",
@@ -408,6 +409,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Después del reinicio, solo podrá acceder al host Proxmox a través de:",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Después de este cambio LXC → VM, reinicia el host para que el nuevo estado de enlace se aplique limpiamente.",
"Agentless monitoring and observability platform.": "Plataforma de monitorización y observabilidad sin agentes.",
"AirDC++ administrator": "Administrador de AirDC++",
"Airsonic Advanced web interface": "Interfaz web de Airsonic Advanced",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced es un servidor de streaming multimedia gratuito y basado en web que da acceso a tu música desde cualquier lugar. Úsalo para compartir tu música con amigos o para escuchar la tuya en el trabajo. Puede transmitir a varios reproductores a la vez, por ejemplo a uno en la cocina y a otro en el salón.",
"Aliases added to .bashrc": "Alias añadidos a .bashrc",
@@ -601,6 +603,9 @@
"Audit socket disabled or not required": "Socket de auditoría deshabilitado o no requerido",
"Auth key is required.": "Se requiere clave de autenticación.",
"Auth:": "Autenticación:",
"Authelia Web UI": "Authelia Interfaz de usuario web",
"Authelia administrator": "administrador Authelia",
"Authelia session cookie domain for this portal (for example example.com)": "dominio de cookie de sesión Authelia para este portal (por ejemplo, ejemplo.com)",
"Authentication": "Autenticación",
"Authentication & Security": "Authentication & Security",
"Authentication Error": "Error de autenticación",
@@ -1925,6 +1930,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Diferencias: sistema actual frente al backup (--- sistema +++ backup)",
"Different host. Backup from:": "Host diferente. Backup procedente de:",
"Direct Connect file sharing with a web interface": "Intercambio de archivos Direct Connect con interfaz web",
"Direct conversion completed for container": "Conversión directa completada para el contenedor",
"Directory Exists": "El directorio existe",
"Directory Not Empty": "Directorio no vacío",
@@ -4888,6 +4894,7 @@
"Open the page of an application in the menu": "Abrir la página de una aplicación en el menú",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Abre la dirección web y crea la primera cuenta: el daemon se registra solo y empieza el primer escaneo.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "abra la dirección web y cree la contraseña del usuario akaadmin en la configuración de primera ejecución.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Abre el panel web y vincula tu cuenta de Movistar/O2 con su verificación por SMS.",
"Open-source AI-powered coding assistant": "Asistente de programación con IA de código abierto",
"Open-source UI for building and debugging multi-agent and RAG applications": "Interfaz de código abierto para crear y depurar aplicaciones multiagente y RAG",
"Open-source in-memory data store.": "Almacén de datos en memoria de código abierto.",
@@ -5386,6 +5393,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interfaz web de Proxmox: Datacenter > Storage > Add > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interfaz web de Proxmox: Datacenter > Storage > Add > iSCSI",
"Proxy address to reach the API (optional)": "Dirección del proxy para llegar a la API (opcional)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS pública utilizada para abrir este portal Authelia (por ejemplo, https://auth.example.com)",
"Public UDP port clients connect to": "Puerto UDP público al que se conectan los clientes",
"Public UDP port peers connect to": "Puerto UDP público al que se conectan los peers",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL pública de phpMyAdmin cuando se sirve detrás de un proxy inverso",
@@ -6436,6 +6444,7 @@
"Simple and easy to use DDNS": "DDNS simple y fácil de usar",
"Simple self-hosted file sharing.": "Servicio sencillo y autoalojado para compartir archivos.",
"Single GPU Warning": "Advertencia de GPU única",
"Single sign-on and multi-factor authentication": "inicio de sesión único y autenticación multifactor",
"Single target found — selected automatically:": "Destino único encontrado: seleccionado automáticamente:",
"Single-node log storage and query UI.": "Almacenamiento de logs para un solo nodo con interfaz de consulta.",
"Single-node observability database for metrics, logs and traces.": "Base de datos de observabilidad de un solo nodo para métricas, registros y trazas.",
@@ -8175,12 +8184,17 @@
"Web access": "Acceso web",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Dirección web del AdGuard Home que recibe los ajustes (por ejemplo, http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Dirección web del AdGuard Home principal, cuyos ajustes se copian (por ejemplo, http://192.168.1.2)",
"Web dashboard": "Panel web",
"Web dashboard and WebDAV": "Panel web y WebDAV",
"Web interface": "interfaz web",
"Web interface for Borg Backup repositories and jobs.": "Interfaz web para gestionar repositorios y tareas de Borg Backup.",
"Web interface for managing 3D printers.": "Interfaz web para gestionar impresoras 3D.",
"Web interface to manage devices running Tasmota firmware.": "Interfaz web para gestionar dispositivos con firmware Tasmota.",
"Web reverse proxy with an administration interface.": "Proxy inverso web con interfaz de administración.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord puede resumirse como un conjunto de medidas de hardening de seguridad y privacidad, reimplementaciones de funciones de Discord, soluciones a fallos de Electron / Chromium / Discord, hojas de estilo, páginas internas y la página https://discord.com encapsulada, diseñado para cumplir los ToS en lo posible (u ocultar a Discord los cambios que podrían incumplirlos).",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Contraseña de WebDAV y del panel (vacío = generada)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV está disponible en el puerto 8080 del contenedor. Hay que vincular la cuenta antes de trabajar con los archivos de la nube.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus es un capturador incremental de EPG xmltv para múltiples sitios. Recoge los datos de la guía de programación de los sitios de guías de TV seleccionados para tus canales favoritos.",
"Webservers & Proxies": "Webservers & Proxies",
"Website": "Sitio web",
+14
View File
@@ -256,6 +256,7 @@
"Acceleration for llama.cpp": "Accélération pour llama.cpp",
"Accept routes from other nodes?": "Accepter les routes d'autres nœuds ?",
"Accept this host monitoring profile?": "Accepter ce profil de surveillance de l'hôte?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Accédez au cloud Movistar et O2 depuis des clients WebDAV et des outils de sauvegarde.",
"Access Scope:": "Portée d'accès :",
"Access bridge": "Pont d'accès",
"Access bridge for Immich": "Pont d'accès pour Immich",
@@ -410,6 +411,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Après le redémarrage, vous ne pourrez accéder à l'hôte Proxmox que via :",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Après ce commutateur LXC → VM, redémarrez l'hôte afin que le nouvel état de liaison soit appliqué proprement.",
"Agentless monitoring and observability platform.": "Plateforme de supervision et d’observabilité sans agent.",
"AirDC++ administrator": "Administrateur AirDC++",
"Airsonic Advanced web interface": "Interface web Airsonic Advanced",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-Advanced est un flux multimédia gratuit, basé sur le web, offrant un accès ubiquitious à votre musique. Utilisez-le pour partager votre musique avec des amis, ou pour écouter votre propre musique au travail. Vous pouvez diffuser simultanément vers plusieurs joueurs, par exemple vers un joueur dans votre cuisine et un autre dans votre salon.",
"Aliases added to .bashrc": "Alias ajoutés à .bashrc",
@@ -603,6 +605,9 @@
"Audit socket disabled or not required": "Socket d'audit désactivé ou non requis",
"Auth key is required.": "La clé d'authentification est requise.",
"Auth:": "Authentification :",
"Authelia Web UI": "interface utilisateur Web Authelia",
"Authelia administrator": "Administrateur Authelia",
"Authelia session cookie domain for this portal (for example example.com)": "domaine de cookie de session Authelia pour ce portail (par exemple exemple.com)",
"Authentication": "Authentification",
"Authentication & Security": "Authentification et sécurité",
"Authentication Error": "Erreur d'authentification",
@@ -1927,6 +1932,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Diff : système actuel vs sauvegarde (--- système +++ sauvegarde)",
"Different host. Backup from:": "Hôte différent. Sauvegarde depuis :",
"Direct Connect file sharing with a web interface": "Partage de fichiers Direct Connect avec une interface web",
"Direct conversion completed for container": "Conversion directe terminée pour le conteneur",
"Directory Exists": "Le répertoire existe",
"Directory Not Empty": "Répertoire non vide",
@@ -4897,6 +4903,7 @@
"Open the page of an application in the menu": "Ouvrir la page d'une application dans le menu",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Ouvrez l'adresse web et créez le premier compte : le daemon s'enregistre tout seul et lance la première analyse.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "ouvrez l'adresse Web et créez le mot de passe de l'utilisateur akadmin lors de la configuration de première exécution.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Ouvrez le tableau de bord web et liez votre compte Movistar/O2 avec sa vérification par SMS.",
"Open-source AI-powered coding assistant": "Assistant de codage à source ouverte",
"Open-source UI for building and debugging multi-agent and RAG applications": "UI open-source pour la construction et le débogage d'applications multi-agents et RAG",
"Open-source in-memory data store.": "Magasin de données en mémoire libre.",
@@ -5396,6 +5403,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interface web Proxmox : Datacenter > Stockage > Ajouter > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interface Web Proxmox : Datacenter > Stockage > Ajouter > iSCSI",
"Proxy address to reach the API (optional)": "Adresse proxy pour accéder à l'API (facultatif)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS publique utilisée pour ouvrir ce portail Authelia (par exemple https://auth.example.com)",
"Public UDP port clients connect to": "Les clients du port UDP public se connectent à",
"Public UDP port peers connect to": "Les pairs du port UDP public se connectent à",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL publique de phpMyAdmin quand elle est servie derrière un proxy inversé",
@@ -6453,6 +6461,7 @@
"Simple and easy to use DDNS": "Simple et facile à utiliser DDNS",
"Simple self-hosted file sharing.": "Service simple et auto-hébergé de partage de fichiers.",
"Single GPU Warning": "Avertissement sur un seul GPU",
"Single sign-on and multi-factor authentication": "authentification unique et authentification multifacteur",
"Single target found — selected automatically:": "Cible unique trouvée — sélectionnée automatiquement :",
"Single-node log storage and query UI.": "Stockage de journaux sur un nœud unique avec interface de requête.",
"Single-node observability database for metrics, logs and traces.": "Base de données d’observabilité à nœud unique pour les métriques, les journaux et les traces.",
@@ -8201,12 +8210,17 @@
"Web access": "Accès au Web",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Adresse Web du AdGuard Home qui reçoit les paramètres (p. ex. http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Adresse Web du principal AdGuard Home, dont les paramètres sont copiés (par exemple http://192.168.1.2)",
"Web dashboard": "Tableau de bord web",
"Web dashboard and WebDAV": "Tableau de bord web et WebDAV",
"Web interface": "interface Web",
"Web interface for Borg Backup repositories and jobs.": "Interface web pour les dépôts et tâches Borg Backup.",
"Web interface for managing 3D printers.": "Interface web de gestion des imprimantes 3D.",
"Web interface to manage devices running Tasmota firmware.": "Interface Web pour gérer les appareils utilisant le firmware Tasmota.",
"Web reverse proxy with an administration interface.": "Proxy inverse web doté d’une interface d’administration.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord peut être résumé comme un paquet de durcissements de sécurité et de confidentialité, les fonctionnalités Discord réimplémentations, Electron / Chromium / Discord bugs workarounds, les feuilles de style, les pages internes et la page https://discord.com enveloppée, conçue pour se conformer à ToS autant qu'il est possible (ou cacher les changements qui pourraient la violer des yeux de Discord).",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Mot de passe WebDAV et du tableau de bord (vide = généré)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV est disponible sur le port 8080 du conteneur. Le compte doit être lié avant toute opération sur les fichiers du cloud.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus est un accrocheur différentiel multisite xmltv egg. Il recueille les données du guide TV-programme à partir de sites sélectionnés pour vos chaînes préférées.",
"Webservers & Proxies": "Serveurs web et proxys",
"Website": "Site web",
+14
View File
@@ -256,6 +256,7 @@
"Acceleration for llama.cpp": "Accelerazione per llama.cpp",
"Accept routes from other nodes?": "Accettare rotte da altri nodi?",
"Accept this host monitoring profile?": "Accettare questo profilo di monitoraggio host?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Accedi al cloud di Movistar e O2 da client WebDAV e strumenti di backup.",
"Access Scope:": "Ambito di accesso:",
"Access bridge": "Bridge di accesso",
"Access bridge for Immich": "Bridge di accesso per Immich",
@@ -410,6 +411,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Dopo il riavvio, potrai accedere all'host Proxmox solo tramite:",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Dopo questo passaggio LXC → VM, riavviare l’host in modo che il nuovo stato di associazione venga applicato correttamente.",
"Agentless monitoring and observability platform.": "Piattaforma di monitoraggio e osservabilità senza agent.",
"AirDC++ administrator": "Amministratore AirDC++",
"Airsonic Advanced web interface": "Interfaccia web Airsonic Advanced",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced è un servizio gratuito di streaming multimediale via web che permette di accedere alla propria musica ovunque. Puoi condividerla con gli amici o ascoltarla mentre lavori. Puoi riprodurla contemporaneamente su più dispositivi, per esempio in cucina e in soggiorno.",
"Aliases added to .bashrc": "Alias aggiunti a .bashrc",
@@ -603,6 +605,9 @@
"Audit socket disabled or not required": "Socket di audit disabilitato o non richiesto",
"Auth key is required.": "È richiesta la chiave di autenticazione.",
"Auth:": "Autenticazione:",
"Authelia Web UI": "interfaccia utente Web Authelia",
"Authelia administrator": "amministratore Authelia",
"Authelia session cookie domain for this portal (for example example.com)": "dominio del cookie di sessione Authelia per questo portale (ad esempio example.com)",
"Authentication": "Autenticazione",
"Authentication & Security": "Autenticazione e sicurezza",
"Authentication Error": "Errore di autenticazione",
@@ -1927,6 +1932,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Differenza: sistema attuale vs backup (--- sistema +++ backup)",
"Different host. Backup from:": "Host diverso. Backup da:",
"Direct Connect file sharing with a web interface": "Condivisione di file Direct Connect con interfaccia web",
"Direct conversion completed for container": "Conversione diretta completata per il container",
"Directory Exists": "Directory esistente",
"Directory Not Empty": "Directory non vuota",
@@ -4897,6 +4903,7 @@
"Open the page of an application in the menu": "Apri la pagina di un'applicazione nel menu",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Apri l'indirizzo web e crea il primo account: il daemon si registra da solo e avvia la prima scansione.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "Apri l'indirizzo web e imposta la password dell'utente akadmin durante la configurazione iniziale.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Apri la dashboard web e collega il tuo account Movistar/O2 con la verifica tramite SMS.",
"Open-source AI-powered coding assistant": "Assistente open source per la programmazione basato sull'IA",
"Open-source UI for building and debugging multi-agent and RAG applications": "Interfaccia open source per creare ed eseguire il debug di applicazioni multiagente e RAG",
"Open-source in-memory data store.": "Archivio dati in memoria open source.",
@@ -5396,6 +5403,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interfaccia web Proxmox: Datacenter > Storage > Aggiungi > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interfaccia web Proxmox: Datacenter > Storage > Aggiungi > iSCSI",
"Proxy address to reach the API (optional)": "Indirizzo proxy per raggiungere l'API (facoltativo)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS pubblico utilizzato per aprire questo portale Authelia (ad esempio https://auth.example.com)",
"Public UDP port clients connect to": "Porta UDP pubblica a cui si connettono i client",
"Public UDP port peers connect to": "Porta UDP pubblica a cui si connettono i peer",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL pubblico di phpMyAdmin quando viene servito dietro un proxy inverso",
@@ -6453,6 +6461,7 @@
"Simple and easy to use DDNS": "DDNS semplice e facile da usare",
"Simple self-hosted file sharing.": "Servizio semplice e self-hosted per condividere file.",
"Single GPU Warning": "Avviso GPU singola",
"Single sign-on and multi-factor authentication": "Single Sign-On e autenticazione a più fattori",
"Single target found — selected automatically:": "Unica destinazione trovata - selezionata automaticamente:",
"Single-node log storage and query UI.": "Archiviazione dei log su un singolo nodo e interfaccia di interrogazione.",
"Single-node observability database for metrics, logs and traces.": "Database di osservabilità a nodo singolo per metriche, log e tracce.",
@@ -8202,12 +8211,17 @@
"Web access": "Accesso web",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Indirizzo web dell'istanza AdGuard Home che riceve le impostazioni (ad es. http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Indirizzo web dell'istanza AdGuard Home principale, da cui vengono copiate le impostazioni (ad es. http://192.168.1.2)",
"Web dashboard": "Dashboard web",
"Web dashboard and WebDAV": "Dashboard web e WebDAV",
"Web interface": "Interfaccia web",
"Web interface for Borg Backup repositories and jobs.": "Interfaccia web per repository e job di Borg Backup.",
"Web interface for managing 3D printers.": "Interfaccia web per gestire stampanti 3D.",
"Web interface to manage devices running Tasmota firmware.": "Interfaccia web per gestire dispositivi con firmware Tasmota.",
"Web reverse proxy with an administration interface.": "Proxy inverso web con interfaccia di amministrazione.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord comprende miglioramenti alla sicurezza e alla privacy, reimplementazioni di funzioni di Discord, soluzioni alternative ai bug di Electron, Chromium e Discord, fogli di stile, pagine interne e la pagina https://discord.com incapsulata. È progettato per rispettare i termini di servizio per quanto possibile (o nascondere a Discord le modifiche che potrebbero violarli).",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Password di WebDAV e della dashboard (campo vuoto = generata)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV è disponibile sulla porta 8080 del container. Prima di operare sui file del cloud bisogna collegare l'account.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus acquisisce dati EPG XMLTV incrementalmente da più siti. Raccoglie le guide TV dai siti selezionati per i canali preferiti.",
"Webservers & Proxies": "Server web e proxy",
"Website": "Sito web",
+14
View File
@@ -256,6 +256,7 @@
"Acceleration for llama.cpp": "Aceleração para llama.cpp",
"Accept routes from other nodes?": "Aceita rotas de outros nós?",
"Accept this host monitoring profile?": "Aceita este perfil de monitorização da máquina?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Acesse a nuvem da Movistar e da O2 a partir de clientes WebDAV e ferramentas de backup.",
"Access Scope:": "Escopo de acesso:",
"Access bridge": "Ponte de acesso",
"Access bridge for Immich": "Ponte de acesso para Immich",
@@ -410,6 +411,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Após a reinicialização, você só poderá acessar o host Proxmox via:",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Após esta opção LXC → VM, reinicialize o host para que o novo estado de ligação seja aplicado de forma limpa.",
"Agentless monitoring and observability platform.": "Plataforma de monitoramento e observabilidade sem agentes.",
"AirDC++ administrator": "Administrador do AirDC++",
"Airsonic Advanced web interface": "Interface Web Airsonic Advanced",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-avanced é uma transmissão de mídia gratuita, baseada na web, proporcionando ubiquitious acesso à sua música. Use-o para compartilhar sua música com amigos, ou para ouvir sua própria música durante o trabalho. Você pode transmitir para vários jogadores simultaneamente, por exemplo para um jogador na sua cozinha e outro na sua sala de estar.",
"Aliases added to .bashrc": "Aliases adicionados a .bashrc",
@@ -603,6 +605,9 @@
"Audit socket disabled or not required": "soquete de auditoria desabilitado ou não necessário",
"Auth key is required.": "A chave de autenticação é obrigatória.",
"Auth:": "Autenticação:",
"Authelia Web UI": "UI da Web Authelia",
"Authelia administrator": "administrador Authelia",
"Authelia session cookie domain for this portal (for example example.com)": "domínio de cookie de sessão Authelia para este portal (por exemplo example.com)",
"Authentication": "Autenticação",
"Authentication & Security": "Autenticação e segurança",
"Authentication Error": "Erro de autenticação",
@@ -1927,6 +1932,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Diferença: sistema atual vs backup (--- backup do sistema +++)",
"Different host. Backup from:": "Anfitrião diferente. Backup de:",
"Direct Connect file sharing with a web interface": "Partilha de ficheiros Direct Connect com interface web",
"Direct conversion completed for container": "Conversão direta concluída para contêiner",
"Directory Exists": "O diretório existe",
"Directory Not Empty": "Diretório não vazio",
@@ -4897,6 +4903,7 @@
"Open the page of an application in the menu": "Abrir a página de uma aplicação no menu",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Abra o endereço web e crie a primeira conta: o daemon regista-se sozinho e inicia a primeira análise.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "Abra o endereço da web e crie a senha do usuário akadmin na configuração inicial.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Abra o painel web e vincule sua conta Movistar/O2 com a verificação por SMS.",
"Open-source AI-powered coding assistant": "Assistente de codificação com código aberto",
"Open-source UI for building and debugging multi-agent and RAG applications": "UI de código aberto para a construção e depuração de aplicações multi-agente e RAG",
"Open-source in-memory data store.": "Armazenamento de dados em memória de código aberto.",
@@ -5396,6 +5403,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Interface web Proxmox: Datacenter > Armazenamento > Adicionar > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Interface web Proxmox: Datacenter > Armazenamento > Adicionar > iSCSI",
"Proxy address to reach the API (optional)": "Endereço proxy para acessar a API (opcional)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "URL HTTPS público usado para abrir este portal Authelia (por exemplo https://auth.example.com)",
"Public UDP port clients connect to": "Clientes públicos de porta UDP se conectam",
"Public UDP port peers connect to": "Os pares públicos de portas UDP se conectam",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "URL público do phpMyAdmin quando é servido por trás de um proxy reverso",
@@ -6453,6 +6461,7 @@
"Simple and easy to use DDNS": "Simples e fácil de usar DDNS",
"Simple self-hosted file sharing.": "Serviço simples e autoalojado para partilha de ficheiros.",
"Single GPU Warning": "Aviso de GPU única",
"Single sign-on and multi-factor authentication": "login único e autenticação multifator",
"Single target found — selected automatically:": "Único alvo encontrado — selecionado automaticamente:",
"Single-node log storage and query UI.": "Armazenamento de logs para um único nó com interface de consulta.",
"Single-node observability database for metrics, logs and traces.": "Banco de dados de observabilidade de nó único para métricas, logs e traces.",
@@ -8202,12 +8211,17 @@
"Web access": "Acesso à Internet",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Endereço Web do AdGuard Home que recebe as definições (por exemplo, http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Endereço Web do AdGuard Home principal, cujas configurações são copiadas (por exemplo, http://192.168.1.2)",
"Web dashboard": "Painel web",
"Web dashboard and WebDAV": "Painel web e WebDAV",
"Web interface": "Interface da Web",
"Web interface for Borg Backup repositories and jobs.": "Interface web para gerenciar repositórios e tarefas do Borg Backup.",
"Web interface for managing 3D printers.": "Interface web para gerenciar impressoras 3D.",
"Web interface to manage devices running Tasmota firmware.": "Interface Web para gerenciar dispositivos rodando firmware Tasmota.",
"Web reverse proxy with an administration interface.": "Proxy reverso web com interface de administração.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord pode ser resumido como um pacote de endurecimentos de segurança e privacidade, recursos de discórdia reimplementações, Electron / Chromium / Erros de discórdia workarounds, stylesheets, páginas internas e página envolto https://discord.com, projetado para se conformar com ToS tanto quanto possível (ou ocultar as alterações que podem violá-lo dos olhos da Discord).",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Senha do WebDAV e do painel (vazio = gerada)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "O WebDAV está disponível na porta 8080 do contêiner. É preciso vincular a conta antes de operar com os arquivos da nuvem.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus é um multi-site incremental xmltv epg grabber. Ele coleta dados de guias de tv-programa de sites selecionados para seus canais favoritos.",
"Webservers & Proxies": "Servidores web e proxies",
"Website": "Site",
+28
View File
@@ -256,6 +256,7 @@
"Acceleration for llama.cpp": "Zrýchlenie pre llama.cpp",
"Accept routes from other nodes?": "Prijímať trasy z iných uzlov?",
"Accept this host monitoring profile?": "Prijať tento profil monitorovania hostiteľa?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Prístup ku cloudu Movistar a O2 z klientov WebDAV a nástrojov na zálohovanie.",
"Access Scope:": "Rozsah prístupu:",
"Access bridge": "Sieťový most (bridge)",
"Access bridge for Immich": "Sieťový most (bridge) pre Immich",
@@ -410,6 +411,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Po reštarte sa k Proxmox hostovi dostanete iba cez:",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Po tomto prepnutí LXC → VM reštartujte host, aby sa nové naviazanie použilo čisto.",
"Agentless monitoring and observability platform.": "Platforma na monitorovanie a observabilitu bez klientskych agentov.",
"AirDC++ administrator": "Správca AirDC++",
"Airsonic Advanced web interface": "Webové rozhranie Airsonic Advanced",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced je bezplatný webový server na streamovanie hudby. Umožňuje zdieľať hudbu s priateľmi alebo počúvať vlastnú zbierku aj v práci. Podporuje súčasné prehrávanie na viacerých zariadeniach, napríklad v kuchyni a obývačke.",
"Aliases added to .bashrc": "Aliasy boli pridané do .bashrc",
@@ -603,6 +605,9 @@
"Audit socket disabled or not required": "Zásuvka auditu je zakázaná alebo sa nevyžaduje",
"Auth key is required.": "Auth key je povinný.",
"Auth:": "Prihlásenie:",
"Authelia Web UI": "Webové rozhranie Authelia",
"Authelia administrator": "Správca Authelia",
"Authelia session cookie domain for this portal (for example example.com)": "Doména súboru cookie relácie Authelia pre tento portál (napríklad example.com)",
"Authentication": "Prihlásenie",
"Authentication & Security": "Overovanie a zabezpečenie",
"Authentication Error": "Chyba prihlásenia",
@@ -1927,6 +1932,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Rozdiel: aktuálny systém vs záloha (--- systém +++ záloha)",
"Different host. Backup from:": "Iný host. Záloha z:",
"Direct Connect file sharing with a web interface": "Zdieľanie súborov cez Direct Connect s webovým rozhraním",
"Direct conversion completed for container": "Priama konverzia bola dokončená pre kontajner",
"Directory Exists": "Priečinok už existuje",
"Directory Not Empty": "Priečinok nie je prázdny",
@@ -4899,6 +4905,7 @@
"Open the page of an application in the menu": "Otvoriť stránku aplikácie v ponuke",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Otvorte webovú adresu a vytvorte prvé konto: daemon sa zaregistruje sám a spustí prvé skenovanie.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "Otvorte webovú adresu a v prvotnom nastavení vytvorte heslo používateľa akadmin.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Otvorte webový panel a prepojte svoj účet Movistar/O2 pomocou overenia cez SMS.",
"Open-source AI-powered coding assistant": "Programátorský asistent využívajúci AI s otvoreným zdrojovým kódom",
"Open-source UI for building and debugging multi-agent and RAG applications": "Rozhranie s otvoreným zdrojovým kódom na tvorbu a ladenie aplikácií s viacerými agentmi a RAG",
"Open-source in-memory data store.": "Úložisko údajov v pamäti s otvoreným zdrojovým kódom.",
@@ -5398,6 +5405,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Webové rozhranie Proxmoxu: Datacenter > Storage > Add > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Webové rozhranie Proxmoxu: Datacenter > Storage > Add > iSCSI",
"Proxy address to reach the API (optional)": "Adresa proxy na prístup k API (voliteľné)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "Verejná adresa HTTPS na otvorenie tohto portálu Authelia (napríklad https://auth.example.com)",
"Public UDP port clients connect to": "Verejný port UDP na pripojenie klientov",
"Public UDP port peers connect to": "Verejný port UDP na pripojenie účastníkov",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "Verejné URL phpMyAdmin, keď sa podáva za reverzným proxy",
@@ -6455,6 +6463,7 @@
"Simple and easy to use DDNS": "Jednoducho použiteľné dynamické DNS (DDNS)",
"Simple self-hosted file sharing.": "Jednoduchá samohostovaná služba na zdieľanie súborov.",
"Single GPU Warning": "Upozornenie na jednu GPU",
"Single sign-on and multi-factor authentication": "Jednotné prihlásenie a viacfaktorové overovanie",
"Single target found — selected automatically:": "Našiel sa jeden target - vybraný automaticky:",
"Single-node log storage and query UI.": "Úložisko logov a vyhľadávacie rozhranie pre jeden uzol.",
"Single-node observability database for metrics, logs and traces.": "Databáza na jednom uzle na ukladanie metrík, logov a záznamov o priebehu požiadaviek.",
@@ -8202,12 +8211,17 @@
"Web access": "Adresa webového rozhrania",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webová adresa AdGuard Home, ktorá prijíma nastavenia (napr. http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webová adresa hlavného AdGuard Home, ktorého nastavenia sú kopírované (napr. http://192.168.1.2)",
"Web dashboard": "Webový panel",
"Web dashboard and WebDAV": "Webový panel a WebDAV",
"Web interface": "Webové rozhranie",
"Web interface for Borg Backup repositories and jobs.": "Webové rozhranie na správu úložísk a úloh Borg Backup.",
"Web interface for managing 3D printers.": "Webové rozhranie na správu 3D tlačiarní.",
"Web interface to manage devices running Tasmota firmware.": "Webové rozhranie pre správu zariadení bežiacich na Tasmota firmware.",
"Web reverse proxy with an administration interface.": "Webový reverzný proxy server s administračným rozhraním.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord je klient obklopujúci stránku https://discord.com, ktorý zlepšuje bezpečnosť a súkromie, upravuje funkcie Discordu, obchádza chyby Electronu, Chromia a Discordu a pridáva štýly aj interné stránky. Snaží sa čo najviac dodržiavať podmienky používania alebo skryť pred Discordom úpravy, ktoré by ich mohli porušovať.",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Heslo pre WebDAV a panel (prázdne = generované)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV je dostupný na porte 8080 kontajnera. Pred prácou so súbormi v cloude je potrebné prepojiť účet.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "WebGrab+Plus priebežne získava elektronický programový sprievodca (EPG) vo formáte XMLTV z viacerých webov. Zhromažďuje televízny program vybraných staníc z podporovaných stránok.",
"Webservers & Proxies": "Webové servery a proxy",
"Website": "Webová stránka",
@@ -8968,6 +8982,20 @@
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disk sa odpojí a odstráni z /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ Záznam v /etc/fstab bude odstránený.",
"⚠ The disk will be unmounted.": "⚠ Disk bude odpojený.",
"A validating, recursive DNS resolver for your private network.": "Validujúci rekurzívny DNS resolver pre vašu súkromnú sieť.",
"Controller path on the Proxmox host; prefer /dev/serial/by-id/...": "Cesta ku kontroléru na hostiteľovi Proxmox; uprednostnite /dev/serial/by-id/...",
"Initial Z-Wave JS UI admin password": "Počiatočné heslo správcu Z-Wave JS UI",
"KDE's twin-panel file manager in a browser-based desktop.": "Dvojpanelový správca súborov KDE v pracovnej ploche dostupnej cez webový prehliadač.",
"KitchenOwl": "KitchenOwl",
"Krusader Web UI": "Webové rozhranie Krusaderu",
"Manage a Z-Wave network from a web dashboard and WebSocket gateway.": "Spravujte sieť Z-Wave cez webový panel a bránu WebSocket.",
"Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured.": "Otvorte webovú adresu a podľa pokynov KitchenOwl vytvorte prvý účet; predvolený účet nie je nastavený.",
"Pass through a Z-Wave USB or serial controller": "Pripojiť USB alebo sériový radič Z-Wave",
"Self-hosted grocery list and recipe manager": "Správca nákupných zoznamov a receptov, ktorý si môžete prevádzkovať sami.",
"Spoolman web interface": "Webové rozhranie Spoolmanu",
"Track 3D printer filament spools and usage.": "Sledujte cievky filamentu pre 3D tlačiareň a ich spotrebu.",
"Z-Wave JS UI": "Z-Wave JS UI",
"Z-Wave JS UI admin login": "Prihlásenie správcu Z-Wave JS UI",
"Computer-aided design and drafting in your browser": "Počítačom podporované navrhovanie a technické kreslenie v prehliadači",
"Presentations and slide decks in your browser": "Prezentácie a snímky v prehliadači",
"Page layout and publishing in your browser": "Zalomenie strán a publikovanie v prehliadači",
+13
View File
@@ -256,6 +256,7 @@
"Acceleration for llama.cpp": "Acceleration för llama.cpp",
"Accept routes from other nodes?": "Acceptera rutter från andra noder?",
"Accept this host monitoring profile?": "Acceptera denna värdövervakningsprofil?",
"Access Movistar and O2 Cloud from WebDAV clients and backup tools.": "Kom åt Movistar- och O2-molnet från WebDAV-klienter och säkerhetskopieringsverktyg.",
"Access Scope:": "Åtkomstomfång:",
"Access bridge": "Tillgång bridge",
"Access bridge for Immich": "Tillgångsbro för Immich",
@@ -410,6 +411,7 @@
"After the reboot, you will only be able to access the Proxmox host via:": "Efter omstarten kommer du bara att kunna komma åt Proxmox-värden via:",
"After this LXC → VM switch, reboot the host so the new binding state is applied cleanly.": "Efter denna LXC → VM-switch, starta om värden så att det nya bindningsläget tillämpas korrekt.",
"Agentless monitoring and observability platform.": "Agentlös plattform för övervakning och observerbarhet.",
"AirDC++ administrator": "AirDC++-administratör",
"Airsonic Advanced web interface": "Airsonic Advanced webbgränssnitt",
"Airsonic-advanced is a free, web-based media streamer, providing ubiquitious access to your music. Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple players simultaneously, for instance to one player in your kitchen and another in your living room.": "Airsonic-advanced är en gratis, webbaserad media streamer, som ger ubiquitious tillgång till din musik. Använd den för att dela din musik med vänner, eller för att lyssna på din egen musik på jobbet. Du kan strömma till flera spelare samtidigt, till exempel till en spelare i ditt kök och en annan i ditt vardagsrum.",
"Aliases added to .bashrc": "Alias har lagts till i .bashrc",
@@ -603,6 +605,8 @@
"Audit socket disabled or not required": "Granskningsuttaget inaktiverat eller inte nödvändigt",
"Auth key is required.": "Auth-nyckel krävs.",
"Auth:": "Auth:",
"Authelia administrator": "Authelia administratör",
"Authelia session cookie domain for this portal (for example example.com)": "Authelia sessionscookiedomän för denna portal (till exempel example.com)",
"Authentication": "Autentisering",
"Authentication & Security": "Autentisering och säkerhet",
"Authentication Error": "Autentiseringsfel",
@@ -1927,6 +1931,7 @@
"Dgraph": "Dgraph",
"Diff: current system vs backup (--- system +++ backup)": "Diff: nuvarande system vs säkerhetskopia (--- system +++ säkerhetskopia)",
"Different host. Backup from:": "Annorlunda värd. Säkerhetskopiering från:",
"Direct Connect file sharing with a web interface": "Fildelning via Direct Connect med webbgränssnitt",
"Direct conversion completed for container": "Direkt konvertering slutförd för container",
"Directory Exists": "Katalog finns",
"Directory Not Empty": "Katalog inte tom",
@@ -4897,6 +4902,7 @@
"Open the page of an application in the menu": "Öppna en applikations sida i menyn",
"Open the web address and create the first account: the daemon registers by itself and starts the first scan.": "Öppna webbadressen och skapa det första kontot: daemonen registrerar sig själv och startar den första skanningen.",
"Open the web address and create the password of the akadmin user in the first-run setup.": "Öppna webbadressen och skapa lösenordet för akadmin-användaren i den första körningen.",
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.": "Öppna webbpanelen och koppla ditt Movistar/O2-konto med SMS-verifieringen.",
"Open-source AI-powered coding assistant": "Open-source AI-driven kodningsassistent",
"Open-source UI for building and debugging multi-agent and RAG applications": "Open-source UI för att bygga och debugga multi-agent- och RAG-applikationer",
"Open-source in-memory data store.": "Öppen datalagring i minnet.",
@@ -5396,6 +5402,7 @@
"Proxmox web interface: Datacenter > Storage > Add > ZFS": "Proxmox webbgränssnitt: Datacenter > Lagring > Lägg till > ZFS",
"Proxmox web interface: Datacenter > Storage > Add > iSCSI": "Proxmox webbgränssnitt: Datacenter > Lagring > Lägg till > iSCSI",
"Proxy address to reach the API (optional)": "Proxyadress för att nå API:t (valfritt)",
"Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)": "Offentlig HTTPS-URL som används för att öppna denna Authelia-portal (till exempel https://auth.example.com)",
"Public UDP port clients connect to": "Offentliga UDP-portklienter ansluter till",
"Public UDP port peers connect to": "Offentliga UDP-portpeers ansluter till",
"Public URL of phpMyAdmin when it is served behind a reverse proxy": "Public URL av phpMyAdmin när den serveras bakom en omvänd proxy",
@@ -6453,6 +6460,7 @@
"Simple and easy to use DDNS": "Enkelt och enkelt att använda DDNS",
"Simple self-hosted file sharing.": "Enkel självhostad fildelning.",
"Single GPU Warning": "Varning för enkel GPU",
"Single sign-on and multi-factor authentication": "Enkel inloggning och multifaktorautentisering",
"Single target found — selected automatically:": "Enstaka mål hittades — väljs automatiskt:",
"Single-node log storage and query UI.": "Gränssnitt för logglagring och loggsökning på en enda nod.",
"Single-node observability database for metrics, logs and traces.": "Observabilitetsdatabas med en nod för mätvärden, loggar och spår.",
@@ -8202,12 +8210,17 @@
"Web access": "Web access",
"Web address of the AdGuard Home that receives the settings (e.g. http://192.168.1.3)": "Webbadress för AdGuard Home som tar emot inställningarna (t.ex. http://192.168.1.3)",
"Web address of the main AdGuard Home, whose settings are copied (e.g. http://192.168.1.2)": "Webbadressen för AdGuard Home, vars inställningar kopieras (t.ex. http://192.168.1.2)",
"Web dashboard": "Webbpanel",
"Web dashboard and WebDAV": "Webbpanel och WebDAV",
"Web interface": "Webbgränssnitt",
"Web interface for Borg Backup repositories and jobs.": "Webbgränssnitt för Borg Backup-arkiv och jobb.",
"Web interface for managing 3D printers.": "Webbgränssnitt för hantering av 3D-skrivare.",
"Web interface to manage devices running Tasmota firmware.": "Webbgränssnitt för att hantera enheter som kör Tasmota firmware.",
"Web reverse proxy with an administration interface.": "Omvänd proxy för webben med administrationsgränssnitt.",
"WebCord can be summarized as a pack of security and privacy hardenings, Discord features reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, internal pages and wrapped https://discord.com page, designed to conform with ToS as much as it is possible (or hide the changes that might violate it from Discord's eyes).": "WebCord kan sammanfattas som ett paket av säkerhets- och sekretesshärdningar, Discord-funktioner reimplementations, Electron / Chromium / Discord bugs workarounds, stylesheets, interna sidor och wrapped https://discord.com sida, utformad för att överensstämma med ToS så mycket som möjligt (eller dölja de förändringar som kan bryta mot det från Discords ögon).",
"WebDAV": "WebDAV",
"WebDAV and dashboard password (leave empty to generate)": "Lösenord för WebDAV och panelen (tom = genererat)",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations.": "WebDAV är tillgängligt på containerns port 8080. Kontot måste kopplas innan du arbetar med filerna i molnet.",
"Webgrabplus is a multi-site incremental xmltv epg grabber. It collects tv-program guide data from selected tvguide sites for your favourite channels.": "Webgrabplus är en multi-site inkrementell xmltv epg grabber. Det samlar in tv-programguide data från utvalda tvguide webbplatser för dina favoritkanaler.",
"Webservers & Proxies": "Webbservrar och proxyservrar",
"Website": "Webbplats",
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- Generated from oci/catalog/verification.json by .github/scripts/oci_validation.py. Do not edit by hand. -->
52 of 546 applications in the OCI catalog have been tested for real: 49 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
52 of 554 applications in the OCI catalog have been tested for real: 49 in the ProxMenux lab and 3 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.
+585
View File
@@ -0,0 +1,585 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-airdcpp-webclient",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "AirDC++ Web Client",
"es_ES": "AirDC++ Web Client"
},
"tagline": {
"en_US": "Direct Connect file sharing with a web interface",
"es_ES": "Intercambio de archivos Direct Connect con interfaz web"
},
"description": {
"en_US": "AirDC++ Web Client using the community-maintained gangefors image. Configuration, downloads and shared content use separate persistent paths.",
"es_ES": "AirDC++ Web Client con la imagen comunitaria de gangefors. La configuración, las descargas y los archivos compartidos usan rutas persistentes separadas."
},
"category": "downloads",
"category_label": "Files & Downloads",
"author": "gangefors",
"developer": "AirDC++",
"icon": "https://raw.githubusercontent.com/airdcpp-web/airdcpp-webui/eeeea5025a5e720f38dac53ced8e002940f00a74/resources/images/logo.svg",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64"
],
"launch": {
"scheme": "http",
"port": 5600,
"path": "/"
},
"website": "https://airdcpp-web.github.io/",
"documentation": "https://github.com/gangefors/docker-airdcpp-webclient",
"repository": "https://github.com/gangefors/docker-airdcpp-webclient",
"tips": [
"The upstream initial login is admin / password. Change it in Settings > System > Users before allowing access beyond a trusted LAN.",
"Configuration uses /.airdcpp on a managed Proxmox volume included in backups. /Downloads and /Share can use managed volumes or explicitly selected host directories.",
"Files under /Share are shared by AirDC++. Select the contents deliberately; router forwarding and hub configuration are separate administrator actions.",
"PUID and PGID default to 1000 and must be at least 101. The original entrypoint creates the application account; volume ownership follows these values.",
"Open HTTP port 5600 on the dedicated LXC address. HTTPS port 5601 uses the upstream self-signed certificate; use a trusted reverse proxy certificate for remote web access.",
"Incoming Direct Connect traffic uses TCP and UDP 21248 and TLS TCP 21249. ProxMenux keeps those native ports and does not configure your router."
],
"mini_changelog": [],
"display_version": null,
"updated_at": "2026-10-25"
},
"source": {
"provider": "gangefors",
"repository": "https://github.com/gangefors/docker-airdcpp-webclient",
"revision": "37f2e939c152fb2c0158bc83bc76cc972aed965e",
"image_repository_url": "https://hub.docker.com/r/gangefors/airdcpp-webclient",
"readme_pushed_at": "2026-10-25T11:51:53Z",
"compose_sha256": "4ce8aec542cfc3e7f5ca142e309a41f04aca444bd1724ca1741c96acb5c3d5bf",
"generated_at": "2026-11-03T11:47:04+00:00",
"default_branch": "master",
"readme_raw_url": "https://raw.githubusercontent.com/gangefors/docker-airdcpp-webclient/37f2e939c152fb2c0158bc83bc76cc972aed965e/docker-compose.yml"
},
"container_contract": {
"service_name": "airdcpp",
"container_name": "airdcpp",
"image": {
"reference": "docker.io/gangefors/airdcpp-webclient:latest",
"registry": "docker.io",
"repository": "docker.io/gangefors/airdcpp-webclient",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "PUID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-compose"
},
{
"name": "PGID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-compose"
},
{
"name": "UMASK",
"example": "0022",
"required": true,
"sensitive": false,
"source": "upstream-compose"
}
],
"volumes": [
{
"id": "config",
"container_path": "/.airdcpp",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 16
}
},
{
"id": "downloads",
"container_path": "/Downloads",
"compose_source_example": "./downloads",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 32
}
},
{
"id": "share",
"container_path": "/Share",
"compose_source_example": "./share",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 32
}
}
],
"ports": [
{
"container_port": 5600,
"published_example": 5600,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 5601,
"published_example": 5601,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 21248,
"published_example": 21248,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 21248,
"published_example": 21248,
"protocol": "udp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 21249,
"published_example": 21249,
"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: airdcpp-webclient\nservices:\n airdcpp:\n image: docker.io/gangefors/airdcpp-webclient:latest\n restart: unless-stopped\n environment:\n PUID: '1000'\n PGID: '1000'\n UMASK: '0022'\n ports:\n - 5600:5600\n - 5601:5601\n - 21248:21248/tcp\n - 21248:21248/udp\n - 21249:21249/tcp\n volumes:\n - ./config:/.airdcpp\n - ./downloads:/Downloads\n - ./share:/Share\n"
},
"compose_stack": {
"project_name": "airdcpp-webclient",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "airdcpp",
"service_count": 1,
"services": [
{
"name": "airdcpp",
"image": "docker.io/gangefors/airdcpp-webclient:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"image": "docker.io/gangefors/airdcpp-webclient:latest",
"restart": "unless-stopped",
"environment": {
"PUID": "1000",
"PGID": "1000",
"UMASK": "0022"
},
"ports": [
"5600:5600",
"5601:5601",
"21248:21248/tcp",
"21248:21248/udp",
"21249:21249/tcp"
],
"volumes": [
"./config:/.airdcpp",
"./downloads:/Downloads",
"./share:/Share"
]
}
}
],
"top_level": {
"name": "airdcpp-webclient"
},
"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": "airdcpp-volume-0",
"service": "airdcpp",
"container_path": "/.airdcpp",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "airdcpp-volume-1",
"service": "airdcpp",
"container_path": "/Downloads",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "airdcpp-volume-2",
"service": "airdcpp",
"container_path": "/Share",
"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": [
"airdcpp"
],
"stop_order": [
"airdcpp"
],
"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": 5600,
"path": "/",
"source": "https://github.com/gangefors/docker-airdcpp-webclient"
}
],
"credentials": [
{
"label": "AirDC++ administrator",
"type": "static-default",
"username": "admin",
"password": "password",
"change_required": true,
"source": "https://github.com/gangefors/docker-airdcpp-webclient#run-the-application"
}
]
},
"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"
},
"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": "not-requested-by-compose"
},
{
"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"
}
],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/.airdcpp",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/Downloads",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/Share",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
"scheme": "http",
"port": 5600,
"path": "/index.html",
"timeout_seconds": 180,
"request_timeout_seconds": 5,
"stability_seconds": 4,
"verify_tls": true
}
}
},
"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",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "Community image and single-LXC contract reviewed against the publisher Compose and entrypoint; Linux amd64 confirmed in the registry manifest. Runtime lifecycle checks remain pending."
},
"validation": {
"schema": "passed-at-generation",
"clean_install": "pending",
"service_health": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending",
"authenticated_login": "pending",
"advanced_install": "pending",
"host_bind_storage": "pending",
"managed_storage": "pending",
"removal": "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
}
}
+453
View File
@@ -0,0 +1,453 @@
{
"schema_version": "0.4.0",
"kind": "proxmenux.oci-template",
"id": "image-authelia",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Authelia",
"es_ES": "Authelia"
},
"tagline": {
"en_US": "Single sign-on and multi-factor authentication",
"es_ES": "Inicio de sesión único y autenticación multifactor"
},
"description": {
"en_US": "An open-source authentication and authorization server for protecting web applications. This single-image profile provides a local SQLite database and file notifications; configure your domain, reverse proxy and access rules before protecting other services.",
"es_ES": "Servidor de autenticación y autorización de código abierto para proteger aplicaciones web. Este perfil de una sola imagen incluye una base de datos SQLite local y notificaciones en archivo; configura el dominio, el proxy inverso y las reglas de acceso antes de proteger otros servicios."
},
"category": "security",
"category_label": "Authentication & Security",
"author": "Authelia",
"developer": "Authelia",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 9091,
"path": "/"
},
"website": "https://www.authelia.com/",
"documentation": "https://www.authelia.com/integration/deployment/docker/",
"repository": "https://github.com/authelia/authelia",
"tips": [
"The installer creates a unique admin password and stores only its Argon2id hash in /config/users_database.yml. The password is shown in the installation result.",
"Authelia requires an HTTPS portal URL and a cookie domain matching that hostname. If you use Nginx Proxy Manager, follow the official integration guide and configure trusted proxies and forward-auth separately.",
"The default notifier writes messages to /config/notification.txt. Configure SMTP in /config/configuration.yml if you want email delivery.",
"The initial access-control policy is one_factor. Add explicit access-control rules before attaching Authelia to protected proxy hosts."
],
"mini_changelog": [],
"display_version": "4.39.28",
"updated_at": "2026-09-17"
},
"source": {
"provider": "authelia",
"repository": "https://github.com/authelia/authelia",
"default_branch": "master",
"revision": "8da42b2348b0351c6284a2940a9c5eec3d4cd3fd",
"readme_raw_url": "https://raw.githubusercontent.com/authelia/authelia/v4.39.28/examples/compose/lite/compose.yml",
"readme_pushed_at": "2026-09-17T12:13:30Z",
"compose_sha256": "848ce0f70e502981a050ac200d9d1882978a6c3b0f9f0dfcc6d52ce66ca37fb5",
"generated_at": "2026-11-03T09:10:57+00:00",
"image_repository_url": "https://hub.docker.com/r/authelia/authelia"
},
"container_contract": {
"service_name": "authelia",
"container_name": "authelia",
"image": {
"reference": "docker.io/authelia/authelia:4.39.28",
"registry": "docker.io",
"repository": "docker.io/authelia/authelia",
"tag": "4.39.28",
"digest": null,
"pull_policy": "resolve-selected-tag-to-architecture-digest-at-install"
},
"environment": [
{
"name": "PROXMENUX_ADMIN_PASSWORD",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-first-start"
},
{
"name": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-generated"
},
{
"name": "AUTHELIA_SESSION_SECRET",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-generated"
},
{
"name": "AUTHELIA_STORAGE_ENCRYPTION_KEY",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-generated"
},
{
"name": "X_AUTHELIA_CONFIG_FILTERS",
"example": "template",
"required": true,
"sensitive": false,
"source": "authelia-container"
},
{
"name": "PROXMENUX_AUTHELIA_COOKIE_DOMAIN",
"example": "",
"prompt": "Authelia session cookie domain for this portal (for example example.com)",
"required": true,
"sensitive": false,
"source": "administrator"
},
{
"name": "PROXMENUX_AUTHELIA_URL",
"example": "",
"prompt": "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)",
"required": true,
"sensitive": false,
"source": "administrator"
}
],
"volumes": [
{
"id": "config",
"container_path": "/config",
"compose_source_example": "./authelia",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
}
],
"ports": [
{
"container_port": 9091,
"published_example": 9091,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n authelia:\n entrypoint:\n - /bin/sh\n - /usr/local/bin/proxmenux-authelia-bootstrap\n environment:\n AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: ${GENERATED_RESET_PASSWORD_JWT_SECRET}\n AUTHELIA_SESSION_SECRET: ${GENERATED_SESSION_SECRET}\n AUTHELIA_STORAGE_ENCRYPTION_KEY: ${GENERATED_STORAGE_ENCRYPTION_KEY}\n PROXMENUX_ADMIN_PASSWORD: ${GENERATED_ADMIN_PASSWORD}\n PROXMENUX_AUTHELIA_COOKIE_DOMAIN: ${AUTHELIA_COOKIE_DOMAIN}\n PROXMENUX_AUTHELIA_URL: ${AUTHELIA_URL}\n X_AUTHELIA_CONFIG_FILTERS: template\n image: docker.io/authelia/authelia:4.39.28\n ports:\n - 9091:9091\n restart: unless-stopped\n volumes:\n - ./authelia:/config\n"
},
"first_run": {
"endpoints": [
{
"label": "Authelia Web UI",
"scheme": "http",
"port": 9091,
"path": "/",
"source": "official-image"
}
],
"credentials": [
{
"label": "Authelia administrator",
"type": "configured-or-installer-generated",
"username": "admin",
"password": null,
"password_environment": "PROXMENUX_ADMIN_PASSWORD",
"change_required": false,
"source": "Generated on first start and stored as an Argon2id hash in the persistent users file."
}
]
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 512,
"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": 60
},
"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": {
"runtime": {
"compose_entrypoint": [
"/bin/sh",
"/usr/local/bin/proxmenux-authelia-bootstrap"
]
},
"generated_sensitive_environment": {
"PROXMENUX_ADMIN_PASSWORD": {
"strategy": "token-hex",
"bytes": 24,
"prompt": false
},
"AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
},
"AUTHELIA_SESSION_SECRET": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
},
"AUTHELIA_STORAGE_ENCRYPTION_KEY": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
}
},
"minimum_secret_length": {
"PROXMENUX_ADMIN_PASSWORD": 24,
"AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": 32,
"AUTHELIA_SESSION_SECRET": 32,
"AUTHELIA_STORAGE_ENCRYPTION_KEY": 32
},
"generated_files": [
{
"container_path": "/config/configuration.yml",
"mode": "0600",
"owner": "mapped-root",
"only_if_missing": true,
"content": "---\nserver:\n address: 'tcp://:9091'\n\nlog:\n level: 'info'\n\ntotp:\n issuer: 'Authelia'\n\nidentity_validation:\n reset_password:\n jwt_secret: 'configured-by-environment'\n\nauthentication_backend:\n file:\n path: '/config/users_database.yml'\n watch: true\n\naccess_control:\n default_policy: 'one_factor'\n\nsession:\n secret: 'configured-by-environment'\n cookies:\n - name: 'authelia_session'\n domain: '{{ env \"PROXMENUX_AUTHELIA_COOKIE_DOMAIN\" }}'\n authelia_url: '{{ env \"PROXMENUX_AUTHELIA_URL\" }}'\n expiration: '1 hour'\n inactivity: '5 minutes'\n\nstorage:\n encryption_key: 'configured-by-environment'\n local:\n path: '/config/db.sqlite3'\n\nnotifier:\n filesystem:\n filename: '/config/notification.txt'\n"
},
{
"container_path": "/usr/local/bin/proxmenux-authelia-bootstrap",
"mode": "0700",
"owner": "mapped-root",
"only_if_missing": false,
"content": "#!/bin/sh\nset -eu\numask 077\n\nusers_file=/config/users_database.yml\nif [ ! -s \"$users_file\" ]; then\n : \"${PROXMENUX_ADMIN_PASSWORD:?ProxMenux did not provide an initial administrator password}\"\n mkdir -p /config\n hash_output=$(authelia crypto hash generate argon2 --password \"$PROXMENUX_ADMIN_PASSWORD\" --no-confirm)\n password_hash=$(printf '%s\\n' \"$hash_output\" | sed -n 's/^Digest: //p')\n case \"$password_hash\" in\n '$argon2id$'*) ;;\n *) printf '%s\\n' 'Authelia did not return an Argon2id password hash' >&2; exit 1 ;;\n esac\n temporary_file=\"${users_file}.tmp.$$\"\n cat >\"$temporary_file\" <<EOF\nusers:\n admin:\n disabled: false\n displayname: 'Authelia Administrator'\n password: \"$password_hash\"\n groups:\n - 'admins'\nEOF\n chmod 0600 \"$temporary_file\"\n mv -f \"$temporary_file\" \"$users_file\"\nfi\nunset PROXMENUX_ADMIN_PASSWORD\nexec /app/entrypoint.sh \"$@\"\n"
}
],
"startup_healthcheck": {
"scheme": "http",
"port": 9091,
"path": "/api/health",
"timeout_seconds": 240,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"adaptations": [
{
"id": "dedicated-lxc-network",
"upstream_behavior": "Docker publishes selected container ports on the Docker host.",
"native_lxc_behavior": "The application listens on the same container ports at its dedicated LXC address.",
"reason": "A native LXC has its own address and does not require Docker port NAT.",
"behavioral_impact": "Users open the LXC address instead of the Proxmox host address.",
"validation": "pending-per-application"
},
{
"id": "compose-environment-overlay",
"upstream_behavior": "Compose environment values override OCI image environment values.",
"native_lxc_behavior": "ProxMenux merges the same values into lxc.environment.runtime while the CT is stopped.",
"reason": "PVE imports image Env automatically; Compose values still need to override it.",
"behavioral_impact": "None expected.",
"validation": "pending-per-application"
},
{
"id": "stop-grace-period",
"upstream_behavior": "Docker waits for Compose stop_grace_period before forcing termination.",
"native_lxc_behavior": "ProxMenux records the same timeout for its pct shutdown lifecycle operations.",
"reason": "Proxmox has no equivalent per-CT persistent restart-policy field; startup.down is not a shutdown timeout.",
"behavioral_impact": "Normal Proxmox node shutdown remains governed by the node-wide shutdown policy.",
"validation": "not-requested-by-compose"
},
{
"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-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": "pending-per-application"
},
{
"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": "not-requested-by-compose"
},
{
"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"
}
]
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"cap_add",
"command",
"container_name",
"cpu_shares",
"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",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "A single-image profile for the Authelia authentication portal. ProxMenux creates a unique initial administrator account; the local notifier and SQLite storage are kept in /config. Reverse-proxy trust and explicit access-control rules remain administrator settings."
},
"validation": {
"schema": "pending-local-validation",
"clean_install": "pending",
"service_health": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending"
},
"lifecycle": {
"update_strategy": "replace-rootfs-from-new-oci-image-preserve-managed-volumes",
"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
}
}
+383
View File
@@ -0,0 +1,383 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-kitchenowl",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "KitchenOwl"
},
"tagline": {
"en_US": "Self-hosted grocery list and recipe manager"
},
"description": {
"en_US": "Manage shared shopping lists, recipes and meal plans in one self-hosted app."
},
"category": "productivity",
"category_label": "Productivity & Workflows",
"author": "Tom Bursch",
"developer": "Tom Bursch",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 80,
"path": "/"
},
"website": "https://kitchenowl.org/",
"documentation": "https://docs.kitchenowl.org/self-hosting/",
"repository": "https://github.com/TomBursch/kitchenowl",
"tips": [
"This recipe follows KitchenOwl’s standard official Compose deployment: a web frontend and backend in separate OCI LXCs.",
"The upstream sample uses PLEASE_CHANGE_ME for JWT_SECRET_KEY; this recipe generates a unique signing key at install time."
],
"mini_changelog": [],
"display_version": null,
"updated_at": "2026-11-01"
},
"source": {
"provider": "tombursch",
"repository": "https://github.com/TomBursch/kitchenowl",
"revision": "60a012ea5cae829878f78a46a585a7ee530b069a",
"image_repository_url": "https://hub.docker.com/r/tombursch/kitchenowl-web",
"readme_pushed_at": "2026-11-01T23:22:22Z",
"compose_sha256": "f620124c2a4943d4f2465ee304677bc9ed172a088f0fe43f5db7cbc8f6424263",
"generated_at": "2026-11-02T20:13:05+00:00",
"default_branch": "main",
"readme_raw_url": "https://raw.githubusercontent.com/TomBursch/kitchenowl/main/docker-compose.yml"
},
"container_contract": {
"service_name": "front",
"container_name": "front",
"image": {
"reference": "tombursch/kitchenowl-web:latest",
"registry": "docker.io",
"repository": "tombursch/kitchenowl-web",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [],
"volumes": [],
"ports": [
{
"container_port": 80,
"published_example": 80,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [
{
"name": "back",
"image": "tombursch/kitchenowl-backend:latest"
}
],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n back:\n environment:\n JWT_SECRET_KEY: ${GENERATED_JWT_SECRET_KEY}\n image: tombursch/kitchenowl-backend:latest\n restart: unless-stopped\n volumes:\n - read_only: false\n source: kitchenowl_data\n target: /data\n front:\n depends_on:\n - back\n image: tombursch/kitchenowl-web:latest\n ports:\n - 80:80\n restart: unless-stopped\n volumes: []\nvolumes:\n kitchenowl_data: null\n"
},
"compose_stack": {
"project_name": "kitchenowl",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "front",
"service_count": 2,
"services": [
{
"name": "back",
"image": "tombursch/kitchenowl-backend:latest",
"is_main": false,
"role": "dependency",
"vmid_offset": 1,
"depends_on": [],
"frontend_network": false,
"private_network": true,
"compose": {
"environment": {
"JWT_SECRET_KEY": "${GENERATED_JWT_SECRET_KEY}"
},
"image": "tombursch/kitchenowl-backend:latest",
"restart": "unless-stopped",
"volumes": [
{
"source": "kitchenowl_data",
"target": "/data",
"read_only": false
}
],
"healthcheck": {
"test": [
"CMD-SHELL",
"uwsgi_curl localhost:5000 /api/health/8M4F88S8ooi4sMbLBfkkV7ctWwgibW6V || exit 1"
],
"interval": "60s",
"timeout": "3s",
"retries": 3
}
}
},
{
"name": "front",
"image": "tombursch/kitchenowl-web:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [
"back"
],
"frontend_network": true,
"private_network": true,
"compose": {
"depends_on": [
"back"
],
"image": "tombursch/kitchenowl-web:latest",
"ports": [
"80:80"
],
"restart": "unless-stopped",
"volumes": [],
"healthcheck": {
"test": [
"CMD-SHELL",
"curl -f http://localhost/ || exit 1"
],
"interval": "30s",
"timeout": "3s",
"retries": 3
}
}
}
],
"top_level": {
"volumes": {
"kitchenowl_data": null
}
},
"networking": {
"frontend": "selected-proxmox-bridge",
"private_required": true,
"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": "back-volume-0",
"service": "back",
"container_path": "/data",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 2,
"start_order": [
"back",
"front"
],
"stop_order": [
"front",
"back"
],
"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": [
{
"id": "generated-jwt-secret-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "back",
"environment_variable": "JWT_SECRET_KEY"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "KitchenOwl",
"scheme": "http",
"port": 80,
"path": "/",
"source": "upstream-documentation"
}
],
"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"
},
"adaptations": [
{
"id": "generated-jwt-signing-key",
"upstream_behavior": "The official Compose example uses the placeholder PLEASE_CHANGE_ME for JWT_SECRET_KEY and instructs operators to replace it.",
"native_lxc_behavior": "ProxMenux generates a unique cryptographically random JWT signing key and supplies it to the backend service.",
"reason": "Avoid deploying the documented placeholder or placing a signing secret in the public recipe.",
"behavioral_impact": "The generated key is not an app login credential; KitchenOwl has no catalog-provided default account.",
"validation": "pending-runtime"
},
{
"id": "image-healthchecks-preserved",
"upstream_behavior": "The web and backend OCI images declare Docker healthchecks in image metadata.",
"native_lxc_behavior": "The current image healthchecks are recorded in the stack so ProxMenux can check backend readiness and frontend availability during first start.",
"reason": "The Compose file inherits these checks from its images instead of declaring them itself.",
"behavioral_impact": "Checks gate stack startup; Docker does not run as a separate health-monitoring daemon inside the LXCs.",
"validation": "pending-runtime"
}
],
"custom_compose_stack": true,
"custom_compose_interpolated": false,
"stack_image_entrypoints": {},
"installer_profile": {
"stack_driver": "generic-multi-lxc-stack",
"volume_preparations": [
{
"container_path": "/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
]
},
"stack_completion_notes": [
"Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured."
]
},
"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",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "The official two-image Compose stack is mapped to native Proxmox OCI LXCs with private service networking. Lab checks confirmed installation, web/API behavior, persistence, backup and recovery, modification, managed storage, and host-bind storage. Advanced custom settings have not been validated."
},
"validation": {
"schema": "passed-at-generation",
"clean_install": "passed-default-official-two-service-stack-and-first-start",
"advanced_install": "pending-custom-install-summary-validation",
"service_health": "passed-web-api-and-created-user-household-list-item",
"restart_persistence": "passed-managed-and-host-bind-data",
"backup_restore": "passed-manager-recover-new-vmids-stack-network-and-data",
"update_preserves_data": "no-update-available-at-check",
"modification": "passed-added-managed-volume-without-image-pull-or-container-recreate",
"volumes": "passed-managed-storage-and-host-bind-with-owner-and-data-check",
"removal": "passed-manager-removal-temporary-data-cleaned-after-retention-check"
},
"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,
"dependency_lifecycle": {
"implementation": "proxmox-hookscript",
"trigger": "main-lxc-pre-start",
"waits_for_dependency_healthchecks": true,
"stops_dependencies_with_main": false
}
}
}
+490
View File
@@ -0,0 +1,490 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-krusader",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Krusader",
"es_ES": "Krusader"
},
"tagline": {
"en_US": "KDE's twin-panel file manager in a browser-based desktop.",
"es_ES": "El gestor de archivos de dos paneles de KDE, disponible desde el navegador."
},
"description": {
"en_US": "Open Krusader in a browser and manage files from a directory you explicitly mount at /storage. The maintained jlesage image provides the graphical desktop; ProxMenux runs it as one native OCI LXC.",
"es_ES": "Abre Krusader en el navegador y gestiona los archivos de una carpeta que montes expresamente en /storage. La imagen mantenida por jlesage incluye el escritorio gráfico; ProxMenux la ejecuta como un LXC OCI nativo."
},
"category": "desktop",
"category_label": "Browsers & Web Desktops",
"author": "jlesage",
"developer": "jlesage",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/krusader.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "https",
"port": 5800,
"path": "/"
},
"website": "https://krusader.org/",
"documentation": "https://github.com/jlesage/docker-krusader",
"repository": "https://github.com/jlesage/docker-krusader",
"tips": [
"The web login uses HTTPS with an automatically generated self-signed certificate and password. Accept the certificate warning on the first visit or configure a trusted certificate.",
"Krusader can read, change and delete files in the directory mounted at /storage. Mount only the directory you intend to manage; the rest of the Proxmox host is not exposed.",
"The VNC port is not published. Use the browser interface on port 5800."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "jlesage",
"repository": "https://github.com/jlesage/docker-krusader",
"revision": "855c8140e1bb65b2a2526d82db44c808f23eb49f",
"image_repository_url": "https://hub.docker.com/r/jlesage/krusader",
"readme_pushed_at": "2026-09-22T15:18:17Z",
"compose_sha256": "9dd16cc30aa19afe7e148598c9cfb0c65c31606d67f3f22254c33a4d05212c7f",
"generated_at": "2026-11-02T20:05:40+00:00",
"default_branch": "master",
"readme_raw_url": "https://raw.githubusercontent.com/jlesage/docker-krusader/855c8140e1bb65b2a2526d82db44c808f23eb49f/README.md"
},
"container_contract": {
"service_name": "krusader",
"container_name": "krusader",
"image": {
"reference": "jlesage/krusader:latest",
"registry": "docker.io",
"repository": "jlesage/krusader",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "USER_ID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default"
},
{
"name": "GROUP_ID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default"
},
{
"name": "TZ",
"example": "Europe/Madrid",
"required": true,
"sensitive": false,
"source": "ProxMenux-local-timezone"
},
{
"name": "VNC_LOCALHOST_ONLY",
"example": "1",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
},
{
"name": "WEB_AUTHENTICATION",
"example": "1",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
},
{
"name": "WEB_AUTHENTICATION_USERNAME",
"example": "admin",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
},
{
"name": "WEB_AUTHENTICATION_PASSWORD",
"example": "${GENERATED_WEB_PASSWORD}",
"required": true,
"sensitive": true,
"source": "ProxMenux-generated-credential"
},
{
"name": "SECURE_CONNECTION",
"example": "1",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
}
],
"volumes": [
{
"id": "krusader-config",
"container_path": "/config",
"compose_source_example": "krusader-config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
},
{
"id": "krusader-storage",
"container_path": "/storage",
"compose_source_example": "/mnt/oci-shared/krusader/storage",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": true,
"default_size_gb": 8
}
}
],
"ports": [
{
"container_port": 5800,
"published_example": 5800,
"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: krusader\nservices:\n krusader:\n image: jlesage/krusader:latest\n container_name: krusader\n restart: unless-stopped\n environment:\n USER_ID: '1000'\n GROUP_ID: '1000'\n TZ: Europe/Madrid\n VNC_LOCALHOST_ONLY: '1'\n WEB_AUTHENTICATION: '1'\n WEB_AUTHENTICATION_USERNAME: admin\n WEB_AUTHENTICATION_PASSWORD: ${GENERATED_WEB_PASSWORD}\n SECURE_CONNECTION: '1'\n ports:\n - 5800:5800/tcp\n volumes:\n - krusader-config:/config\n - /mnt/oci-shared/krusader/storage:/storage\nvolumes:\n krusader-config: {}\n"
},
"compose_stack": {
"project_name": "krusader",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "krusader",
"service_count": 1,
"services": [
{
"name": "krusader",
"image": "jlesage/krusader:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"environment": {
"GROUP_ID": "1000",
"SECURE_CONNECTION": "1",
"TZ": "Europe/Madrid",
"USER_ID": "1000",
"VNC_LOCALHOST_ONLY": "1",
"WEB_AUTHENTICATION": "1",
"WEB_AUTHENTICATION_PASSWORD": "${GENERATED_GENERATED_WEB_PASSWORD}",
"WEB_AUTHENTICATION_USERNAME": "admin"
},
"image": "jlesage/krusader:latest",
"ports": [
"5800:5800"
],
"volumes": [
"./config:/config",
"./storage:/storage"
]
}
}
],
"top_level": {
"name": "krusader"
},
"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": "krusader-volume-0",
"service": "krusader",
"container_path": "/config",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "krusader-volume-1",
"service": "krusader",
"container_path": "/storage",
"mode": "user-selectable",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": "Host directory for Krusader:/storage",
"default": "host-bind",
"installation_choice": [
"managed-volume",
"host-bind"
]
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
"krusader"
],
"stop_order": [
"krusader"
],
"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": [
{
"id": "generated-web-password",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "krusader",
"environment_variable": "WEB_AUTHENTICATION_PASSWORD"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Krusader Web UI",
"scheme": "https",
"port": 5800,
"path": "/",
"source": "upstream-jlesage-secure-connection-and-web-authentication"
}
],
"credentials": [
{
"label": "Krusader Web UI",
"type": "configured-or-installer-generated",
"username": "admin",
"password": null,
"username_environment": "WEB_AUTHENTICATION_USERNAME",
"password_environment": "WEB_AUTHENTICATION_PASSWORD",
"change_required": false,
"source": "ProxMenux generated password and upstream image environment",
"retrieval": null
}
]
},
"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"
},
"adaptations": [
{
"id": "web-authentication-https",
"upstream_behavior": "The jlesage image can protect its browser GUI with WEB_AUTHENTICATION and requires SECURE_CONNECTION for authenticated web access.",
"native_lxc_behavior": "Enable authenticated HTTPS on port 5800, generate a unique password at install and keep VNC bound to localhost.",
"reason": "Krusader can modify files exposed under /storage, so the browser endpoint must not be unauthenticated.",
"behavioral_impact": "The image generates a self-signed TLS certificate; a browser may ask to trust it on first access.",
"validation": "pending"
},
{
"id": "scoped-storage-mount",
"upstream_behavior": "Docker Compose mounts the selected host directory at /storage.",
"native_lxc_behavior": "The same selected directory is mounted in the dedicated OCI LXC; no other host paths are exposed.",
"reason": "The file manager must only see data explicitly selected by the user.",
"behavioral_impact": "Write access permits changing or deleting files in that one directory.",
"validation": "pending"
},
{
"id": "dedicated-lxc-network",
"upstream_behavior": "Docker publishes web port 5800 on the Docker host.",
"native_lxc_behavior": "The native LXC listens on its own IP at port 5800 without Docker NAT.",
"reason": "The LXC has a dedicated network namespace and address.",
"behavioral_impact": "The port is reached at the LXC address rather than through a host-published port.",
"validation": "pending"
}
],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/config",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"uid": 1000,
"gid": 1000,
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/storage",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"uid": 1000,
"gid": 1000,
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
"scheme": "https",
"port": 5800,
"path": "/",
"timeout_seconds": 240,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": false
},
"generated_sensitive_environment": {
"WEB_AUTHENTICATION_PASSWORD": {
"strategy": "token-hex",
"bytes": 16,
"prompt": false
}
},
"volume_owner": {
"uid": 1000,
"gid": 1000
}
}
},
"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",
"stop_signal",
"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": "passed-first-start",
"service_health": "passed-login-page-and-authenticated-session-http-200",
"restart_persistence": "passed-container-restart-and-host-bind-write-check",
"backup_restore": "pending",
"update_preserves_data": "pending",
"image_resolution": "passed-skopeo-327mb-11-layers-integrity",
"credentials": "passed-generated-password-login-and-session-cookie",
"advanced_install": "pending-custom-install-summary-validation",
"managed_volume": "passed-managed-config-volume",
"host_bind": "passed-storage-bind-owner-and-app-user-write",
"browser_visual": "pending-self-signed-certificate-review",
"modification": "pending",
"removal": "passed-manager-remove-ct-disks-record-and-empty-test-path-cleaned",
"web_authentication": "passed-generated-credential-post-and-session-cookie"
},
"lifecycle": {
"update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update",
"registry_state": {
"resolved_architecture": "amd64",
"resolved_digest": "sha256:ecb77bb73b1d3c66720eefb07805f70289349b449d1ddaa15d120c399bd89cc4",
"image_version_label": "26.09.1",
"image_created": "2026-09-22T15:22:56.230858151Z"
},
"change_detection": "compare-compose-sha256-and-resolved-latest-image-digest",
"automatic_unattended_updates": false
}
}
+24 -2
View File
@@ -68,7 +68,7 @@
"source": "docker-compose"
},
{
"name": "LLAMA_ARG_CONTEXT_SIZE",
"name": "LLAMA_ARG_CTX_SIZE",
"example": "8192",
"required": true,
"sensitive": false,
@@ -142,7 +142,7 @@
],
"environment": [
"LLAMA_ARG_N_GPU_LAYERS=999",
"LLAMA_ARG_CONTEXT_SIZE=8192",
"LLAMA_ARG_CTX_SIZE=8192",
"LLAMA_ARG_MODELS_DIR=/models"
],
"devices": [
@@ -342,6 +342,28 @@
"0x1002"
]
}
],
"environment": [
{
"name": "LLAMA_ARG_FLASH_ATTN",
"value": "off"
},
{
"name": "LLAMA_ARG_UBATCH",
"value": "32"
},
{
"name": "LLAMA_ARG_BATCH",
"value": "128"
},
{
"name": "GGML_VK_DISABLE_F16",
"value": "1"
},
{
"name": "GGML_VK_DISABLE_ASYNC",
"value": "1"
}
]
},
{
+609
View File
@@ -0,0 +1,609 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-movistar-webdav-proxy",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Movistar / O2 Cloud WebDAV Proxy"
},
"tagline": {
"en_US": "Access Movistar and O2 Cloud from WebDAV clients and backup tools."
},
"description": {
"en_US": "Official uniextra image with a web dashboard for Movistar/O2 Cloud account linking. Persistent OAuth credentials, authenticated WebDAV and disk-backed temporary uploads."
},
"category": "backup",
"category_label": "Backup & Recovery",
"author": "uniextra",
"developer": "uniextra",
"icon": "https://raw.githubusercontent.com/uniextra/MovistarWebDAVProxy/main/static/favicon.svg",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 5000,
"path": "/"
},
"website": "https://github.com/uniextra/MovistarWebDAVProxy",
"documentation": "https://github.com/uniextra/MovistarWebDAVProxy#readme",
"repository": "https://github.com/uniextra/MovistarWebDAVProxy",
"tips": [
"Link your Movistar or O2 account from the dashboard after installation.",
"Use the container IP and port 8080 for WebDAV. Docker's published port 5050 is not used by native OCI LXC.",
"Temporary uploads use a separate disk volume, excluded from backups, to avoid filling RAM."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "uniextra",
"repository": "https://github.com/uniextra/MovistarWebDAVProxy",
"revision": "e48485bc8bdf9745caa6249fdc92db6d28214549",
"image_repository_url": "https://hub.docker.com/r/uniextra/movistar-webdav-proxy",
"readme_pushed_at": "",
"compose_sha256": "e51607e979c1a776ececda038caeffb4195f443ebc017eba753a4b4ff8fd5c28",
"generated_at": "2026-11-02T23:42:04+00:00",
"readme_raw_url": "https://raw.githubusercontent.com/uniextra/MovistarWebDAVProxy/e48485bc8bdf9745caa6249fdc92db6d28214549/README.md"
},
"container_contract": {
"service_name": "movistar-webdav-proxy",
"container_name": "movistar-webdav-proxy",
"image": {
"reference": "uniextra/movistar-webdav-proxy:latest",
"registry": "docker.io",
"repository": "uniextra/movistar-webdav-proxy",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "WEBDAV_USER",
"example": "admin",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "WEBDAV_PASSWORD",
"example": "",
"required": true,
"sensitive": true,
"source": "docker-compose",
"prompt": "WebDAV and dashboard password (leave empty to generate)"
},
{
"name": "REQUIRE_AUTH",
"example": "true",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "TOKENS_ENCRYPTION_KEY",
"example": "",
"required": true,
"sensitive": true,
"source": "docker-compose"
},
{
"name": "MAX_UPLOAD_MB",
"example": "4096",
"required": false,
"sensitive": false,
"source": "docker-compose"
}
],
"volumes": [
{
"id": "tokens",
"container_path": "/app/tokens",
"compose_source_example": "./tokens",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 16
}
},
{
"id": "uploads",
"container_path": "/tmp",
"compose_source_example": "./tmp",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 16
}
}
],
"ports": [
{
"container_port": 5000,
"published_example": 5000,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 8080,
"published_example": 8080,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n movistar-webdav-proxy:\n image: uniextra/movistar-webdav-proxy:v2.1.0\n container_name: movistar-webdav-proxy\n restart: unless-stopped\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n environment:\n - WEBDAV_USER=${WEBDAV_USER:-}\n - WEBDAV_PASSWORD=${WEBDAV_PASSWORD:-}\n - WEBUI_USER=${WEBUI_USER:-}\n - WEBUI_PASSWORD=${WEBUI_PASSWORD:-}\n ports:\n - \"${BIND_IP:-0.0.0.0}:${WEB_PORT:-5000}:5000\" # Panel Web de gestión y logs (usa BIND_IP=127.0.0.1 para acceso solo local)\n - \"${BIND_IP:-0.0.0.0}:${WEBDAV_PORT:-5050}:8080\" # Endpoint WebDAV (Duplicati / Synology HyperBackup)\n volumes:\n - movistar_tokens:/app/tokens\n\nvolumes:\n movistar_tokens:\n name: movistar_webdav_tokens\n"
},
"compose_stack": {
"project_name": "movistar-webdav-proxy",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "movistar-webdav-proxy",
"service_count": 1,
"services": [
{
"name": "movistar-webdav-proxy",
"image": "uniextra/movistar-webdav-proxy:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"environment": {
"MAX_UPLOAD_MB": "4096",
"REQUIRE_AUTH": "true",
"TOKENS_ENCRYPTION_KEY": "${GENERATED_TOKENS_ENCRYPTION_KEY}",
"WEBDAV_PASSWORD": "${GENERATED_WEBDAV_PASSWORD}",
"WEBDAV_USER": "admin"
},
"image": "uniextra/movistar-webdav-proxy:latest",
"ports": [
"5000:5000",
"8080:8080"
],
"volumes": [
"./tokens:/app/tokens",
"./tmp:/tmp"
]
}
}
],
"top_level": {
"name": "movistar-webdav-proxy"
},
"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": "movistar-webdav-proxy-volume-0",
"service": "movistar-webdav-proxy",
"container_path": "/app/tokens",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "movistar-webdav-proxy-volume-1",
"service": "movistar-webdav-proxy",
"container_path": "/tmp",
"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": [
"movistar-webdav-proxy"
],
"stop_order": [
"movistar-webdav-proxy"
],
"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": [
{
"id": "tokens-encryption-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "movistar-webdav-proxy",
"environment_variable": "TOKENS_ENCRYPTION_KEY"
}
]
},
{
"id": "webdav-password",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "movistar-webdav-proxy",
"environment_variable": "WEBDAV_PASSWORD"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Web dashboard",
"scheme": "http",
"port": 5000,
"path": "/",
"source": "upstream-documentation"
},
{
"label": "WebDAV",
"scheme": "http",
"port": 8080,
"path": "/",
"source": "native-container-port"
}
],
"credentials": [
{
"label": "Web dashboard and WebDAV",
"type": "configured-or-installer-generated",
"username": "admin",
"username_environment": "WEBDAV_USER",
"password": null,
"password_environment": "WEBDAV_PASSWORD",
"change_required": false,
"source": "installer-generated",
"retrieval": null
}
]
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 1,
"memory_mb": 512,
"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"
},
"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": "not-requested-by-compose"
},
{
"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": "disk-backed-upload-scratch",
"upstream_behavior": "Compose mounts /tmp as a tmpfs.",
"native_lxc_behavior": "A separate managed disk volume provides /tmp and is excluded from backups.",
"reason": "Uploads can reach 4096 MB; a disk-backed scratch volume avoids consuming the LXC RAM limit.",
"behavioral_impact": "Scratch data uses disk and can survive a restart; the application removes completed temporary uploads.",
"validation": "passed-in-lab"
},
{
"id": "native-rootfs-permissions",
"upstream_behavior": "Docker Compose requests read_only for the root filesystem.",
"native_lxc_behavior": "Proxmox uses a writable native root disk; /app is owned by root, while the upstream entrypoint drops to UID 10001. Tokens and upload scratch have separate writable volumes.",
"reason": "The Proxmox pre-start setup requires a writable root disk. Protect application code through root ownership instead of declaring unsupported read-only startup.",
"behavioral_impact": "This is not a fully read-only root filesystem; the application user cannot replace the root-owned application directory or binaries.",
"validation": "passed-in-lab"
}
],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/app",
"remove_lost_found": false,
"owner_strategy": "mapped-root",
"mode": "0755"
},
{
"container_path": "/app/tokens",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"mode": "0700"
},
{
"container_path": "/tmp",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"mode": "1777"
}
],
"volume_owner": {
"uid": 10001,
"gid": 10001
},
"generated_sensitive_environment": {
"WEBDAV_PASSWORD": {
"strategy": "token-hex",
"bytes": 16,
"prompt": true
},
"TOKENS_ENCRYPTION_KEY": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
}
},
"security": {
"options": {
"no_new_privileges": true
}
},
"startup_healthcheck": {
"scheme": "http",
"port": 5000,
"path": "/health",
"timeout_seconds": 90,
"request_timeout_seconds": 5,
"stability_seconds": 4,
"verify_tls": false
},
"completion_notes": [
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations."
]
}
},
"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",
"stop_signal",
"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": "passed",
"service_health": "passed",
"restart_persistence": "passed",
"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
}
}
+477
View File
@@ -0,0 +1,477 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-spoolman",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Spoolman"
},
"tagline": {
"en_US": "Track 3D printer filament spools and usage."
},
"description": {
"en_US": "A self-hosted filament inventory for 3D printers. Manage spools, materials, manufacturers, locations and labels in the web UI, and integrate with Moonraker, OctoPrint and other printer tools. The official image uses SQLite by default; no separate database container is required."
},
"category": "tools",
"category_label": "Tools",
"author": "Donkie",
"developer": "Donkie",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/spoolman.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 8000,
"path": "/"
},
"website": "https://github.com/Donkie/Spoolman",
"documentation": "https://github.com/Donkie/Spoolman/wiki/Installation",
"repository": "https://github.com/Donkie/Spoolman",
"tips": [
"Spoolman has no built-in authentication. Keep it on a trusted network; do not expose it directly to the internet.",
"The official image uses SQLite by default and stores the database, automatic backups, logs and cache in the persistent data directory."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "Donkie",
"repository": "https://github.com/Donkie/Spoolman",
"revision": "ff8efd517b76a4b2783f29c89e9b9fbcd8cd167b",
"image_repository_url": "https://github.com/Donkie/Spoolman/pkgs/container/spoolman",
"readme_pushed_at": "2026-10-31T09:39:19Z",
"compose_sha256": "d9b115c4c37589f9813e54f926fdd22e7a10a44a6e56160f837b1247638fac83",
"generated_at": "2026-11-02T20:02:46+00:00",
"default_branch": "master",
"readme_raw_url": "https://github.com/Donkie/Spoolman/wiki/Installation"
},
"container_contract": {
"service_name": "spoolman",
"container_name": "spoolman",
"image": {
"reference": "ghcr.io/donkie/spoolman:latest",
"registry": "ghcr.io",
"repository": "ghcr.io/donkie/spoolman",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "PUID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default",
"prompt_user": false
},
{
"name": "PGID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default",
"prompt_user": false
},
{
"name": "TZ",
"example": "Etc/UTC",
"required": true,
"sensitive": false,
"source": "upstream-compose",
"prompt_user": false
}
],
"volumes": [
{
"id": "volume-0",
"container_path": "/home/app/.local/share/spoolman",
"compose_source_example": "./data",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
}
],
"ports": [
{
"container_port": 8000,
"published_example": 7912,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n spoolman:\n image: ghcr.io/donkie/spoolman:latest\n container_name: spoolman\n restart: unless-stopped\n ports:\n - \"7912:8000/tcp\"\n volumes:\n - ./data:/home/app/.local/share/spoolman\n environment:\n PUID: \"1000\"\n PGID: \"1000\"\n TZ: Etc/UTC\n"
},
"compose_stack": {
"project_name": "spoolman",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "spoolman",
"service_count": 1,
"services": [
{
"name": "spoolman",
"image": "ghcr.io/donkie/spoolman:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"image": "ghcr.io/donkie/spoolman:latest",
"container_name": "spoolman",
"restart": "unless-stopped",
"ports": [
"7912:8000/tcp"
],
"volumes": [
"./data:/home/app/.local/share/spoolman"
],
"environment": {
"PUID": "1000",
"PGID": "1000",
"TZ": "Etc/UTC"
}
}
}
],
"top_level": {
"name": "spoolman"
},
"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": "spoolman-volume-0",
"service": "spoolman",
"container_path": "/home/app/.local/share/spoolman",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
"spoolman"
],
"stop_order": [
"spoolman"
],
"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": "Spoolman web interface",
"scheme": "http",
"port": 8000,
"path": "/",
"source": "upstream-image"
}
],
"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"
},
"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": "not-requested-by-compose"
},
{
"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"
}
],
"installer_profile": {
"startup_healthcheck": {
"scheme": "http",
"port": 8000,
"path": "/",
"timeout_seconds": 180,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
},
"volume_preparations": [
{
"container_path": "/home/app/.local/share/spoolman",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"volume_owner": {
"uid": 1000,
"gid": 1000
}
}
},
"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": "passed-default-official-image-skopeo-and-first-start-http",
"advanced_install": "pending-custom-install-summary-validation",
"service_health": "passed-web-api-crud",
"restart_persistence": "passed-managed-and-host-bind-database-and-records",
"backup_restore": "passed-pbs-backup-and-manager-recover-record-preserved",
"update_preserves_data": "no-update-available-at-check",
"modification": "passed-cpu-ram-up-and-down-without-image-pull-or-recreate",
"volumes": "passed-managed-storage-and-host-bind-crud-owner-and-data-check",
"removal": "passed-manager-removal-host-path-retained-then-temporary-data-cleaned"
},
"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
}
}
+17 -27
View File
@@ -82,22 +82,6 @@
"backup": true,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/dev/net/tun",
"compose_source_example": "/dev/net/tun",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 8
}
}
],
"ports": [],
@@ -180,17 +164,6 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "tailscale-volume-1",
"service": "tailscale",
"container_path": "/dev/net/tun",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -288,6 +261,23 @@
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"device_requests": [
{
"id": "dev-net-tun",
"kind": "character-device",
"purpose": "tun",
"enable_prompt": "Pass /dev/net/tun to the LXC",
"enabled_default": true,
"required_by_compose": true,
"path_prompt": "Host device for /dev/net/tun",
"host_path_default": "/dev/net/tun",
"container_path": "/dev/net/tun",
"mode": "preserve-host",
"deny_write": false,
"gid_strategy": "host-device-gid",
"source_mapping": "/dev/net/tun:/dev/net/tun"
}
]
},
"adaptations": [
+292
View File
@@ -0,0 +1,292 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-unbound",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Unbound"
},
"tagline": {
"en_US": "A validating, recursive DNS resolver for your private network."
},
"description": {
"en_US": "Run the community-maintained klutchell image of Unbound as a DNS resolver on its own Proxmox LXC. It listens on TCP and UDP port 53 and can load optional custom configuration files."
},
"category": "adblock",
"category_label": "Adblock & DNS",
"author": "Kyle Harding",
"developer": "Kyle Harding (klutchell)",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/unbound.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64",
"armhf"
],
"launch": {
"scheme": "http",
"port": null,
"path": "/"
},
"website": "https://unbound.net/",
"documentation": "https://github.com/klutchell/unbound-docker#readme",
"repository": "https://github.com/klutchell/unbound-docker",
"tips": [
"Unbound has no web interface. Use the LXC IP as a DNS server on TCP and UDP port 53.",
"The default image config listens on all interfaces and accepts clients from private IPv4 ranges. Keep it behind your trusted LAN firewall; do not expose recursive DNS publicly.",
"The original GitHub repository has moved to klutchell/unbound-docker; the published Docker Hub image remains klutchell/unbound:main. The image is an unofficial community image, not published by NLnet Labs.",
"Optional custom .conf files go under /etc/unbound/custom.conf.d and must be readable by UID/GID 101:102 or world-readable."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "klutchell",
"repository": "https://github.com/klutchell/unbound-docker",
"revision": "1efbea960bf9462b6fb6dd71aa702ad32d6820d7",
"default_branch": "main",
"image_repository_url": "https://hub.docker.com/r/klutchell/unbound",
"readme_pushed_at": "2026-11-02T14:31:22Z",
"compose_sha256": "faf73a93c1f7d4d43f1eb61ba2e62a4418bd501f0c827b080ea9897964e75848",
"generated_at": "2026-11-02T21:21:54+00:00",
"readme_raw_url": "https://raw.githubusercontent.com/klutchell/unbound-docker/main/README.md"
},
"container_contract": {
"service_name": "unbound",
"container_name": "unbound",
"image": {
"reference": "klutchell/unbound:main",
"registry": "docker.io",
"repository": "klutchell/unbound",
"tag": "main",
"digest": null,
"pull_policy": "resolve-selected-tag-to-architecture-digest-at-install"
},
"environment": [],
"volumes": [
{
"id": "volume-0",
"container_path": "/etc/unbound/custom.conf.d",
"compose_source_example": "./config",
"read_only": false,
"required": false,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
{
"container_port": 53,
"published_example": 53,
"protocol": "udp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 53,
"published_example": 53,
"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: unbound\nservices:\n unbound:\n image: klutchell/unbound:main\n container_name: unbound\n restart: unless-stopped\n ports:\n - 53:53/udp\n - 53:53/tcp\n volumes:\n - ./config:/etc/unbound/custom.conf.d\n"
},
"compose_stack": {
"project_name": "unbound",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "unbound",
"service_count": 1,
"services": [
{
"name": "unbound",
"image": "klutchell/unbound:main",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"image": "klutchell/unbound:main",
"container_name": "unbound",
"restart": "unless-stopped",
"ports": [
"53:53/udp",
"53:53/tcp"
],
"volumes": [
"./config:/etc/unbound/custom.conf.d"
]
}
}
],
"top_level": {
"name": "unbound"
},
"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": "unbound-volume-0",
"service": "unbound",
"container_path": "/etc/unbound/custom.conf.d",
"mode": "user-selectable",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": "Host directory containing optional custom Unbound .conf files",
"default": "host-bind",
"installation_choice": [
"managed-volume",
"host-bind"
]
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
"unbound"
],
"stop_order": [
"unbound"
],
"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"
]
}
},
"first_run": {
"endpoints": [],
"credentials": []
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 512,
"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": 120
},
"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"
},
"adaptations": [],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/etc/unbound/custom.conf.d",
"owner_strategy": "mapped-root",
"remove_lost_found": true,
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
"type": "running",
"timeout_seconds": 90,
"stability_seconds": 20
}
},
"catalog": {
"replaces_discovered_ids": []
}
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"container_name",
"image",
"ports",
"restart",
"volumes"
],
"untranslated_blockers": [],
"policy": "This single-image Compose contract maps to one unprivileged Proxmox LXC. The optional custom-config directory can use a Proxmox-managed volume or a host directory; the image defaults work with an empty directory."
},
"validation": {
"schema": "passed-at-generation",
"image_resolution": "passed-skopeo-16-layers",
"clean_install": "passed-default-install-and-first-start-healthcheck",
"advanced_install": "pending",
"service_health": "passed-recursion-and-custom-local-data-over-udp-and-tcp",
"restart_persistence": "passed-custom-zone-after-pct-reboot",
"backup_restore": "pending",
"update_preserves_data": "pending-update-check-not-run",
"modification": "pending-manager-ui-unresponsive-no-changes-applied",
"managed_volume": "passed-config-volume-1g",
"host_bind": "pending",
"removal": "passed-manager-remove-ct-disks-and-record-cleaned"
},
"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
}
}
+302
View File
@@ -0,0 +1,302 @@
{
"schema_version": "0.3.0",
"kind": "proxmenux.oci-template",
"id": "image-zwave-js-ui",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Z-Wave JS UI"
},
"tagline": {
"en_US": "Manage a Z-Wave network from a web dashboard and WebSocket gateway."
},
"description": {
"en_US": "A self-hosted Z-Wave control panel and MQTT gateway. Its web interface can start without a Z-Wave controller; device control requires a compatible USB or serial controller and network security keys."
},
"category": "smarthome",
"author": "Z-Wave JS",
"developer": "Z-Wave JS",
"icon": "https://raw.githubusercontent.com/zwave-js/zwave-js-ui/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/public/apple-touch-icon-180x180.png",
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 8091,
"path": "/"
},
"website": "https://zwave-js.github.io/zwave-js-ui/",
"documentation": "https://zwave-js.github.io/zwave-js-ui/#/getting-started/docker",
"repository": "https://github.com/zwave-js/zwave-js-ui",
"tips": [
"The web UI can start without a Z-Wave controller. To control devices, pass through a compatible USB/serial controller and select its stable /dev/serial/by-id/... path in Settings → Z-Wave → Serial Port.",
"Configure and securely back up the S0/S2 network keys in Settings → Z-Wave before pairing security-enabled devices.",
"Port 3000 is the Z-Wave JS WebSocket server for integrations such as Home Assistant; it listens only after a Z-Wave client is configured. Upstream documents it as unauthenticated, so expose it only to trusted integrations and networks.",
"ProxMenux enables the web login and generates the initial admin password. Configure trusted HTTPS before remote access over an untrusted network."
],
"mini_changelog": [],
"display_version": "11.25.2",
"updated_at": "2026-11-02",
"category_label": "IoT & Smart Home"
},
"source": {
"provider": "Z-Wave JS",
"repository": "https://github.com/zwave-js/zwave-js-ui",
"default_branch": "master",
"revision": "c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e",
"readme_raw_url": "https://raw.githubusercontent.com/zwave-js/zwave-js-ui/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/README.md",
"image_repository_url": "https://hub.docker.com/r/zwavejs/zwave-js-ui",
"compose_sha256": "ed371d9e9805db5d2abd356b54e3ff5fdb677d637857313f6aac3f498f480f87",
"generated_at": "2026-11-02T20:10:33+00:00"
},
"container_contract": {
"service_name": "zwave-js-ui",
"container_name": "zwave-js-ui",
"image": {
"reference": "docker.io/zwavejs/zwave-js-ui:latest",
"registry": "docker.io",
"repository": "zwavejs/zwave-js-ui",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-selected-tag-to-architecture-digest-at-install"
},
"environment": [
{
"name": "SESSION_SECRET",
"example": "${GENERATED_SESSION_SECRET}",
"required": true,
"sensitive": true,
"source": "ProxMenux-generated; supported by upstream image"
},
{
"name": "DEFAULT_PASSWORD",
"example": "${GENERATED_ADMIN_PASSWORD}",
"prompt": "Initial Z-Wave JS UI admin password",
"required": true,
"sensitive": true,
"source": "Upstream image; generated by ProxMenux"
}
],
"volumes": [
{
"id": "zwave-config",
"container_path": "/usr/src/app/store",
"compose_source_example": "zwave-config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
}
],
"ports": [
{
"container_port": 8091,
"published_example": 8091,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 3000,
"published_example": 3000,
"protocol": "tcp",
"required": false,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "always",
"stop_grace_period": null,
"original_compose": "version: '3.7'\nservices:\n zwave-js-ui:\n container_name: zwave-js-ui\n image: zwavejs/zwave-js-ui:latest\n restart: always\n tty: true\n stop_signal: SIGINT\n environment:\n SESSION_SECRET: ${GENERATED_SESSION_SECRET}\n DEFAULT_PASSWORD: ${GENERATED_ADMIN_PASSWORD}\n networks:\n - zwave\n devices:\n - /dev/serial/by-id/insert_stick_reference_here:/dev/zwave\n volumes:\n - zwave-config:/usr/src/app/store\n ports:\n - '8091:8091'\n - '3000:3000'\nnetworks:\n zwave:\nvolumes:\n zwave-config:\n name: zwave-config\n"
},
"first_run": {
"endpoints": [
{
"label": "Z-Wave JS UI",
"scheme": "http",
"port": 8091,
"path": "/",
"source": "official-docker-documentation"
}
],
"credentials": [
{
"label": "Z-Wave JS UI admin login",
"type": "configured-or-installer-generated",
"username": "admin",
"password": null,
"password_environment": "DEFAULT_PASSWORD",
"change_required": false,
"source": "Upstream DEFAULT_PASSWORD, generated by ProxMenux",
"retrieval": null
}
]
},
"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": true,
"features": [
"nesting=1"
],
"shutdown_timeout_seconds": 60
},
"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": "apply-compose-override"
},
"adaptations": [
{
"id": "dedicated-lxc-network",
"upstream_behavior": "Docker publishes ports on a Docker host address and attaches the service to its Compose network.",
"native_lxc_behavior": "The OCI process listens on the dedicated LXC address; ProxMenux maps the single-service network to the LXC network namespace.",
"reason": "A one-service native OCI LXC has its own network namespace and address.",
"behavioral_impact": "Use the LXC address directly; no Docker port NAT or separate bridge is created.",
"validation": "pending-per-application"
},
{
"id": "generated-login-and-session-secret",
"upstream_behavior": "The image defaults to user admin and password zwave; web authentication is disabled by default, and the Compose example uses a sample session secret.",
"native_lxc_behavior": "ProxMenux generates a unique initial admin password and session secret, and seeds gateway.authEnabled=true before first boot.",
"reason": "Avoid shipping a shared password, sample session key or unauthenticated web UI.",
"behavioral_impact": "The installer must display the generated admin password; change it after first login.",
"validation": "pending-per-application"
},
{
"id": "stop-signal",
"upstream_behavior": "Compose requests SIGINT when stopping the container.",
"native_lxc_behavior": "ProxMenux uses the same signal for the OCI process shutdown lifecycle.",
"reason": "Preserve the upstream graceful-shutdown behavior.",
"behavioral_impact": "None expected.",
"validation": "pending-per-application"
}
],
"installer_profile": {
"generated_sensitive_environment": {
"SESSION_SECRET": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
},
"DEFAULT_PASSWORD": {
"strategy": "token-hex",
"bytes": 20,
"prompt": true
}
},
"generated_files": [
{
"container_path": "/usr/src/app/store/settings.json",
"mode": "0600",
"owner": "mapped-root",
"content": "{\"gateway\":{\"authEnabled\":true}}\n",
"only_if_missing": true
}
],
"volume_preparations": [
{
"container_path": "/usr/src/app/store",
"remove_lost_found": true,
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"optional_devices": [
{
"id": "zwave-serial-controller",
"kind": "character-device",
"purpose": "serial",
"enable_prompt": "Pass through a Z-Wave USB or serial controller",
"enabled_default": false,
"required_by_compose": false,
"path_prompt": "Controller path on the Proxmox host; prefer /dev/serial/by-id/...",
"host_path_default": "/dev/serial/by-id/insert_stick_reference_here",
"container_path_strategy": "same-as-host",
"mode": "preserve-host",
"deny_write": false,
"gid_strategy": "host-device-gid"
}
],
"startup_healthcheck": {
"scheme": "http",
"port": 8091,
"path": "/",
"timeout_seconds": 180,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": false
}
}
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"container_name",
"devices",
"environment",
"image",
"networks",
"ports",
"restart",
"stop_signal",
"tty",
"volumes"
],
"untranslated_blockers": [],
"policy": "The single-image recipe has passed installation, UI/login, restart, backup and recovery, modification, and engine checks. Host-bind storage has not been checked. Radio control remains untested because no compatible Z-Wave coordinator was attached during the lab test."
},
"validation": {
"schema": "passed-static-validation",
"image_resolution": "passed-skopeo-download-and-layer-verification",
"clean_install": "passed-default-install-and-health-check",
"advanced_install": "pending-custom-install-summary-validation",
"web_response": "passed-ui-and-generated-login",
"credentials": "passed-generated-login-before-and-after-restore",
"restart_persistence": "passed-restart-with-store-preserved",
"backup_restore": "passed-proxmox-backup-and-manager-recover-new-vmid",
"update_preserves_data": "no-update-available-at-check",
"modification": "passed-cpu-ram-up-down-without-recreation",
"managed_volume": "passed-store-volume-and-recovery",
"host_bind": "pending",
"hardware_radio": "not-tested-no-z-wave-coordinator-on-host",
"removal": "passed-remove-original-and-restored-ct-and-local-backup-cleanup"
},
"lifecycle": {
"update_strategy": "replace-rootfs-from-new-oci-image-preserve-managed-volumes",
"registry_state": {
"resolved_architecture": null,
"resolved_digest": null,
"image_version_label": "11.25.2",
"image_created": null
},
"change_detection": "compare-resolved-architecture-digest",
"automatic_unattended_updates": false
}
}
+5 -1
View File
@@ -432,6 +432,10 @@
"metabase": "monitoring",
"kopia": "backup",
"restic-rest-server": "backup",
"netalertx": "monitoring"
"netalertx": "monitoring",
"kitchenowl": "productivity",
"unbound": "adblock",
"spoolman": "tools",
"zwave-js-ui": "smarthome"
}
}
+585
View File
@@ -0,0 +1,585 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-airdcpp-webclient",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "AirDC++ Web Client",
"es_ES": "AirDC++ Web Client"
},
"tagline": {
"en_US": "Direct Connect file sharing with a web interface",
"es_ES": "Intercambio de archivos Direct Connect con interfaz web"
},
"description": {
"en_US": "AirDC++ Web Client using the community-maintained gangefors image. Configuration, downloads and shared content use separate persistent paths.",
"es_ES": "AirDC++ Web Client con la imagen comunitaria de gangefors. La configuración, las descargas y los archivos compartidos usan rutas persistentes separadas."
},
"category": "downloads",
"category_label": "Files & Downloads",
"author": "gangefors",
"developer": "AirDC++",
"icon": "https://raw.githubusercontent.com/airdcpp-web/airdcpp-webui/eeeea5025a5e720f38dac53ced8e002940f00a74/resources/images/logo.svg",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64"
],
"launch": {
"scheme": "http",
"port": 5600,
"path": "/"
},
"website": "https://airdcpp-web.github.io/",
"documentation": "https://github.com/gangefors/docker-airdcpp-webclient",
"repository": "https://github.com/gangefors/docker-airdcpp-webclient",
"tips": [
"The upstream initial login is admin / password. Change it in Settings > System > Users before allowing access beyond a trusted LAN.",
"Configuration uses /.airdcpp on a managed Proxmox volume included in backups. /Downloads and /Share can use managed volumes or explicitly selected host directories.",
"Files under /Share are shared by AirDC++. Select the contents deliberately; router forwarding and hub configuration are separate administrator actions.",
"PUID and PGID default to 1000 and must be at least 101. The original entrypoint creates the application account; volume ownership follows these values.",
"Open HTTP port 5600 on the dedicated LXC address. HTTPS port 5601 uses the upstream self-signed certificate; use a trusted reverse proxy certificate for remote web access.",
"Incoming Direct Connect traffic uses TCP and UDP 21248 and TLS TCP 21249. ProxMenux keeps those native ports and does not configure your router."
],
"mini_changelog": [],
"display_version": null,
"updated_at": "2026-10-25"
},
"source": {
"provider": "gangefors",
"repository": "https://github.com/gangefors/docker-airdcpp-webclient",
"revision": "37f2e939c152fb2c0158bc83bc76cc972aed965e",
"image_repository_url": "https://hub.docker.com/r/gangefors/airdcpp-webclient",
"readme_pushed_at": "2026-10-25T11:51:53Z",
"compose_sha256": "4ce8aec542cfc3e7f5ca142e309a41f04aca444bd1724ca1741c96acb5c3d5bf",
"generated_at": "2026-11-03T11:47:04+00:00",
"default_branch": "master",
"readme_raw_url": "https://raw.githubusercontent.com/gangefors/docker-airdcpp-webclient/37f2e939c152fb2c0158bc83bc76cc972aed965e/docker-compose.yml"
},
"container_contract": {
"service_name": "airdcpp",
"container_name": "airdcpp",
"image": {
"reference": "docker.io/gangefors/airdcpp-webclient:latest",
"registry": "docker.io",
"repository": "docker.io/gangefors/airdcpp-webclient",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "PUID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-compose"
},
{
"name": "PGID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-compose"
},
{
"name": "UMASK",
"example": "0022",
"required": true,
"sensitive": false,
"source": "upstream-compose"
}
],
"volumes": [
{
"id": "config",
"container_path": "/.airdcpp",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 16
}
},
{
"id": "downloads",
"container_path": "/Downloads",
"compose_source_example": "./downloads",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 32
}
},
{
"id": "share",
"container_path": "/Share",
"compose_source_example": "./share",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 32
}
}
],
"ports": [
{
"container_port": 5600,
"published_example": 5600,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 5601,
"published_example": 5601,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 21248,
"published_example": 21248,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 21248,
"published_example": 21248,
"protocol": "udp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 21249,
"published_example": 21249,
"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: airdcpp-webclient\nservices:\n airdcpp:\n image: docker.io/gangefors/airdcpp-webclient:latest\n restart: unless-stopped\n environment:\n PUID: '1000'\n PGID: '1000'\n UMASK: '0022'\n ports:\n - 5600:5600\n - 5601:5601\n - 21248:21248/tcp\n - 21248:21248/udp\n - 21249:21249/tcp\n volumes:\n - ./config:/.airdcpp\n - ./downloads:/Downloads\n - ./share:/Share\n"
},
"compose_stack": {
"project_name": "airdcpp-webclient",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "airdcpp",
"service_count": 1,
"services": [
{
"name": "airdcpp",
"image": "docker.io/gangefors/airdcpp-webclient:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"image": "docker.io/gangefors/airdcpp-webclient:latest",
"restart": "unless-stopped",
"environment": {
"PUID": "1000",
"PGID": "1000",
"UMASK": "0022"
},
"ports": [
"5600:5600",
"5601:5601",
"21248:21248/tcp",
"21248:21248/udp",
"21249:21249/tcp"
],
"volumes": [
"./config:/.airdcpp",
"./downloads:/Downloads",
"./share:/Share"
]
}
}
],
"top_level": {
"name": "airdcpp-webclient"
},
"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": "airdcpp-volume-0",
"service": "airdcpp",
"container_path": "/.airdcpp",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "airdcpp-volume-1",
"service": "airdcpp",
"container_path": "/Downloads",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "airdcpp-volume-2",
"service": "airdcpp",
"container_path": "/Share",
"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": [
"airdcpp"
],
"stop_order": [
"airdcpp"
],
"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": 5600,
"path": "/",
"source": "https://github.com/gangefors/docker-airdcpp-webclient"
}
],
"credentials": [
{
"label": "AirDC++ administrator",
"type": "static-default",
"username": "admin",
"password": "password",
"change_required": true,
"source": "https://github.com/gangefors/docker-airdcpp-webclient#run-the-application"
}
]
},
"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"
},
"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": "not-requested-by-compose"
},
{
"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"
}
],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/.airdcpp",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/Downloads",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/Share",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
"scheme": "http",
"port": 5600,
"path": "/index.html",
"timeout_seconds": 180,
"request_timeout_seconds": 5,
"stability_seconds": 4,
"verify_tls": true
}
}
},
"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",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "Community image and single-LXC contract reviewed against the publisher Compose and entrypoint; Linux amd64 confirmed in the registry manifest. Runtime lifecycle checks remain pending."
},
"validation": {
"schema": "passed-at-generation",
"clean_install": "pending",
"service_health": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending",
"authenticated_login": "pending",
"advanced_install": "pending",
"host_bind_storage": "pending",
"managed_storage": "pending",
"removal": "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
}
}
+453
View File
@@ -0,0 +1,453 @@
{
"schema_version": "0.4.0",
"kind": "proxmenux.oci-template",
"id": "image-authelia",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Authelia",
"es_ES": "Authelia"
},
"tagline": {
"en_US": "Single sign-on and multi-factor authentication",
"es_ES": "Inicio de sesión único y autenticación multifactor"
},
"description": {
"en_US": "An open-source authentication and authorization server for protecting web applications. This single-image profile provides a local SQLite database and file notifications; configure your domain, reverse proxy and access rules before protecting other services.",
"es_ES": "Servidor de autenticación y autorización de código abierto para proteger aplicaciones web. Este perfil de una sola imagen incluye una base de datos SQLite local y notificaciones en archivo; configura el dominio, el proxy inverso y las reglas de acceso antes de proteger otros servicios."
},
"category": "security",
"category_label": "Authentication & Security",
"author": "Authelia",
"developer": "Authelia",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 9091,
"path": "/"
},
"website": "https://www.authelia.com/",
"documentation": "https://www.authelia.com/integration/deployment/docker/",
"repository": "https://github.com/authelia/authelia",
"tips": [
"The installer creates a unique admin password and stores only its Argon2id hash in /config/users_database.yml. The password is shown in the installation result.",
"Authelia requires an HTTPS portal URL and a cookie domain matching that hostname. If you use Nginx Proxy Manager, follow the official integration guide and configure trusted proxies and forward-auth separately.",
"The default notifier writes messages to /config/notification.txt. Configure SMTP in /config/configuration.yml if you want email delivery.",
"The initial access-control policy is one_factor. Add explicit access-control rules before attaching Authelia to protected proxy hosts."
],
"mini_changelog": [],
"display_version": "4.39.28",
"updated_at": "2026-09-17"
},
"source": {
"provider": "authelia",
"repository": "https://github.com/authelia/authelia",
"default_branch": "master",
"revision": "8da42b2348b0351c6284a2940a9c5eec3d4cd3fd",
"readme_raw_url": "https://raw.githubusercontent.com/authelia/authelia/v4.39.28/examples/compose/lite/compose.yml",
"readme_pushed_at": "2026-09-17T12:13:30Z",
"compose_sha256": "848ce0f70e502981a050ac200d9d1882978a6c3b0f9f0dfcc6d52ce66ca37fb5",
"generated_at": "2026-11-03T09:10:57+00:00",
"image_repository_url": "https://hub.docker.com/r/authelia/authelia"
},
"container_contract": {
"service_name": "authelia",
"container_name": "authelia",
"image": {
"reference": "docker.io/authelia/authelia:4.39.28",
"registry": "docker.io",
"repository": "docker.io/authelia/authelia",
"tag": "4.39.28",
"digest": null,
"pull_policy": "resolve-selected-tag-to-architecture-digest-at-install"
},
"environment": [
{
"name": "PROXMENUX_ADMIN_PASSWORD",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-first-start"
},
{
"name": "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-generated"
},
{
"name": "AUTHELIA_SESSION_SECRET",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-generated"
},
{
"name": "AUTHELIA_STORAGE_ENCRYPTION_KEY",
"example": "",
"required": true,
"sensitive": true,
"source": "proxmenux-generated"
},
{
"name": "X_AUTHELIA_CONFIG_FILTERS",
"example": "template",
"required": true,
"sensitive": false,
"source": "authelia-container"
},
{
"name": "PROXMENUX_AUTHELIA_COOKIE_DOMAIN",
"example": "",
"prompt": "Authelia session cookie domain for this portal (for example example.com)",
"required": true,
"sensitive": false,
"source": "administrator"
},
{
"name": "PROXMENUX_AUTHELIA_URL",
"example": "",
"prompt": "Public HTTPS URL used to open this Authelia portal (for example https://auth.example.com)",
"required": true,
"sensitive": false,
"source": "administrator"
}
],
"volumes": [
{
"id": "config",
"container_path": "/config",
"compose_source_example": "./authelia",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
}
],
"ports": [
{
"container_port": 9091,
"published_example": 9091,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n authelia:\n entrypoint:\n - /bin/sh\n - /usr/local/bin/proxmenux-authelia-bootstrap\n environment:\n AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: ${GENERATED_RESET_PASSWORD_JWT_SECRET}\n AUTHELIA_SESSION_SECRET: ${GENERATED_SESSION_SECRET}\n AUTHELIA_STORAGE_ENCRYPTION_KEY: ${GENERATED_STORAGE_ENCRYPTION_KEY}\n PROXMENUX_ADMIN_PASSWORD: ${GENERATED_ADMIN_PASSWORD}\n PROXMENUX_AUTHELIA_COOKIE_DOMAIN: ${AUTHELIA_COOKIE_DOMAIN}\n PROXMENUX_AUTHELIA_URL: ${AUTHELIA_URL}\n X_AUTHELIA_CONFIG_FILTERS: template\n image: docker.io/authelia/authelia:4.39.28\n ports:\n - 9091:9091\n restart: unless-stopped\n volumes:\n - ./authelia:/config\n"
},
"first_run": {
"endpoints": [
{
"label": "Authelia Web UI",
"scheme": "http",
"port": 9091,
"path": "/",
"source": "official-image"
}
],
"credentials": [
{
"label": "Authelia administrator",
"type": "configured-or-installer-generated",
"username": "admin",
"password": null,
"password_environment": "PROXMENUX_ADMIN_PASSWORD",
"change_required": false,
"source": "Generated on first start and stored as an Argon2id hash in the persistent users file."
}
]
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 512,
"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": 60
},
"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": {
"runtime": {
"compose_entrypoint": [
"/bin/sh",
"/usr/local/bin/proxmenux-authelia-bootstrap"
]
},
"generated_sensitive_environment": {
"PROXMENUX_ADMIN_PASSWORD": {
"strategy": "token-hex",
"bytes": 24,
"prompt": false
},
"AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
},
"AUTHELIA_SESSION_SECRET": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
},
"AUTHELIA_STORAGE_ENCRYPTION_KEY": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
}
},
"minimum_secret_length": {
"PROXMENUX_ADMIN_PASSWORD": 24,
"AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET": 32,
"AUTHELIA_SESSION_SECRET": 32,
"AUTHELIA_STORAGE_ENCRYPTION_KEY": 32
},
"generated_files": [
{
"container_path": "/config/configuration.yml",
"mode": "0600",
"owner": "mapped-root",
"only_if_missing": true,
"content": "---\nserver:\n address: 'tcp://:9091'\n\nlog:\n level: 'info'\n\ntotp:\n issuer: 'Authelia'\n\nidentity_validation:\n reset_password:\n jwt_secret: 'configured-by-environment'\n\nauthentication_backend:\n file:\n path: '/config/users_database.yml'\n watch: true\n\naccess_control:\n default_policy: 'one_factor'\n\nsession:\n secret: 'configured-by-environment'\n cookies:\n - name: 'authelia_session'\n domain: '{{ env \"PROXMENUX_AUTHELIA_COOKIE_DOMAIN\" }}'\n authelia_url: '{{ env \"PROXMENUX_AUTHELIA_URL\" }}'\n expiration: '1 hour'\n inactivity: '5 minutes'\n\nstorage:\n encryption_key: 'configured-by-environment'\n local:\n path: '/config/db.sqlite3'\n\nnotifier:\n filesystem:\n filename: '/config/notification.txt'\n"
},
{
"container_path": "/usr/local/bin/proxmenux-authelia-bootstrap",
"mode": "0700",
"owner": "mapped-root",
"only_if_missing": false,
"content": "#!/bin/sh\nset -eu\numask 077\n\nusers_file=/config/users_database.yml\nif [ ! -s \"$users_file\" ]; then\n : \"${PROXMENUX_ADMIN_PASSWORD:?ProxMenux did not provide an initial administrator password}\"\n mkdir -p /config\n hash_output=$(authelia crypto hash generate argon2 --password \"$PROXMENUX_ADMIN_PASSWORD\" --no-confirm)\n password_hash=$(printf '%s\\n' \"$hash_output\" | sed -n 's/^Digest: //p')\n case \"$password_hash\" in\n '$argon2id$'*) ;;\n *) printf '%s\\n' 'Authelia did not return an Argon2id password hash' >&2; exit 1 ;;\n esac\n temporary_file=\"${users_file}.tmp.$$\"\n cat >\"$temporary_file\" <<EOF\nusers:\n admin:\n disabled: false\n displayname: 'Authelia Administrator'\n password: \"$password_hash\"\n groups:\n - 'admins'\nEOF\n chmod 0600 \"$temporary_file\"\n mv -f \"$temporary_file\" \"$users_file\"\nfi\nunset PROXMENUX_ADMIN_PASSWORD\nexec /app/entrypoint.sh \"$@\"\n"
}
],
"startup_healthcheck": {
"scheme": "http",
"port": 9091,
"path": "/api/health",
"timeout_seconds": 240,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"adaptations": [
{
"id": "dedicated-lxc-network",
"upstream_behavior": "Docker publishes selected container ports on the Docker host.",
"native_lxc_behavior": "The application listens on the same container ports at its dedicated LXC address.",
"reason": "A native LXC has its own address and does not require Docker port NAT.",
"behavioral_impact": "Users open the LXC address instead of the Proxmox host address.",
"validation": "pending-per-application"
},
{
"id": "compose-environment-overlay",
"upstream_behavior": "Compose environment values override OCI image environment values.",
"native_lxc_behavior": "ProxMenux merges the same values into lxc.environment.runtime while the CT is stopped.",
"reason": "PVE imports image Env automatically; Compose values still need to override it.",
"behavioral_impact": "None expected.",
"validation": "pending-per-application"
},
{
"id": "stop-grace-period",
"upstream_behavior": "Docker waits for Compose stop_grace_period before forcing termination.",
"native_lxc_behavior": "ProxMenux records the same timeout for its pct shutdown lifecycle operations.",
"reason": "Proxmox has no equivalent per-CT persistent restart-policy field; startup.down is not a shutdown timeout.",
"behavioral_impact": "Normal Proxmox node shutdown remains governed by the node-wide shutdown policy.",
"validation": "not-requested-by-compose"
},
{
"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-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": "pending-per-application"
},
{
"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": "not-requested-by-compose"
},
{
"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"
}
]
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"cap_add",
"command",
"container_name",
"cpu_shares",
"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",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "A single-image profile for the Authelia authentication portal. ProxMenux creates a unique initial administrator account; the local notifier and SQLite storage are kept in /config. Reverse-proxy trust and explicit access-control rules remain administrator settings."
},
"validation": {
"schema": "pending-local-validation",
"clean_install": "pending",
"service_health": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending"
},
"lifecycle": {
"update_strategy": "replace-rootfs-from-new-oci-image-preserve-managed-volumes",
"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
}
}
+383
View File
@@ -0,0 +1,383 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-kitchenowl",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "KitchenOwl"
},
"tagline": {
"en_US": "Self-hosted grocery list and recipe manager"
},
"description": {
"en_US": "Manage shared shopping lists, recipes and meal plans in one self-hosted app."
},
"category": "productivity",
"category_label": "Productivity & Workflows",
"author": "Tom Bursch",
"developer": "Tom Bursch",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 80,
"path": "/"
},
"website": "https://kitchenowl.org/",
"documentation": "https://docs.kitchenowl.org/self-hosting/",
"repository": "https://github.com/TomBursch/kitchenowl",
"tips": [
"This recipe follows KitchenOwl’s standard official Compose deployment: a web frontend and backend in separate OCI LXCs.",
"The upstream sample uses PLEASE_CHANGE_ME for JWT_SECRET_KEY; this recipe generates a unique signing key at install time."
],
"mini_changelog": [],
"display_version": null,
"updated_at": "2026-11-01"
},
"source": {
"provider": "tombursch",
"repository": "https://github.com/TomBursch/kitchenowl",
"revision": "60a012ea5cae829878f78a46a585a7ee530b069a",
"image_repository_url": "https://hub.docker.com/r/tombursch/kitchenowl-web",
"readme_pushed_at": "2026-11-01T23:22:22Z",
"compose_sha256": "f620124c2a4943d4f2465ee304677bc9ed172a088f0fe43f5db7cbc8f6424263",
"generated_at": "2026-11-02T20:13:05+00:00",
"default_branch": "main",
"readme_raw_url": "https://raw.githubusercontent.com/TomBursch/kitchenowl/main/docker-compose.yml"
},
"container_contract": {
"service_name": "front",
"container_name": "front",
"image": {
"reference": "tombursch/kitchenowl-web:latest",
"registry": "docker.io",
"repository": "tombursch/kitchenowl-web",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [],
"volumes": [],
"ports": [
{
"container_port": 80,
"published_example": 80,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [
{
"name": "back",
"image": "tombursch/kitchenowl-backend:latest"
}
],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n back:\n environment:\n JWT_SECRET_KEY: ${GENERATED_JWT_SECRET_KEY}\n image: tombursch/kitchenowl-backend:latest\n restart: unless-stopped\n volumes:\n - read_only: false\n source: kitchenowl_data\n target: /data\n front:\n depends_on:\n - back\n image: tombursch/kitchenowl-web:latest\n ports:\n - 80:80\n restart: unless-stopped\n volumes: []\nvolumes:\n kitchenowl_data: null\n"
},
"compose_stack": {
"project_name": "kitchenowl",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "front",
"service_count": 2,
"services": [
{
"name": "back",
"image": "tombursch/kitchenowl-backend:latest",
"is_main": false,
"role": "dependency",
"vmid_offset": 1,
"depends_on": [],
"frontend_network": false,
"private_network": true,
"compose": {
"environment": {
"JWT_SECRET_KEY": "${GENERATED_JWT_SECRET_KEY}"
},
"image": "tombursch/kitchenowl-backend:latest",
"restart": "unless-stopped",
"volumes": [
{
"source": "kitchenowl_data",
"target": "/data",
"read_only": false
}
],
"healthcheck": {
"test": [
"CMD-SHELL",
"uwsgi_curl localhost:5000 /api/health/8M4F88S8ooi4sMbLBfkkV7ctWwgibW6V || exit 1"
],
"interval": "60s",
"timeout": "3s",
"retries": 3
}
}
},
{
"name": "front",
"image": "tombursch/kitchenowl-web:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [
"back"
],
"frontend_network": true,
"private_network": true,
"compose": {
"depends_on": [
"back"
],
"image": "tombursch/kitchenowl-web:latest",
"ports": [
"80:80"
],
"restart": "unless-stopped",
"volumes": [],
"healthcheck": {
"test": [
"CMD-SHELL",
"curl -f http://localhost/ || exit 1"
],
"interval": "30s",
"timeout": "3s",
"retries": 3
}
}
}
],
"top_level": {
"volumes": {
"kitchenowl_data": null
}
},
"networking": {
"frontend": "selected-proxmox-bridge",
"private_required": true,
"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": "back-volume-0",
"service": "back",
"container_path": "/data",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 2,
"start_order": [
"back",
"front"
],
"stop_order": [
"front",
"back"
],
"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": [
{
"id": "generated-jwt-secret-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "back",
"environment_variable": "JWT_SECRET_KEY"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "KitchenOwl",
"scheme": "http",
"port": 80,
"path": "/",
"source": "upstream-documentation"
}
],
"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"
},
"adaptations": [
{
"id": "generated-jwt-signing-key",
"upstream_behavior": "The official Compose example uses the placeholder PLEASE_CHANGE_ME for JWT_SECRET_KEY and instructs operators to replace it.",
"native_lxc_behavior": "ProxMenux generates a unique cryptographically random JWT signing key and supplies it to the backend service.",
"reason": "Avoid deploying the documented placeholder or placing a signing secret in the public recipe.",
"behavioral_impact": "The generated key is not an app login credential; KitchenOwl has no catalog-provided default account.",
"validation": "pending-runtime"
},
{
"id": "image-healthchecks-preserved",
"upstream_behavior": "The web and backend OCI images declare Docker healthchecks in image metadata.",
"native_lxc_behavior": "The current image healthchecks are recorded in the stack so ProxMenux can check backend readiness and frontend availability during first start.",
"reason": "The Compose file inherits these checks from its images instead of declaring them itself.",
"behavioral_impact": "Checks gate stack startup; Docker does not run as a separate health-monitoring daemon inside the LXCs.",
"validation": "pending-runtime"
}
],
"custom_compose_stack": true,
"custom_compose_interpolated": false,
"stack_image_entrypoints": {},
"installer_profile": {
"stack_driver": "generic-multi-lxc-stack",
"volume_preparations": [
{
"container_path": "/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
]
},
"stack_completion_notes": [
"Open the web address and follow KitchenOwl’s onboarding to create the first account; no default account is configured."
]
},
"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",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "The official two-image Compose stack is mapped to native Proxmox OCI LXCs with private service networking. Lab checks confirmed installation, web/API behavior, persistence, backup and recovery, modification, managed storage, and host-bind storage. Advanced custom settings have not been validated."
},
"validation": {
"schema": "passed-at-generation",
"clean_install": "passed-default-official-two-service-stack-and-first-start",
"advanced_install": "pending-custom-install-summary-validation",
"service_health": "passed-web-api-and-created-user-household-list-item",
"restart_persistence": "passed-managed-and-host-bind-data",
"backup_restore": "passed-manager-recover-new-vmids-stack-network-and-data",
"update_preserves_data": "no-update-available-at-check",
"modification": "passed-added-managed-volume-without-image-pull-or-container-recreate",
"volumes": "passed-managed-storage-and-host-bind-with-owner-and-data-check",
"removal": "passed-manager-removal-temporary-data-cleaned-after-retention-check"
},
"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,
"dependency_lifecycle": {
"implementation": "proxmox-hookscript",
"trigger": "main-lxc-pre-start",
"waits_for_dependency_healthchecks": true,
"stops_dependencies_with_main": false
}
}
}
+490
View File
@@ -0,0 +1,490 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-krusader",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Krusader",
"es_ES": "Krusader"
},
"tagline": {
"en_US": "KDE's twin-panel file manager in a browser-based desktop.",
"es_ES": "El gestor de archivos de dos paneles de KDE, disponible desde el navegador."
},
"description": {
"en_US": "Open Krusader in a browser and manage files from a directory you explicitly mount at /storage. The maintained jlesage image provides the graphical desktop; ProxMenux runs it as one native OCI LXC.",
"es_ES": "Abre Krusader en el navegador y gestiona los archivos de una carpeta que montes expresamente en /storage. La imagen mantenida por jlesage incluye el escritorio gráfico; ProxMenux la ejecuta como un LXC OCI nativo."
},
"category": "desktop",
"category_label": "Browsers & Web Desktops",
"author": "jlesage",
"developer": "jlesage",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/krusader.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "https",
"port": 5800,
"path": "/"
},
"website": "https://krusader.org/",
"documentation": "https://github.com/jlesage/docker-krusader",
"repository": "https://github.com/jlesage/docker-krusader",
"tips": [
"The web login uses HTTPS with an automatically generated self-signed certificate and password. Accept the certificate warning on the first visit or configure a trusted certificate.",
"Krusader can read, change and delete files in the directory mounted at /storage. Mount only the directory you intend to manage; the rest of the Proxmox host is not exposed.",
"The VNC port is not published. Use the browser interface on port 5800."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "jlesage",
"repository": "https://github.com/jlesage/docker-krusader",
"revision": "855c8140e1bb65b2a2526d82db44c808f23eb49f",
"image_repository_url": "https://hub.docker.com/r/jlesage/krusader",
"readme_pushed_at": "2026-09-22T15:18:17Z",
"compose_sha256": "9dd16cc30aa19afe7e148598c9cfb0c65c31606d67f3f22254c33a4d05212c7f",
"generated_at": "2026-11-02T20:05:40+00:00",
"default_branch": "master",
"readme_raw_url": "https://raw.githubusercontent.com/jlesage/docker-krusader/855c8140e1bb65b2a2526d82db44c808f23eb49f/README.md"
},
"container_contract": {
"service_name": "krusader",
"container_name": "krusader",
"image": {
"reference": "jlesage/krusader:latest",
"registry": "docker.io",
"repository": "jlesage/krusader",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "USER_ID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default"
},
{
"name": "GROUP_ID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default"
},
{
"name": "TZ",
"example": "Europe/Madrid",
"required": true,
"sensitive": false,
"source": "ProxMenux-local-timezone"
},
{
"name": "VNC_LOCALHOST_ONLY",
"example": "1",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
},
{
"name": "WEB_AUTHENTICATION",
"example": "1",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
},
{
"name": "WEB_AUTHENTICATION_USERNAME",
"example": "admin",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
},
{
"name": "WEB_AUTHENTICATION_PASSWORD",
"example": "${GENERATED_WEB_PASSWORD}",
"required": true,
"sensitive": true,
"source": "ProxMenux-generated-credential"
},
{
"name": "SECURE_CONNECTION",
"example": "1",
"required": true,
"sensitive": false,
"source": "upstream-image-security-setting"
}
],
"volumes": [
{
"id": "krusader-config",
"container_path": "/config",
"compose_source_example": "krusader-config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
},
{
"id": "krusader-storage",
"container_path": "/storage",
"compose_source_example": "/mnt/oci-shared/krusader/storage",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": true,
"default_size_gb": 8
}
}
],
"ports": [
{
"container_port": 5800,
"published_example": 5800,
"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: krusader\nservices:\n krusader:\n image: jlesage/krusader:latest\n container_name: krusader\n restart: unless-stopped\n environment:\n USER_ID: '1000'\n GROUP_ID: '1000'\n TZ: Europe/Madrid\n VNC_LOCALHOST_ONLY: '1'\n WEB_AUTHENTICATION: '1'\n WEB_AUTHENTICATION_USERNAME: admin\n WEB_AUTHENTICATION_PASSWORD: ${GENERATED_WEB_PASSWORD}\n SECURE_CONNECTION: '1'\n ports:\n - 5800:5800/tcp\n volumes:\n - krusader-config:/config\n - /mnt/oci-shared/krusader/storage:/storage\nvolumes:\n krusader-config: {}\n"
},
"compose_stack": {
"project_name": "krusader",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "krusader",
"service_count": 1,
"services": [
{
"name": "krusader",
"image": "jlesage/krusader:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"environment": {
"GROUP_ID": "1000",
"SECURE_CONNECTION": "1",
"TZ": "Europe/Madrid",
"USER_ID": "1000",
"VNC_LOCALHOST_ONLY": "1",
"WEB_AUTHENTICATION": "1",
"WEB_AUTHENTICATION_PASSWORD": "${GENERATED_GENERATED_WEB_PASSWORD}",
"WEB_AUTHENTICATION_USERNAME": "admin"
},
"image": "jlesage/krusader:latest",
"ports": [
"5800:5800"
],
"volumes": [
"./config:/config",
"./storage:/storage"
]
}
}
],
"top_level": {
"name": "krusader"
},
"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": "krusader-volume-0",
"service": "krusader",
"container_path": "/config",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "krusader-volume-1",
"service": "krusader",
"container_path": "/storage",
"mode": "user-selectable",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": "Host directory for Krusader:/storage",
"default": "host-bind",
"installation_choice": [
"managed-volume",
"host-bind"
]
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
"krusader"
],
"stop_order": [
"krusader"
],
"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": [
{
"id": "generated-web-password",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "krusader",
"environment_variable": "WEB_AUTHENTICATION_PASSWORD"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Krusader Web UI",
"scheme": "https",
"port": 5800,
"path": "/",
"source": "upstream-jlesage-secure-connection-and-web-authentication"
}
],
"credentials": [
{
"label": "Krusader Web UI",
"type": "configured-or-installer-generated",
"username": "admin",
"password": null,
"username_environment": "WEB_AUTHENTICATION_USERNAME",
"password_environment": "WEB_AUTHENTICATION_PASSWORD",
"change_required": false,
"source": "ProxMenux generated password and upstream image environment",
"retrieval": null
}
]
},
"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"
},
"adaptations": [
{
"id": "web-authentication-https",
"upstream_behavior": "The jlesage image can protect its browser GUI with WEB_AUTHENTICATION and requires SECURE_CONNECTION for authenticated web access.",
"native_lxc_behavior": "Enable authenticated HTTPS on port 5800, generate a unique password at install and keep VNC bound to localhost.",
"reason": "Krusader can modify files exposed under /storage, so the browser endpoint must not be unauthenticated.",
"behavioral_impact": "The image generates a self-signed TLS certificate; a browser may ask to trust it on first access.",
"validation": "pending"
},
{
"id": "scoped-storage-mount",
"upstream_behavior": "Docker Compose mounts the selected host directory at /storage.",
"native_lxc_behavior": "The same selected directory is mounted in the dedicated OCI LXC; no other host paths are exposed.",
"reason": "The file manager must only see data explicitly selected by the user.",
"behavioral_impact": "Write access permits changing or deleting files in that one directory.",
"validation": "pending"
},
{
"id": "dedicated-lxc-network",
"upstream_behavior": "Docker publishes web port 5800 on the Docker host.",
"native_lxc_behavior": "The native LXC listens on its own IP at port 5800 without Docker NAT.",
"reason": "The LXC has a dedicated network namespace and address.",
"behavioral_impact": "The port is reached at the LXC address rather than through a host-published port.",
"validation": "pending"
}
],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/config",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"uid": 1000,
"gid": 1000,
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/storage",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"uid": 1000,
"gid": 1000,
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
"scheme": "https",
"port": 5800,
"path": "/",
"timeout_seconds": 240,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": false
},
"generated_sensitive_environment": {
"WEB_AUTHENTICATION_PASSWORD": {
"strategy": "token-hex",
"bytes": 16,
"prompt": false
}
},
"volume_owner": {
"uid": 1000,
"gid": 1000
}
}
},
"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",
"stop_signal",
"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": "passed-first-start",
"service_health": "passed-login-page-and-authenticated-session-http-200",
"restart_persistence": "passed-container-restart-and-host-bind-write-check",
"backup_restore": "pending",
"update_preserves_data": "pending",
"image_resolution": "passed-skopeo-327mb-11-layers-integrity",
"credentials": "passed-generated-password-login-and-session-cookie",
"advanced_install": "pending-custom-install-summary-validation",
"managed_volume": "passed-managed-config-volume",
"host_bind": "passed-storage-bind-owner-and-app-user-write",
"browser_visual": "pending-self-signed-certificate-review",
"modification": "pending",
"removal": "passed-manager-remove-ct-disks-record-and-empty-test-path-cleaned",
"web_authentication": "passed-generated-credential-post-and-session-cookie"
},
"lifecycle": {
"update_strategy": "resolve-latest-image-then-apply-reviewed-native-lxc-update",
"registry_state": {
"resolved_architecture": "amd64",
"resolved_digest": "sha256:ecb77bb73b1d3c66720eefb07805f70289349b449d1ddaa15d120c399bd89cc4",
"image_version_label": "26.09.1",
"image_created": "2026-09-22T15:22:56.230858151Z"
},
"change_detection": "compare-compose-sha256-and-resolved-latest-image-digest",
"automatic_unattended_updates": false
}
}
+24 -2
View File
@@ -68,7 +68,7 @@
"source": "docker-compose"
},
{
"name": "LLAMA_ARG_CONTEXT_SIZE",
"name": "LLAMA_ARG_CTX_SIZE",
"example": "8192",
"required": true,
"sensitive": false,
@@ -142,7 +142,7 @@
],
"environment": [
"LLAMA_ARG_N_GPU_LAYERS=999",
"LLAMA_ARG_CONTEXT_SIZE=8192",
"LLAMA_ARG_CTX_SIZE=8192",
"LLAMA_ARG_MODELS_DIR=/models"
],
"devices": [
@@ -342,6 +342,28 @@
"0x1002"
]
}
],
"environment": [
{
"name": "LLAMA_ARG_FLASH_ATTN",
"value": "off"
},
{
"name": "LLAMA_ARG_UBATCH",
"value": "32"
},
{
"name": "LLAMA_ARG_BATCH",
"value": "128"
},
{
"name": "GGML_VK_DISABLE_F16",
"value": "1"
},
{
"name": "GGML_VK_DISABLE_ASYNC",
"value": "1"
}
]
},
{
@@ -0,0 +1,609 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-movistar-webdav-proxy",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Movistar / O2 Cloud WebDAV Proxy"
},
"tagline": {
"en_US": "Access Movistar and O2 Cloud from WebDAV clients and backup tools."
},
"description": {
"en_US": "Official uniextra image with a web dashboard for Movistar/O2 Cloud account linking. Persistent OAuth credentials, authenticated WebDAV and disk-backed temporary uploads."
},
"category": "backup",
"category_label": "Backup & Recovery",
"author": "uniextra",
"developer": "uniextra",
"icon": "https://raw.githubusercontent.com/uniextra/MovistarWebDAVProxy/main/static/favicon.svg",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 5000,
"path": "/"
},
"website": "https://github.com/uniextra/MovistarWebDAVProxy",
"documentation": "https://github.com/uniextra/MovistarWebDAVProxy#readme",
"repository": "https://github.com/uniextra/MovistarWebDAVProxy",
"tips": [
"Link your Movistar or O2 account from the dashboard after installation.",
"Use the container IP and port 8080 for WebDAV. Docker's published port 5050 is not used by native OCI LXC.",
"Temporary uploads use a separate disk volume, excluded from backups, to avoid filling RAM."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "uniextra",
"repository": "https://github.com/uniextra/MovistarWebDAVProxy",
"revision": "e48485bc8bdf9745caa6249fdc92db6d28214549",
"image_repository_url": "https://hub.docker.com/r/uniextra/movistar-webdav-proxy",
"readme_pushed_at": "",
"compose_sha256": "e51607e979c1a776ececda038caeffb4195f443ebc017eba753a4b4ff8fd5c28",
"generated_at": "2026-11-02T23:42:04+00:00",
"readme_raw_url": "https://raw.githubusercontent.com/uniextra/MovistarWebDAVProxy/e48485bc8bdf9745caa6249fdc92db6d28214549/README.md"
},
"container_contract": {
"service_name": "movistar-webdav-proxy",
"container_name": "movistar-webdav-proxy",
"image": {
"reference": "uniextra/movistar-webdav-proxy:latest",
"registry": "docker.io",
"repository": "uniextra/movistar-webdav-proxy",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "WEBDAV_USER",
"example": "admin",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "WEBDAV_PASSWORD",
"example": "",
"required": true,
"sensitive": true,
"source": "docker-compose",
"prompt": "WebDAV and dashboard password (leave empty to generate)"
},
{
"name": "REQUIRE_AUTH",
"example": "true",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "TOKENS_ENCRYPTION_KEY",
"example": "",
"required": true,
"sensitive": true,
"source": "docker-compose"
},
{
"name": "MAX_UPLOAD_MB",
"example": "4096",
"required": false,
"sensitive": false,
"source": "docker-compose"
}
],
"volumes": [
{
"id": "tokens",
"container_path": "/app/tokens",
"compose_source_example": "./tokens",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 16
}
},
{
"id": "uploads",
"container_path": "/tmp",
"compose_source_example": "./tmp",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 16
}
}
],
"ports": [
{
"container_port": 5000,
"published_example": 5000,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 8080,
"published_example": 8080,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n movistar-webdav-proxy:\n image: uniextra/movistar-webdav-proxy:v2.1.0\n container_name: movistar-webdav-proxy\n restart: unless-stopped\n read_only: true\n tmpfs:\n - /tmp\n security_opt:\n - no-new-privileges:true\n environment:\n - WEBDAV_USER=${WEBDAV_USER:-}\n - WEBDAV_PASSWORD=${WEBDAV_PASSWORD:-}\n - WEBUI_USER=${WEBUI_USER:-}\n - WEBUI_PASSWORD=${WEBUI_PASSWORD:-}\n ports:\n - \"${BIND_IP:-0.0.0.0}:${WEB_PORT:-5000}:5000\" # Panel Web de gestión y logs (usa BIND_IP=127.0.0.1 para acceso solo local)\n - \"${BIND_IP:-0.0.0.0}:${WEBDAV_PORT:-5050}:8080\" # Endpoint WebDAV (Duplicati / Synology HyperBackup)\n volumes:\n - movistar_tokens:/app/tokens\n\nvolumes:\n movistar_tokens:\n name: movistar_webdav_tokens\n"
},
"compose_stack": {
"project_name": "movistar-webdav-proxy",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "movistar-webdav-proxy",
"service_count": 1,
"services": [
{
"name": "movistar-webdav-proxy",
"image": "uniextra/movistar-webdav-proxy:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"environment": {
"MAX_UPLOAD_MB": "4096",
"REQUIRE_AUTH": "true",
"TOKENS_ENCRYPTION_KEY": "${GENERATED_TOKENS_ENCRYPTION_KEY}",
"WEBDAV_PASSWORD": "${GENERATED_WEBDAV_PASSWORD}",
"WEBDAV_USER": "admin"
},
"image": "uniextra/movistar-webdav-proxy:latest",
"ports": [
"5000:5000",
"8080:8080"
],
"volumes": [
"./tokens:/app/tokens",
"./tmp:/tmp"
]
}
}
],
"top_level": {
"name": "movistar-webdav-proxy"
},
"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": "movistar-webdav-proxy-volume-0",
"service": "movistar-webdav-proxy",
"container_path": "/app/tokens",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "movistar-webdav-proxy-volume-1",
"service": "movistar-webdav-proxy",
"container_path": "/tmp",
"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": [
"movistar-webdav-proxy"
],
"stop_order": [
"movistar-webdav-proxy"
],
"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": [
{
"id": "tokens-encryption-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "movistar-webdav-proxy",
"environment_variable": "TOKENS_ENCRYPTION_KEY"
}
]
},
{
"id": "webdav-password",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "movistar-webdav-proxy",
"environment_variable": "WEBDAV_PASSWORD"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Web dashboard",
"scheme": "http",
"port": 5000,
"path": "/",
"source": "upstream-documentation"
},
{
"label": "WebDAV",
"scheme": "http",
"port": 8080,
"path": "/",
"source": "native-container-port"
}
],
"credentials": [
{
"label": "Web dashboard and WebDAV",
"type": "configured-or-installer-generated",
"username": "admin",
"username_environment": "WEBDAV_USER",
"password": null,
"password_environment": "WEBDAV_PASSWORD",
"change_required": false,
"source": "installer-generated",
"retrieval": null
}
]
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 1,
"memory_mb": 512,
"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"
},
"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": "not-requested-by-compose"
},
{
"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": "disk-backed-upload-scratch",
"upstream_behavior": "Compose mounts /tmp as a tmpfs.",
"native_lxc_behavior": "A separate managed disk volume provides /tmp and is excluded from backups.",
"reason": "Uploads can reach 4096 MB; a disk-backed scratch volume avoids consuming the LXC RAM limit.",
"behavioral_impact": "Scratch data uses disk and can survive a restart; the application removes completed temporary uploads.",
"validation": "passed-in-lab"
},
{
"id": "native-rootfs-permissions",
"upstream_behavior": "Docker Compose requests read_only for the root filesystem.",
"native_lxc_behavior": "Proxmox uses a writable native root disk; /app is owned by root, while the upstream entrypoint drops to UID 10001. Tokens and upload scratch have separate writable volumes.",
"reason": "The Proxmox pre-start setup requires a writable root disk. Protect application code through root ownership instead of declaring unsupported read-only startup.",
"behavioral_impact": "This is not a fully read-only root filesystem; the application user cannot replace the root-owned application directory or binaries.",
"validation": "passed-in-lab"
}
],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/app",
"remove_lost_found": false,
"owner_strategy": "mapped-root",
"mode": "0755"
},
{
"container_path": "/app/tokens",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"mode": "0700"
},
{
"container_path": "/tmp",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"mode": "1777"
}
],
"volume_owner": {
"uid": 10001,
"gid": 10001
},
"generated_sensitive_environment": {
"WEBDAV_PASSWORD": {
"strategy": "token-hex",
"bytes": 16,
"prompt": true
},
"TOKENS_ENCRYPTION_KEY": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
}
},
"security": {
"options": {
"no_new_privileges": true
}
},
"startup_healthcheck": {
"scheme": "http",
"port": 5000,
"path": "/health",
"timeout_seconds": 90,
"request_timeout_seconds": 5,
"stability_seconds": 4,
"verify_tls": false
},
"completion_notes": [
"Open the web dashboard and link your Movistar/O2 account using its SMS flow.",
"WebDAV is available on container port 8080. Account linking is required before cloud file operations."
]
}
},
"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",
"stop_signal",
"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": "passed",
"service_health": "passed",
"restart_persistence": "passed",
"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
}
}
+477
View File
@@ -0,0 +1,477 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-spoolman",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Spoolman"
},
"tagline": {
"en_US": "Track 3D printer filament spools and usage."
},
"description": {
"en_US": "A self-hosted filament inventory for 3D printers. Manage spools, materials, manufacturers, locations and labels in the web UI, and integrate with Moonraker, OctoPrint and other printer tools. The official image uses SQLite by default; no separate database container is required."
},
"category": "tools",
"category_label": "Tools",
"author": "Donkie",
"developer": "Donkie",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/spoolman.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 8000,
"path": "/"
},
"website": "https://github.com/Donkie/Spoolman",
"documentation": "https://github.com/Donkie/Spoolman/wiki/Installation",
"repository": "https://github.com/Donkie/Spoolman",
"tips": [
"Spoolman has no built-in authentication. Keep it on a trusted network; do not expose it directly to the internet.",
"The official image uses SQLite by default and stores the database, automatic backups, logs and cache in the persistent data directory."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "Donkie",
"repository": "https://github.com/Donkie/Spoolman",
"revision": "ff8efd517b76a4b2783f29c89e9b9fbcd8cd167b",
"image_repository_url": "https://github.com/Donkie/Spoolman/pkgs/container/spoolman",
"readme_pushed_at": "2026-10-31T09:39:19Z",
"compose_sha256": "d9b115c4c37589f9813e54f926fdd22e7a10a44a6e56160f837b1247638fac83",
"generated_at": "2026-11-02T20:02:46+00:00",
"default_branch": "master",
"readme_raw_url": "https://github.com/Donkie/Spoolman/wiki/Installation"
},
"container_contract": {
"service_name": "spoolman",
"container_name": "spoolman",
"image": {
"reference": "ghcr.io/donkie/spoolman:latest",
"registry": "ghcr.io",
"repository": "ghcr.io/donkie/spoolman",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "PUID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default",
"prompt_user": false
},
{
"name": "PGID",
"example": "1000",
"required": true,
"sensitive": false,
"source": "upstream-image-default",
"prompt_user": false
},
{
"name": "TZ",
"example": "Etc/UTC",
"required": true,
"sensitive": false,
"source": "upstream-compose",
"prompt_user": false
}
],
"volumes": [
{
"id": "volume-0",
"container_path": "/home/app/.local/share/spoolman",
"compose_source_example": "./data",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
}
],
"ports": [
{
"container_port": 8000,
"published_example": 7912,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "services:\n spoolman:\n image: ghcr.io/donkie/spoolman:latest\n container_name: spoolman\n restart: unless-stopped\n ports:\n - \"7912:8000/tcp\"\n volumes:\n - ./data:/home/app/.local/share/spoolman\n environment:\n PUID: \"1000\"\n PGID: \"1000\"\n TZ: Etc/UTC\n"
},
"compose_stack": {
"project_name": "spoolman",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "spoolman",
"service_count": 1,
"services": [
{
"name": "spoolman",
"image": "ghcr.io/donkie/spoolman:latest",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"image": "ghcr.io/donkie/spoolman:latest",
"container_name": "spoolman",
"restart": "unless-stopped",
"ports": [
"7912:8000/tcp"
],
"volumes": [
"./data:/home/app/.local/share/spoolman"
],
"environment": {
"PUID": "1000",
"PGID": "1000",
"TZ": "Etc/UTC"
}
}
}
],
"top_level": {
"name": "spoolman"
},
"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": "spoolman-volume-0",
"service": "spoolman",
"container_path": "/home/app/.local/share/spoolman",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
"spoolman"
],
"stop_order": [
"spoolman"
],
"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": "Spoolman web interface",
"scheme": "http",
"port": 8000,
"path": "/",
"source": "upstream-image"
}
],
"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"
},
"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": "not-requested-by-compose"
},
{
"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"
}
],
"installer_profile": {
"startup_healthcheck": {
"scheme": "http",
"port": 8000,
"path": "/",
"timeout_seconds": 180,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
},
"volume_preparations": [
{
"container_path": "/home/app/.local/share/spoolman",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"volume_owner": {
"uid": 1000,
"gid": 1000
}
}
},
"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": "passed-default-official-image-skopeo-and-first-start-http",
"advanced_install": "pending-custom-install-summary-validation",
"service_health": "passed-web-api-crud",
"restart_persistence": "passed-managed-and-host-bind-database-and-records",
"backup_restore": "passed-pbs-backup-and-manager-recover-record-preserved",
"update_preserves_data": "no-update-available-at-check",
"modification": "passed-cpu-ram-up-and-down-without-image-pull-or-recreate",
"volumes": "passed-managed-storage-and-host-bind-crud-owner-and-data-check",
"removal": "passed-manager-removal-host-path-retained-then-temporary-data-cleaned"
},
"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
}
}
+292
View File
@@ -0,0 +1,292 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-unbound",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Unbound"
},
"tagline": {
"en_US": "A validating, recursive DNS resolver for your private network."
},
"description": {
"en_US": "Run the community-maintained klutchell image of Unbound as a DNS resolver on its own Proxmox LXC. It listens on TCP and UDP port 53 and can load optional custom configuration files."
},
"category": "adblock",
"category_label": "Adblock & DNS",
"author": "Kyle Harding",
"developer": "Kyle Harding (klutchell)",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/unbound.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64",
"armhf"
],
"launch": {
"scheme": "http",
"port": null,
"path": "/"
},
"website": "https://unbound.net/",
"documentation": "https://github.com/klutchell/unbound-docker#readme",
"repository": "https://github.com/klutchell/unbound-docker",
"tips": [
"Unbound has no web interface. Use the LXC IP as a DNS server on TCP and UDP port 53.",
"The default image config listens on all interfaces and accepts clients from private IPv4 ranges. Keep it behind your trusted LAN firewall; do not expose recursive DNS publicly.",
"The original GitHub repository has moved to klutchell/unbound-docker; the published Docker Hub image remains klutchell/unbound:main. The image is an unofficial community image, not published by NLnet Labs.",
"Optional custom .conf files go under /etc/unbound/custom.conf.d and must be readable by UID/GID 101:102 or world-readable."
],
"mini_changelog": [],
"display_version": null,
"updated_at": null
},
"source": {
"provider": "klutchell",
"repository": "https://github.com/klutchell/unbound-docker",
"revision": "1efbea960bf9462b6fb6dd71aa702ad32d6820d7",
"default_branch": "main",
"image_repository_url": "https://hub.docker.com/r/klutchell/unbound",
"readme_pushed_at": "2026-11-02T14:31:22Z",
"compose_sha256": "faf73a93c1f7d4d43f1eb61ba2e62a4418bd501f0c827b080ea9897964e75848",
"generated_at": "2026-11-02T21:21:54+00:00",
"readme_raw_url": "https://raw.githubusercontent.com/klutchell/unbound-docker/main/README.md"
},
"container_contract": {
"service_name": "unbound",
"container_name": "unbound",
"image": {
"reference": "klutchell/unbound:main",
"registry": "docker.io",
"repository": "klutchell/unbound",
"tag": "main",
"digest": null,
"pull_policy": "resolve-selected-tag-to-architecture-digest-at-install"
},
"environment": [],
"volumes": [
{
"id": "volume-0",
"container_path": "/etc/unbound/custom.conf.d",
"compose_source_example": "./config",
"read_only": false,
"required": false,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
{
"container_port": 53,
"published_example": 53,
"protocol": "udp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 53,
"published_example": 53,
"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: unbound\nservices:\n unbound:\n image: klutchell/unbound:main\n container_name: unbound\n restart: unless-stopped\n ports:\n - 53:53/udp\n - 53:53/tcp\n volumes:\n - ./config:/etc/unbound/custom.conf.d\n"
},
"compose_stack": {
"project_name": "unbound",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "unbound",
"service_count": 1,
"services": [
{
"name": "unbound",
"image": "klutchell/unbound:main",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [],
"frontend_network": true,
"private_network": false,
"compose": {
"image": "klutchell/unbound:main",
"container_name": "unbound",
"restart": "unless-stopped",
"ports": [
"53:53/udp",
"53:53/tcp"
],
"volumes": [
"./config:/etc/unbound/custom.conf.d"
]
}
}
],
"top_level": {
"name": "unbound"
},
"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": "unbound-volume-0",
"service": "unbound",
"container_path": "/etc/unbound/custom.conf.d",
"mode": "user-selectable",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": "Host directory containing optional custom Unbound .conf files",
"default": "host-bind",
"installation_choice": [
"managed-volume",
"host-bind"
]
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
"unbound"
],
"stop_order": [
"unbound"
],
"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"
]
}
},
"first_run": {
"endpoints": [],
"credentials": []
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 512,
"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": 120
},
"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"
},
"adaptations": [],
"installer_profile": {
"volume_preparations": [
{
"container_path": "/etc/unbound/custom.conf.d",
"owner_strategy": "mapped-root",
"remove_lost_found": true,
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
"type": "running",
"timeout_seconds": 90,
"stability_seconds": 20
}
},
"catalog": {
"replaces_discovered_ids": []
}
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"container_name",
"image",
"ports",
"restart",
"volumes"
],
"untranslated_blockers": [],
"policy": "This single-image Compose contract maps to one unprivileged Proxmox LXC. The optional custom-config directory can use a Proxmox-managed volume or a host directory; the image defaults work with an empty directory."
},
"validation": {
"schema": "passed-at-generation",
"image_resolution": "passed-skopeo-16-layers",
"clean_install": "passed-default-install-and-first-start-healthcheck",
"advanced_install": "pending",
"service_health": "passed-recursion-and-custom-local-data-over-udp-and-tcp",
"restart_persistence": "passed-custom-zone-after-pct-reboot",
"backup_restore": "pending",
"update_preserves_data": "pending-update-check-not-run",
"modification": "pending-manager-ui-unresponsive-no-changes-applied",
"managed_volume": "passed-config-volume-1g",
"host_bind": "pending",
"removal": "passed-manager-remove-ct-disks-and-record-cleaned"
},
"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
}
}
+302
View File
@@ -0,0 +1,302 @@
{
"schema_version": "0.3.0",
"kind": "proxmenux.oci-template",
"id": "image-zwave-js-ui",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Z-Wave JS UI"
},
"tagline": {
"en_US": "Manage a Z-Wave network from a web dashboard and WebSocket gateway."
},
"description": {
"en_US": "A self-hosted Z-Wave control panel and MQTT gateway. Its web interface can start without a Z-Wave controller; device control requires a compatible USB or serial controller and network security keys."
},
"category": "smarthome",
"author": "Z-Wave JS",
"developer": "Z-Wave JS",
"icon": "https://raw.githubusercontent.com/zwave-js/zwave-js-ui/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/public/apple-touch-icon-180x180.png",
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 8091,
"path": "/"
},
"website": "https://zwave-js.github.io/zwave-js-ui/",
"documentation": "https://zwave-js.github.io/zwave-js-ui/#/getting-started/docker",
"repository": "https://github.com/zwave-js/zwave-js-ui",
"tips": [
"The web UI can start without a Z-Wave controller. To control devices, pass through a compatible USB/serial controller and select its stable /dev/serial/by-id/... path in Settings → Z-Wave → Serial Port.",
"Configure and securely back up the S0/S2 network keys in Settings → Z-Wave before pairing security-enabled devices.",
"Port 3000 is the Z-Wave JS WebSocket server for integrations such as Home Assistant; it listens only after a Z-Wave client is configured. Upstream documents it as unauthenticated, so expose it only to trusted integrations and networks.",
"ProxMenux enables the web login and generates the initial admin password. Configure trusted HTTPS before remote access over an untrusted network."
],
"mini_changelog": [],
"display_version": "11.25.2",
"updated_at": "2026-11-02",
"category_label": "IoT & Smart Home"
},
"source": {
"provider": "Z-Wave JS",
"repository": "https://github.com/zwave-js/zwave-js-ui",
"default_branch": "master",
"revision": "c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e",
"readme_raw_url": "https://raw.githubusercontent.com/zwave-js/zwave-js-ui/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/README.md",
"image_repository_url": "https://hub.docker.com/r/zwavejs/zwave-js-ui",
"compose_sha256": "ed371d9e9805db5d2abd356b54e3ff5fdb677d637857313f6aac3f498f480f87",
"generated_at": "2026-11-02T20:10:33+00:00"
},
"container_contract": {
"service_name": "zwave-js-ui",
"container_name": "zwave-js-ui",
"image": {
"reference": "docker.io/zwavejs/zwave-js-ui:latest",
"registry": "docker.io",
"repository": "zwavejs/zwave-js-ui",
"tag": "latest",
"digest": null,
"pull_policy": "resolve-selected-tag-to-architecture-digest-at-install"
},
"environment": [
{
"name": "SESSION_SECRET",
"example": "${GENERATED_SESSION_SECRET}",
"required": true,
"sensitive": true,
"source": "ProxMenux-generated; supported by upstream image"
},
{
"name": "DEFAULT_PASSWORD",
"example": "${GENERATED_ADMIN_PASSWORD}",
"prompt": "Initial Z-Wave JS UI admin password",
"required": true,
"sensitive": true,
"source": "Upstream image; generated by ProxMenux"
}
],
"volumes": [
{
"id": "zwave-config",
"container_path": "/usr/src/app/store",
"compose_source_example": "zwave-config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 4
}
}
],
"ports": [
{
"container_port": 8091,
"published_example": 8091,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 3000,
"published_example": 3000,
"protocol": "tcp",
"required": false,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [],
"restart": "always",
"stop_grace_period": null,
"original_compose": "version: '3.7'\nservices:\n zwave-js-ui:\n container_name: zwave-js-ui\n image: zwavejs/zwave-js-ui:latest\n restart: always\n tty: true\n stop_signal: SIGINT\n environment:\n SESSION_SECRET: ${GENERATED_SESSION_SECRET}\n DEFAULT_PASSWORD: ${GENERATED_ADMIN_PASSWORD}\n networks:\n - zwave\n devices:\n - /dev/serial/by-id/insert_stick_reference_here:/dev/zwave\n volumes:\n - zwave-config:/usr/src/app/store\n ports:\n - '8091:8091'\n - '3000:3000'\nnetworks:\n zwave:\nvolumes:\n zwave-config:\n name: zwave-config\n"
},
"first_run": {
"endpoints": [
{
"label": "Z-Wave JS UI",
"scheme": "http",
"port": 8091,
"path": "/",
"source": "official-docker-documentation"
}
],
"credentials": [
{
"label": "Z-Wave JS UI admin login",
"type": "configured-or-installer-generated",
"username": "admin",
"password": null,
"password_environment": "DEFAULT_PASSWORD",
"change_required": false,
"source": "Upstream DEFAULT_PASSWORD, generated by ProxMenux",
"retrieval": null
}
]
},
"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": true,
"features": [
"nesting=1"
],
"shutdown_timeout_seconds": 60
},
"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": "apply-compose-override"
},
"adaptations": [
{
"id": "dedicated-lxc-network",
"upstream_behavior": "Docker publishes ports on a Docker host address and attaches the service to its Compose network.",
"native_lxc_behavior": "The OCI process listens on the dedicated LXC address; ProxMenux maps the single-service network to the LXC network namespace.",
"reason": "A one-service native OCI LXC has its own network namespace and address.",
"behavioral_impact": "Use the LXC address directly; no Docker port NAT or separate bridge is created.",
"validation": "pending-per-application"
},
{
"id": "generated-login-and-session-secret",
"upstream_behavior": "The image defaults to user admin and password zwave; web authentication is disabled by default, and the Compose example uses a sample session secret.",
"native_lxc_behavior": "ProxMenux generates a unique initial admin password and session secret, and seeds gateway.authEnabled=true before first boot.",
"reason": "Avoid shipping a shared password, sample session key or unauthenticated web UI.",
"behavioral_impact": "The installer must display the generated admin password; change it after first login.",
"validation": "pending-per-application"
},
{
"id": "stop-signal",
"upstream_behavior": "Compose requests SIGINT when stopping the container.",
"native_lxc_behavior": "ProxMenux uses the same signal for the OCI process shutdown lifecycle.",
"reason": "Preserve the upstream graceful-shutdown behavior.",
"behavioral_impact": "None expected.",
"validation": "pending-per-application"
}
],
"installer_profile": {
"generated_sensitive_environment": {
"SESSION_SECRET": {
"strategy": "token-hex",
"bytes": 32,
"prompt": false
},
"DEFAULT_PASSWORD": {
"strategy": "token-hex",
"bytes": 20,
"prompt": true
}
},
"generated_files": [
{
"container_path": "/usr/src/app/store/settings.json",
"mode": "0600",
"owner": "mapped-root",
"content": "{\"gateway\":{\"authEnabled\":true}}\n",
"only_if_missing": true
}
],
"volume_preparations": [
{
"container_path": "/usr/src/app/store",
"remove_lost_found": true,
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"optional_devices": [
{
"id": "zwave-serial-controller",
"kind": "character-device",
"purpose": "serial",
"enable_prompt": "Pass through a Z-Wave USB or serial controller",
"enabled_default": false,
"required_by_compose": false,
"path_prompt": "Controller path on the Proxmox host; prefer /dev/serial/by-id/...",
"host_path_default": "/dev/serial/by-id/insert_stick_reference_here",
"container_path_strategy": "same-as-host",
"mode": "preserve-host",
"deny_write": false,
"gid_strategy": "host-device-gid"
}
],
"startup_healthcheck": {
"scheme": "http",
"port": 8091,
"path": "/",
"timeout_seconds": 180,
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": false
}
}
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"container_name",
"devices",
"environment",
"image",
"networks",
"ports",
"restart",
"stop_signal",
"tty",
"volumes"
],
"untranslated_blockers": [],
"policy": "The single-image recipe has passed installation, UI/login, restart, backup and recovery, modification, and engine checks. Host-bind storage has not been checked. Radio control remains untested because no compatible Z-Wave coordinator was attached during the lab test."
},
"validation": {
"schema": "passed-static-validation",
"image_resolution": "passed-skopeo-download-and-layer-verification",
"clean_install": "passed-default-install-and-health-check",
"advanced_install": "pending-custom-install-summary-validation",
"web_response": "passed-ui-and-generated-login",
"credentials": "passed-generated-login-before-and-after-restore",
"restart_persistence": "passed-restart-with-store-preserved",
"backup_restore": "passed-proxmox-backup-and-manager-recover-new-vmid",
"update_preserves_data": "no-update-available-at-check",
"modification": "passed-cpu-ram-up-down-without-recreation",
"managed_volume": "passed-store-volume-and-recovery",
"host_bind": "pending",
"hardware_radio": "not-tested-no-z-wave-coordinator-on-host",
"removal": "passed-remove-original-and-restored-ct-and-local-backup-cleanup"
},
"lifecycle": {
"update_strategy": "replace-rootfs-from-new-oci-image-preserve-managed-volumes",
"registry_state": {
"resolved_architecture": null,
"resolved_digest": null,
"image_version_label": "11.25.2",
"image_created": null
},
"change_detection": "compare-resolved-architecture-digest",
"automatic_unattended_updates": false
}
}
+266 -1
View File
@@ -2,7 +2,7 @@
"schema_version": "0.2.0",
"kind": "proxmenux.oci-catalog-index",
"provider": "multiple-container-images",
"generated_at": "2026-10-30T18:13:36+00:00",
"generated_at": "2026-11-02T21:25:05+00:00",
"applications": [
{
"id": "2fauth",
@@ -9980,6 +9980,39 @@
"hidden": false,
"template_id": "image-motioneye"
},
{
"id": "movistar-webdav-proxy",
"provider": "uniextra",
"template_family": "curated-profile",
"title": "Movistar / O2 Cloud WebDAV Proxy",
"repository": "https://github.com/uniextra/MovistarWebDAVProxy",
"description": "Official uniextra image with a web dashboard for Movistar/O2 Cloud account linking. Persistent OAuth credentials, authenticated WebDAV and disk-backed temporary uploads.",
"website": "https://github.com/uniextra/MovistarWebDAVProxy",
"icon": "https://raw.githubusercontent.com/uniextra/MovistarWebDAVProxy/main/static/favicon.svg",
"architectures": [
"amd64",
"arm64"
],
"updated_at": null,
"main_image": "uniextra/movistar-webdav-proxy:latest",
"category": "backup",
"category_label": "Backup & Recovery",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/movistar-webdav-proxy.json",
"template_id": "image-movistar-webdav-proxy",
"template": "apps/movistar-webdav-proxy.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
},
{
"id": "msedge",
"provider": "linuxserver.io",
@@ -18734,6 +18767,238 @@
"hidden": true,
"template_id": "image-ztnet"
},
{
"id": "authelia",
"provider": "authelia",
"template_family": "curated-profile",
"title": "Authelia",
"repository": "https://github.com/authelia/authelia",
"description": "An open-source authentication and authorization server for protecting web applications. This single-image profile provides a local SQLite database and file notifications; configure your domain, reverse proxy and access rules before protecting other services.",
"website": "https://www.authelia.com/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": "2026-09-17",
"main_image": "docker.io/authelia/authelia:4.39.28",
"category": "security",
"category_label": "Authentication & Security",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/authelia.json",
"template": "apps/authelia.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-authelia"
},
{
"id": "airdcpp-webclient",
"provider": "gangefors",
"template_family": "curated-profile",
"title": "AirDC++ Web Client",
"repository": "https://github.com/gangefors/docker-airdcpp-webclient",
"description": "AirDC++ Web Client using the community-maintained gangefors image. Configuration, downloads and shared content use separate persistent paths.",
"website": "https://airdcpp-web.github.io/",
"icon": "https://raw.githubusercontent.com/airdcpp-web/airdcpp-webui/eeeea5025a5e720f38dac53ced8e002940f00a74/resources/images/logo.svg",
"architectures": [
"amd64"
],
"updated_at": "2026-10-25",
"main_image": "docker.io/gangefors/airdcpp-webclient:latest",
"category": "downloads",
"category_label": "Files & Downloads",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/airdcpp-webclient.json",
"template_id": "image-airdcpp-webclient",
"template": "apps/airdcpp-webclient.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
},
{
"id": "kitchenowl",
"provider": "tombursch",
"template_family": "curated-profile",
"title": "KitchenOwl",
"repository": "https://github.com/TomBursch/kitchenowl",
"description": "Manage shared shopping lists, recipes and meal plans in one self-hosted app.",
"website": "https://kitchenowl.org/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": "2026-11-01",
"main_image": "tombursch/kitchenowl-web:latest",
"category": "productivity",
"category_label": "Productivity & Workflows",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/kitchenowl.json",
"template_id": "image-kitchenowl",
"template": "apps/kitchenowl.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": true
},
{
"id": "krusader",
"provider": "jlesage",
"template_family": "curated-profile",
"title": "Krusader",
"repository": "https://github.com/jlesage/docker-krusader",
"description": "Open Krusader in a browser and manage files from a directory you explicitly mount at /storage. The maintained jlesage image provides the graphical desktop; ProxMenux runs it as one native OCI LXC.",
"website": "https://krusader.org/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/krusader.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": null,
"main_image": "jlesage/krusader:latest",
"category": "desktop",
"category_label": "Browsers & Web Desktops",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/krusader.json",
"template_id": "image-krusader",
"template": "apps/krusader.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
},
{
"id": "spoolman",
"provider": "Donkie",
"template_family": "curated-profile",
"title": "Spoolman",
"repository": "https://github.com/Donkie/Spoolman",
"description": "A self-hosted filament inventory for 3D printers. Manage spools, materials, manufacturers, locations and labels in the web UI, and integrate with Moonraker, OctoPrint and other printer tools. The official image uses SQLite by default; no separate database container is required.",
"website": "https://github.com/Donkie/Spoolman",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/spoolman.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": null,
"main_image": "ghcr.io/donkie/spoolman:latest",
"category": "tools",
"category_label": "Tools",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/spoolman.json",
"template": "apps/spoolman.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-spoolman",
"source_app_id": "spoolman",
"default_branch": "master",
"source_path": "OCI recipe from upstream installation guide",
"source_revision": "ff8efd517b76a4b2783f29c89e9b9fbcd8cd167b",
"pushed_at": "2026-10-31T09:39:19Z"
},
{
"id": "unbound",
"provider": "klutchell",
"template_family": "curated-profile",
"title": "Unbound",
"repository": "https://github.com/klutchell/unbound-docker",
"description": "Run the community-maintained klutchell image of Unbound as a DNS resolver on its own Proxmox LXC. It listens on TCP and UDP port 53 and can load optional custom configuration files.",
"website": "https://unbound.net/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/unbound.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": null,
"main_image": "klutchell/unbound:main",
"category": "adblock",
"category_label": "Adblock & DNS",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/unbound.json",
"template_id": "image-unbound",
"template": "apps/unbound.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
},
{
"id": "zwave-js-ui",
"provider": "Z-Wave JS",
"template_family": "curated-profile",
"title": "Z-Wave JS UI",
"repository": "https://github.com/zwave-js/zwave-js-ui",
"description": "A self-hosted Z-Wave control panel and MQTT gateway. Its web interface can start without a Z-Wave controller; device control requires a compatible USB or serial controller and network security keys.",
"website": "https://zwave-js.github.io/zwave-js-ui/",
"icon": "https://raw.githubusercontent.com/zwave-js/zwave-js-ui/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/public/apple-touch-icon-180x180.png",
"architectures": [
"amd64",
"arm64"
],
"updated_at": "2026-11-02",
"main_image": "docker.io/zwavejs/zwave-js-ui:latest",
"category": "smarthome",
"category_label": "IoT & Smart Home",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/zwave-js-ui.json",
"template_id": "image-zwave-js-ui",
"template": "apps/zwave-js-ui.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,
"multi_container": false
},
{
"id": "cadcraft",
"provider": "junkerderprovinz",
+10
View File
@@ -116,6 +116,13 @@
"unpackerr": [
"/data"
],
"airdcpp-webclient": [
"/Downloads",
"/Share"
],
"spoolman": [
"/home/app/.local/share/spoolman"
],
"cadcraft": [
"/storage"
],
@@ -157,6 +164,9 @@
]
},
"sizes": {
"krusader": {
"/config": 4
},
"sharecovex": {
"/config": 1
}
+20
View File
@@ -239,6 +239,26 @@ def recover_untouched(root, journal):
return False
def recover_failed_update(root, journal):
"""Attempt the guarded rollback after an update/Modify command fails.
Unlike recovery of an untouched guest, replacement failures may have
parked its volumes or removed its root disk. The existing recovery
journal validates ownership, configuration and backup before restoring.
Stack transactions remain the responsibility of their coordinator.
"""
try:
with instances.locked(root):
state = json.loads(Path(journal).read_text())
if state.get('coordinated') or state.get('phase') in TERMINAL:
return False
recover(root, Path(journal))
return True
except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error:
log(f'automatic update recovery failed: {error}')
return False
def fit(text):
"""A step line that is rewritten in place must not wrap."""
width = max(shutil.get_terminal_size((80, 24)).columns - 8, 30)
+1 -1
View File
@@ -289,7 +289,7 @@ def main():
except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error:
transaction.report_error(error, f'update-{args.vmid}')
journal = transaction.pending_journal()
if journal and not transaction.recover_untouched(instances.ROOT, journal):
if journal and not transaction.recover_failed_update(instances.ROOT, journal):
transaction.recovery_hint()
return 1
+24
View File
@@ -187,6 +187,24 @@ def _split_short_mount(value: str) -> tuple[str | None, str, str | None]:
return ":".join(parts[:-2]), parts[-2], parts[-1]
# Host device nodes a Compose file may bind as a volume. They are passed to
# the LXC as devices: a mount at that path would hide the device.
DEVICE_NODE_BINDS = ("/dev/net/tun", "/dev/fuse", "/dev/kvm")
def _device_bind(item: Any) -> str | None:
"""The `source:target` of a volume that binds a host device node."""
if isinstance(item, dict):
if item.get("type", "bind") != "bind":
return None
source, target = item.get("source"), item.get("target")
else:
source, target, _ = _split_short_mount(str(item))
if str(source or "") in DEVICE_NODE_BINDS and str(target or "") == str(source):
return f"{source}:{target}"
return None
def _mount_contract(value: Any, optional: set[str]) -> list[dict[str, Any]]:
if value is None:
return []
@@ -194,6 +212,8 @@ def _mount_contract(value: Any, optional: set[str]) -> list[dict[str, Any]]:
raise ConversionError("volumes must be a list")
result: list[dict[str, Any]] = []
for index, item in enumerate(value):
if _device_bind(item):
continue
if isinstance(item, dict):
source = item.get("source")
target = item.get("target")
@@ -606,6 +626,10 @@ def _device_installer_profile(
) -> tuple[dict[str, Any], list[str]]:
optional = optional or set()
raw_devices = service.get("devices")
volumes = service.get("volumes")
device_binds = [bind for bind in map(_device_bind, volumes) if bind] if isinstance(volumes, list) else []
if device_binds and (raw_devices is None or isinstance(raw_devices, list)):
raw_devices = [*(raw_devices or []), *device_binds]
requests: list[dict[str, Any]] = []
blockers: list[str] = []
nvidia_requested = _nvidia_requested(service)
+61
View File
@@ -0,0 +1,61 @@
"""The publisher contract survives native catalog composition and both wizards."""
from pathlib import Path
import json
import sys
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(ROOT / 'src'), str(ROOT / 'remote'), str(ROOT / 'tests')]
from proxmenux_oci.catalog import Catalog
from proxmenux_oci.custom import template_from_oci_compose
from proxmenux_oci.installer import build_deployment, DEFAULT_MODE, ADVANCED_MODE
from test_advanced_flow_order import RecordingUI, storages, addresses
class AirDCppRecipeTests(unittest.TestCase):
def test_import_roundtrip_keeps_udp_and_all_persistent_paths(self):
catalog = Catalog(ROOT)
template = catalog.compose('airdcpp-webclient')
restored = template_from_oci_compose(json.dumps(template))
catalog.validate(restored)
ports = {(p['container_port'], p['protocol']) for p in restored['container_contract']['ports']}
self.assertEqual(ports, {(5600,'tcp'),(5601,'tcp'),(21248,'tcp'),(21248,'udp'),(21249,'tcp')})
paths = {v['container_path']:v for v in restored['container_contract']['volumes']}
self.assertEqual(set(paths), {'/.airdcpp','/Downloads','/Share'})
self.assertEqual(paths['/.airdcpp']['installation_choice'], ['managed-volume'])
for path in ('/Downloads','/Share'):
self.assertIn('host-bind', paths[path]['installation_choice'])
self.assertEqual(restored['catalog_ui']['architectures'], ['amd64'])
self.assertEqual(restored['proxmox']['installer_profile']['startup_healthcheck']['path'], '/index.html')
self.assertEqual(restored['proxmox']['image_metadata_policy']['entrypoint'], 'import-from-oci-image')
def test_wizards_preserve_managed_configuration_and_use_selected_shared_directories(self):
from contextlib import ExitStack
with ExitStack() as stack:
for target, value in [
('proxmenux_oci.installer.host.storages', storages),
('proxmenux_oci.installer.access.ask_addresses', addresses)]:
stack.enter_context(patch(target, side_effect=value))
stack.enter_context(patch('proxmenux_oci.i18n.language', return_value='en'))
stack.enter_context(patch('proxmenux_oci.installer.host.bridges',return_value=[{'iface':'vmbr0','cidr':'192.0.2.10/24'}]))
stack.enter_context(patch('proxmenux_oci.installer.host.timezone',return_value='Europe/Madrid'))
stack.enter_context(patch('proxmenux_oci.installer.host.gpus',return_value={'intel':[],'amd':[],'nvidia':False}))
stack.enter_context(patch('proxmenux_oci.installer.access.ask_ipv4',return_value=('dhcp',None)))
for mode in (DEFAULT_MODE, ADVANCED_MODE):
with self.subTest(mode=mode):
ui = RecordingUI({'Where to store /Downloads':'host-bind','Where to store /Share':'host-bind'})
plan = build_deployment(Catalog(ROOT).compose('airdcpp-webclient'), ui, mode)
mounts = {item['container_path']:item for item in plan['mounts']}
self.assertEqual(mounts['/.airdcpp']['type'], 'managed-volume')
self.assertTrue(mounts['/.airdcpp']['backup'])
self.assertEqual(mounts['/Downloads']['type'], 'host-bind')
self.assertEqual(mounts['/Share']['type'], 'host-bind')
env = {item['name']:item['value'] for item in plan['environment']}
self.assertEqual((env['PUID'],env['PGID']),('1000','1000'))
self.assertEqual(plan['resources']['cores'],2)
self.assertTrue(plan['security']['unprivileged'])
if __name__ == '__main__':
unittest.main()
+99
View File
@@ -0,0 +1,99 @@
"""The Authelia profile bootstraps a usable single-image authentication portal."""
import json
from pathlib import Path
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.catalog import Catalog
class AutheliaRecipeTests(unittest.TestCase):
def setUp(self):
self.path = ROOT / "catalog/curated/authelia.json"
self.recipe = json.loads(self.path.read_text(encoding="utf-8"))
self.index_copy = json.loads(
(ROOT / "catalog/apps/authelia.json").read_text(encoding="utf-8")
)
self.profile = self.recipe["proxmox"]["installer_profile"]
self.generated = {
item["container_path"]: item
for item in self.profile["generated_files"]
}
def test_local_catalog_lists_and_loads_the_curated_recipe(self):
catalog = Catalog(ROOT)
item = catalog.find_item("authelia")
self.assertEqual(item["template_family"], "curated-profile")
self.assertEqual(catalog.compose("authelia")["id"], "image-authelia")
self.assertEqual(self.recipe, self.index_copy)
def test_official_image_tag_icon_and_supported_architectures_are_declared(self):
self.assertEqual(
self.recipe["container_contract"]["image"]["reference"],
"docker.io/authelia/authelia:4.39.28",
)
self.assertEqual(self.recipe["catalog_ui"]["architectures"], ["amd64", "arm64"])
self.assertEqual(
self.recipe["catalog_ui"]["icon"],
"https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authelia.webp",
)
def test_sensitive_secrets_are_generated_and_domain_settings_are_prompted(self):
environment = {
item["name"]: item
for item in self.recipe["container_contract"]["environment"]
}
for name in (
"PROXMENUX_ADMIN_PASSWORD",
"AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET",
"AUTHELIA_SESSION_SECRET",
"AUTHELIA_STORAGE_ENCRYPTION_KEY",
):
self.assertTrue(environment[name]["sensitive"])
self.assertEqual(self.profile["generated_sensitive_environment"][name]["strategy"], "token-hex")
self.assertTrue(environment["PROXMENUX_AUTHELIA_COOKIE_DOMAIN"]["required"])
self.assertTrue(environment["PROXMENUX_AUTHELIA_URL"]["required"])
self.assertIn("cookie domain", environment["PROXMENUX_AUTHELIA_COOKIE_DOMAIN"]["prompt"])
self.assertIn("HTTPS URL", environment["PROXMENUX_AUTHELIA_URL"]["prompt"])
self.assertEqual(environment["PROXMENUX_AUTHELIA_COOKIE_DOMAIN"]["example"], "")
self.assertEqual(environment["PROXMENUX_AUTHELIA_URL"]["example"], "")
self.assertEqual(environment["X_AUTHELIA_CONFIG_FILTERS"]["example"], "template")
def test_config_is_persistent_and_has_a_working_initial_policy(self):
config = self.generated["/config/configuration.yml"]["content"]
self.assertIn("path: '/config/users_database.yml'", config)
self.assertIn("path: '/config/db.sqlite3'", config)
self.assertIn("filename: '/config/notification.txt'", config)
self.assertIn("default_policy: 'one_factor'", config)
self.assertIn('{{ env \"PROXMENUX_AUTHELIA_COOKIE_DOMAIN\" }}', config)
self.assertIn('{{ env \"PROXMENUX_AUTHELIA_URL\" }}', config)
self.assertTrue(self.generated["/config/configuration.yml"]["only_if_missing"])
volume = self.recipe["container_contract"]["volumes"][0]
self.assertEqual(volume["container_path"], "/config")
self.assertEqual(volume["installation_choice"], ["managed-volume"])
self.assertTrue(volume["managed_volume"]["backup"])
def test_bootstrap_hashes_initial_password_and_never_ships_a_default_password(self):
bootstrap = self.generated["/usr/local/bin/proxmenux-authelia-bootstrap"]["content"]
self.assertIn("authelia crypto hash generate argon2", bootstrap)
self.assertIn("unset PROXMENUX_ADMIN_PASSWORD", bootstrap)
self.assertIn("exec /app/entrypoint.sh", bootstrap)
self.assertNotIn("password: authelia", bootstrap)
self.assertEqual(
self.recipe["first_run"]["credentials"][0]["password_environment"],
"PROXMENUX_ADMIN_PASSWORD",
)
def test_healthcheck_uses_authelias_documented_endpoint_and_profile_remains_unvalidated(self):
healthcheck = self.profile["startup_healthcheck"]
self.assertEqual((healthcheck["port"], healthcheck["path"]), (9091, "/api/health"))
self.assertFalse(self.recipe["compatibility"]["validated"])
self.assertEqual(self.recipe["status"], "generated-unvalidated")
if __name__ == "__main__":
unittest.main()
+39
View File
@@ -0,0 +1,39 @@
"""A Compose volume that binds a host device node, such as /dev/net/tun for
Tailscale, is passed to the LXC as a device: a mount there hides the device."""
from pathlib import Path
import sys
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from proxmenux_oci import converter
class DeviceNodeBindTests(unittest.TestCase):
service = {"image": "tailscale/tailscale:latest", "volumes": [
{"type": "bind", "source": "/DATA/AppData/tailscale", "target": "/var/lib/tailscale"},
{"type": "bind", "source": "/dev/net/tun", "target": "/dev/net/tun"},
"/dev/fuse:/dev/fuse"]}
def test_device_nodes_are_not_volumes(self):
mounts = converter._mount_contract(self.service["volumes"], set())
self.assertEqual([m["container_path"] for m in mounts], ["/var/lib/tailscale"])
def test_device_nodes_become_device_requests(self):
profile, blockers = converter._device_installer_profile(self.service)
self.assertEqual(blockers, [])
self.assertEqual([(r["id"], r["container_path"]) for r in profile["device_requests"]],
[("dev-net-tun", "/dev/net/tun"), ("dev-fuse", "/dev/fuse")])
def test_a_device_directory_is_left_to_its_own_handling(self):
self.assertIsNone(converter._device_bind("/dev/dri:/dev/dri"))
self.assertIsNone(converter._device_bind({"type": "volume", "source": "/dev/net/tun", "target": "/dev/net/tun"}))
def test_the_tailscale_recipe_passes_the_tun_device(self):
import json
recipe = json.loads((Path(__file__).resolve().parents[1] / "catalog/apps/tailscale.json").read_text())
self.assertNotIn("/dev/net/tun", [v["container_path"] for v in recipe["container_contract"]["volumes"]])
self.assertIn("dev-net-tun", [r["id"] for r in recipe["proxmox"]["installer_profile"]["device_requests"]])
if __name__ == "__main__":
unittest.main()
+151
View File
@@ -0,0 +1,151 @@
"""The KitchenOwl entry follows the upstream two-service Compose deployment."""
import json
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
from jsonschema import validate
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.catalog import Catalog
from proxmenux_oci.custom import template_from_oci_compose
from proxmenux_oci.stack import assess, build_stack, DefaultsUI
from proxmenux_oci.installer import DEFAULT_MODE
CURATED_PATH = ROOT / "catalog/curated/kitchenowl.json"
APPS_PATH = ROOT / "catalog/apps/kitchenowl.json"
def compile_stack(template):
"""Compile the recipe with deterministic answers, without touching Proxmox."""
with patch("proxmenux_oci.installer.ask_storage", return_value="local-zfs"), \
patch("proxmenux_oci.installer.ask_bridge", return_value="vmbr0"), \
patch("proxmenux_oci.host.timezone", return_value="UTC"), \
patch("proxmenux_oci.network.ask_addresses",
side_effect=lambda ui, bridge, labels: ({name: "dhcp" for name in labels}, None)):
return build_stack(template, DefaultsUI(), DEFAULT_MODE)
class KitchenOwlRecipeTests(unittest.TestCase):
def setUp(self):
self.curated = json.loads(CURATED_PATH.read_text(encoding="utf-8"))
self.generated = json.loads(APPS_PATH.read_text(encoding="utf-8"))
self.index = json.loads((ROOT / "catalog/index.json").read_text(encoding="utf-8"))
self.services = {
service["name"]: service
for service in self.curated["compose_stack"]["services"]
}
def test_curated_and_catalog_templates_match_and_validate(self):
self.assertEqual(self.curated, self.generated)
schema = json.loads((ROOT / "schemas/oci-template.schema.json").read_text(encoding="utf-8"))
validate(self.curated, schema)
def test_official_compose_is_the_two_image_frontend_backend_stack(self):
self.assertEqual(set(self.services), {"front", "back"})
self.assertEqual(self.curated["compose_stack"]["service_count"], 2)
self.assertEqual(self.curated["compose_stack"]["main_service"], "front")
self.assertEqual(self.services["front"]["image"], "tombursch/kitchenowl-web:latest")
self.assertEqual(self.services["back"]["image"], "tombursch/kitchenowl-backend:latest")
self.assertEqual(self.services["front"]["depends_on"], ["back"])
self.assertEqual(self.curated["compose_stack"]["orchestration"]["start_order"], ["back", "front"])
self.assertTrue(self.services["front"]["frontend_network"])
self.assertTrue(self.services["front"]["private_network"])
self.assertFalse(self.services["back"]["frontend_network"])
self.assertTrue(self.services["back"]["private_network"])
self.assertNotIn("postgres", self.services)
self.assertNotIn("redis", self.services)
def test_frontend_uses_the_official_backend_alias_and_port(self):
# BACK_URL is inherited from the official web image (back:5000); leaving
# the override absent preserves the image default and Compose service alias.
self.assertNotIn("BACK_URL", self.services["front"]["compose"].get("environment", {}))
image_policy = self.curated["proxmox"]["image_metadata_policy"]
self.assertEqual(image_policy["environment"], "import-image-env-then-apply-compose-overrides")
self.assertEqual(self.curated["container_contract"]["ports"][0]["container_port"], 80)
def test_backend_data_and_generated_jwt_key_are_preserved_safely(self):
back = self.services["back"]["compose"]
self.assertEqual(back["volumes"][0]["target"], "/data")
self.assertEqual(back["environment"]["JWT_SECRET_KEY"], "${GENERATED_JWT_SECRET_KEY}")
self.assertNotIn("PLEASE_CHANGE_ME", json.dumps(back))
secret = self.curated["compose_stack"]["installer_inputs"]["generated_secrets"]
self.assertEqual(len(secret), 1)
self.assertEqual(secret[0]["bindings"], [{"service": "back", "environment_variable": "JWT_SECRET_KEY"}])
volume = next(v for v in self.curated["compose_stack"]["storage"] if v["service"] == "back")
self.assertEqual(volume["container_path"], "/data")
self.assertEqual(volume["mode"], "managed-volume")
self.assertTrue(volume["user_selectable"])
self.assertTrue(volume["backup"])
def test_image_healthchecks_and_first_run_setup_are_recorded(self):
self.assertIn("uwsgi_curl localhost:5000 /api/health/", str(self.services["back"]["compose"]["healthcheck"]))
self.assertIn("curl -f http://localhost/", str(self.services["front"]["compose"]["healthcheck"]))
self.assertEqual(self.curated["first_run"]["endpoints"][0]["port"], 80)
self.assertEqual(self.curated["first_run"]["credentials"], [])
self.assertTrue(self.curated["proxmox"]["stack_completion_notes"])
def test_candidate_passes_stack_assessment_and_round_trips_through_catalog_reader(self):
catalog = Catalog(ROOT)
template = catalog.compose("kitchenowl")
self.assertEqual(assess(template), [])
self.assertEqual(template["compose_stack"]["project_name"], "kitchenowl")
reloaded = template_from_oci_compose(json.dumps(template))
expected = dict(template)
# Reading a pasted/shared recipe deliberately resets trust status; the
# catalog reapplies laboratory verification only for its own entry.
expected["status"] = "generated-unvalidated"
self.assertEqual(reloaded, expected)
def test_compiled_plan_keeps_private_backend_health_and_persistent_data(self):
template = Catalog(ROOT).compose("kitchenowl")
plan = compile_stack(template)
self.assertEqual(plan["stack_name"], "kitchenowl")
self.assertEqual(plan["network"]["private_allocation"], "automatic")
self.assertEqual(plan["network"]["private_bridge"], "vmbr10")
services = {service["name"]: service for service in plan["services"]}
self.assertEqual(list(services), ["back", "front"])
back = services["back"]
front = services["front"]
self.assertIn("back", back["aliases"])
self.assertIn("front", front["aliases"])
self.assertNotIn("frontend", back)
self.assertTrue(front["frontend"])
backend_health = back["healthcheck"]
self.assertEqual(backend_health["type"], "exec")
self.assertEqual(backend_health["timeout_seconds"], 189)
self.assertIn("uwsgi_curl", backend_health["argv"][-1])
frontend_health = front["healthcheck"]
self.assertEqual(frontend_health["type"], "exec")
self.assertEqual(frontend_health["timeout_seconds"], 99)
self.assertIn("curl -f http://localhost/", frontend_health["argv"][-1])
self.assertEqual(frontend_health["endpoint"], {"scheme": "http", "port": 80, "path": "/"})
self.assertEqual([mount["container_path"] for mount in back["deployment"]["mounts"]], ["/data"])
self.assertEqual(back["deployment"]["mounts"][0]["type"], "managed-volume")
self.assertTrue(back["deployment"]["mounts"][0]["backup"])
self.assertEqual(front["deployment"]["mounts"], [])
preparation = back["template"]["proxmox"]["installer_profile"]["volume_preparations"]
self.assertEqual(preparation[0]["container_path"], "/data")
self.assertEqual(preparation[0]["only_when_mount_type"], "managed-volume")
self.assertEqual(template["compose_stack"]["storage"][0]["service"], "back")
self.assertFalse(template["compose_stack"]["storage"][0]["shared_with_other_lxc"])
def test_icon_architectures_and_unvalidated_status_are_accurate(self):
ui = self.curated["catalog_ui"]
self.assertEqual(ui["icon"], "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp")
self.assertEqual(ui["architectures"], ["amd64", "arm64"])
self.assertEqual(self.curated["status"], "generated-unvalidated")
self.assertFalse(self.curated["compatibility"]["validated"])
entry = next(app for app in self.index["applications"] if app["id"] == "kitchenowl")
self.assertEqual(entry["template"], "apps/kitchenowl.json")
self.assertTrue(entry["multi_container"])
self.assertEqual(entry["category"], "productivity")
if __name__ == "__main__":
unittest.main()
+135
View File
@@ -0,0 +1,135 @@
"""Keep the native Krusader OCI profile aligned with its upstream image."""
import json
from pathlib import Path
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.catalog import Catalog
class KrusaderRecipeTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.curated = json.loads(
(ROOT / "catalog/curated/krusader.json").read_text(encoding="utf-8")
)
cls.app = json.loads(
(ROOT / "catalog/apps/krusader.json").read_text(encoding="utf-8")
)
cls.template = Catalog(ROOT).compose("krusader")
def test_catalog_copy_matches_curated_profile(self):
self.assertEqual(self.curated, self.app)
def test_upstream_image_and_manifest_architectures_are_recorded(self):
contract = self.curated["container_contract"]
self.assertEqual(contract["image"]["reference"], "jlesage/krusader:latest")
self.assertEqual(self.curated["source"]["provider"], "jlesage")
self.assertEqual(self.curated["catalog_ui"]["architectures"], ["amd64", "arm64"])
def test_web_gui_uses_authenticated_https_and_does_not_expose_vnc(self):
env = {
entry["name"]: entry.get("example")
for entry in self.template["container_contract"]["environment"]
}
self.assertEqual(env["WEB_AUTHENTICATION"], "1")
self.assertEqual(env["SECURE_CONNECTION"], "1")
self.assertEqual(env["VNC_LOCALHOST_ONLY"], "1")
self.assertEqual(
self.template["catalog_ui"]["launch"],
{"scheme": "https", "port": 5800, "path": "/"},
)
self.assertEqual(
[port["container_port"] for port in self.template["container_contract"]["ports"]],
[5800],
)
credential = self.template["first_run"]["credentials"][0]
self.assertEqual(credential["username"], "admin")
self.assertEqual(credential["password_environment"], "WEB_AUTHENTICATION_PASSWORD")
self.assertIn(
"WEB_AUTHENTICATION_PASSWORD",
self.template["proxmox"]["installer_profile"]["generated_sensitive_environment"],
)
def test_only_config_and_selected_storage_are_persistent(self):
volumes = {
volume["container_path"]: volume
for volume in self.template["container_contract"]["volumes"]
}
self.assertEqual(set(volumes), {"/config", "/storage"})
self.assertEqual(volumes["/config"]["installation_choice"], ["managed-volume"])
self.assertEqual(volumes["/storage"]["installation_choice"], ["managed-volume", "host-bind"])
self.assertEqual(volumes["/storage"]["default"], "host-bind")
self.assertTrue(all(volume["managed_volume"]["backup"] for volume in volumes.values()))
self.assertEqual(volumes["/config"]["managed_volume"]["default_size_gb"], 4)
compose = self.template["compose_stack"]["services"][0]["compose"]
self.assertEqual(compose["image"], "jlesage/krusader:latest")
def test_storage_directory_offers_host_bind_with_the_image_user_identity(self):
profile = self.template["proxmox"]["installer_profile"]
storage = next(
item for item in self.template["container_contract"]["volumes"]
if item["container_path"] == "/storage"
)
compose_storage = next(
item for item in self.template["compose_stack"]["storage"]
if item["container_path"] == "/storage"
)
self.assertEqual(storage["installation_choice"], ["managed-volume", "host-bind"])
self.assertEqual(storage["default"], "host-bind")
self.assertTrue(compose_storage["user_selectable"])
self.assertEqual(compose_storage["mode"], "user-selectable")
self.assertEqual(
compose_storage["installation_choice"], ["managed-volume", "host-bind"]
)
self.assertEqual(
profile["volume_owner"], {"uid": 1000, "gid": 1000}
)
self.assertEqual(
{entry["name"]: entry["example"] for entry in self.template["container_contract"]["environment"]}
["USER_ID"],
"1000",
)
self.assertEqual(
{entry["name"]: entry["example"] for entry in self.template["container_contract"]["environment"]}
["GROUP_ID"],
"1000",
)
def test_partial_live_evidence_does_not_mark_app_as_fully_verified(self):
self.assertEqual(
self.curated["validation"]["clean_install"], "passed-first-start"
)
self.assertEqual(
self.curated["validation"]["service_health"],
"passed-login-page-and-authenticated-session-http-200",
)
self.assertEqual(
self.curated["validation"]["credentials"],
"passed-generated-password-login-and-session-cookie",
)
self.assertEqual(
self.curated["validation"]["web_authentication"],
"passed-generated-credential-post-and-session-cookie",
)
self.assertEqual(
self.curated["validation"]["restart_persistence"],
"passed-container-restart-and-host-bind-write-check",
)
self.assertEqual(
self.curated["validation"]["browser_visual"],
"pending-self-signed-certificate-review",
)
self.assertEqual(self.curated["validation"]["backup_restore"], "pending")
self.assertEqual(self.curated["validation"]["update_preserves_data"], "pending")
self.assertEqual(self.curated["status"], "generated-unvalidated")
self.assertFalse(self.curated["compatibility"]["validated"])
if __name__ == "__main__":
unittest.main()
+59 -1
View File
@@ -11,7 +11,7 @@ import socket
import sys
import tempfile
import unittest
from unittest.mock import patch
from unittest.mock import Mock, patch
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'remote'))
import oci_instances as instances
@@ -129,6 +129,37 @@ class UpdateRetainedDiskTests(unittest.TestCase):
self.assertEqual(instances.read(self.root,115)['observed']['image'],self.saved['observed']['image'])
self.assertEqual(json.loads(self.journal().read_text())['phase'],'rolled-back')
def test_update_command_recovers_a_failed_replacement_without_manual_recovery(self):
import oci_update_current as update
self.fail_install = True
with patch.object(update, 'update', side_effect=lambda *a: self.apply()), \
patch.object(instances, 'ROOT', self.root), \
patch.object(update, 'os', Mock(geteuid=lambda: 0)), \
patch.object(sys, 'argv', ['oci_update_current.py', '115']), \
patch.object(transaction, 'report_error'), \
patch.object(transaction, 'recovery_hint') as hint:
self.assertEqual(update.main(), 1)
self.assert_retained()
self.assertEqual(instances.read(self.root,115)['status'], 'installed')
self.assertEqual(instances.read(self.root,115)['observed']['image'],self.saved['observed']['image'])
self.assertEqual(json.loads(self.journal().read_text())['phase'],'rolled-back')
hint.assert_not_called()
def test_update_command_keeps_the_recovery_hint_when_rollback_is_blocked(self):
import oci_update_current as update
self.fail_install = True
with patch.object(update, 'update', side_effect=lambda *a: self.apply()), \
patch.object(instances, 'ROOT', self.root), \
patch.object(update, 'os', Mock(geteuid=lambda: 0)), \
patch.object(sys, 'argv', ['oci_update_current.py', '115']), \
patch.object(transaction, 'report_error'), \
patch.object(transaction, 'recover', side_effect=ValueError('configuration changed')), \
patch.object(transaction, 'recovery_hint') as hint:
self.assertEqual(update.main(), 1)
hint.assert_called_once_with()
self.assertEqual(instances.read(self.root,115)['status'], 'updating')
self.assertNotEqual(json.loads(self.journal().read_text())['phase'],'rolled-back')
def test_recovery_after_parking_data_returns_the_original_unused_disks(self):
with self.assertRaisesRegex(RuntimeError,'Lab interruption'):self.apply(interrupt_after='data-parked')
transaction.recover(self.root,self.journal());self.assert_retained()
@@ -145,4 +176,31 @@ class UpdateRetainedDiskTests(unittest.TestCase):
self.assert_retained()
class AutomaticRecoveryGuardTests(unittest.TestCase):
def test_committed_rolled_back_and_coordinated_journals_are_not_recovered(self):
for state in ({'phase':'committed'}, {'phase':'rolled-back'},
{'phase':'installing-candidate','coordinated':True}):
with self.subTest(state=state), tempfile.TemporaryDirectory() as directory:
root=Path(directory)/'instances'; journal=Path(directory)/'journal.json'
journal.write_text(json.dumps(state))
with patch.object(transaction,'recover') as recover:
self.assertFalse(transaction.recover_failed_update(root,journal))
recover.assert_not_called()
def test_missing_and_malformed_journals_leave_recovery_pending(self):
with tempfile.TemporaryDirectory() as directory:
root=Path(directory)/'instances'; journal=Path(directory)/'journal.json'
with patch.object(transaction,'recover') as recover, patch.object(transaction,'log'):
self.assertFalse(transaction.recover_failed_update(root,journal))
journal.write_text('{')
self.assertFalse(transaction.recover_failed_update(root,journal))
recover.assert_not_called()
def test_a_busy_registry_does_not_start_recovery(self):
with patch.object(instances,'locked',side_effect=BlockingIOError('busy')), \
patch.object(transaction,'recover') as recover, patch.object(transaction,'log'):
self.assertFalse(transaction.recover_failed_update(Path('/unused'),Path('/unused/journal')))
recover.assert_not_called()
if __name__=='__main__':unittest.main()
+140
View File
@@ -0,0 +1,140 @@
"""Spoolman uses its upstream single-image SQLite deployment in OCI Manager."""
import json
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.catalog import Catalog
from proxmenux_oci.installer import DEFAULT_MODE, build_deployment
class StorageChoiceUI:
def __init__(self, volume_mode):
self.volume_mode = volume_mode
def choose(self, _prompt, options, default=None, **_kwargs):
available = {key for key, _label in options}
if self.volume_mode not in available:
raise AssertionError(f"{self.volume_mode} was not offered; got {available}")
return self.volume_mode
def ask(self, _prompt, default="", **_kwargs):
return default
def confirm(self, _prompt, default=False):
return default
def message(self, *_args, **_kwargs):
pass
class SpoolmanRecipeTests(unittest.TestCase):
def setUp(self):
self.catalog = Catalog(ROOT)
self.recipe = self.catalog.compose("spoolman")
self.generated = json.loads(
(ROOT / "catalog/apps/spoolman.json").read_text(encoding="utf-8")
)
self.curated = json.loads(
(ROOT / "catalog/curated/spoolman.json").read_text(encoding="utf-8")
)
def test_generated_and_curated_recipes_match(self):
self.assertEqual(self.generated, self.curated)
def test_official_image_and_catalog_architectures_match_engine_support(self):
image = self.recipe["container_contract"]["image"]
self.assertEqual(image["reference"], "ghcr.io/donkie/spoolman:latest")
self.assertEqual(image["registry"], "ghcr.io")
self.assertEqual(image["repository"], "ghcr.io/donkie/spoolman")
# The OCI catalog currently supports amd64 and arm64. The upstream
# manifest also has arm/v7, which the current catalog engine does not
# offer as an installable architecture.
self.assertEqual(self.recipe["catalog_ui"]["architectures"], ["amd64", "arm64"])
self.assertEqual(
self.recipe["source"]["image_repository_url"],
"https://github.com/Donkie/Spoolman/pkgs/container/spoolman",
)
def test_sqlite_is_the_default_and_no_database_service_is_required(self):
contract = self.recipe["container_contract"]
self.assertEqual(self.recipe["compose_stack"]["service_count"], 1)
self.assertEqual(self.recipe["compose_stack"]["services"][0]["image"],
"ghcr.io/donkie/spoolman:latest")
self.assertEqual(contract["related_services"], [])
env_names = {item["name"] for item in contract["environment"]}
self.assertNotIn("SPOOLMAN_DB_TYPE", env_names)
self.assertNotIn("SPOOLMAN_DB_NAME", env_names)
self.assertNotIn("SPOOLMAN_DIR_DATA", env_names)
data_path = "/home/app/.local/share/spoolman"
self.assertEqual([volume["container_path"] for volume in contract["volumes"]],
[data_path])
self.assertIn("SQLite", self.recipe["catalog_ui"]["description"]["en_US"])
def test_web_launch_and_first_start_healthcheck_use_container_port_8000(self):
self.assertEqual(self.recipe["container_contract"]["ports"][0]["container_port"], 8000)
self.assertEqual(self.recipe["catalog_ui"]["launch"]["port"], 8000)
self.assertEqual(self.recipe["first_run"]["endpoints"][0]["port"], 8000)
health = self.recipe["proxmox"]["installer_profile"]["startup_healthcheck"]
self.assertEqual((health["scheme"], health["port"], health["path"]),
("http", 8000, "/"))
self.assertIn("7912:8000", self.recipe["container_contract"]["original_compose"])
def test_persistent_data_volume_and_application_uid_are_declared(self):
path = "/home/app/.local/share/spoolman"
volume = self.recipe["container_contract"]["volumes"][0]
policy = json.loads(
(ROOT / "catalog/volume-policy.json").read_text(encoding="utf-8")
)
self.assertIn(path, policy["applications"]["spoolman"])
self.assertEqual(volume["installation_choice"], ["managed-volume", "host-bind"])
self.assertEqual(volume["default"], "managed-volume")
preparation = self.recipe["proxmox"]["installer_profile"]["volume_preparations"][0]
self.assertEqual(preparation["container_path"], path)
self.assertEqual(preparation["owner_strategy"], "mapped-application-user")
self.assertEqual(preparation["only_when_mount_type"], "managed-volume")
self.assertEqual(self.recipe["proxmox"]["installer_profile"]["volume_owner"],
{"uid": 1000, "gid": 1000})
def test_default_install_preserves_data_for_both_storage_choices(self):
for mode in ("managed-volume", "host-bind"):
with self.subTest(mode=mode):
with patch("proxmenux_oci.installer.ask_storage",
return_value="local-zfs"), \
patch("proxmenux_oci.installer.host.default_bridge",
return_value="vmbr0"), \
patch("proxmenux_oci.installer.host.timezone",
return_value="UTC"), \
patch("proxmenux_oci.installer.access.ask_ipv4",
return_value=("dhcp", None)):
plan = build_deployment(
self.recipe, StorageChoiceUI(mode), DEFAULT_MODE
)
self.assertEqual(len(plan["mounts"]), 1)
mount = plan["mounts"][0]
self.assertEqual(mount["container_path"],
"/home/app/.local/share/spoolman")
self.assertEqual(mount["type"], mode)
self.assertFalse(mount["read_only"])
if mode == "managed-volume":
self.assertEqual(mount["source"], "local-zfs")
self.assertEqual(mount["size_gb"], 4)
self.assertTrue(mount["backup"])
else:
self.assertEqual(
mount["source"],
"/mnt/oci-shared/spoolman/home-app-local-share-spoolman",
)
self.assertIsNone(mount["size_gb"])
self.assertFalse(mount["backup"])
if __name__ == "__main__":
unittest.main()
+128
View File
@@ -0,0 +1,128 @@
"""Guard the Unbound catalog contract against upstream image behavior."""
import json
from pathlib import Path
import sys
import unittest
import yaml
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
sys.path.insert(0, str(ROOT / "remote"))
from proxmenux_oci.catalog import Catalog
import oci_instance_transaction
class UnboundRecipeTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.curated = json.loads((ROOT / "catalog/curated/unbound.json").read_text(encoding="utf-8"))
cls.app = json.loads((ROOT / "catalog/apps/unbound.json").read_text(encoding="utf-8"))
cls.composed = Catalog(ROOT).compose("unbound")
cls.index = json.loads((ROOT / "catalog/index.json").read_text(encoding="utf-8"))
cls.index_entry = next(item for item in cls.index["applications"] if item["id"] == "unbound")
cls.original_compose = yaml.safe_load(cls.curated["container_contract"]["original_compose"])
def test_both_catalog_templates_and_image_metadata_match_upstream(self):
self.assertEqual(self.curated, self.app)
image = self.curated["container_contract"]["image"]
self.assertEqual(image["reference"], "klutchell/unbound:main")
self.assertEqual(image["registry"], "docker.io")
self.assertEqual(image["repository"], "klutchell/unbound")
self.assertEqual(image["tag"], "main")
self.assertIsNone(image["digest"]) # resolve the selected tag for each host architecture at install time
self.assertEqual(
image["pull_policy"],
"resolve-selected-tag-to-architecture-digest-at-install",
)
self.assertEqual(
self.curated["source"]["repository"],
"https://github.com/klutchell/unbound-docker",
)
self.assertEqual(
self.curated["source"]["revision"],
"1efbea960bf9462b6fb6dd71aa702ad32d6820d7",
)
def test_icon_and_supported_architectures_are_in_both_catalog_views(self):
icon = "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/unbound.webp"
self.assertEqual(self.curated["catalog_ui"]["icon"], icon)
self.assertEqual(self.index_entry["icon"], icon)
self.assertEqual(self.index_entry["template_id"], "image-unbound")
# The upstream manifest has amd64, arm64, arm/v7 and arm/v6. The catalog
# normalizes the two 32-bit ARM variants to armhf; the install index
# only lists the architectures currently supported by ProxMenux.
self.assertEqual(self.curated["catalog_ui"]["architectures"], ["amd64", "arm64", "armhf"])
self.assertEqual(self.index_entry["architectures"], ["amd64", "arm64"])
def test_dns_listener_publishes_tcp_and_udp_53(self):
expected = {("tcp", 53), ("udp", 53)}
contract_ports = {
(port["protocol"], port["container_port"])
for port in self.curated["container_contract"]["ports"]
}
self.assertEqual(contract_ports, expected)
original = self.original_compose["services"]["unbound"]
self.assertEqual(set(original["ports"]), {"53:53/tcp", "53:53/udp"})
profile_service = self.curated["compose_stack"]["services"][0]["compose"]
self.assertEqual(set(profile_service["ports"]), {"53:53/tcp", "53:53/udp"})
def test_upstream_entrypoint_is_preserved_and_health_uses_running_fallback(self):
image_policy = self.curated["proxmox"]["image_metadata_policy"]
self.assertEqual(image_policy["entrypoint"], "import-from-oci-image")
self.assertEqual(image_policy["cmd"], "import-from-oci-image")
# Upstream Dockerfile sets ENTRYPOINT ["unbound"] and no HEALTHCHECK.
# Preserve the image command; check process stability on install/update,
# then verify DNS answers separately with a query to port 53.
for service in (self.original_compose["services"]["unbound"],
self.curated["compose_stack"]["services"][0]["compose"]):
self.assertNotIn("entrypoint", service)
self.assertNotIn("command", service)
self.assertNotIn("healthcheck", service)
self.assertEqual(self.curated["catalog_ui"]["launch"]["port"], None)
self.assertEqual(self.curated["first_run"]["endpoints"], [])
check = self.composed["proxmox"]["installer_profile"]["startup_healthcheck"]
self.assertEqual(check, {"type": "running", "timeout_seconds": 90, "stability_seconds": 20})
self.assertEqual(oci_instance_transaction.effective_healthcheck(self.composed), check)
def test_custom_configuration_mount_is_persistent_and_exposes_both_storage_modes(self):
path = "/etc/unbound/custom.conf.d"
volume = next(v for v in self.curated["container_contract"]["volumes"]
if v["container_path"] == path)
self.assertFalse(volume["read_only"])
self.assertFalse(volume["required"])
self.assertEqual(volume["installation_choice"], ["managed-volume", "host-bind"])
self.assertEqual(volume["default"], "host-bind")
self.assertTrue(volume["managed_volume"]["backup"])
original_service = self.original_compose["services"]["unbound"]
self.assertIn("./config:/etc/unbound/custom.conf.d", original_service["volumes"])
profile_service = self.curated["compose_stack"]["services"][0]["compose"]
self.assertIn("./config:/etc/unbound/custom.conf.d", profile_service["volumes"])
storage = next(v for v in self.curated["compose_stack"]["storage"]
if v["container_path"] == path)
self.assertEqual(storage["mode"], "user-selectable")
self.assertEqual(storage["installation_choice"], ["managed-volume", "host-bind"])
self.assertTrue(storage["backup"])
prepared = next(v for v in self.composed["proxmox"]["installer_profile"]["volume_preparations"]
if v["container_path"] == path)
self.assertEqual(prepared["owner_strategy"], "mapped-application-user")
def test_dns_service_remains_unprivileged_and_data_is_not_misrepresented_as_web_ui(self):
self.assertTrue(self.curated["proxmox"]["defaults"]["unprivileged"])
self.assertFalse(self.index_entry["requires_privileged_lxc"])
self.assertEqual(self.curated["catalog_ui"]["launch"]["port"], None)
self.assertTrue(any("no web interface" in tip.casefold()
for tip in self.curated["catalog_ui"]["tips"]))
self.assertTrue(any("do not expose recursive dns publicly" in tip.casefold()
for tip in self.curated["catalog_ui"]["tips"]))
if __name__ == "__main__":
unittest.main()
+94
View File
@@ -0,0 +1,94 @@
"""Z-Wave JS UI's native OCI profile is safe to start without a radio controller."""
from pathlib import Path
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
sys.path.insert(0, str(ROOT / "remote"))
from proxmenux_oci.catalog import Catalog, supported_architectures
import oci_instance_transaction as transaction
class ZWaveJsUIRecipeTests(unittest.TestCase):
def setUp(self):
self.catalog = Catalog(ROOT)
self.curated_path = ROOT / "catalog/curated/zwave-js-ui.json"
self.generated_path = ROOT / "catalog/apps/zwave-js-ui.json"
def test_generated_template_matches_curated_profile(self):
self.assertEqual(self.curated_path.read_bytes(), self.generated_path.read_bytes())
def test_uses_the_official_image_and_only_catalog_supported_architectures(self):
template = self.catalog.compose("zwave-js-ui")
self.assertEqual(template["id"], "image-zwave-js-ui")
self.assertEqual(
template["container_contract"]["image"]["reference"],
"docker.io/zwavejs/zwave-js-ui:latest",
)
self.assertEqual(
template["catalog_ui"]["architectures"], ["amd64", "arm64"]
)
self.assertEqual(
supported_architectures(template["catalog_ui"]["architectures"]),
["amd64", "arm64"],
)
self.assertIn(
"/c38e0d2cbbb826bf2bab2db596a3452e59ce0e8e/public/apple-touch-icon-180x180.png",
template["catalog_ui"]["icon"],
)
def test_web_ui_is_the_startup_check_when_no_controller_is_attached(self):
template = self.catalog.compose("zwave-js-ui")
check = transaction.effective_healthcheck(template)
self.assertEqual((check["scheme"], check["port"], check["path"]),
("http", 8091, "/"))
self.assertNotEqual(check["path"], "/health")
endpoint = template["first_run"]["endpoints"][0]
self.assertEqual((endpoint["port"], endpoint["path"]), (8091, "/"))
devices = template["proxmox"]["installer_profile"]["optional_devices"]
self.assertEqual(len(devices), 1)
self.assertFalse(devices[0]["enabled_default"])
self.assertFalse(devices[0]["required_by_compose"])
self.assertTrue(devices[0]["host_path_default"].startswith("/dev/serial/by-id/"))
def test_fresh_store_enables_login_and_keeps_generated_credentials_out_of_recipe(self):
template = self.catalog.compose("zwave-js-ui")
profile = template["proxmox"]["installer_profile"]
generated = profile["generated_sensitive_environment"]
self.assertEqual(generated["SESSION_SECRET"]["bytes"], 32)
self.assertEqual(generated["DEFAULT_PASSWORD"]["bytes"], 20)
seeded = next(item for item in profile["generated_files"]
if item["container_path"] == "/usr/src/app/store/settings.json")
self.assertTrue(seeded["only_if_missing"])
self.assertTrue(seeded["owner"] == "mapped-root")
self.assertIn('"authEnabled":true', seeded["content"])
credential = template["first_run"]["credentials"][0]
self.assertEqual(credential["username"], "admin")
self.assertIsNone(credential["password"])
self.assertEqual(credential["password_environment"], "DEFAULT_PASSWORD")
self.assertNotIn("mysupersecretkey", template["container_contract"]["original_compose"])
def test_state_is_persisted_on_a_managed_volume(self):
template = self.catalog.compose("zwave-js-ui")
volume = template["container_contract"]["volumes"][0]
self.assertEqual(volume["container_path"], "/usr/src/app/store")
self.assertEqual(volume["installation_choice"], ["managed-volume"])
self.assertTrue(volume["managed_volume"]["backup"])
prep = template["proxmox"]["installer_profile"]["volume_preparations"]
self.assertEqual(prep[0]["container_path"], "/usr/src/app/store")
self.assertEqual(prep[0]["owner_strategy"], "mapped-root")
def test_includes_web_and_optional_zwave_websocket_ports(self):
template = self.catalog.compose("zwave-js-ui")
ports = {item["container_port"]: item
for item in template["container_contract"]["ports"]}
self.assertTrue(ports[8091]["required"])
self.assertFalse(ports[3000]["required"])
self.assertEqual(ports[8091]["protocol"], "tcp")
self.assertEqual(ports[3000]["protocol"], "tcp")
if __name__ == "__main__":
unittest.main()