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 @@
>
+
+
+
+
+