mirror of
https://github.com/nextcloud/all-in-one.git
synced 2025-01-01 03:22:48 +08:00
Merge pull request #455 from nextcloud/enh/47/auto-backup
add option to enable daily backups
This commit is contained in:
commit
888d16d790
19 changed files with 314 additions and 16 deletions
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
26
Containers/mastercontainer/backup-time-file-watcher.sh
Normal file
26
Containers/mastercontainer/backup-time-file-watcher.sh
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
while true; do
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -133,7 +133,8 @@
|
|||
"COLLABORA_ENABLED=%COLLABORA_ENABLED%",
|
||||
"COLLABORA_HOST=nextcloud-aio-collabora",
|
||||
"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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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!");
|
||||
|
|
|
@ -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
|
||||
|
|
29
php/src/Cron/BackupNotification.php
Normal file
29
php/src/Cron/BackupNotification.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// increase memory limit to 2GB
|
||||
ini_set('memory_limit', '2048M');
|
||||
|
||||
use DI\Container;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
$container = \AIO\DependencyInjection::GetContainer();
|
||||
|
||||
/** @var \AIO\Docker\DockerActionManager $dockerActionManger */
|
||||
$dockerActionManger = $container->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.');
|
||||
}
|
20
php/src/Cron/DailyBackup.php
Normal file
20
php/src/Cron/DailyBackup.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// increase memory limit to 2GB
|
||||
ini_set('memory_limit', '2048M');
|
||||
|
||||
use DI\Container;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
$container = \AIO\DependencyInjection::GetContainer();
|
||||
|
||||
/** @var \AIO\Controller\DockerController $dockerController */
|
||||
$dockerController = $container->get(\AIO\Controller\DockerController::class);
|
||||
|
||||
// Stop container and start backup
|
||||
$dockerController->startBackup();
|
||||
|
||||
// Start apache
|
||||
$dockerController->startTopContainer();
|
17
php/src/Cron/UpdateMastercontainer.php
Normal file
17
php/src/Cron/UpdateMastercontainer.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// increase memory limit to 2GB
|
||||
ini_set('memory_limit', '2048M');
|
||||
|
||||
use DI\Container;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
$container = \AIO\DependencyInjection::GetContainer();
|
||||
|
||||
/** @var \AIO\Controller\DockerController $dockerController */
|
||||
$dockerController = $container->get(\AIO\Controller\DockerController::class);
|
||||
|
||||
# Update the mastercontainer
|
||||
$dockerController->startWatchtower();
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,19 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if isWatchtowerRunning == true %}
|
||||
{% if is_daily_backup_running == true %}
|
||||
<span class="status running"></span> Daily backup currently running. (<a href="/api/docker/logs?id=nextcloud-aio-mastercontainer">Logs</a>)<br /><br />
|
||||
It will update all containers and all apps if the backup is successful.<br /><br />
|
||||
{% if is_mastercontainer_update_available == true %}
|
||||
Since the mastercontainer gets updated, it will restart the container which will make it unavailable for a moment. (<a href="/api/docker/logs?id=nextcloud-aio-watchtower">Logs</a>)<br /><br />
|
||||
{% endif %}
|
||||
{% if has_update_available == false %}
|
||||
The whole process should not take more than a few minutes.<br /><br />
|
||||
{% else %}
|
||||
The whole process can take a while because your containers get updated.<br /><br />
|
||||
{% endif %}
|
||||
<a href="" class="button reload">Reload ↻</a><br/>
|
||||
{% elseif isWatchtowerRunning == true %}
|
||||
<span class="status running"></span> Mastercontainer update currently running. It will restart the mastercontainer soon which will make it unavailable for a moment. Please wait until that's done. (<a href="/api/docker/logs?id=nextcloud-aio-watchtower">Logs</a>)<br /><br />
|
||||
<a href="" class="button reload">Reload ↻</a><br/>
|
||||
{% else %}
|
||||
|
@ -335,6 +347,26 @@
|
|||
</select>
|
||||
<input class="button" type="submit" value="Restore selected backup" onclick="return confirm('Restore the selected backup? Are you sure that you want to restore the selected backup? This will stop all running containers and restore the selected backup. It is recommended to create a backup first. You might also want to check the backup integrity.')" />
|
||||
</form>
|
||||
|
||||
<h3>Daily backup creation</h3>
|
||||
{% if daily_backup_time == "" %}
|
||||
By entering a time below, you can enable daily backups. It will create them at the entered time in 24h format. E.g. <b>04:00</b> will create backups at 4 am CT and <b>16:00</b> at 4 pm CT.<br><br/>
|
||||
<form method="POST" action="/api/configuration" class="xhr">
|
||||
<input type="text" name="daily_backup_time" value="04:00" placeholder="04:00"/>
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input class="button" type="submit" value="Submit" />
|
||||
</form>
|
||||
This option will also automatically update your containers and apps and will send a notification about the result of the backup.<br><br/>
|
||||
{% else %}
|
||||
Daily backups will be created at <b>{{ daily_backup_time }} CT</b> which includes a notification about the result of the backup and automatic updates of your containers and apps. You can disable this option again by clicking on the button below.<br><br/>
|
||||
<form method="POST" action="/api/configuration" class="xhr">
|
||||
<input type="hidden" name="delete_daily_backup_time" value="yes"/>
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input class="button" type="submit" value="Disable daily backups" />
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if has_backup_run_once == false %}
|
||||
|
@ -402,7 +434,7 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if isApacheStarting == true or is_backup_container_running == true or isWatchtowerRunning == true %}
|
||||
{% if isApacheStarting == true or is_backup_container_running == true or isWatchtowerRunning == true or is_daily_backup_running == true %}
|
||||
<script type="text/javascript" src="automatic_reload.js"></script>
|
||||
{% else %}
|
||||
<script type="text/javascript" src="before-unload.js"></script>
|
||||
|
|
Loading…
Reference in a new issue