From 21fbb58c96b4cbc8315bfc4af07645b0bfec02b5 Mon Sep 17 00:00:00 2001
From: Lorenzo Moscati
Date: Sat, 23 Aug 2025 01:30:20 +0200
Subject: [PATCH 1/3] Rewrite all AIO interface paths to be relative
Signed-off-by: Lorenzo Moscati
---
php/public/base_path.js | 3 +
php/public/index.php | 6 +-
.../Controller/ConfigurationController.php | 2 +-
php/src/Controller/DockerController.php | 19 +++--
php/src/Controller/LoginController.php | 12 +--
php/src/Middleware/AuthMiddleware.php | 10 ++-
php/templates/already-installed.twig | 6 +-
php/templates/components/container-state.twig | 6 +-
php/templates/containers.twig | 83 ++++++++++---------
.../includes/community-containers.twig | 2 +-
.../includes/optional-containers.twig | 10 +--
php/templates/layout.twig | 4 +-
php/templates/login.twig | 6 +-
php/templates/setup.twig | 6 +-
14 files changed, 96 insertions(+), 79 deletions(-)
create mode 100644 php/public/base_path.js
diff --git a/php/public/base_path.js b/php/public/base_path.js
new file mode 100644
index 00000000..67c1a4a7
--- /dev/null
+++ b/php/public/base_path.js
@@ -0,0 +1,3 @@
+document.addEventListener("DOMContentLoaded", function(event) {
+ document.getElementById("base_path") && (document.getElementById("base_path").value = window.location.pathname.slice(0, -11));
+});
\ No newline at end of file
diff --git a/php/public/index.php b/php/public/index.php
index aac83826..d3fbbeb9 100644
--- a/php/public/index.php
+++ b/php/public/index.php
@@ -178,17 +178,17 @@ $app->get('/', function (\Psr\Http\Message\RequestInterface $request, Response $
$setup = $container->get(\AIO\Data\Setup::class);
if($setup->CanBeInstalled()) {
return $response
- ->withHeader('Location', '/setup')
+ ->withHeader('Location', 'setup')
->withStatus(302);
}
if($authManager->IsAuthenticated()) {
return $response
- ->withHeader('Location', '/containers')
+ ->withHeader('Location', 'containers')
->withStatus(302);
} else {
return $response
- ->withHeader('Location', '/login')
+ ->withHeader('Location', 'login')
->withStatus(302);
}
});
diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php
index ed3be505..5727c364 100644
--- a/php/src/Controller/ConfigurationController.php
+++ b/php/src/Controller/ConfigurationController.php
@@ -161,7 +161,7 @@ readonly class ConfigurationController {
$this->configurationManager->DeleteBorgBackupLocationVars();
}
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
} catch (InvalidSettingConfigurationException $ex) {
$response->getBody()->write($ex->getMessage());
return $response->withStatus(422);
diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php
index 678bbdc9..dd6b1076 100644
--- a/php/src/Controller/DockerController.php
+++ b/php/src/Controller/DockerController.php
@@ -85,7 +85,7 @@ readonly class DockerController {
public function StartBackupContainerBackup(Request $request, Response $response, array $args) : Response {
$forceStopNextcloud = true;
$this->startBackup($forceStopNextcloud);
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function startBackup(bool $forceStopNextcloud = false) : void {
@@ -102,7 +102,7 @@ readonly class DockerController {
public function StartBackupContainerCheck(Request $request, Response $response, array $args) : Response {
$this->checkBackup();
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function checkBackup() : void {
@@ -132,7 +132,7 @@ readonly class DockerController {
$id = 'nextcloud-aio-borgbackup';
$this->PerformRecursiveContainerStart($id);
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function StartBackupContainerCheckRepair(Request $request, Response $response, array $args) : Response {
@@ -148,7 +148,7 @@ readonly class DockerController {
$config['backup-mode'] = 'check';
$this->configurationManager->WriteConfig($config);
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function StartBackupContainerTest(Request $request, Response $response, array $args) : Response {
@@ -163,7 +163,7 @@ readonly class DockerController {
$id = 'nextcloud-aio-borgbackup';
$this->PerformRecursiveContainerStart($id);
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function StartContainer(Request $request, Response $response, array $args) : Response
@@ -171,6 +171,7 @@ readonly class DockerController {
$uri = $request->getUri();
$host = $uri->getHost();
$port = $uri->getPort();
+ $path = $request->getParsedBody()['base_path'];
if ($port === 8000) {
error_log('The AIO_URL-port was discovered to be 8000 which is not expected. It is now set to 443.');
$port = 443;
@@ -184,7 +185,7 @@ readonly class DockerController {
$config = $this->configurationManager->GetConfig();
// set AIO_URL
- $config['AIO_URL'] = $host . ':' . $port;
+ $config['AIO_URL'] = $host . ':' . $port . $path;
// set wasStartButtonClicked
$config['wasStartButtonClicked'] = 1;
// set install_latest_major
@@ -204,7 +205,7 @@ readonly class DockerController {
// Temporarily disabled as it leads much faster to docker rate limits
// apcu_clear_cache();
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function startTopContainer(bool $pullImage) : void {
@@ -223,7 +224,7 @@ readonly class DockerController {
public function StartWatchtowerContainer(Request $request, Response $response, array $args) : Response {
$this->startWatchtower();
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function startWatchtower() : void {
@@ -261,7 +262,7 @@ readonly class DockerController {
$forceStopNextcloud = true;
$this->PerformRecursiveContainerStop($id, $forceStopNextcloud);
- return $response->withStatus(201)->withHeader('Location', '/');
+ return $response->withStatus(201)->withHeader('Location', '.');
}
public function stopTopContainer() : void {
diff --git a/php/src/Controller/LoginController.php b/php/src/Controller/LoginController.php
index 196e7138..233a795e 100644
--- a/php/src/Controller/LoginController.php
+++ b/php/src/Controller/LoginController.php
@@ -19,33 +19,33 @@ readonly class LoginController {
public function TryLogin(Request $request, Response $response, array $args) : Response {
if (!$this->dockerActionManager->isLoginAllowed()) {
$response->getBody()->write("The login is blocked since Nextcloud is running.");
- return $response->withHeader('Location', '/')->withStatus(422);
+ return $response->withHeader('Location', '.')->withStatus(422);
}
$password = $request->getParsedBody()['password'] ?? '';
if($this->authManager->CheckCredentials($password)) {
$this->authManager->SetAuthState(true);
- return $response->withHeader('Location', '/')->withStatus(201);
+ return $response->withHeader('Location', '.')->withStatus(201);
}
$response->getBody()->write("The password is incorrect.");
- return $response->withHeader('Location', '/')->withStatus(422);
+ return $response->withHeader('Location', '.')->withStatus(422);
}
public function GetTryLogin(Request $request, Response $response, array $args) : Response {
$token = $request->getQueryParams()['token'] ?? '';
if($this->authManager->CheckToken($token)) {
$this->authManager->SetAuthState(true);
- return $response->withHeader('Location', '/')->withStatus(302);
+ return $response->withHeader('Location', '../..')->withStatus(302);
}
- return $response->withHeader('Location', '/')->withStatus(302);
+ return $response->withHeader('Location', '../..')->withStatus(302);
}
public function Logout(Request $request, Response $response, array $args) : Response
{
$this->authManager->SetAuthState(false);
return $response
- ->withHeader('Location', '/')
+ ->withHeader('Location', '.')
->withStatus(302);
}
}
diff --git a/php/src/Middleware/AuthMiddleware.php b/php/src/Middleware/AuthMiddleware.php
index f8d44857..a54f47a6 100644
--- a/php/src/Middleware/AuthMiddleware.php
+++ b/php/src/Middleware/AuthMiddleware.php
@@ -27,7 +27,15 @@ readonly class AuthMiddleware {
if(!in_array($request->getUri()->getPath(), $publicRoutes)) {
if(!$this->authManager->IsAuthenticated()) {
$status = 302;
- $headers = ['Location' => '/'];
+ if(count(explode('/', $request->getUri()->getPath())) > 2) {
+ $location = '..';
+ for($i = 0; $i < count(explode('/', $request->getUri()->getPath())) - 3; $i++) {
+ $location = $location . '/..';
+ }
+ } else {
+ $location = '.';
+ }
+ $headers = ['Location' => $location];
$response = new Response($status, $headers);
return $response;
}
diff --git a/php/templates/already-installed.twig b/php/templates/already-installed.twig
index fa18f988..e16e6792 100644
--- a/php/templates/already-installed.twig
+++ b/php/templates/already-installed.twig
@@ -3,11 +3,11 @@
{% block body %}
{% endblock %}
diff --git a/php/templates/components/container-state.twig b/php/templates/components/container-state.twig
index 4cf5dd4e..8375d033 100644
--- a/php/templates/components/container-state.twig
+++ b/php/templates/components/container-state.twig
@@ -4,15 +4,15 @@
{% if c.GetStartingState().value == 'starting' %}
{{ c.GetDisplayName() }}
- (Starting )
+ (Starting )
{% elseif c.GetRunningState().value == 'running' %}
{{ c.GetDisplayName() }}
- (Running )
+ (Running )
{% else %}
{{ c.GetDisplayName() }}
- (Stopped )
+ (Stopped )
{% endif %}
{% if c.GetDocumentation() != '' %}
(docs )
diff --git a/php/templates/containers.twig b/php/templates/containers.twig
index 12b4d81b..1e795149 100644
--- a/php/templates/containers.twig
+++ b/php/templates/containers.twig
@@ -6,9 +6,9 @@
-
+
-
+
@@ -164,7 +164,7 @@
{% endif %}
Choose the backup that you want to restore and click on the button below to restore the selected backup. This will restore the whole AIO instance. Please note that the current AIO passphrase will be kept and the previous AIO passphrase will not be restored from backup!
Important: If the backup that you want to restore contained any community container , you need to restore the same backup a second time after this attempt so that the community container data is also correctly restored.
-
+
@@ -178,7 +178,7 @@
{% endif %}
{% elseif borg_backup_mode == 'restore' %}
{% if backup_exit_code > 0 %}
- Last restore failed! (Logs )
+ Last restore failed! (Logs )
The restore process has unexpectedly failed! Please adjust the path and encryption password, test it and try to restore again!
{% endif %}
{% endif %}
@@ -197,7 +197,7 @@
remote borg repo url
if stored remotely; and the encryption password of the backup archive below and submit all values:
-
+
Local backup location
Remote borg repo
Borg passphrase
@@ -210,7 +210,7 @@
{% endif %}
{% else %}
Everything set! Click on the button below to test the path and encryption password:
-
+
@@ -223,14 +223,14 @@
{% if was_start_button_clicked == true %}
{% if current_channel starts with 'latest' or current_channel starts with 'beta' or current_channel starts with 'develop' %}
- You are running the {{ current_channel }} channel. (Logs )
+ You are running the {{ current_channel }} channel. (Logs )
{% else %}
No channel was found. This means that AIO is not able to update itself and its component and will also not be able to report about updates. Updates need to be done externally.
{% endif %}
{% endif %}
{% if is_backup_container_running == true %}
- Backup container is currently running: {{ borg_backup_mode }} (Logs )
+ Backup container is currently running: {{ borg_backup_mode }} (Logs )
Reload ↻
{% endif %}
@@ -259,7 +259,7 @@
{% else %}
It seems at least one container was not able to start correctly and is currently restarting.
To break this endless loop, you can stop the containers below and investigate the issue in the container logs before starting the containers again.
-
+
@@ -312,7 +312,7 @@
You can find all changes here
{% endif %}
{% endif %}
-
+
@@ -327,31 +327,34 @@
{% endif %}
{% if is_mastercontainer_update_available == true %}
⚠️ A mastercontainer update is available. Please click on the button below to update it.
-
+
{% else %}
{% if was_start_button_clicked == false %}
-
+
+
{% if newMajorVersionString != '' %}
Install Nextcloud Hub {{ newMajorVersionString }} (if unchecked, Nextcloud Hub 10 will get installed)
{% endif %}
{% elseif has_update_available == false %}
-
+
+
{% else %}
-
+
+
{% if bypass_container_update == true %}
{% endif %}
@@ -376,7 +379,7 @@
remote borg repo url and submit it .
You will be provided with an SSH public key for authorization at the remote afterwards.
-
+
Local backup location
Remote borg repo
@@ -393,13 +396,13 @@
{% if is_backup_container_running == false %}
Backup and restore
{% if backup_exit_code > 0 %}
- Last {{ borg_backup_mode }} failed! (Logs )
+ Last {{ borg_backup_mode }} failed! (Logs )
{% if borg_backup_mode == "check" %}
The backup check was not successful. This might indicate a corrupt archive (look at the logs). If that should be the case, you can try to fix it by following this documentation
Reveal repair option
Below is the option to repair the integrity of your backup. Please note: Please only use this after you have read the documentation above! (It will run the command 'borg check --repair' for you.)
-
+
@@ -417,7 +420,7 @@
{% endif %}
You may change the backup path again since the initial backup was not successful. After submitting the new value, you need to click on Create Backup to test the new value.
-
+
Local backup location
Remote borg repo
@@ -427,9 +430,9 @@
{% endif %}
{% elseif backup_exit_code == 0 %}
{% if borg_backup_mode == "backup" %}
- Last {{ borg_backup_mode }} successful on {{ last_backup_time }} UTC! (Logs )
+ Last {{ borg_backup_mode }} successful on {{ last_backup_time }} UTC! (Logs )
{% else %}
- Last {{ borg_backup_mode }} successful! (Logs )
+ Last {{ borg_backup_mode }} successful! (Logs )
{% endif %}
{% endif %}
{% endif %}
@@ -464,7 +467,7 @@
{% if isApacheStarting != true %}
Backup creation
Clicking on the button below will create a backup.
-
+
@@ -479,7 +482,7 @@
{% endif %}
is wrong, you can reset it by clicking on the button below.
-
+
@@ -493,7 +496,7 @@
Backup check
Click on the button below to perform a backup integrity check. This is an option that verifies that your backup is intact. It shouldn't be needed in most situations.
-
+
@@ -501,7 +504,7 @@
Backup restore
Choose the backup that you want to restore and click on the button below to restore the selected backup. This will overwrite all your files with the chosen backup so you should consider creating a backup first. You can run an integrity check before restoring your files but this shouldn't be needed in most situations. Please note that this will not restore additionally chosen backup directories! The restore process should be pretty fast as rsync, which only transfers changed files, is used to restore the chosen backup.
-
+
@@ -515,7 +518,7 @@
Daily backup and automatic updates
{% if daily_backup_time == "" %}
By entering a time below and submitting it, you can enable daily backups. It will create them at the entered time in 24h format. E.g. 04:00 will create backups at 4 am UTC and 16:00 at 4 pm UTC. When creating the backup, containers will be stopped and restarted after the backup is complete.
-
+
@@ -529,7 +532,7 @@
Also your containers, the mastercontainer and, on Saturdays, your Nextcloud apps will be automatically updated.
{% endif %}
To change your backup time first disable Daily Backups, then enter your new backup time, and then re-enable them.
-
+
@@ -539,7 +542,7 @@
Back up additional directories and docker volumes of your host
Below you can enter directories and docker volumes of your host that will be backed up into the same borg backup archive. Make sure to press the submit button after changing anything.
-
+
{{ additional_backup_directories }}
@@ -566,7 +569,7 @@
Click here to change your AIO passphrase
You can change your AIO passphrase below:
-
+
@@ -592,7 +595,7 @@
{% if timezone == "" %}
To get the correct time values for certain Nextcloud features, set the timezone for Nextcloud to the one that your users mainly use. Please note that this setting does not apply to the mastercontainer and any backup option.
You can configure the timezone for Nextcloud below (Do not forget to submit the value!):
-
+
@@ -601,7 +604,7 @@
You need to make sure that the timezone that you enter is valid. An example is Europe/Berlin . You can get valid values by looking at the 'TZ identifier' column of this list: click here . The default is Etc/UTC if nothing is entered.
{% else %}
The timezone for Nextcloud is currently set to {{ timezone }} . You can change the timezone by clicking on the button below.
-
+
@@ -620,6 +623,8 @@
{% endif %}
+
+
{% endblock %}
diff --git a/php/templates/includes/community-containers.twig b/php/templates/includes/community-containers.twig
index f74e3756..66cceb2b 100644
--- a/php/templates/includes/community-containers.twig
+++ b/php/templates/includes/community-containers.twig
@@ -8,7 +8,7 @@
{% endif %}
Show/Hide available Community Containers
-
+
@@ -160,7 +160,7 @@
{% if collabora_dictionaries == "" %}
In order to get the correct dictionaries in Collabora, you may configure the dictionaries below:
-
+
@@ -169,7 +169,7 @@
You need to make sure that the dictionaries that you enter are valid. An example is de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru .
{% else %}
The dictionaries for Collabora are currently set to {{ collabora_dictionaries }} . You can reset them again by clicking on the button below.
-
+
@@ -182,7 +182,7 @@
{% if collabora_additional_options == "" %}
You can configure additional options for collabora below.
(This can be used for configuring the net.content_security_policy and more. Make sure to submit the value!)
-
+
@@ -191,7 +191,7 @@
You need to make sure that the options that you enter are valid. An example is --o:net.content_security_policy=frame-ancestors *.example.com:*; .
{% else %}
The additioinal options for Collabora are currently set to {{ collabora_additional_options }} . You can reset them again by clicking on the button below.
-
+
diff --git a/php/templates/layout.twig b/php/templates/layout.twig
index e20ca3e0..4d842e3d 100644
--- a/php/templates/layout.twig
+++ b/php/templates/layout.twig
@@ -1,8 +1,8 @@
AIO
-
-
+
+
diff --git a/php/templates/login.twig b/php/templates/login.twig
index cf5cc0c3..1c5420c2 100644
--- a/php/templates/login.twig
+++ b/php/templates/login.twig
@@ -3,14 +3,14 @@
{% block body %}
-
-
+
+
Nextcloud Logo
Nextcloud AIO Login
{% if is_login_allowed == true %}
Log in using your Nextcloud AIO passphrase:
-
+
diff --git a/php/templates/setup.twig b/php/templates/setup.twig
index f1d4d1dc..7cc9227a 100644
--- a/php/templates/setup.twig
+++ b/php/templates/setup.twig
@@ -3,14 +3,14 @@
{% block body %}
-
-
+
+
Nextcloud Logo
All-in-One setup
The official Nextcloud installation method. Nextcloud All-in-One provides easy deployment and maintenance with most features included in this one Nextcloud instance.
⚠️ Please note down the passphrase to access the AIO interface and don't lose it!
Passphrase {{ password }}
-
Open Nextcloud AIO login ↗
+
Open Nextcloud AIO login ↗
{% endblock %}
From 3bb9cdf31dc7db3b5d4b41bbbeac59b5fded1163 Mon Sep 17 00:00:00 2001
From: Lorenzo Moscati
Date: Tue, 16 Sep 2025 13:39:01 +0200
Subject: [PATCH 2/3] Guard against null or missing keys.
Signed-off-by: Lorenzo Moscati
---
php/src/Controller/DockerController.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php
index dd6b1076..8473ed57 100644
--- a/php/src/Controller/DockerController.php
+++ b/php/src/Controller/DockerController.php
@@ -171,7 +171,7 @@ readonly class DockerController {
$uri = $request->getUri();
$host = $uri->getHost();
$port = $uri->getPort();
- $path = $request->getParsedBody()['base_path'];
+ $path = $request->getParsedBody()['base_path'] ?? '';
if ($port === 8000) {
error_log('The AIO_URL-port was discovered to be 8000 which is not expected. It is now set to 443.');
$port = 443;
From 10529a597c3292ff8ecc8e51692433240ebe2b9d Mon Sep 17 00:00:00 2001
From: Lorenzo Moscati
Date: Tue, 14 Oct 2025 15:59:28 +0200
Subject: [PATCH 3/3] Added suggested changes
Signed-off-by: Lorenzo Moscati
---
php/public/base_path.js | 8 ++++++--
php/src/Controller/LoginController.php | 2 +-
php/src/Middleware/AuthMiddleware.php | 17 +++++++++++++----
3 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/php/public/base_path.js b/php/public/base_path.js
index 67c1a4a7..a55ed943 100644
--- a/php/public/base_path.js
+++ b/php/public/base_path.js
@@ -1,3 +1,7 @@
-document.addEventListener("DOMContentLoaded", function(event) {
- document.getElementById("base_path") && (document.getElementById("base_path").value = window.location.pathname.slice(0, -11));
+document.addEventListener("DOMContentLoaded", function() {
+ basePath = document.getElementById("base_path")
+ if (basePath) {
+ // Remove '/containers' from the end of the path, to get the base path only
+ basePath.value = window.location.pathname.slice(0, -11);
+ }
});
\ No newline at end of file
diff --git a/php/src/Controller/LoginController.php b/php/src/Controller/LoginController.php
index 233a795e..412ff9df 100644
--- a/php/src/Controller/LoginController.php
+++ b/php/src/Controller/LoginController.php
@@ -45,7 +45,7 @@ readonly class LoginController {
{
$this->authManager->SetAuthState(false);
return $response
- ->withHeader('Location', '.')
+ ->withHeader('Location', '../..')
->withStatus(302);
}
}
diff --git a/php/src/Middleware/AuthMiddleware.php b/php/src/Middleware/AuthMiddleware.php
index a54f47a6..724f1776 100644
--- a/php/src/Middleware/AuthMiddleware.php
+++ b/php/src/Middleware/AuthMiddleware.php
@@ -27,14 +27,23 @@ readonly class AuthMiddleware {
if(!in_array($request->getUri()->getPath(), $publicRoutes)) {
if(!$this->authManager->IsAuthenticated()) {
$status = 302;
- if(count(explode('/', $request->getUri()->getPath())) > 2) {
+
+ // Check the url of the request: split the string by '/' and count the number of elements
+ // Note that the path that gets to this middleware is not aware of any base path managed by a reverse proxy, so if the url is 'https://example.com/AIO/somepage', the path will be 'https://mastercontainer/somepage'
+ if (count(explode('/', $request->getUri()->getPath())) < 2) {
+ // If there are less than 2 elements it means we are somewhere in the root folder (no '/', so no subfolder), so we redirect to the same folder level to offload the redirection to the appropriate page to 'index.php' (specifically, once in the root level the login page will be loaded since we are not authenticated)
+ $location = '.';
+ } else {
+ // If there are 2 or more elements it means we are in a subfolder, so we need to go back to the root folder
+ // In the best case we need to go back by 1 level only
$location = '..';
- for($i = 0; $i < count(explode('/', $request->getUri()->getPath())) - 3; $i++) {
+ // In the worst case we need to go back by n levels, where n is the number of elements - 2 (the first element is not a folder, the second element is already accounted for by the initial '..')
+ for ($i = 1; $i < count(explode('/', $request->getUri()->getPath())) - 2; $i++) {
+ // For each extra level we need to go back by another level
$location = $location . '/..';
}
- } else {
- $location = '.';
}
+
$headers = ['Location' => $location];
$response = new Response($status, $headers);
return $response;