diff --git a/lang/de.json b/lang/de.json index 58af0108..7b7eb26a 100644 --- a/lang/de.json +++ b/lang/de.json @@ -8967,5 +8967,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Festplattendaten werden NICHT gelöscht.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Die Festplatte wird ausgehängt und aus /etc/fstab entfernt.", "⚠ The /etc/fstab entry will be removed.": "⚠ Der /etc/fstab-Eintrag wird entfernt.", - "⚠ The disk will be unmounted.": "⚠ Die Festplatte wird ausgehängt." + "⚠ The disk will be unmounted.": "⚠ Die Festplatte wird ausgehängt.", + "AirDC++ administrator": "AirDC++-Administrator", + "Direct Connect file sharing with a web interface": "Direct-Connect-Dateifreigabe mit Weboberfläche" } diff --git a/lang/es.json b/lang/es.json index 9c8eaef7..3033d169 100644 --- a/lang/es.json +++ b/lang/es.json @@ -8937,5 +8937,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Los datos del disco NO se borrarán.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ El disco se desmontará y se eliminará de /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ Se eliminará la entrada /etc/fstab.", - "⚠ The disk will be unmounted.": "⚠ El disco se desmontará." + "⚠ The disk will be unmounted.": "⚠ El disco se desmontará.", + "AirDC++ administrator": "Administrador de AirDC++", + "Direct Connect file sharing with a web interface": "Intercambio de archivos Direct Connect con interfaz web" } diff --git a/lang/fr.json b/lang/fr.json index ab81108d..797b5054 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -8966,5 +8966,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Les données du disque ne seront PAS effacées.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Le disque sera démonté et supprimé de /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ L'entrée /etc/fstab sera supprimée.", - "⚠ The disk will be unmounted.": "⚠ Le disque sera démonté." + "⚠ The disk will be unmounted.": "⚠ Le disque sera démonté.", + "AirDC++ administrator": "Administrateur AirDC++", + "Direct Connect file sharing with a web interface": "Partage de fichiers Direct Connect avec une interface web" } diff --git a/lang/it.json b/lang/it.json index 1b45ff15..41208636 100644 --- a/lang/it.json +++ b/lang/it.json @@ -8967,5 +8967,7 @@ "⚠ Disk data will NOT be erased.": "⚠ I dati del disco NON verranno cancellati.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Il disco verrà smontato e rimosso da /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ La voce /etc/fstab verrà rimossa.", - "⚠ The disk will be unmounted.": "⚠ Il disco verrà smontato." + "⚠ The disk will be unmounted.": "⚠ Il disco verrà smontato.", + "AirDC++ administrator": "Amministratore AirDC++", + "Direct Connect file sharing with a web interface": "Condivisione di file Direct Connect con interfaccia web" } diff --git a/lang/pt.json b/lang/pt.json index 8e859683..ac0324c0 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -8967,5 +8967,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Os dados do disco NÃO serão apagados.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ O disco será desmontado e removido de /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ A entrada /etc/fstab será removida.", - "⚠ The disk will be unmounted.": "⚠ O disco será desmontado." + "⚠ The disk will be unmounted.": "⚠ O disco será desmontado.", + "AirDC++ administrator": "Administrador do AirDC++", + "Direct Connect file sharing with a web interface": "Partilha de ficheiros Direct Connect com interface web" } diff --git a/lang/sk.json b/lang/sk.json index a96f5f2f..538335ac 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -8967,5 +8967,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Dáta na disku sa NEVYMAŽÚ.", "⚠ 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ý." + "⚠ The disk will be unmounted.": "⚠ Disk bude odpojený.", + "AirDC++ administrator": "Správca AirDC++", + "Direct Connect file sharing with a web interface": "Zdieľanie súborov cez Direct Connect s webovým rozhraním" } diff --git a/lang/sv.json b/lang/sv.json index 467df8b8..92b84cac 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -8967,5 +8967,7 @@ "⚠ Disk data will NOT be erased.": "⚠ Diskdata kommer INTE att raderas.", "⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disken kommer att avmonteras och tas bort från /etc/fstab.", "⚠ The /etc/fstab entry will be removed.": "⚠ /etc/fstab-posten kommer att tas bort.", - "⚠ The disk will be unmounted.": "⚠ Disken kommer att avmonteras." + "⚠ The disk will be unmounted.": "⚠ Disken kommer att avmonteras.", + "AirDC++ administrator": "AirDC++-administratör", + "Direct Connect file sharing with a web interface": "Fildelning via Direct Connect med webbgränssnitt" } diff --git a/oci/VALIDATION.md b/oci/VALIDATION.md index cba69cf7..93f3c103 100644 --- a/oci/VALIDATION.md +++ b/oci/VALIDATION.md @@ -2,7 +2,7 @@ -52 of 533 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 534 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. diff --git a/oci/catalog/apps/airdcpp-webclient.json b/oci/catalog/apps/airdcpp-webclient.json new file mode 100644 index 00000000..efbc1e26 --- /dev/null +++ b/oci/catalog/apps/airdcpp-webclient.json @@ -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": "/", + "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 + } +} diff --git a/oci/catalog/curated/airdcpp-webclient.json b/oci/catalog/curated/airdcpp-webclient.json new file mode 100644 index 00000000..efbc1e26 --- /dev/null +++ b/oci/catalog/curated/airdcpp-webclient.json @@ -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": "/", + "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 + } +} diff --git a/oci/catalog/index.json b/oci/catalog/index.json index 9e0f916a..0671b5ff 100644 --- a/oci/catalog/index.json +++ b/oci/catalog/index.json @@ -18733,6 +18733,37 @@ "requires_security_confirmation": false, "hidden": true, "template_id": "image-ztnet" + }, + { + "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 } ], "discovery": { diff --git a/oci/catalog/volume-policy.json b/oci/catalog/volume-policy.json index d1305cbd..12e3a551 100644 --- a/oci/catalog/volume-policy.json +++ b/oci/catalog/volume-policy.json @@ -115,6 +115,10 @@ ], "unpackerr": [ "/data" + ], + "airdcpp-webclient": [ + "/Downloads", + "/Share" ] }, "sizes": { diff --git a/oci/tests/test_airdcpp_recipe.py b/oci/tests/test_airdcpp_recipe.py new file mode 100644 index 00000000..34c22100 --- /dev/null +++ b/oci/tests/test_airdcpp_recipe.py @@ -0,0 +1,60 @@ +"""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']['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()