Refuse a full restore when the table prefix differs
This commit is contained in:
@@ -24,6 +24,8 @@ All-in-One onto a plain Nextcloud with PostgreSQL).
|
|||||||
- A full restore no longer refuses when apps are missing: it restores their data and lists the
|
- A full restore no longer refuses when apps are missing: it restores their data and lists the
|
||||||
apps to install afterwards. `nextcloud-aio` is never required, so a backup of Nextcloud
|
apps to install afterwards. `nextcloud-aio` is never required, so a backup of Nextcloud
|
||||||
All-in-One can be restored onto a plain Nextcloud.
|
All-in-One can be restored onto a plain Nextcloud.
|
||||||
|
- A full restore onto an installation with a different table prefix (`dbtableprefix`) is refused
|
||||||
|
before it starts, with what to change, instead of restoring into tables nobody uses.
|
||||||
- All problems are listed at once, with the app each missing table probably belongs to. For
|
- All problems are listed at once, with the app each missing table probably belongs to. For
|
||||||
backups made before this version, `--skip-missing-tables` leaves out tables whose structure is
|
backups made before this version, `--skip-missing-tables` leaves out tables whose structure is
|
||||||
unknown.
|
unknown.
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ final class DbDumper {
|
|||||||
*/
|
*/
|
||||||
public function restore(Repository $repo, array $manifest, ?array &$report = null, ?callable $heartbeat = null, bool $skipMissing = false): int {
|
public function restore(Repository $repo, array $manifest, ?array &$report = null, ?callable $heartbeat = null, bool $skipMissing = false): int {
|
||||||
$report = ['skipped' => [], 'restored' => [], 'created' => [], 'missing' => []];
|
$report = ['skipped' => [], 'restored' => [], 'created' => [], 'missing' => []];
|
||||||
|
$source = self::dumpPrefix($manifest);
|
||||||
|
if ($source !== null && $source !== $this->prefix) {
|
||||||
|
// Every table would look missing and be created under the old prefix, unused.
|
||||||
|
throw new \RuntimeException("The backup's tables use the prefix '$source', this installation '{$this->prefix}'. Install Nextcloud again with 'dbtableprefix' => '$source' in config/config.php before occ maintenance:install, and restore again.");
|
||||||
|
}
|
||||||
$missing = $this->missingTables($manifest);
|
$missing = $this->missingTables($manifest);
|
||||||
$unknown = array_values(array_filter($missing, fn (string $t) => !isset($manifest['tables'][$t]['schema'])));
|
$unknown = array_values(array_filter($missing, fn (string $t) => !isset($manifest['tables'][$t]['schema'])));
|
||||||
if ($unknown !== [] && !$skipMissing) {
|
if ($unknown !== [] && !$skipMissing) {
|
||||||
@@ -241,6 +246,22 @@ final class DbDumper {
|
|||||||
return $total;
|
return $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table prefix of the instance the dump was made on (from its appconfig table), or null
|
||||||
|
* when the dump has none.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static function dumpPrefix(array $manifest): ?string {
|
||||||
|
/** @var list<string> $tables table names */
|
||||||
|
$tables = array_keys($manifest['tables'] ?? []);
|
||||||
|
foreach ($tables as $name) {
|
||||||
|
if (str_ends_with($name, 'appconfig') && !str_ends_with($name, '_ex_appconfig')) {
|
||||||
|
return substr($name, 0, -strlen('appconfig'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tables in the dump that do not exist on this instance.
|
* Tables in the dump that do not exist on this instance.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -63,19 +63,25 @@ final class RestoreFullService {
|
|||||||
$enabled = array_flip($this->appManager->getEnabledApps());
|
$enabled = array_flip($this->appManager->getEnabledApps());
|
||||||
$missingApps = array_values(array_diff(array_keys($meta['apps'] ?? []), array_keys($enabled), self::ENVIRONMENT_APPS));
|
$missingApps = array_values(array_diff(array_keys($meta['apps'] ?? []), array_keys($enabled), self::ENVIRONMENT_APPS));
|
||||||
$missingTables = [];
|
$missingTables = [];
|
||||||
|
$prefixMismatch = null;
|
||||||
if (isset($meta['db'])) {
|
if (isset($meta['db'])) {
|
||||||
$manifest = json_decode($repo->getObject($meta['db']), true, 512, JSON_THROW_ON_ERROR);
|
$manifest = json_decode($repo->getObject($meta['db']), true, 512, JSON_THROW_ON_ERROR);
|
||||||
$prefix = $this->config->getSystemValueString('dbtableprefix', 'oc_');
|
$prefix = $this->config->getSystemValueString('dbtableprefix', 'oc_');
|
||||||
|
$sourcePrefix = DbDumper::dumpPrefix($manifest);
|
||||||
|
if ($sourcePrefix !== null && $sourcePrefix !== $prefix) {
|
||||||
|
$prefixMismatch = [$sourcePrefix, $prefix];
|
||||||
|
}
|
||||||
$dumper = new DbDumper($this->db->getInner(), $prefix);
|
$dumper = new DbDumper($this->db->getInner(), $prefix);
|
||||||
$missingTables = array_values(array_filter($dumper->missingTables($manifest),
|
$missingTables = array_values(array_filter($dumper->missingTables($manifest),
|
||||||
fn (string $t) => !isset($manifest['tables'][$t]['schema']) && !str_starts_with($t, $prefix . 'ngb_')));
|
fn (string $t) => !isset($manifest['tables'][$t]['schema']) && !str_starts_with($t, $prefix . 'ngb_')));
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
'ok' => $currentMajor === $snapshotMajor && ($missingTables === [] || $skipMissingTables),
|
'ok' => $currentMajor === $snapshotMajor && $prefixMismatch === null && ($missingTables === [] || $skipMissingTables),
|
||||||
'currentMajor' => $currentMajor,
|
'currentMajor' => $currentMajor,
|
||||||
'snapshotMajor' => $snapshotMajor,
|
'snapshotMajor' => $snapshotMajor,
|
||||||
'missingApps' => $missingApps,
|
'missingApps' => $missingApps,
|
||||||
'missingTables' => $missingTables,
|
'missingTables' => $prefixMismatch === null ? $missingTables : [],
|
||||||
|
'prefixMismatch' => $prefixMismatch,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +230,10 @@ final class RestoreFullService {
|
|||||||
if ($check['currentMajor'] !== $check['snapshotMajor']) {
|
if ($check['currentMajor'] !== $check['snapshotMajor']) {
|
||||||
$parts[] = "this installation is Nextcloud {$check['currentMajor']}, the backup is from {$check['snapshotMajor']} (same major version required)";
|
$parts[] = "this installation is Nextcloud {$check['currentMajor']}, the backup is from {$check['snapshotMajor']} (same major version required)";
|
||||||
}
|
}
|
||||||
|
if (($check['prefixMismatch'] ?? null) !== null) {
|
||||||
|
[$source, $here] = $check['prefixMismatch'];
|
||||||
|
$parts[] = "the backup's tables use the prefix '$source', this installation '$here': install Nextcloud again with 'dbtableprefix' => '$source' in config/config.php before occ maintenance:install, and restore again";
|
||||||
|
}
|
||||||
if ($check['missingTables'] !== []) {
|
if ($check['missingTables'] !== []) {
|
||||||
$prefix = $this->config->getSystemValueString('dbtableprefix', 'oc_');
|
$prefix = $this->config->getSystemValueString('dbtableprefix', 'oc_');
|
||||||
$parts[] = DbDumper::describeMissing($check['missingTables'], $prefix);
|
$parts[] = DbDumper::describeMissing($check['missingTables'], $prefix);
|
||||||
|
|||||||
Reference in New Issue
Block a user