Files
nextcloud-ng_backup/lib/Command/RunBackup.php
T
dillard 6540a9314a A name a snapshot cannot hold gets a notification of its own
Reported apart from unreadable files, with example paths and the advice to rename
them, instead of advice about permissions. Start of the 1.1.0 plan in PLAN.md.
2026-11-06 23:38:40 +01:00

96 lines
3.8 KiB
PHP

<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: 2026 Dillard Blom
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\NgBackup\Command;
use OCA\NgBackup\Db\Run;
use OCA\NgBackup\Service\BackupService;
use OCA\NgBackup\Service\TargetService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
final class RunBackup extends Command {
public function __construct(
private BackupService $backups,
private TargetService $targets,
) {
parent::__construct();
}
protected function configure(): void {
$this->setName('backup:run')
->setDescription('Back up this Nextcloud (database, config, data) to a location now')
->addArgument('target', InputArgument::OPTIONAL, 'Location name or id (default: all locations)')
->addOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label for the snapshot', '')
->addOption('background', 'b', InputOption::VALUE_NONE, 'Only start the run; background jobs finish it')
->setHelp(<<<'HELP'
Starts a backup (or continues one that is already running for that location) and runs it to
the end in this process. Unchanged files are not read again; only new data is uploaded.
Examples:
occ backup:run back up to every location
occ backup:run offsite -l "before upgrade"
occ backup:run offsite --background
HELP);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$name = $input->getArgument('target');
try {
$targets = $name === null ? $this->targets->list() : [$this->targets->get($name)];
} catch (\Throwable) {
$output->writeln('<error>No such location.</error>');
return 1;
}
if ($targets === []) {
$output->writeln('<error>No backup locations yet (occ backup:target:add).</error>');
return 1;
}
$failed = 0;
foreach ($targets as $t) {
try {
$run = $this->backups->start($t, (string)$input->getOption('label'));
} catch (\Throwable $e) {
$output->writeln('<error>' . $t->getName() . ': ' . $e->getMessage() . '</error>');
$failed++;
continue;
}
$output->writeln(sprintf('<info>%s</info>: run %d, snapshot %s', $t->getName(), $run->getId(), $run->getSnapshotId()));
if ($input->getOption('background')) {
continue;
}
$start = microtime(true);
$run = $this->backups->runToCompletion($run, function (Run $r) use ($output, $start) {
$s = json_decode($r->getStats() ?? '{}', true);
$f = $s['files'] ?? [];
$output->writeln(sprintf(' %5.0fs %-6s files %d (read %d, unchanged %d), uploaded %.1f MiB',
microtime(true) - $start, $r->getPhase(), $f['files'] ?? 0, $f['read'] ?? 0, $f['reused'] ?? 0, ($f['uploaded'] ?? 0) / 1048576));
});
if ($run->getStatus() === Run::DONE) {
$output->writeln(sprintf(' done in %.0fs', microtime(true) - $start));
$f = json_decode($run->getStats() ?? '{}', true)['files'] ?? [];
if (($f['unreadable'] ?? 0) > 0) {
$output->writeln(sprintf('<comment> warning: %d unreadable files or folders are missing from this backup (see the server log)</comment>', $f['unreadable']));
}
if (($f['badNames'] ?? 0) > 0) {
$output->writeln(sprintf('<comment> warning: %d files or folders are missing from this backup because their name is not supported; rename them and the next backup includes them (see the server log)</comment>', $f['badNames']));
}
if (($f['changing'] ?? 0) > 0) {
$output->writeln(sprintf('<comment> warning: %d files kept changing and were saved as read (see the server log)</comment>', $f['changing']));
}
} else {
$output->writeln('<error> ' . $run->getStatus() . ': ' . $run->getError() . '</error>');
$failed++;
}
}
return $failed === 0 ? 0 : 1;
}
}