mirror of
https://github.com/nextcloud/all-in-one.git
synced 2025-12-19 22:16:49 +00:00
allow to restore the whole instance from backup
Signed-off-by: szaimen <szaimen@e.mail.de>
This commit is contained in:
parent
6aa0b7097a
commit
6c6c56fa1b
11 changed files with 199 additions and 28 deletions
|
|
@ -173,6 +173,17 @@ if [ "$BORG_MODE" = restore ]; then
|
|||
echo "Could not mount the backup!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Save current aio password
|
||||
AIO_PASSWORD="$(grep -oP '"password":"[a-zA-Z0-9 ]+"' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)"
|
||||
AIO_PASSWORD="${AIO_PASSWORD##\"password\":\"}"
|
||||
AIO_PASSWORD="${AIO_PASSWORD%\"}"
|
||||
|
||||
# Save current path
|
||||
BORG_LOCATION="$(grep -oP '"borg_backup_host_location":"[\\/a-zA-Z0-9 ]+"' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)"
|
||||
BORG_LOCATION="${BORG_LOCATION##\"borg_backup_host_location\":\"}"
|
||||
BORG_LOCATION="${BORG_LOCATION%\"}"
|
||||
|
||||
if ! rsync --stats --archive --human-readable -vv --delete \
|
||||
--exclude "nextcloud_aio_mastercontainer/session/"** \
|
||||
--exclude "nextcloud_aio_mastercontainer/certs/"** \
|
||||
|
|
@ -183,8 +194,6 @@ if [ "$BORG_MODE" = restore ]; then
|
|||
fi
|
||||
umount /tmp/borg
|
||||
|
||||
# TODO: reset fetchtimes in configuration.json so that it doesn't get the latest directly...
|
||||
|
||||
# Inform user
|
||||
get_expiration_time
|
||||
echo "Restore finished successfully on $END_DATE_READABLE ($DURATION_READABLE)"
|
||||
|
|
@ -196,6 +205,22 @@ if [ "$BORG_MODE" = restore ]; then
|
|||
# Set backup-mode to restore since it was a restore
|
||||
sed -i 's/"backup-mode":"[a-z]\+"/"backup-mode":"restore"/g' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json
|
||||
|
||||
# Reset the AIO password to the currently used one
|
||||
sed -i "s/\"password\":\"[a-zA-Z0-9 ]\+\"/\"password\":\"$AIO_PASSWORD\"/g" /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json
|
||||
|
||||
# Reset the backup path to the currently used one
|
||||
if [ -n "$BORG_LOCATION" ]; then
|
||||
# shellcheck disable=SC2143
|
||||
if [ -n "$(grep -oP '"borg_backup_host_location":"[\\/a-zA-Z0-9 ]+"' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" ]; then
|
||||
sed -i "s/\"borg_backup_host_location\":\"[\\/a-zA-Z0-9 ]\+\"/\"borg_backup_host_location\":\"$BORG_LOCATION\"/g" /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json
|
||||
else
|
||||
echo "Could not set the borg_backup_host_location as it was empty."
|
||||
echo "Probably the regex did not match."
|
||||
fi
|
||||
else
|
||||
echo "Could not get the borg_backup_host_location as it was empty."
|
||||
echo "Probably the regex did not match."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
@ -215,3 +240,23 @@ if [ "$BORG_MODE" = check ]; then
|
|||
echo "Check finished successfully on $END_DATE_READABLE ($DURATION_READABLE)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Do the backup test
|
||||
if [ "$BORG_MODE" = test ]; then
|
||||
if ! [ -d "$BORG_BACKUP_DIRECTORY" ]; then
|
||||
echo "No 'borg' directory in the given backup directory found!"
|
||||
echo "Please adjust the directory so that the borg archive is positioned in a folder named 'borg' inside the given directory!"
|
||||
exit 1
|
||||
elif ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then
|
||||
echo "A 'borg' directory was found but could not find the borg archive."
|
||||
echo "It must be positioned directly in the 'borg' subfolder."
|
||||
exit 1
|
||||
elif ! borg list "$BORG_BACKUP_DIRECTORY"; then
|
||||
echo "The entered path seems to be valid but could not open the backup archive."
|
||||
echo "Most likely the entered password was wrong so please adjust it accordingly!"
|
||||
exit 1
|
||||
else
|
||||
echo "Everything looks fine so feel free to continue!"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,19 +4,23 @@
|
|||
export BORG_BACKUP_DIRECTORY="/mnt/borgbackup/borg"
|
||||
|
||||
# Validate BORG_PASSWORD
|
||||
if [ -z "$BORG_PASSWORD" ]; then
|
||||
echo "BORG_PASSWORD is not allowed to be empty."
|
||||
if [ -z "$BORG_PASSWORD" ] && [ -z "$BACKUP_RESTORE_PASSWORD" ]; then
|
||||
echo "Neither BORG_PASSWORD nor BACKUP_RESTORE_PASSWORD are set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Export defaults
|
||||
export BORG_PASSPHRASE="$BORG_PASSWORD"
|
||||
if [ -n "$BACKUP_RESTORE_PASSWORD" ]; then
|
||||
export BORG_PASSPHRASE="$BACKUP_RESTORE_PASSWORD"
|
||||
else
|
||||
export BORG_PASSPHRASE="$BORG_PASSWORD"
|
||||
fi
|
||||
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
||||
export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
|
||||
|
||||
# Validate BORG_MODE
|
||||
if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != restore ] && [ "$BORG_MODE" != check ]; then
|
||||
echo "No correct BORG_MODE mode applied. Valid are 'backup' and 'restore'."
|
||||
if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != restore ] && [ "$BORG_MODE" != check ] && [ "$BORG_MODE" != test ]; then
|
||||
echo "No correct BORG_MODE mode applied. Valid are 'backup', 'check', 'restore' and 'test'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,8 @@
|
|||
"environmentVariables": [
|
||||
"BORG_PASSWORD=%BORGBACKUP_PASSWORD%",
|
||||
"BORG_MODE=%BORGBACKUP_MODE%",
|
||||
"SELECTED_RESTORE_TIME=%SELECTED_RESTORE_TIME%"
|
||||
"SELECTED_RESTORE_TIME=%SELECTED_RESTORE_TIME%",
|
||||
"BACKUP_RESTORE_PASSWORD=%BACKUP_RESTORE_PASSWORD%"
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,21 +18,10 @@
|
|||
<MissingParamType occurrences="1">
|
||||
<code>$args</code>
|
||||
</MissingParamType>
|
||||
<PossiblyInvalidArrayAccess occurrences="2">
|
||||
<code>$request->getParsedBody()['borg_backup_host_location']</code>
|
||||
<code>$request->getParsedBody()['domain']</code>
|
||||
</PossiblyInvalidArrayAccess>
|
||||
<PossiblyNullArgument occurrences="2">
|
||||
<code>$request->getParsedBody()['borg_backup_host_location']</code>
|
||||
<code>$request->getParsedBody()['domain']</code>
|
||||
</PossiblyNullArgument>
|
||||
<PossiblyNullArrayAccess occurrences="2">
|
||||
<code>$request->getParsedBody()['borg_backup_host_location']</code>
|
||||
<code>$request->getParsedBody()['domain']</code>
|
||||
</PossiblyNullArrayAccess>
|
||||
</file>
|
||||
<file src="src/Controller/DockerController.php">
|
||||
<MissingParamType occurrences="7">
|
||||
<MissingParamType occurrences="8">
|
||||
<code>$args</code>
|
||||
<code>$args</code>
|
||||
<code>$args</code>
|
||||
<code>$args</code>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ $app->get('/api/docker/getwatchtower', AIO\Controller\DockerController::class .
|
|||
$app->post('/api/docker/start', AIO\Controller\DockerController::class . ':StartContainer');
|
||||
$app->post('/api/docker/backup', AIO\Controller\DockerController::class . ':StartBackupContainerBackup');
|
||||
$app->post('/api/docker/backup-check', AIO\Controller\DockerController::class . ':StartBackupContainerCheck');
|
||||
$app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest');
|
||||
$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore');
|
||||
$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer');
|
||||
$app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs');
|
||||
|
|
@ -90,6 +91,7 @@ $app->get('/containers', function ($request, $response, $args) use ($container)
|
|||
'is_onlyoffice_enabled' => $configurationManager->isOnlyofficeEnabled(),
|
||||
'is_collabora_enabled' => $configurationManager->isCollaboraEnabled(),
|
||||
'is_talk_enabled' => $configurationManager->isTalkEnabled(),
|
||||
'borg_restore_password' => $configurationManager->GetBorgRestorePassword(),
|
||||
]);
|
||||
})->setName('profile');
|
||||
$app->get('/login', function ($request, $response, $args) use ($container) {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ class ConfigurationController
|
|||
public function SetConfig(Request $request, Response $response, $args) : Response {
|
||||
try {
|
||||
if (isset($request->getParsedBody()['domain'])) {
|
||||
$this->configurationManager->SetDomain($request->getParsedBody()['domain']);
|
||||
$domain = $request->getParsedBody()['domain'] ?? '';
|
||||
$this->configurationManager->SetDomain($domain);
|
||||
}
|
||||
|
||||
if (isset($request->getParsedBody()['current-master-password']) || isset($request->getParsedBody()['new-master-password'])) {
|
||||
|
|
@ -32,7 +33,14 @@ class ConfigurationController
|
|||
}
|
||||
|
||||
if (isset($request->getParsedBody()['borg_backup_host_location'])) {
|
||||
$this->configurationManager->SetBorgBackupHostLocation($request->getParsedBody()['borg_backup_host_location']);
|
||||
$location = $request->getParsedBody()['borg_backup_host_location'] ?? '';
|
||||
$this->configurationManager->SetBorgBackupHostLocation($location);
|
||||
}
|
||||
|
||||
if (isset($request->getParsedBody()['borg_restore_host_location']) || isset($request->getParsedBody()['borg_restore_password'])) {
|
||||
$restoreLocation = $request->getParsedBody()['borg_restore_host_location'] ?? '';
|
||||
$borgPassword = $request->getParsedBody()['borg_restore_password'] ?? '';
|
||||
$this->configurationManager->SetBorgRestoreHostLocationAndPassword($restoreLocation, $borgPassword);
|
||||
}
|
||||
|
||||
if (isset($request->getParsedBody()['options-form'])) {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,20 @@ class DockerController
|
|||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartBackupContainerTest(Request $request, Response $response, $args) : Response {
|
||||
$config = $this->configurationManager->GetConfig();
|
||||
$config['backup-mode'] = 'test';
|
||||
$this->configurationManager->WriteConfig($config);
|
||||
|
||||
$id = self::TOP_CONTAINER;
|
||||
$this->PerformRecursiveContainerStop($id);
|
||||
|
||||
$id = 'nextcloud-aio-borgbackup';
|
||||
$this->PerformRecursiveContainerStart($id);
|
||||
|
||||
return $response->withStatus(201)->withHeader('Location', '/');
|
||||
}
|
||||
|
||||
public function StartContainer(Request $request, Response $response, $args) : Response
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
|
|
|
|||
|
|
@ -237,6 +237,8 @@ class ConfigurationManager
|
|||
// Write domain
|
||||
$config = $this->GetConfig();
|
||||
$config['domain'] = $domain;
|
||||
// Reset the borg restore password when setting the domain
|
||||
$config['borg_restore_password'] = '';
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
|
|
@ -311,6 +313,33 @@ class ConfigurationManager
|
|||
/**
|
||||
* @throws InvalidSettingConfigurationException
|
||||
*/
|
||||
public function SetBorgRestoreHostLocationAndPassword(string $location, string $password) : void {
|
||||
if ($location === '') {
|
||||
throw new InvalidSettingConfigurationException("Please enter a path!");
|
||||
}
|
||||
|
||||
$isValidPath = false;
|
||||
if (str_starts_with($location, '/') && !str_ends_with($location, '/')) {
|
||||
$isValidPath = true;
|
||||
}
|
||||
|
||||
if(!$isValidPath) {
|
||||
throw new InvalidSettingConfigurationException("The path may start with '/mnt/', '/media/' or '/host_mnt/' or may be equal to '/var/backups'.");
|
||||
}
|
||||
|
||||
if ($password === '') {
|
||||
throw new InvalidSettingConfigurationException("Please enter the password!");
|
||||
}
|
||||
|
||||
$config = $this->GetConfig();
|
||||
$config['borg_backup_host_location'] = $location;
|
||||
$config['borg_restore_password'] = $password;
|
||||
$this->WriteConfig($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidSettingConfigurationException
|
||||
*/
|
||||
public function ChangeMasterPassword(string $currentPassword, string $newPassword) : void {
|
||||
if ($currentPassword === '') {
|
||||
throw new InvalidSettingConfigurationException("Please enter your current password.");
|
||||
|
|
@ -384,6 +413,15 @@ class ConfigurationManager
|
|||
return $config['borg_backup_host_location'];
|
||||
}
|
||||
|
||||
public function GetBorgRestorePassword() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['borg_restore_password'])) {
|
||||
$config['borg_restore_password'] = '';
|
||||
}
|
||||
|
||||
return $config['borg_restore_password'];
|
||||
}
|
||||
|
||||
public function GetBorgBackupMode() : string {
|
||||
$config = $this->GetConfig();
|
||||
if(!isset($config['backup-mode'])) {
|
||||
|
|
|
|||
|
|
@ -241,6 +241,8 @@ class DockerActionManager
|
|||
$replacements[1] = $this->configurationManager->GetApachePort();
|
||||
} elseif ($out[1] === 'NEXTCLOUD_MOUNT') {
|
||||
$replacements[1] = $this->configurationManager->GetNextcloudMount();
|
||||
} elseif ($out[1] === 'BACKUP_RESTORE_PASSWORD') {
|
||||
$replacements[1] = $this->configurationManager->GetBorgRestorePassword();
|
||||
} elseif ($out[1] === 'CLAMAV_ENABLED') {
|
||||
if ($this->configurationManager->isClamavEnabled()) {
|
||||
$replacements[1] = 'yes';
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@
|
|||
<input class="button" type="submit" value="Update mastercontainer" />
|
||||
</form>
|
||||
{% else %}
|
||||
Please type in the domain that will be used for Nextcloud:<br><br />
|
||||
Nextcloud AIO stands for Nextcloud All In One and provides easy deployment and maintenance with most features included in this one Nextcloud instance.<br><br>
|
||||
<h2>New AIO instance</h2>
|
||||
Please type in the domain that will be used for Nextcloud if you want to create a new instance:<br><br />
|
||||
<form method="POST" action="/api/configuration" class="xhr">
|
||||
<input type="text" name="domain" value="{{ domain }}" placeholder="nextcloud.yourdomain.com"/>
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
|
|
@ -74,8 +76,75 @@
|
|||
<input class="button" type="submit" value="Submit" />
|
||||
</form>
|
||||
Make sure that this server is reachable on Port 443 and you've correctly set up the DNS config for the domain that you enter. <br><br>
|
||||
If you have a dynamic IP-address, you can use e.g. <a href="https://ddclient.net/">DDclient</a> with a compatible domain provider for DNS updates.
|
||||
If you have a dynamic IP-address, you can use e.g. <a href="https://ddclient.net/">DDclient</a> with a compatible domain provider for DNS updates. <br /><br/>
|
||||
|
||||
<h2>Restore AIO instance from backup</h2>
|
||||
You can alternatively restore an AIO instance from backup.<br><br>
|
||||
|
||||
{% if borg_backup_host_location == '' or borg_restore_password == '' or borg_backup_mode not in ['test', 'check'] or backup_exit_code != 0 %}
|
||||
Please enter the location of the backup archive on your host and the password of the backup archive below:<br><br>
|
||||
<form method="POST" action="/api/configuration" class="xhr">
|
||||
<input type="text" name="borg_restore_host_location" placeholder="/mnt/backup"/>
|
||||
<input type="text" name="borg_restore_password" placeholder="enter the borg password"/>
|
||||
<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>
|
||||
The folder path that you enter may start with <b>/mnt/</b>, <b>/media/</b> or <b>/host_mnt/</b> or may be equal to <b>/var/backups</b>.<br>So e.g. <b>/mnt/backup</b> on Linux and macOS or <b>/host_mnt/c/backup/directory</b> on Windows. (This Windows example would be equivalent to 'C:\backup\directory' on the Windows host. So you need to translate the path that you want to use into the correct format.)<br><br>
|
||||
⚠ Note that the backup archive must be located in a subfolder of the folder that you enter here and the subfolder which contains the archive must be named 'borg'. Otherwise will the backup container not find the backup archive!<br><br>
|
||||
{% endif %}
|
||||
|
||||
{% if borg_backup_host_location != '' and borg_restore_password != '' %}
|
||||
{% if borg_backup_mode not in ['test', 'check'] or backup_exit_code != 0 %}
|
||||
<b>Everything set!</b> Click on the button below to test the path and password:
|
||||
<form method="POST" action="/api/docker/backup-tests" class="xhr">
|
||||
<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="Test path and password"/><br/>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if borg_backup_mode in ['test', 'check'] %}
|
||||
{% if backup_exit_code > 0 %}
|
||||
<span class="status error"></span> Last {{ backup_exit_code }} failed! (<a href="/api/docker/logs?id=nextcloud-aio-borgbackup">Logs</a>)<br /><br />
|
||||
{% if borg_backup_mode == 'test' %}
|
||||
Please adjust the path and/or password in order to make it work!
|
||||
{% elseif borg_backup_mode == 'check' %}
|
||||
The backup archive seems to be corrupt. Please try to use a different intact backup archive or try to fix it by following <a href="https://borgbackup.readthedocs.io/en/stable/faq.html?highlight=repair#:~:text=repairing%20a%20damaged%20repository"><b>this documentation</b></a>
|
||||
{% endif %}
|
||||
{% elseif backup_exit_code == 0 %}
|
||||
<span class="status success"></span> Last {{ borg_backup_mode }} successful! (<a href="/api/docker/logs?id=nextcloud-aio-borgbackup">Logs</a>)<br /><br />
|
||||
{% if borg_backup_mode == 'test' %}
|
||||
Feel free to check the integrity of the backup archive below before starting the restore process in order to make double-sure that the restore will work. This can take a long time though depending on the size of the backup archive and is thus not required.<br><br>
|
||||
<form method="POST" action="/api/docker/backup-check" class="xhr">
|
||||
<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="Check backup integrity"/><br/>
|
||||
</form>
|
||||
{% 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 from backup. Please not that the current AIO password will be kept and the AIO password not restored from backup!<br><br>
|
||||
<form method="POST" action="/api/docker/restore" class="xhr" id="restore_selection">
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<select id="selected_restore_time" name="selected_restore_time" form="restore_selection">
|
||||
{% for restore_time in backup_times %}
|
||||
<option value="{{ restore_time }}">{{ restore_time }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input class="button" type="submit" value="Restore selected backup"/>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% elseif borg_backup_mode == 'restore' %}
|
||||
{% if backup_exit_code > 0 %}
|
||||
<span class="status error"></span> Last restore failed! (<a href="/api/docker/logs?id=nextcloud-aio-borgbackup">Logs</a>)<br /><br />
|
||||
Somehow the restore failed which is unexpected! Please adjust the path and password, test it and try to restore again!
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if isBackupContainerRunning == true and domain == "" %}
|
||||
Backup container is currently running.<br /><br />
|
||||
<a href="" class="button reload">Reload ↻</a><br/><br>
|
||||
{% endif %}
|
||||
|
||||
{% if domain != "" %}
|
||||
|
|
@ -191,7 +260,7 @@
|
|||
|
||||
{% if was_start_button_clicked == true %}
|
||||
|
||||
{% if isBackupOrRestoreRunning == false and borg_backup_host_location == "" and isApacheStarting != true %}
|
||||
{% if isBackupContainerRunning == false and borg_backup_host_location == "" and isApacheStarting != true %}
|
||||
<h2>Backup and restore</h2>
|
||||
Please type in the directory where backups will get created on the host system:<br><br>
|
||||
<form method="POST" action="/api/configuration" class="xhr">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
<img src="/img/logo-blue.svg" style="margin-left: auto;margin-right: auto;display: block;">
|
||||
<h1>Your password for Nextcloud AIO Beta</h1>
|
||||
<p>Please note down the password to access the AIO interface and don't loose it!</p>
|
||||
<p>Nextcloud AIO stands for Nextcloud All In One and provides easy deployment and maintenance with most features included in this one Nextcloud instance.</p>
|
||||
<strong>Password</strong><br/> <span class="monospace">{{ password }}</span><br>
|
||||
<a href="/" class="button" target="_blank" rel="noopener">Open Nextcloud AIO login ↗</a>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue