From 987191ac14a4192b16e721d47e8b731ce278477b Mon Sep 17 00:00:00 2001 From: Oleksandr Piskun Date: Wed, 18 Feb 2026 14:45:44 +0200 Subject: [PATCH] feat(app-api): add HaRP container (#7493) Signed-off-by: Oleksander Piskun Signed-off-by: bigcat88 Signed-off-by: Oleksandr Piskun Signed-off-by: Simon L. Co-authored-by: Simon L. --- Containers/apache/Caddyfile | 5 ++ Containers/nextcloud/entrypoint.sh | 4 +- manual-install/readme.md | 2 +- manual-install/update-yaml.sh | 4 ++ php/containers.json | 55 +++++++++++++++++-- php/public/containers-form-submit.js | 11 +++- php/public/disable-harp.js | 7 +++ php/public/index.php | 1 + php/src/ContainerDefinitionFetcher.php | 8 +++ .../Controller/ConfigurationController.php | 1 + php/src/Data/ConfigurationManager.php | 6 ++ .../includes/optional-containers.twig | 17 +++++- 12 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 php/public/disable-harp.js diff --git a/Containers/apache/Caddyfile b/Containers/apache/Caddyfile index 4b92d807..92e84b49 100644 --- a/Containers/apache/Caddyfile +++ b/Containers/apache/Caddyfile @@ -58,6 +58,11 @@ http://{$APACHE_HOST}:23973, # For Collabora callback and WOPI requests, see con reverse_proxy {$WHITEBOARD_HOST}:3002 } + # HaRP (ExApps) + route /exapps/* { + reverse_proxy {$HARP_HOST}:8780 + } + # Nextcloud route { header Strict-Transport-Security max-age=31536000; diff --git a/Containers/nextcloud/entrypoint.sh b/Containers/nextcloud/entrypoint.sh index d4b4f253..760c5b58 100644 --- a/Containers/nextcloud/entrypoint.sh +++ b/Containers/nextcloud/entrypoint.sh @@ -1025,13 +1025,13 @@ else fi fi -# Docker socket proxy +# Docker socket proxy / HaRP # app_api is a shipped app if [ -d "/var/www/html/custom_apps/app_api" ]; then php /var/www/html/occ app:disable app_api rm -r "/var/www/html/custom_apps/app_api" fi -if [ "$DOCKER_SOCKET_PROXY_ENABLED" = 'yes' ]; then +if [ "$DOCKER_SOCKET_PROXY_ENABLED" = 'yes' ] || [ "$HARP_ENABLED" = 'yes' ]; then if [ "$(php /var/www/html/occ config:app:get app_api enabled)" != "yes" ]; then php /var/www/html/occ app:enable app_api fi diff --git a/manual-install/readme.md b/manual-install/readme.md index ea2c2978..6908db09 100644 --- a/manual-install/readme.md +++ b/manual-install/readme.md @@ -12,7 +12,7 @@ You can run the containers that are build for AIO with docker-compose. This come - You lose the AIO interface - You lose update notifications and automatic updates - You lose all AIO backup and restore features -- You lose the built-in [Docker Socket Proxy container](https://github.com/nextcloud/docker-socket-proxy#readme) (needed for [Nextcloud App API](https://github.com/nextcloud/app_api#nextcloud-appapi)) +- You lose the built-in [Docker Socket Proxy container](https://github.com/nextcloud/docker-socket-proxy#readme) and [HaRP container](https://github.com/nextcloud/HaRP) (needed for [Nextcloud App API](https://github.com/nextcloud/app_api#nextcloud-appapi)) - You lose all community containers: https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers - **You need to know what you are doing, especially when modifying the compose.yaml file** - For updating, you need to strictly follow the at the bottom described update routine diff --git a/manual-install/update-yaml.sh b/manual-install/update-yaml.sh index 928275da..0f0c630f 100644 --- a/manual-install/update-yaml.sh +++ b/manual-install/update-yaml.sh @@ -27,6 +27,8 @@ OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "next OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "nextcloud-aio-borgbackup"))')" OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "nextcloud-aio-docker-socket-proxy"))')" OUTPUT="$(echo "$OUTPUT" | jq '.services[] |= if has("depends_on") then .depends_on |= if contains(["nextcloud-aio-docker-socket-proxy"]) then del(.[index("nextcloud-aio-docker-socket-proxy")]) else . end else . end')" +OUTPUT="$(echo "$OUTPUT" | jq 'del(.services[] | select(.container_name == "nextcloud-aio-harp"))')" +OUTPUT="$(echo "$OUTPUT" | jq '.services[] |= if has("depends_on") then .depends_on |= if contains(["nextcloud-aio-harp"]) then del(.[index("nextcloud-aio-harp")]) else . end else . end')" OUTPUT="$(echo "$OUTPUT" | jq '.services[] |= if has("depends_on") then .depends_on |= map({ (.): { "condition": "service_started", "required": false } }) else . end' | jq '.services[] |= if has("depends_on") then .depends_on |= reduce .[] as $item ({}; . + $item) else . end')" sudo snap install yq @@ -45,6 +47,8 @@ sed -i 's|- ip_binding: |- |' containers.yml sed -i '/AIO_TOKEN/d' containers.yml sed -i '/AIO_URL/d' containers.yml sed -i '/DOCKER_SOCKET_PROXY_ENABLED/d' containers.yml +sed -i '/HARP_ENABLED/d' containers.yml +sed -i '/HP_SHARED_KEY/d' containers.yml sed -i '/ADDITIONAL_TRUSTED_PROXY/d' containers.yml sed -i '/TURN_DOMAIN/d' containers.yml sed -i '/NC_AIO_VERSION/d' containers.yml diff --git a/php/containers.json b/php/containers.json index 8e9218ac..56a64240 100644 --- a/php/containers.json +++ b/php/containers.json @@ -10,6 +10,7 @@ "nextcloud-aio-talk", "nextcloud-aio-notify-push", "nextcloud-aio-whiteboard", + "nextcloud-aio-harp", "nextcloud-aio-nextcloud" ], "display_name": "Apache", @@ -49,7 +50,8 @@ "APACHE_MAX_SIZE=%APACHE_MAX_SIZE%", "APACHE_MAX_TIME=%NEXTCLOUD_MAX_TIME%", "NOTIFY_PUSH_HOST=nextcloud-aio-notify-push", - "WHITEBOARD_HOST=nextcloud-aio-whiteboard" + "WHITEBOARD_HOST=nextcloud-aio-whiteboard", + "HARP_HOST=nextcloud-aio-harp" ], "volumes": [ { @@ -172,7 +174,8 @@ "SIGNALING_SECRET", "FULLTEXTSEARCH_PASSWORD", "IMAGINARY_SECRET", - "WHITEBOARD_SECRET" + "WHITEBOARD_SECRET", + "HP_SHARED_KEY" ], "volumes": [ { @@ -258,7 +261,9 @@ "THIS_IS_AIO=true", "IMAGINARY_SECRET=%IMAGINARY_SECRET%", "WHITEBOARD_SECRET=%WHITEBOARD_SECRET%", - "WHITEBOARD_ENABLED=%WHITEBOARD_ENABLED%" + "WHITEBOARD_ENABLED=%WHITEBOARD_ENABLED%", + "HARP_ENABLED=%HARP_ENABLED%", + "HP_SHARED_KEY=%HP_SHARED_KEY%" ], "stop_grace_period": 600, "restart": "unless-stopped", @@ -824,7 +829,7 @@ { "container_name": "nextcloud-aio-docker-socket-proxy", "image_tag": "%AIO_CHANNEL%", - "display_name": "Docker Socket Proxy", + "display_name": "Docker Socket Proxy (deprecated)", "image": "ghcr.io/nextcloud-releases/aio-docker-socket-proxy", "init": true, "internal_port": "2375", @@ -847,6 +852,48 @@ "NET_RAW" ] }, + { + "container_name": "nextcloud-aio-harp", + "image_tag": "release", + "display_name": "HaRP", + "image": "ghcr.io/nextcloud/nextcloud-appapi-harp", + "init": true, + "internal_port": "8780", + "expose": [ + "8780" + ], + "environment": [ + "HP_SHARED_KEY=%HP_SHARED_KEY%", + "NC_INSTANCE_URL=https://%NC_DOMAIN%", + "HP_LOG_LEVEL=warning", + "HP_FRP_DISABLE_TLS=true", + "TZ=%TIMEZONE%" + ], + "secrets": [ + "HP_SHARED_KEY" + ], + "volumes": [ + { + "source": "%WATCHTOWER_DOCKER_SOCKET_PATH%", + "destination": "/var/run/docker.sock", + "writeable": false + }, + { + "source": "nextcloud_aio_harp", + "destination": "/certs", + "writeable": true + } + ], + "restart": "unless-stopped", + "read_only": true, + "tmpfs": [ + "/tmp", + "/run/harp" + ], + "cap_drop": [ + "NET_RAW" + ] + }, { "container_name": "nextcloud-aio-whiteboard", "image_tag": "%AIO_CHANNEL%", diff --git a/php/public/containers-form-submit.js b/php/public/containers-form-submit.js index 2c8e0e63..cda62d60 100644 --- a/php/public/containers-form-submit.js +++ b/php/public/containers-form-submit.js @@ -121,13 +121,22 @@ document.addEventListener("DOMContentLoaded", function () { function handleDockerSocketProxyWarning() { if (document.getElementById("docker-socket-proxy").checked) { - alert('⚠️ Warning! Enabling this container comes with possible Security problems since you are exposing the docker socket and all its privileges to the Nextcloud container. Enable this only if you are sure what you are doing!'); + alert('⚠️ The docker socket proxy container is deprecated. Please use the HaRP (High-availability Reverse Proxy for Nextcloud ExApps) instead!'); + document.getElementById("docker-socket-proxy").checked = false + } + } + + function handleHarpWarning() { + if (document.getElementById("harp").checked) { + alert('⚠️ Warning! Enabling this container comes with possible Security problems since you are exposing the docker socket and all its privileges to the HaRP container. Enable this only if you are sure what you are doing!'); + document.getElementById("docker-socket-proxy").checked = false } } // Initialize event listeners for specific behaviors document.getElementById("talk").addEventListener('change', handleTalkVisibility); document.getElementById("docker-socket-proxy").addEventListener('change', handleDockerSocketProxyWarning); + document.getElementById("harp").addEventListener('change', handleHarpWarning); // Initialize talk-recording visibility on page load handleTalkVisibility(); // Ensure talk-recording is correctly initialized diff --git a/php/public/disable-harp.js b/php/public/disable-harp.js new file mode 100644 index 00000000..fb3b992b --- /dev/null +++ b/php/public/disable-harp.js @@ -0,0 +1,7 @@ +document.addEventListener("DOMContentLoaded", function(event) { + // HaRP + let harp = document.getElementById("harp"); + if (harp) { + harp.disabled = true; + } +}); diff --git a/php/public/index.php b/php/public/index.php index cc06bb90..e0ac8dec 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -136,6 +136,7 @@ $app->get('/containers', function (Request $request, Response $response, array $ 'is_nvidia_gpu_enabled' => $configurationManager->enableNvidiaGpu, 'is_talk_recording_enabled' => $configurationManager->isTalkRecordingEnabled, 'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled, + 'is_harp_enabled' => $configurationManager->isHarpEnabled, 'is_whiteboard_enabled' => $configurationManager->isWhiteboardEnabled, 'community_containers' => $configurationManager->listAvailableCommunityContainers(), 'community_containers_enabled' => $configurationManager->aioCommunityContainers, diff --git a/php/src/ContainerDefinitionFetcher.php b/php/src/ContainerDefinitionFetcher.php index bacc322b..5481cd00 100644 --- a/php/src/ContainerDefinitionFetcher.php +++ b/php/src/ContainerDefinitionFetcher.php @@ -91,6 +91,10 @@ readonly class ContainerDefinitionFetcher { if (!$this->configurationManager->isDockerSocketProxyEnabled) { continue; } + } elseif ($entry['container_name'] === 'nextcloud-aio-harp') { + if (!$this->configurationManager->isHarpEnabled) { + continue; + } } elseif ($entry['container_name'] === 'nextcloud-aio-whiteboard') { if (!$this->configurationManager->isWhiteboardEnabled) { continue; @@ -200,6 +204,10 @@ readonly class ContainerDefinitionFetcher { if (!$this->configurationManager->isDockerSocketProxyEnabled) { continue; } + } elseif ($value === 'nextcloud-aio-harp') { + if (!$this->configurationManager->isHarpEnabled) { + continue; + } } elseif ($value === 'nextcloud-aio-whiteboard') { if (!$this->configurationManager->isWhiteboardEnabled) { continue; diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php index 2cc56b65..4dbe3ae2 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -96,6 +96,7 @@ readonly class ConfigurationController { $this->configurationManager->isImaginaryEnabled = isset($request->getParsedBody()['imaginary']); $this->configurationManager->isFulltextsearchEnabled = isset($request->getParsedBody()['fulltextsearch']); $this->configurationManager->isDockerSocketProxyEnabled = isset($request->getParsedBody()['docker-socket-proxy']); + $this->configurationManager->isHarpEnabled = isset($request->getParsedBody()['harp']); $this->configurationManager->isWhiteboardEnabled = isset($request->getParsedBody()['whiteboard']); } diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php index 9c03cedb..4b23f746 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -30,6 +30,11 @@ class ConfigurationManager set { $this->set('isDockerSocketProxyEnabled', $value); } } + public bool $isHarpEnabled { + get => $this->get('isHarpEnabled', false); + set { $this->set('isHarpEnabled', $value); } + } + public bool $isWhiteboardEnabled { // Type-cast because old configs could have 1/0 for this key. get => (bool) $this->get('isWhiteboardEnabled', true); @@ -1035,6 +1040,7 @@ class ConfigurationManager 'IMAGINARY_ENABLED' => $this->isImaginaryEnabled ? 'yes' : '', 'FULLTEXTSEARCH_ENABLED' => $this->isFulltextsearchEnabled ? 'yes' : '', 'DOCKER_SOCKET_PROXY_ENABLED' => $this->isDockerSocketProxyEnabled ? 'yes' : '', + 'HARP_ENABLED' => $this->isHarpEnabled ? 'yes' : '', 'NEXTCLOUD_UPLOAD_LIMIT' => $this->nextcloudUploadLimit, 'NEXTCLOUD_MEMORY_LIMIT' => $this->nextcloudMemoryLimit, 'NEXTCLOUD_MAX_TIME' => $this->nextcloudMaxTime, diff --git a/php/templates/includes/optional-containers.twig b/php/templates/includes/optional-containers.twig index 9651cf29..08f98634 100644 --- a/php/templates/includes/optional-containers.twig +++ b/php/templates/includes/optional-containers.twig @@ -196,7 +196,21 @@ data-initial-state="false" {% endif %} > - + +

+

+ +

+