Compare commits

..

1 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
121e60491d Extract Nextcloud major upgrade logic to script and add UI button
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/8cd11b09-5073-4e27-8e59-9afffaf96c1f

Rename sendNotification to execCommandInContainer and reuse for upgrade method

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/88744552-9d64-4de2-9f64-5a98a5e3b200

Add $cmd array validation to execCommandInContainer

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/45d5228c-7834-404e-ba54-90b5c8c207c8

Apply suggestion from @szaimen

Signed-off-by: Simon L. <szaimen@e.mail.de>

Apply suggestion from @szaimen

Signed-off-by: Simon L. <szaimen@e.mail.de>

Apply suggestion from @szaimen

Signed-off-by: Simon L. <szaimen@e.mail.de>

Apply suggestion from @szaimen

Signed-off-by: Simon L. <szaimen@e.mail.de>

Set installLatestMajor when upgrade-to-latest-major button is clicked

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/7b977c85-9b74-4027-a536-152e49a01976

Extract getLatestMajorVersion() to avoid duplicating the version string

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/d5ec921f-8629-4f6e-949a-e8f89f1eb85f

Address PR review comments and hardcode updater channel to stable

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/c40941ff-2bf8-4a57-82be-2a0bd22b19a2

Restore sendNotification(), update cron files, extract getPlainStreamingCallback()

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/a5b6cd86-d278-4771-8a11-976c4a862966

Remove getPlainStreamingCallback, unify on getAddToStreamingResponseBody

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/15a4b815-076b-469f-95b2-c61df688a28d

Revert "Remove getPlainStreamingCallback, unify on getAddToStreamingResponseBody"

This reverts commit 6846c3a99549703121461f910cc26e6c116e0dc4.
2026-05-21 11:43:00 +02:00
14 changed files with 161 additions and 150 deletions

View File

@@ -18,30 +18,6 @@ get_expiration_time() {
DURATION_HOUR=$((DURATION / 3600))
DURATION_READABLE=$(printf "%02d hours %02d minutes %02d seconds" $DURATION_HOUR $DURATION_MIN $DURATION_SEC)
}
# Run "borg info" and handle the exit code.
# If the exit code indicates a connection failure (80 = ConnectionClosed,
# 81 = ConnectionClosedWithHint) and a remote repo is configured, the SSH
# auth error signal file is created so the mastercontainer can show a
# targeted error message. Returns the original borg exit code.
borg_info() {
borg "$BORG_LOG_LEVEL_FLAG" info > /dev/null
local _exit=$?
if [ -n "$BORG_REMOTE_REPO" ] && { [ "$_exit" -eq 80 ] || [ "$_exit" -eq 81 ]; }; then
touch "$SSH_AUTH_ERROR_FILE"
fi
return $_exit
}
# Signal file written when an SSH authentication failure is detected so the
# mastercontainer can show a targeted error without needing to scan container logs.
# Borg exit codes 80 (ConnectionClosed) and 81 (ConnectionClosedWithHint) indicate
# connection failures that occur before the Borg protocol is established, which covers
# SSH authentication errors and host-key verification failures.
# These codes are available because BORG_EXIT_CODES=modern is set in start.sh.
SSH_AUTH_ERROR_FILE="/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg_ssh_auth_error"
# Start with a clean state for every run
rm -f "$SSH_AUTH_ERROR_FILE"
# Test if all volumes aren't empty
VOLUME_DIRS="$(find /nextcloud_aio_volumes -mindepth 1 -maxdepth 1 -type d)"
@@ -151,7 +127,7 @@ if [ "$BORG_MODE" = backup ]; then
fi
# Initialize the repository if can't get info from target
if ! borg_info; then
if ! borg "$BORG_LOG_LEVEL_FLAG" info > /dev/null; then
# Don't initialize if already initialized
if [ -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config" ]; then
if [ -n "$BORG_REMOTE_REPO" ]; then
@@ -612,7 +588,7 @@ fi
# Do the backup test
if [ "$BORG_MODE" = test ]; then
if [ -n "$BORG_REMOTE_REPO" ]; then
if ! borg_info; then
if ! borg "$BORG_LOG_LEVEL_FLAG" info > /dev/null; then
echo "Borg could not get info from the remote repo."
echo "See the above borg info output for details."
exit 1

View File

@@ -29,8 +29,6 @@ else
fi
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
# Use specific exit codes (80/81 for connection failures) instead of the legacy generic exit code 2
export BORG_EXIT_CODES=modern
if [ -n "$BORG_REMOTE_REPO" ]; then
export BORG_REPO="$BORG_REMOTE_REPO"

View File

@@ -2,4 +2,5 @@
$CONFIG = array (
'one-click-instance' => true,
'one-click-instance.user-limit' => 100,
'update_channel' => 'stable',
);

View File

@@ -1,4 +1,4 @@
<?php
$CONFIG = array (
'serverid' => hexdec(hash('xxh32', gethostname()) & 0x1FF,
'serverid' => crc32(gethostname()) % 512,
);

View File

@@ -419,41 +419,12 @@ EOF
# AIO update to latest start # Do not remove or change this line!
if [ "$INSTALL_LATEST_MAJOR" = yes ]; then
php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater"
INSTALLED_AT="$(php /var/www/html/occ config:app:get core installedat)"
if [ -n "${INSTALLED_AT}" ]; then
# Set the installdat to 00 which will allow to skip staging and install the next major directly
# shellcheck disable=SC2001
INSTALLED_AT="$(echo "${INSTALLED_AT}" | sed "s|[0-9][0-9]$|00|")"
php /var/www/html/occ config:app:set core installedat --value="${INSTALLED_AT}"
fi
php /var/www/html/updater/updater.phar --no-interaction --no-backup
if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then
echo "Installation of Nextcloud failed!"
touch "$NEXTCLOUD_DATA_DIR/install.failed"
if ! bash /upgrade-latest-major.sh; then
echo "Upgrade to latest major version failed! Check the output above for details."
exit 1
fi
# shellcheck disable=SC2016
installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')"
INSTALLED_MAJOR="${installed_version%%.*}"
IMAGE_MAJOR="${image_version%%.*}"
# If a valid upgrade path, trigger the Nextcloud built-in Updater
if ! [ "$INSTALLED_MAJOR" -gt "$IMAGE_MAJOR" ]; then
php /var/www/html/updater/updater.phar --no-interaction --no-backup
if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then
echo "Installation of Nextcloud failed!"
# TODO: Add a hint here about what to do / where to look / updater.log?
touch "$NEXTCLOUD_DATA_DIR/install.failed"
exit 1
fi
# shellcheck disable=SC2016
installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')"
fi
php /var/www/html/occ config:system:set updatechecker --type=bool --value=true
php /var/www/html/occ app:enable nextcloud-aio --force
php /var/www/html/occ db:add-missing-columns
php /var/www/html/occ db:add-missing-primary-keys
yes | php /var/www/html/occ db:convert-filecache-bigint
fi
# AIO update to latest end # Do not remove or change this line!

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# shellcheck disable=SC2016
image_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')"
IMAGE_MAJOR="${image_version%%.*}"
php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater"
INSTALLED_AT="$(php /var/www/html/occ config:app:get core installedat)"
if [ -n "${INSTALLED_AT}" ]; then
# Set the installedat to 00 which will allow to skip staging and install the next major directly
# shellcheck disable=SC2001
INSTALLED_AT="$(echo "${INSTALLED_AT}" | sed "s|[0-9][0-9]$|00|")"
php /var/www/html/occ config:app:set core installedat --value="${INSTALLED_AT}"
fi
php /var/www/html/updater/updater.phar --no-interaction --no-backup
if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then
echo "Installation of Nextcloud failed!"
touch "$NEXTCLOUD_DATA_DIR/install.failed"
exit 1
fi
# shellcheck disable=SC2016
installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')"
INSTALLED_MAJOR="${installed_version%%.*}"
# If a valid upgrade path, trigger the Nextcloud built-in Updater
if ! [ "$INSTALLED_MAJOR" -gt "$IMAGE_MAJOR" ]; then
php /var/www/html/updater/updater.phar --no-interaction --no-backup
if ! php /var/www/html/occ -V || php /var/www/html/occ status | grep maintenance | grep -q 'true'; then
echo "Installation of Nextcloud failed!"
# TODO: Add a hint here about what to do / where to look / updater.log?
touch "$NEXTCLOUD_DATA_DIR/install.failed"
exit 1
fi
fi
php /var/www/html/occ config:system:set updatechecker --type=bool --value=true
php /var/www/html/occ app:enable nextcloud-aio --force
php /var/www/html/occ db:add-missing-columns
php /var/www/html/occ db:add-missing-primary-keys
yes | php /var/www/html/occ db:convert-filecache-bigint

View File

@@ -42,15 +42,6 @@ if ! [ -f /var/www/html/custom_apps/notify_push/bin/"$CPU_ARCH"/notify_push ] &&
exit 1
fi
# Logic for ipv6 disabled servers
BIND="::"
if grep -q "1" /sys/module/ipv6/parameters/disable \
|| grep -q "1" /proc/sys/net/ipv6/conf/all/disable_ipv6 \
|| grep -q "1" /proc/sys/net/ipv6/conf/default/disable_ipv6; then
BIND="0.0.0.0"
fi
export BIND
echo "notify-push was started"

View File

@@ -104,6 +104,7 @@ $app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . '
$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore');
$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer');
$app->post('/api/docker/backup-reset-location', AIO\Controller\DockerController::class . ':DeleteBorgBackupConfig');
$app->post('/api/docker/nextcloud-upgrade-to-latest-major', AIO\Controller\DockerController::class . ':RunNextcloudUpgradeToLatestMajor');
$app->post('/api/docker/prune', AIO\Controller\DockerController::class . ':SystemPrune');
$app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs');
$app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin');
@@ -143,7 +144,6 @@ $app->get('/containers', function (Request $request, Response $response, array $
'has_backup_run_once' => $configurationManager->hasBackupRunOnce(),
'is_backup_container_running' => $dockerActionManager->isBackupContainerRunning(),
'backup_exit_code' => $dockerActionManager->GetBackupcontainerExitCode(),
'is_ssh_auth_error' => $dockerActionManager->isBorgBackupSshAuthError(),
'is_instance_restore_attempt' => $configurationManager->instanceRestoreAttempt,
'borg_backup_mode' => $configurationManager->backupMode,
'was_start_button_clicked' => $configurationManager->wasStartButtonClicked,

View File

@@ -14,6 +14,7 @@ use Slim\Psr7\NonBufferedBody;
readonly class DockerController {
private const string TOP_CONTAINER = 'nextcloud-aio-apache';
private const string LATEST_MAJOR_VERSION = '33';
public function __construct(
private DockerActionManager $dockerActionManager,
@@ -221,7 +222,7 @@ readonly class DockerController {
}
if (isset($request->getParsedBody()['install_latest_major'])) {
$installLatestMajor = '33';
$installLatestMajor = self::LATEST_MAJOR_VERSION;
} else {
$installLatestMajor = '';
}
@@ -333,6 +334,20 @@ readonly class DockerController {
return $response->withStatus(201)->withHeader('Location', '.');
}
public function RunNextcloudUpgradeToLatestMajor(Request $request, Response $response, array $args) : Response {
$this->configurationManager->installLatestMajor = self::LATEST_MAJOR_VERSION;
// Get streaming response start and closure
$nonbufResp = $this->startStreamingResponse($response);
$addToStreamingResponseBody = $this->getPlainStreamingCallback($nonbufResp);
$this->dockerActionManager->RunNextcloudUpgradeToLatestMajor($addToStreamingResponseBody);
// End streaming response
$this->finalizeStreamingResponse($nonbufResp);
return $nonbufResp;
}
public function SystemPrune(Request $request, Response $response, array $args) : Response {
// Get streaming response start and closure
$nonbufResp = $this->startStreamingResponse($response);
@@ -430,12 +445,18 @@ readonly class DockerController {
// if it'll actually pull an image), but which should not need to know anything about the
// wanted markup or formatting.
$addToStreamingResponseBody = function (Container $container, string $message) use ($nonbufResp) : void {
$nonbufResp->getBody()->write("<div>{$container->displayName}: {$message}</div>");
$nonbufResp->getBody()->write("<div>" . htmlspecialchars("{$container->displayName}: {$message}", ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</div>");
};
return $addToStreamingResponseBody;
}
private function getPlainStreamingCallback(Response $nonbufResp) : \Closure {
return function (string $message) use ($nonbufResp) : void {
$nonbufResp->getBody()->write("<div>" . htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "</div>");
};
}
private function finalizeStreamingResponse(Response $nonbufResp) : void {
$nonbufResp->getBody()->write($this->getStreamingResponseHtmlEnd());
}

View File

@@ -68,10 +68,6 @@ class DataConst {
return (string)realpath(__DIR__ . '/../../containers.json');
}
public static function GetBorgSshAuthErrorFile() : string {
return self::GetDataDirectory() . '/borg_ssh_auth_error';
}
public static function GetAioVersionFile() : string {
return (string)realpath(__DIR__ . '/../../templates/includes/aio-version.twig');
}

View File

@@ -761,48 +761,79 @@ readonly class DockerActionManager {
}
public function sendNotification(Container $container, string $subject, string $message, string $file = '/notify.sh'): void {
if ($this->GetContainerStartingState($container) === ContainerState::Running) {
$this->execCommandInContainer($container, ['bash', $file, $subject, $message]);
}
$containerName = $container->identifier;
public function execCommandInContainer(Container $container, array $cmd, ?\Closure $outputCallback = null): void {
if ($cmd === []) {
throw new \InvalidArgumentException('$cmd must not be empty.');
}
foreach ($cmd as $arg) {
if (!is_string($arg) || $arg === '') {
throw new \InvalidArgumentException('Every element of $cmd must be a non-empty string.');
}
}
// schedule the exec
$url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName)));
$response = json_decode(
$this->guzzleClient->request(
'POST',
$url,
[
'json' => [
'AttachStdout' => true,
'Tty' => true,
'Cmd' => [
'bash',
$file,
$subject,
$message
],
],
]
)->getBody()->getContents(),
true,
512,
JSON_THROW_ON_ERROR,
);
if ($this->GetContainerStartingState($container) !== ContainerState::Running) {
return;
}
$id = $response['Id'];
$containerName = $container->identifier;
// start the exec
$url = $this->BuildApiUrl(sprintf('exec/%s/start', $id));
// Create exec instance
$url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName)));
$response = json_decode(
$this->guzzleClient->request(
'POST',
$url,
[
'json' => [
'Detach' => false,
'AttachStdout' => true,
'AttachStderr' => true,
'Tty' => true,
'Cmd' => $cmd,
],
]
);
)->getBody()->getContents(),
true,
512,
JSON_THROW_ON_ERROR,
);
$execId = $response['Id'];
// Start exec
$url = $this->BuildApiUrl(sprintf('exec/%s/start', $execId));
$requestOptions = [
'json' => [
'Detach' => false,
'Tty' => true,
],
];
if ($outputCallback !== null) {
$requestOptions['stream'] = true;
}
$startResponse = $this->guzzleClient->request('POST', $url, $requestOptions);
if ($outputCallback !== null) {
$body = $startResponse->getBody();
$buffer = '';
while (!$body->eof()) {
$chunk = $body->read(1024);
$buffer .= $chunk;
while (($pos = strpos($buffer, "\n")) !== false) {
$line = substr($buffer, 0, $pos);
$buffer = substr($buffer, $pos + 1);
$line = rtrim($line, "\r");
if ($line !== '') {
$outputCallback($line);
}
}
}
if (trim($buffer) !== '') {
$outputCallback(trim($buffer));
}
}
}
@@ -916,10 +947,6 @@ readonly class DockerActionManager {
}
}
public function isBorgBackupSshAuthError(): bool {
return file_exists(DataConst::GetBorgSshAuthErrorFile());
}
public function GetBackupcontainerExitCode(): int {
$containerName = 'nextcloud-aio-borgbackup';
$url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName)));
@@ -1031,6 +1058,11 @@ readonly class DockerActionManager {
}
}
public function RunNextcloudUpgradeToLatestMajor(\Closure $addToStreamingResponseBody): void {
$container = $this->containerDefinitionFetcher->GetContainerById('nextcloud-aio-nextcloud');
$this->execCommandInContainer($container, ['bash', '/upgrade-latest-major.sh'], $addToStreamingResponseBody);
}
public function SystemPrune(?\Closure $addToStreamingResponseBody = null): void {
$endpoints = [
// Remove stopped containers

View File

@@ -191,17 +191,10 @@
{% if not hasBackupLocation or borg_backup_mode not in ['test', 'check', ''] or backup_exit_code > 0 %}
{% if borg_remote_repo and backup_exit_code > 0 %}
{% if is_ssh_auth_error %}
<p>
⚠️ <strong>SSH key not authorized on the remote server.</strong> You must add the following SSH public key to the <code>authorized_keys</code> file on your remote backup server before the restore test can succeed:<br><br><strong>{{ borg_public_key }}</strong><br><br>
Once you have added the key on the remote server, resubmit your location and rerun the test.
</p>
{% else %}
<p>
You may still need to authorize this pubkey on your borg remote:<br><strong>{{ borg_public_key }}</strong><br>
To try again, resubmit your location and rerun the test.
</p>
{% endif %}
<p>
You may still need to authorize this pubkey on your borg remote:<br><strong>{{ borg_public_key }}</strong><br>
To try again, resubmit your location and rerun the test.
</p>
{% endif %}
<p>
@@ -305,7 +298,12 @@
{% if newMajorVersionString != '' and isAnyRunning == true and isApacheStarting != true %}
<details>
<summary>Note about <strong>Nextcloud Hub {{ newMajorVersionString }}</strong></summary>
<p>If you haven't upgraded to Nextcloud Hub {{ newMajorVersionString }} yet and want to do that now, feel free to follow <strong><a target="_blank" href="https://github.com/nextcloud/all-in-one/discussions/7523">this documentation</a></strong></p>
<p>If you haven't upgraded to Nextcloud Hub {{ newMajorVersionString }} yet and want to do that now, feel free to click the button below. ⚠️ Warning: make sure to create a backup before clicking the button as the update can go wrong and will leave your instance in a broken state!</p>
<form method="POST" action="api/docker/nextcloud-upgrade-to-latest-major" target="overlay-log">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Upgrade to Nextcloud Hub {{ newMajorVersionString }}" data-confirm="Upgrade to Nextcloud Hub {{ newMajorVersionString }}? You should consider creating a backup first." />
</form>
</details>
{% endif %}
{% endif %}
@@ -427,28 +425,21 @@
{% if has_backup_run_once == false %}
<p>The initial backup was not successful.</p>
{% if borg_remote_repo and is_ssh_auth_error %}
<p>
⚠️ <strong>SSH key not authorized on the remote server.</strong> You must add the following SSH public key to the <code>authorized_keys</code> file on your remote backup server before the backup can succeed:<br><br><strong>{{ borg_public_key }}</strong><br><br>
Once you have added the key on the remote server, click <strong>Create backup</strong> to try again.
</p>
{% else %}
<p>
You may want to reset the backup location which allows you to enter a new one afterwards.
</p>
<p>
If the configured backup host location <strong>{{ borg_backup_host_location }}</strong>
{% if borg_remote_repo %}
or the remote repo <strong>{{ borg_remote_repo }}</strong>
{% endif %}
is wrong or if you want to reset the backup location due to other reasons, you can do so by clicking on the button below.
</p>
<form method="POST" action="api/docker/backup-reset-location" class="xhr">
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Reset backup location" data-confirm='Are you sure that you want to reset the backup location?' />
</form>
{% if borg_remote_repo %}
<p>
You may still need to authorize this pubkey on your borg remote:<br><strong>{{ borg_public_key }}</strong><br>
To try again, click <strong>Create backup</strong>.
</p>
{% endif %}
<p>You may change the backup path again since the initial backup was not successful. After submitting the new value, you need to click on <strong>Create Backup</strong> to test the new value.</p>
<form method="POST" action="api/configuration" class="xhr">
<label>Local backup location</label> <input type="text" name="borg_backup_host_location" placeholder="/mnt/backup"/><br>
<label>Remote borg repo</label> <input type="text" name="borg_remote_repo" placeholder="ssh://user@host:port/path/to/repo"/><br>
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
<input type="submit" value="Set backup location again" />
</form>
{% endif %}
{% elseif backup_exit_code == 0 %}
{% if borg_backup_mode == "backup" %}

View File

@@ -23,10 +23,8 @@
- [ ] Both a local backup location and a remote repo URL should not be accepted at the same time
- [ ] The page should now reload
- [ ] Now click on `Create backup`
- [ ] After the first failed backup attempt with a remote repo, the page should show **"The initial backup was not successful."** and one of two things depending on why it failed:
- [ ] **SSH auth error** (exit codes 80/81 connection closed before Borg protocol established): a prominent ⚠️ **"SSH key not authorized on the remote server."** warning should appear with the public key displayed. After adding the key to `~/.ssh/authorized_keys` on the remote server, click **Create backup** again to retry.
- [ ] **Other error** (wrong path, unreachable host, etc.): instead of the ⚠️ warning, a **"Reset backup location"** button should appear (with a confirmation prompt) that allows resetting the configured location so a new one can be entered. Note: there are no longer inline text inputs to re-enter the location at this point.
- [ ] After authorizing the SSH key on the remote, scroll down and click on `Create backup` again to create another backup. This time it should succeed.
- [ ] After the first failed backup attempt with a remote repo, the SSH public key for borg should be shown so it can be authorized on the remote server
- [ ] After authorizing the server on the remote, scroll down and click on `Create backup` again to create another backup. This time it should succeed.
- [ ] The initial Nextcloud credentials on top of the page that are visible when the containers are running should now be hidden in a details tag
- [ ] After a while and a few automatic reloads (as long as the side is focused), you should be redirected to the usual page and seen in the Backup and restore section that the last backup was successful.
- [ ] Below that you should see a details tag that allows to reveal all backup options

View File

@@ -22,9 +22,7 @@ For the below to work, you need a backup archive of an AIO instance and the loca
- [ ] Enter an invalid remote repo URL (e.g. `user` without `@` and `:`) which should send an error
- [ ] Enter a valid remote borg repo URL and the correct backup password:
- [ ] Should reload and should hide all options except the option to test the path and password
- [ ] After the first failed connection attempt, the behavior depends on the failure reason:
- [ ] **SSH auth error** (exit codes 80/81 connection closed before Borg protocol established): a prominent ⚠️ **"SSH key not authorized on the remote server."** warning should appear with the SSH public key displayed and instructions to add it to `~/.ssh/authorized_keys` on the remote server. After adding the key, scroll down and click on the test button again.
- [ ] **Other error** (wrong path, unreachable host, etc.): a generic message should appear noting the public key that may still need to be authorized on the remote.
- [ ] After the first failed connection attempt, the SSH public key for borg should be shown so it can be authorized on the remote server
- [ ] After authorizing the key on the remote server, scroll down and click on the test button again. This time it should succeed and show the options to check the integrity and list backup archives
- [ ] After the test you should see the options to check the integrity of the backup and a list of backup archives that you can choose from to restore your instance
- [ ] Clicking on either option should show a window prompt that lets you cancel the operation