From a18ef97ab411cd2acfff8cd25ddec25b9acef723 Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Tue, 20 Jan 2026 15:42:06 +0200 Subject: [PATCH] feat(app-api): add HaRP container Signed-off-by: Oleksander Piskun Signed-off-by: bigcat88 --- Containers/apache/Caddyfile | 5 ++ Containers/nextcloud/entrypoint.sh | 4 +- manual-install/readme.md | 2 +- manual-install/update-yaml.sh | 4 ++ php/containers.json | 59 +++++++++++++++++-- php/public/containers-form-submit.js | 7 +++ php/public/disable-harp.js | 7 +++ php/public/index.php | 1 + php/src/ContainerDefinitionFetcher.php | 8 +++ .../Controller/ConfigurationController.php | 5 ++ php/src/Data/ConfigurationManager.php | 15 +++++ php/src/Docker/DockerActionManager.php | 1 + .../includes/optional-containers.twig | 15 +++++ 13 files changed, 126 insertions(+), 7 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 43432e6d..c5f926b4 100644 --- a/Containers/nextcloud/entrypoint.sh +++ b/Containers/nextcloud/entrypoint.sh @@ -1020,13 +1020,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 af746aee..95df1c41 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 diff --git a/php/containers.json b/php/containers.json index 8c507f91..e33bfa58 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": [ { @@ -146,7 +148,8 @@ "nextcloud-aio-fulltextsearch", "nextcloud-aio-talk-recording", "nextcloud-aio-imaginary", - "nextcloud-aio-docker-socket-proxy" + "nextcloud-aio-docker-socket-proxy", + "nextcloud-aio-harp" ], "display_name": "Nextcloud", "image": "ghcr.io/nextcloud-releases/aio-nextcloud", @@ -172,7 +175,8 @@ "SIGNALING_SECRET", "FULLTEXTSEARCH_PASSWORD", "IMAGINARY_SECRET", - "WHITEBOARD_SECRET" + "WHITEBOARD_SECRET", + "HP_SHARED_KEY" ], "volumes": [ { @@ -257,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", @@ -846,6 +852,51 @@ "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" + ], + "backup_volumes": [ + "nextcloud_aio_harp" + ] + }, { "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 b7ffd2d8..8e10a887 100644 --- a/php/public/containers-form-submit.js +++ b/php/public/containers-form-submit.js @@ -75,9 +75,16 @@ document.addEventListener("DOMContentLoaded", function () { } } + 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!'); + } + } + // 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 b57f65a5..d4a2d47c 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->isNvidiaGpuEnabled(), '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->GetEnabledCommunityContainers(), diff --git a/php/src/ContainerDefinitionFetcher.php b/php/src/ContainerDefinitionFetcher.php index 7b092e45..5769e290 100644 --- a/php/src/ContainerDefinitionFetcher.php +++ b/php/src/ContainerDefinitionFetcher.php @@ -90,6 +90,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; @@ -199,6 +203,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 45586f9c..4484be1c 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -119,6 +119,11 @@ readonly class ConfigurationController { } else { $this->configurationManager->SetDockerSocketProxyEnabledState(0); } + if (isset($request->getParsedBody()['harp'])) { + $this->configurationManager->SetHarpEnabledState(1); + } else { + $this->configurationManager->SetHarpEnabledState(0); + } if (isset($request->getParsedBody()['whiteboard'])) { $this->configurationManager->SetWhiteboardEnabledState(1); } else { diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php index 320bc477..740cc335 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -162,6 +162,21 @@ class ConfigurationManager $this->WriteConfig($config); } + public function isHarpEnabled() : bool { + $config = $this->GetConfig(); + if (isset($config['isHarpEnabled']) && $config['isHarpEnabled'] === 1) { + return true; + } else { + return false; + } + } + + public function SetHarpEnabledState(int $value) : void { + $config = $this->GetConfig(); + $config['isHarpEnabled'] = $value; + $this->WriteConfig($config); + } + public function isWhiteboardEnabled() : bool { $config = $this->GetConfig(); if (isset($config['isWhiteboardEnabled']) && $config['isWhiteboardEnabled'] === 0) { diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 9e8a8ff2..18cd0e32 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -567,6 +567,7 @@ readonly class DockerActionManager { 'IMAGINARY_ENABLED' => $this->configurationManager->isImaginaryEnabled() ? 'yes' : '', 'FULLTEXTSEARCH_ENABLED' => $this->configurationManager->isFulltextsearchEnabled() ? 'yes' : '', 'DOCKER_SOCKET_PROXY_ENABLED' => $this->configurationManager->isDockerSocketProxyEnabled() ? 'yes' : '', + 'HARP_ENABLED' => $this->configurationManager->isHarpEnabled() ? 'yes' : '', 'NEXTCLOUD_UPLOAD_LIMIT' => $this->configurationManager->GetNextcloudUploadLimit(), 'NEXTCLOUD_MEMORY_LIMIT' => $this->configurationManager->GetNextcloudMemoryLimit(), 'NEXTCLOUD_MAX_TIME' => $this->configurationManager->GetNextcloudMaxTime(), diff --git a/php/templates/includes/optional-containers.twig b/php/templates/includes/optional-containers.twig index b4764592..e4ac6bfd 100644 --- a/php/templates/includes/optional-containers.twig +++ b/php/templates/includes/optional-containers.twig @@ -126,6 +126,20 @@ >

+

+ + +

+