diff --git a/Containers/borgbackup/backupscript.sh b/Containers/borgbackup/backupscript.sh
index ae378635..a0374cf4 100644
--- a/Containers/borgbackup/backupscript.sh
+++ b/Containers/borgbackup/backupscript.sh
@@ -178,6 +178,7 @@ if [ "$BORG_MODE" = restore ]; then
if ! rsync --stats --archive --human-readable -vv --delete \
--exclude "nextcloud_aio_mastercontainer/session/"** \
--exclude "nextcloud_aio_mastercontainer/certs/"** \
+ --exclude "nextcloud_aio_mastercontainer/data/daily_backup_running" \
--exclude "nextcloud_aio_mastercontainer/data/configuration.json" \
/tmp/borg/nextcloud_aio_volumes/ /nextcloud_aio_volumes; then
echo "Something failed while restoring from backup."
diff --git a/Containers/mastercontainer/Dockerfile b/Containers/mastercontainer/Dockerfile
index 18a44082..284fce66 100644
--- a/Containers/mastercontainer/Dockerfile
+++ b/Containers/mastercontainer/Dockerfile
@@ -26,6 +26,7 @@ RUN apt-get update; \
openssl \
sudo \
dpkg-dev \
+ netcat \
; \
rm -rf /var/lib/apt/lists/*
@@ -84,10 +85,12 @@ RUN mkdir /var/log/supervisord; \
COPY Caddyfile /
COPY start.sh /usr/bin/
+COPY backup-time-file-watcher.sh /
COPY cron.sh /
COPY supervisord.conf /
RUN chmod +x /usr/bin/start.sh; \
- chmod +x /cron.sh
+ chmod +x /cron.sh; \
+ chmod +x /backup-time-file-watcher.sh
USER root
diff --git a/Containers/mastercontainer/backup-time-file-watcher.sh b/Containers/mastercontainer/backup-time-file-watcher.sh
new file mode 100644
index 00000000..69b40ef6
--- /dev/null
+++ b/Containers/mastercontainer/backup-time-file-watcher.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+restart_process() {
+ set -x
+ pkill cron.sh
+ set +x
+}
+
+file_present() {
+ if [ -f "/mnt/docker-aio-config/data/daily_backup_time" ]; then
+ if [ "$FILE_PRESENT" = 0 ]; then
+ restart_process
+ fi
+ FILE_PRESENT=1
+ else
+ if [ "$FILE_PRESENT" = 1 ]; then
+ restart_process
+ fi
+ FILE_PRESENT=0
+ fi
+}
+
+while true; do
+ file_present
+ sleep 5
+done
diff --git a/Containers/mastercontainer/cron.sh b/Containers/mastercontainer/cron.sh
index 58fecf03..88b8d631 100644
--- a/Containers/mastercontainer/cron.sh
+++ b/Containers/mastercontainer/cron.sh
@@ -1,12 +1,93 @@
-#!/bin/sh
-set -eux
+#!/bin/bash
while true; do
+ if [ -f "/mnt/docker-aio-config/data/daily_backup_time" ]; then
+ set -x
+ BACKUP_TIME="$(cat "/mnt/docker-aio-config/data/daily_backup_time")"
+ DAILY_BACKUP=1
+ set +x
+ else
+ BACKUP_TIME="04:00"
+ DAILY_BACKUP=0
+ fi
+
+ if [ -f "/mnt/docker-aio-config/data/daily_backup_running" ]; then
+ LOCK_FILE_PRESENT=1
+ else
+ LOCK_FILE_PRESENT=0
+ fi
+
+ # Allow to continue directly if e.g. the mastercontainer was updated. Otherwise wait for the next execution
+ if [ "$LOCK_FILE_PRESENT" = 0 ]; then
+ while [ "$(date +%H:%M)" != "$BACKUP_TIME" ]; do
+ sleep 1
+ done
+ fi
+
+ if [ "$DAILY_BACKUP" = 1 ]; then
+ echo "Daily backup has started"
+
+ # Delete all active sessions and create a lock file
+ rm -f "/mnt/docker-aio-config/session/"*
+ sudo -u www-data touch "/mnt/docker-aio-config/data/daily_backup_running"
+
+ # Check if apache is running/stopped, watchtower is stopped and backupcontainer is stopped
+ APACHE_PORT="$(docker inspect nextcloud-aio-apache --format "{{.HostConfig.PortBindings}}" | grep -oP '[0-9]+' | head -1)"
+ while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-apache$" && ! nc -z nextcloud-aio-apache "$APACHE_PORT"; do
+ echo "Waiting for apache to become available"
+ sleep 30
+ done
+ while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-watchtower$"; do
+ echo "Waiting for watchtower to stop"
+ sleep 30
+ done
+ while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-borgbackup$"; do
+ echo "Waiting for borgbackup to stop"
+ sleep 30
+ done
+
+ # Update the mastercontainer
+ sudo -u www-data php /var/www/docker-aio/php/src/Cron/UpdateMastercontainer.php
+
+ # Wait for watchtower to stop
+ if ! docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-watchtower$"; then
+ echo "Something seems to be wrong: Watchtower should be started at this step."
+ else
+ while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-watchtower$"; do
+ echo "Waiting for watchtower to stop"
+ sleep 30
+ done
+ fi
+
+ # Execute the backup itself and some related tasks
+ sudo -u www-data php /var/www/docker-aio/php/src/Cron/DailyBackup.php
+
+ # Delete the lock file
+ rm -f "/mnt/docker-aio-config/data/daily_backup_running"
+
+ # Wait for the nextcloud container to start and send if the backup was successful
+ if ! docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-nextcloud$"; then
+ echo "Something seems to be wrong: Nextcloud should be started at this step."
+ else
+ while docker ps --format "{{.Names}}" | grep -q "^nextcloud-aio-nextcloud$" && ! nc -z nextcloud-aio-nextcloud 9000; do
+ echo "Waiting for the Nextcloud container to start"
+ sleep 30
+ done
+ fi
+ sudo -u www-data php /var/www/docker-aio/php/src/Cron/BackupNotification.php
+
+ echo "Daily backup has finished"
+ fi
+
+ # Make sure to delete the lock file always
+ rm -f "/mnt/docker-aio-config/data/daily_backup_running"
+
# Check for updates and send notification if yes
- sudo -u www-data php /var/www/docker-aio/php/src/Cron/cron.php
- # Remove dangling images
- sudo -u www-data docker image prune -f
+ sudo -u www-data php /var/www/docker-aio/php/src/Cron/UpdateNotification.php
+
# Remove sessions older than 24h
find "/mnt/docker-aio-config/session/" -mindepth 1 -mmin +1440 -delete
- sleep 1d
+
+ # Remove dangling images
+ sudo -u www-data docker image prune -f
done
diff --git a/Containers/mastercontainer/supervisord.conf b/Containers/mastercontainer/supervisord.conf
index 7ed7eaf5..7d108cb2 100644
--- a/Containers/mastercontainer/supervisord.conf
+++ b/Containers/mastercontainer/supervisord.conf
@@ -28,3 +28,10 @@ stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/cron.sh
+
+[program:backup-time-file-watcher]
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+command=/backup-time-file-watcher.sh
diff --git a/Containers/nextcloud/cron.sh b/Containers/nextcloud/cron.sh
index bfdfafbe..0fe5f589 100644
--- a/Containers/nextcloud/cron.sh
+++ b/Containers/nextcloud/cron.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
set -eu
while true; do
diff --git a/Containers/nextcloud/entrypoint.sh b/Containers/nextcloud/entrypoint.sh
index e3aa667e..b7492c5f 100644
--- a/Containers/nextcloud/entrypoint.sh
+++ b/Containers/nextcloud/entrypoint.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# version_greater A B returns whether A > B
version_greater() {
@@ -230,6 +230,11 @@ if ! [ -f "/mnt/ncdata/skip.update" ]; then
php /var/www/html/occ maintenance:mimetype:update-db
fi
fi
+
+ # Performing update of all apps if daily backups are enabled, running and successful
+ if [ "$DAILY_BACKUP_RUNNING" = 'yes' ]; then
+ php /var/www/html/occ app:update --all
+ fi
fi
# Check if appdata is present
diff --git a/php/containers.json b/php/containers.json
index 25ee579e..3147bb28 100644
--- a/php/containers.json
+++ b/php/containers.json
@@ -132,7 +132,8 @@
"ONLYOFFICE_ENABLED=%ONLYOFFICE_ENABLED%",
"COLLABORA_ENABLED=%COLLABORA_ENABLED%",
"TALK_ENABLED=%TALK_ENABLED%",
- "ONLYOFFICE_HOST=nextcloud-aio-onlyoffice"
+ "ONLYOFFICE_HOST=nextcloud-aio-onlyoffice",
+ "DAILY_BACKUP_RUNNING=%DAILY_BACKUP_RUNNING%"
],
"maxShutdownTime": 10,
"restartPolicy": "unless-stopped"
diff --git a/php/public/index.php b/php/public/index.php
index e2a1e641..eddd71ca 100644
--- a/php/public/index.php
+++ b/php/public/index.php
@@ -93,6 +93,8 @@ $app->get('/containers', function ($request, $response, $args) use ($container)
'is_collabora_enabled' => $configurationManager->isCollaboraEnabled(),
'is_talk_enabled' => $configurationManager->isTalkEnabled(),
'borg_restore_password' => $configurationManager->GetBorgRestorePassword(),
+ 'daily_backup_time' => $configurationManager->GetDailyBackupTime(),
+ 'is_daily_backup_running' => $configurationManager->isDailyBackupRunning(),
]);
})->setName('profile');
$app->get('/login', function ($request, $response, $args) use ($container) {
diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php
index dbb67ffa..70dd6c54 100644
--- a/php/src/Controller/ConfigurationController.php
+++ b/php/src/Controller/ConfigurationController.php
@@ -43,6 +43,15 @@ class ConfigurationController
$this->configurationManager->SetBorgRestoreHostLocationAndPassword($restoreLocation, $borgPassword);
}
+ if (isset($request->getParsedBody()['daily_backup_time'])) {
+ $dailyBackupTime = $request->getParsedBody()['daily_backup_time'] ?? '';
+ $this->configurationManager->SetDailyBackupTime($dailyBackupTime);
+ }
+
+ if (isset($request->getParsedBody()['delete_daily_backup_time'])) {
+ $this->configurationManager->DeleteDailyBackupTime();
+ }
+
if (isset($request->getParsedBody()['options-form'])) {
if (isset($request->getParsedBody()['collabora']) && isset($request->getParsedBody()['onlyoffice'])) {
throw new InvalidSettingConfigurationException("Collabora and Onlyoffice are not allowed to be enabled at the same time!");
diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php
index 6afbfdd4..5257be19 100644
--- a/php/src/Controller/DockerController.php
+++ b/php/src/Controller/DockerController.php
@@ -70,6 +70,11 @@ class DockerController
}
public function StartBackupContainerBackup(Request $request, Response $response, $args) : Response {
+ $this->startBackup();
+ return $response->withStatus(201)->withHeader('Location', '/');
+ }
+
+ public function startBackup() : void {
$config = $this->configurationManager->GetConfig();
$config['backup-mode'] = 'backup';
$this->configurationManager->WriteConfig($config);
@@ -79,8 +84,6 @@ class DockerController
$id = 'nextcloud-aio-borgbackup';
$this->PerformRecursiveContainerStart($id);
-
- return $response->withStatus(201)->withHeader('Location', '/');
}
public function StartBackupContainerCheck(Request $request, Response $response, $args) : Response {
@@ -134,6 +137,16 @@ class DockerController
$config['AIO_URL'] = $host . ':' . $port;
// set wasStartButtonClicked
$config['wasStartButtonClicked'] = 1;
+ $this->configurationManager->WriteConfig($config);
+
+ // Start container
+ $this->startTopContainer();
+
+ return $response->withStatus(201)->withHeader('Location', '/');
+ }
+
+ public function startTopContainer() : void {
+ $config = $this->configurationManager->GetConfig();
// set AIO_TOKEN
$config['AIO_TOKEN'] = bin2hex(random_bytes(24));
$this->configurationManager->WriteConfig($config);
@@ -144,14 +157,17 @@ class DockerController
$id = self::TOP_CONTAINER;
$this->PerformRecursiveContainerStart($id);
- return $response->withStatus(201)->withHeader('Location', '/');
}
public function StartWatchtowerContainer(Request $request, Response $response, $args) : Response {
+ $this->startWatchtower();
+ return $response->withStatus(201)->withHeader('Location', '/');
+ }
+
+ public function startWatchtower() : void {
$id = 'nextcloud-aio-watchtower';
$this->PerformRecursiveContainerStart($id);
- return $response->withStatus(201)->withHeader('Location', '/');
}
private function PerformRecursiveContainerStop(string $id) : void
diff --git a/php/src/Cron/BackupNotification.php b/php/src/Cron/BackupNotification.php
new file mode 100644
index 00000000..a570031b
--- /dev/null
+++ b/php/src/Cron/BackupNotification.php
@@ -0,0 +1,29 @@
+get(\AIO\Docker\DockerActionManager::class);
+/** @var \AIO\ContainerDefinitionFetcher $containerDefinitionFetcher */
+$containerDefinitionFetcher = $container->get(\AIO\ContainerDefinitionFetcher::class);
+
+$id = 'nextcloud-aio-nextcloud';
+$nextcloudContainer = $containerDefinitionFetcher->GetContainerById($id);
+
+$backupExitCode = $dockerActionManger->GetBackupcontainerExitCode();
+
+if ($backupExitCode === 0) {
+ $dockerActionManger->sendNotification($nextcloudContainer, 'Daily backup successful!', 'You can get further info by looking at the backup logs in the AIO interface.');
+}
+
+if ($backupExitCode > 0) {
+ $dockerActionManger->sendNotification($nextcloudContainer, 'Daily backup failed!', 'You can get further info by looking at the backup logs in the AIO interface.');
+}
diff --git a/php/src/Cron/DailyBackup.php b/php/src/Cron/DailyBackup.php
new file mode 100644
index 00000000..932bd04f
--- /dev/null
+++ b/php/src/Cron/DailyBackup.php
@@ -0,0 +1,20 @@
+get(\AIO\Controller\DockerController::class);
+
+// Stop container and start backup
+$dockerController->startBackup();
+
+// Start apache
+$dockerController->startTopContainer();
diff --git a/php/src/Cron/UpdateMastercontainer.php b/php/src/Cron/UpdateMastercontainer.php
new file mode 100644
index 00000000..01c28e59
--- /dev/null
+++ b/php/src/Cron/UpdateMastercontainer.php
@@ -0,0 +1,17 @@
+get(\AIO\Controller\DockerController::class);
+
+# Update the mastercontainer
+$dockerController->startWatchtower();
diff --git a/php/src/Cron/cron.php b/php/src/Cron/UpdateNotification.php
similarity index 100%
rename from php/src/Cron/cron.php
rename to php/src/Cron/UpdateNotification.php
diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php
index e13e1e67..afe30b5a 100644
--- a/php/src/Data/ConfigurationManager.php
+++ b/php/src/Data/ConfigurationManager.php
@@ -444,4 +444,39 @@ class ConfigurationManager
$defaultValue = 'nextcloud_aio_nextcloud_data';
return $this->GetEnvironmentalVariableOrConfig($envVariableName, $configName, $defaultValue);
}
+
+ /**
+ * @throws InvalidSettingConfigurationException
+ */
+ public function SetDailyBackupTime(string $time) : void {
+ if ($time === "") {
+ throw new InvalidSettingConfigurationException("The daily backup time must not be empty!");
+ }
+
+ if (!preg_match("#^[0-1][0-9]:[0-5][0-9]$#", $time) && !preg_match("#^2[0-3]:[0-5][0-9]$#", $time)) {
+ throw new InvalidSettingConfigurationException("You did not enter a correct time! One correct example is '04:00'!");
+ }
+
+ file_put_contents(DataConst::GetDailyBackupTimeFile(), $time);
+ }
+
+ public function GetDailyBackupTime() : string {
+ if (!file_exists(DataConst::GetDailyBackupTimeFile())) {
+ return '';
+ }
+ return file_get_contents(DataConst::GetDailyBackupTimeFile());
+ }
+
+ public function DeleteDailyBackupTime() : void {
+ if (file_exists(DataConst::GetDailyBackupTimeFile())) {
+ unlink(DataConst::GetDailyBackupTimeFile());
+ }
+ }
+
+ public function isDailyBackupRunning() : bool {
+ if (file_exists(DataConst::GetDailyBackupBlockFile())) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/php/src/Data/DataConst.php b/php/src/Data/DataConst.php
index 4c7643d8..5ffaf403 100644
--- a/php/src/Data/DataConst.php
+++ b/php/src/Data/DataConst.php
@@ -27,6 +27,14 @@ class DataConst {
return self::GetDataDirectory() . '/backupsecret';
}
+ public static function GetDailyBackupTimeFile() : string {
+ return self::GetDataDirectory() . '/daily_backup_time';
+ }
+
+ public static function GetDailyBackupBlockFile() : string {
+ return self::GetDataDirectory() . '/daily_backup_running';
+ }
+
public static function GetBackupKeyFile() : string {
return self::GetDataDirectory() . '/borg.config';
}
diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php
index 70e65b48..d7ff2aed 100644
--- a/php/src/Docker/DockerActionManager.php
+++ b/php/src/Docker/DockerActionManager.php
@@ -267,6 +267,12 @@ class DockerActionManager
} else {
$replacements[1] = '';
}
+ } elseif ($out[1] === 'DAILY_BACKUP_RUNNING') {
+ if ($this->configurationManager->isDailyBackupRunning()) {
+ $replacements[1] = 'yes';
+ } else {
+ $replacements[1] = '';
+ }
} else {
$replacements[1] = $this->configurationManager->GetSecret($out[1]);
}
diff --git a/php/templates/containers.twig b/php/templates/containers.twig
index adc26f71..13728612 100644
--- a/php/templates/containers.twig
+++ b/php/templates/containers.twig
@@ -50,7 +50,19 @@
{% endif %}
{% endfor %}
- {% if isWatchtowerRunning == true %}
+ {% if is_daily_backup_running == true %}
+ Daily backup currently running. (Logs)
+ It will update all containers and all apps if the backup is successful.
+ {% if is_mastercontainer_update_available == true %}
+ Since the mastercontainer gets updated, it will restart the container which will make it unavailable for a moment. (Logs)
+ {% endif %}
+ {% if has_update_available == false %}
+ The whole process should not take more than a few minutes.
+ {% else %}
+ The whole process can take a while because your containers get updated.
+ {% endif %}
+ Reload ↻
+ {% elseif isWatchtowerRunning == true %}
Mastercontainer update currently running. It will restart the mastercontainer soon which will make it unavailable for a moment. Please wait until that's done. (Logs)
Reload ↻
{% else %}
@@ -335,6 +347,26 @@
+
+