08534975d7
PLAN: 1.0.1 item 11 (Docs tab, done); 1.1.0 item 9: remove the feedback request in 1.1.0 or 1.2.0.
584 lines
43 KiB
Markdown
584 lines
43 KiB
Markdown
# ng_backup — plan
|
||
|
||
Een webgebaseerde, versleutelde backup- en restore-app voor Nextcloud 32–35, bedoeld voor MKB
|
||
zonder eigen IT-afdeling en voor gehoste Nextcloud zonder SSH. occ-commando's onder `backup:`.
|
||
|
||
Opgesteld 2026-10-01, na de evaluatie van nextcloud/backup op NC34 (zie onderaan).
|
||
|
||
## 1. Uitgangspunten
|
||
|
||
1. **Werkt zonder shell.** Geen `pg_dump`, `mysqldump`, rclone, rsync, tar of extra PHP-extensies.
|
||
Alleen wat Nextcloud zelf meelevert (files_external-opslagklassen, AWS-SDK, phpseclib, sabre,
|
||
Doctrine/DBAL, sodium).
|
||
2. **Werkt op elke variant:** AIO, losse installatie (Docker, bare metal, snap) én gehoste
|
||
Nextcloud zonder SSH. Juist gehoste gebruikers hebben er het meeste aan; alles wat alleen met
|
||
shell kan, is hooguit een optionele versnelling.
|
||
3. **Backuplocaties zijn tussentijds niet gekoppeld.** Geen mount in Files, geen open verbinding
|
||
buiten een run; de locatie is voor gebruikers onzichtbaar.
|
||
4. **Restore is een hoofdfunctie, geen bijzaak.** Alles wat via occ kan, kan ook via de webinterface.
|
||
5. **Nooit een instantie achterlaten in onderhoudsmodus.** Backups draaien zonder onderhoudsmodus;
|
||
waar die toch nodig is (volledige restore), altijd met gegarandeerd herstel.
|
||
6. **Hervatbaar.** Gehoste omgevingen hebben korte PHP-tijdslimieten en 512 MB geheugen: elk werk
|
||
gebeurt in kleine, hervatbare stappen met checkpoints. Geheugengebruik onafhankelijk van datagrootte.
|
||
7. **Streamend van bron naar doel, zonder lokale tussenkopie.** Lezen → opdelen → comprimeren →
|
||
versleutelen → uploaden gebeurt als één doorlopende stroom; restore andersom (downloaden →
|
||
ontsleutelen → uitpakken → wegschrijven). Er is nooit een lokaal backupbestand of een tweede
|
||
kopie van de data nodig; de lokale schijf hoeft hooguit ruimte te hebben voor één pack in
|
||
aanbouw (~32 MB, instelbaar), en ook dat bij voorkeur in het geheugen.
|
||
8. **Alleen wat nodig is.** Data, database en config, geen Nextcloud-code en geen apps uit de store
|
||
(die zijn opnieuw te installeren; de lijst met apps en versies gaat wel mee).
|
||
9. **Aantoonbaar betrouwbaar.** Elke release draait in CI een volledige backup→restore-rondgang op
|
||
NC32–35 × SQLite/MySQL/PostgreSQL.
|
||
|
||
## 2. Wat de app doet (functioneel)
|
||
|
||
| Functie | Fase |
|
||
|---|---|
|
||
| Geplande backups (dagelijks/wekelijks, tijdvenster) en handmatig starten | 1 |
|
||
| Versleuteling, altijd aan, met herstelsleutel om te downloaden | 1 |
|
||
| Opslag via files_external (S3, SFTP, WebDAV, lokaal pad), aangestuurd door ng_backup, zonder koppeling of share | 1 |
|
||
| FTP(S), SMB, Swift; NFS/rclone via gateway (zie 3.5) | 2 |
|
||
| Append-only-modus en advies voor object lock (bescherming tegen ransomware) | 1 |
|
||
| Restore van één bestand of map, per gebruiker, naar elk herstelpunt | 1 |
|
||
| Backup en restore per gebruiker via user_migration (bestanden, agenda, contacten, Deck, Mail-instellingen, profiel, prullenbak, ...) | 2 |
|
||
| Volledige restore (disaster recovery) op een verse installatie | 2 |
|
||
| Volledige restore in-place op de bestaande instantie | 3 (onderzoeken) |
|
||
| Bewaarbeleid (bijv. 7 dagelijks, 4 wekelijks, 6 maandelijks) | 1 |
|
||
| Verificatie: periodiek checksums controleren, steekproef-restore | 2 |
|
||
| Meldingen bij mislukken of te oude backup (Nextcloud-notificatie + e-mail), setup-check in Beheer | 1 |
|
||
| Differentiële/incrementele backups met deduplicatie | 1 (zit in het formaat) |
|
||
|
||
## 3. Architectuur
|
||
|
||
### 3.1 Backupformaat
|
||
|
||
Content-addressed opslag, vergelijkbaar met restic/borg, zodat incrementeel en deduplicatie vanzelf gaan:
|
||
|
||
- **Blobs:** bestandsinhoud in stukken van max. 4–8 MB, geïdentificeerd met een hash (BLAKE2b via sodium)
|
||
van de *onversleutelde* inhoud met een geheime sleutel (keyed hash, zodat de hash niets verraadt).
|
||
Een blob die al op de opslag staat, wordt niet opnieuw geüpload.
|
||
- **Packs:** blobs worden gebundeld tot packbestanden van ~32 MB (minder kleine objecten op S3/SFTP).
|
||
- **Snapshot-manifest:** per herstelpunt een versleuteld manifest: tijdstip, NC-versie, app-lijst met
|
||
versies, databasetype, en per gebruiker/map de boom met bestanden → blobs, plus de database-dump.
|
||
- **Index:** welke blob in welk pack zit, zodat restore van één bestand alleen de nodige packs ophaalt.
|
||
|
||
**Datastroom zonder tussenopslag:**
|
||
|
||
- *Backup:* bestand lezen als stream → blobs van 4–8 MB → elke blob gecomprimeerd (zstd als
|
||
beschikbaar, anders gzip via zlib) en versleuteld in het geheugen → toegevoegd aan het pack in
|
||
aanbouw. Een vol pack (~32 MB) wordt direct geüpload (bij S3 als multipart-onderdeel, bij
|
||
SFTP/WebDAV als stream) en daarna uit het geheugen verwijderd. Piekgebruik: één blob + één pack,
|
||
ongeacht de grootte van de backup.
|
||
- *Database:* de dump gaat tabel voor tabel als stream door dezelfde pijplijn; er komt nooit een
|
||
dumpbestand op schijf.
|
||
- *Restore:* alleen de benodigde packs worden opgehaald, per blob ontsleuteld en gecontroleerd
|
||
(AEAD) en direct naar het doelbestand geschreven. Een groot bestand terugzetten kost dus geen
|
||
dubbele ruimte; een half teruggezet bestand wordt eerst als tijdelijk bestand naast het doel
|
||
geschreven en pas na volledige controle op zijn plek gezet (alleen dát ene bestand dubbel).
|
||
- *Volledige restore van de database:* rijen worden in batches uit de stroom geïmporteerd.
|
||
- Als het geheugen te krap is voor een pack in aanbouw, kan dat naar een tijdelijk bestand
|
||
(instelbaar), maar nooit meer dan één pack tegelijk.
|
||
|
||
Waarom niet zip/tar per backup: daarmee is één bestand of één gebruiker terugzetten traag (alles
|
||
uitpakken) en kost elke backup weer de volle ruimte. Dat was precies het probleem bij nextcloud/backup.
|
||
|
||
### 3.2 Versleuteling
|
||
|
||
- libsodium **secretstream (XChaCha20-Poly1305)**: streamend, dus het geheugen blijft klein, ongeacht de grootte.
|
||
- Een willekeurige **hoofdsleutel** per backup-repository; die wordt versleuteld opgeslagen met een
|
||
sleutel afgeleid van een **wachtwoordzin** (Argon2id, `sodium_crypto_pwhash`).
|
||
- **Herstelkit** (wachtwoordzin-hint, versleutelde hoofdsleutel, repository-ID; als tekst/PDF + QR,
|
||
te downloaden bij inrichten). Zonder die kit is een backup na verlies van de server onbruikbaar.
|
||
- **Verplichte bevestiging door de beheerder** voordat de eerste backup draait (en opnieuw bij een
|
||
nieuwe sleutel):
|
||
1. de kit downloaden (de knop verder blijft uit tot de download is gestart);
|
||
2. een vinkje: *"Ik heb de herstelkit gedownload en veilig opgeslagen, buiten deze server. Ik
|
||
begrijp dat het zorgvuldig bewaren van deze sleutels mijn eigen verantwoordelijkheid is en dat
|
||
backups zonder deze sleutels niet te herstellen zijn."*;
|
||
3. pas dan wordt de knop *"Yes, I confirm"* actief; klikken bevestigt en start de inrichting.
|
||
Wie, wanneer en welke sleutelversie bevestigd is, wordt vastgelegd (audit log + instellingenpagina).
|
||
- De herstelkit kan op verzoek ook versleuteld naar het e-mailadres van de beheerder worden
|
||
gestuurd, als extra kopie; dit vervangt de bevestiging niet.
|
||
- De sleutel staat **niet onversleuteld in de database**; de database gaat zelf ook mee in de backup.
|
||
- Integriteit: elk object is geauthenticeerd (AEAD); manifesten verwijzen met hashes naar hun inhoud.
|
||
|
||
### 3.3 Database-dump zonder externe tools
|
||
|
||
- **Schema:** via Nextcloud's eigen `SchemaEncoder`/Doctrine-schema-export (zoals `occ db:export-schema`).
|
||
- **Data:** per tabel in batches (bijv. 5.000 rijen, gesorteerd op primaire sleutel) via `IDBConnection`,
|
||
weggeschreven als regels JSON met type-informatie, gestreamd door de versleuteling heen. Dit is
|
||
hetzelfde principe als `occ db:convert-type`, dat ook rijen tussen databases kopieert.
|
||
- **Consistentie:** de dump draait binnen één transactie met `REPEATABLE READ` (PostgreSQL/MySQL),
|
||
zodat de snapshot consistent is zonder onderhoudsmodus. Voor SQLite: een kopie van het bestand.
|
||
- **Restore:** werkt ook naar een ander databasetype (bijv. van MySQL naar PostgreSQL bij verhuizen).
|
||
- Grote tabellen die opnieuw op te bouwen zijn (bijv. `filecache` van externe opslag, previews,
|
||
zoekindexen) zijn instelbaar uit te sluiten.
|
||
|
||
### 3.4 Bestanden
|
||
|
||
- Gelezen via de Nextcloud Files-API / opslaglaag (zodat ook object storage als primaire opslag
|
||
werkt), niet rechtstreeks uit de datamap.
|
||
- Bron van wijzigingen: `filecache` (mtime, etag, size), zodat een incrementele run niet alle
|
||
bestanden opnieuw hoeft te lezen.
|
||
- Externe opslag gaat standaard **niet** mee (instelbaar per koppeling).
|
||
- Server-side encryptie van Nextcloud: bestanden worden ontsleuteld gelezen en in de backup
|
||
opnieuw versleuteld; documenteren dat de master key dan ook in de herstelkit moet.
|
||
|
||
### 3.5 Opslagdoelen
|
||
|
||
**files_external is een vereiste; ng_backup stuurt het aan, zonder koppeling.** De meegeleverde
|
||
app files_external heeft alle verbindingscode al: backends voor S3, SFTP, FTP, SMB, Swift en
|
||
WebDAV, met hun inlogmethoden (wachtwoord, SFTP-sleutel, enz.) en de beschrijving van hun
|
||
instellingenvelden. ng_backup bouwt daar niets van na, maar gebruikt files_external zoals het zichzelf
|
||
gebruikt (in NC34 nagekeken):
|
||
|
||
1. `BackendService::getAvailableBackends()` levert de beschikbare backends en hun parameters; het
|
||
instellingenformulier van ng_backup wordt daaruit **automatisch opgebouwd** (zelfde velden en
|
||
validatie als in files_external, ook voor backends die later bijkomen).
|
||
2. Bij een run maakt ng_backup een **niet-opgeslagen** `StorageConfig`, laat backend en
|
||
inlogmethode hun `manipulateStorageConfig()` doen, en maakt de opslag aan met
|
||
`new $storageClass($options)`, precies zoals `ConfigAdapter::constructStorage()` dat doet.
|
||
3. Na de run wordt het object vrijgegeven; er blijft geen verbinding open.
|
||
|
||
**Waarom geen (verborgen) koppeling in files_external:** een globale koppeling zonder toegewezen
|
||
gebruikers of groepen geldt in files_external juist voor **alle** gebruikers. Een verborgen
|
||
koppeling zou een aparte groep of systeemgebruiker vereisen; één verkeerde groepswijziging en
|
||
de backups zijn zichtbaar en wijzigbaar in Files. Met de werkwijze hierboven bestaat de
|
||
backuplocatie nergens als koppeling of share:
|
||
|
||
- niet zichtbaar in Files, niet gescand, niet deelbaar, ook niet voor beheerders;
|
||
- geen verbinding buiten een run ("tussentijds niet gemount");
|
||
- instellingen en inloggegevens in de eigen tabellen van ng_backup, versleuteld met `ICrypto`.
|
||
|
||
**Installatie en instellingen van files_external:**
|
||
- files_external is een meegeleverde app (`core/shipped.json`): aanwezig in elke reguliere
|
||
installatie, standaard **uitgeschakeld**. ng_backup **verandert de status niet**: staat het uit,
|
||
dan blijft het uit en laadt ng_backup alleen de klassen (in fase 0 getest: werkt); staat het
|
||
aan omdat de beheerder het zelf gebruikt, dan blijft het aan. Uit is hier juist goed:
|
||
onzichtbaarheid van de backuplocatie beperkt wat ransomware of een overgenomen account kan zien.
|
||
Laden van een uitgeschakelde app gaat via interne API → in de adapter, met CI per NC-versie.
|
||
Ontbreekt files_external helemaal (aangepaste installatie), dan alleen de eigen doelen.
|
||
- Gebruikers hoeven files_external niet te zien: "gebruikers mogen externe opslag koppelen" kan
|
||
uit blijven en er hoeven geen koppelingen te bestaan. ng_backup controleert dat en waarschuwt
|
||
als gebruikers zelf externe opslag mogen koppelen (geen vereiste, wel een tip).
|
||
- Dunne eigen adapter (`IBackend`: put/get/list/delete/stat met streams) bovenop de opslagobjecten
|
||
van files_external, omdat dit interne klassen zijn: wijzigingen tussen NC-versies op één plek
|
||
opvangen, met CI op elke versie. Een eigen implementatie alleen waar files_external tekortschiet
|
||
(bijv. S3 multipart met object lock).
|
||
|
||
| Doel | Via | Opmerking |
|
||
|---|---|---|
|
||
| S3 en compatibel (incl. MinIO, Wasabi, Hetzner Object Storage) | files_external `AmazonS3` of eigen adapter op `aws/aws-sdk-php` (core) | voorkeursdoel, object lock mogelijk |
|
||
| SFTP | files_external `SFTP` (phpseclib) | sleutel of wachtwoord |
|
||
| FTP(S) | files_external `FTP` | |
|
||
| SMB/CIFS | files_external `SMB` (icewind/smb) | vereist libsmbclient of `smbclient` op de server; op hosted vaak niet beschikbaar → melden bij instellen |
|
||
| WebDAV (ook een andere Nextcloud) | core DAV-opslag | |
|
||
| Swift | files_external `Swift` | |
|
||
| NFS | alleen als lokaal pad | NFS kan PHP niet zelf aanspreken; vereist een koppeling op OS-niveau, wat botst met "tussentijds niet gemount". Advies: NFS-doel via een SFTP/S3-gateway, of een systemd-automount die de beheerder buiten de app regelt. Op hosted niet mogelijk. |
|
||
| rclone | niet rechtstreeks | rclone is een los programma (shell nodig). Alternatief: `rclone serve sftp/webdav/s3` op de doelmachine en ng_backup daarheen laten schrijven; daarmee komen alle rclone-doelen beschikbaar zonder binary op de Nextcloud-server. |
|
||
|
||
**Bescherming van de backups zelf (ransomware, gehackte server):** de Nextcloud-server heeft
|
||
noodzakelijk schrijfrechten op het doel; wie de server overneemt, kan dus ook backups wissen.
|
||
Daarom:
|
||
- aanbevolen inrichting per doel documenteren: S3 met **versioning + object lock** en een sleutel
|
||
zonder delete-recht; SFTP met een chroot-gebruiker zonder shell; bij voorkeur een doel dat niet
|
||
op hetzelfde netwerk staat;
|
||
- **append-only-modus** als optie: ng_backup verwijdert dan zelf niets; opruimen (bewaarbeleid)
|
||
gebeurt via een levenscyclusregel op het doel of via een aparte, handmatig gestarte run met
|
||
andere inloggegevens;
|
||
- meerdere doelen tegelijk (3-2-1), elk met eigen inloggegevens.
|
||
|
||
### 3.6 Uitvoering: hervatbare jobs
|
||
|
||
- Eén `TimedJob` start een run binnen het ingestelde tijdvenster; het werk zelf is een
|
||
**toestandsmachine** (dump DB → verzamel bestanden → upload → manifest → opruimen) waarvan elke stap
|
||
in delen van ~20–30 seconden draait en zijn voortgang opslaat (eigen tabel `ng_backup_runs`).
|
||
- Werkt met system cron, webcron én AJAX-cron (dan trager, wel betrouwbaar). Bij system cron kan
|
||
`occ backup:run` ook in één keer doorlopen.
|
||
- **Lock** per repository (`ILockingProvider` + heartbeat), zodat twee runs nooit tegelijk schrijven;
|
||
een verlopen lock (crash) wordt na een time-out netjes vrijgegeven.
|
||
- Een afgebroken run wordt de volgende keer **hervat**, niet opnieuw begonnen.
|
||
|
||
### 3.7 Onderhoudsmodus en volledige restore
|
||
|
||
- **Backup:** nooit onderhoudsmodus (consistentie via DB-transactie, zie 3.3).
|
||
- **Volledige restore, fase 2 (disaster recovery):** op een **verse** Nextcloud met dezelfde
|
||
hoofdversie: ng_backup installeren → herstelkit invoeren → opslagdoel koppelen → herstelpunt
|
||
kiezen → restore. Database wordt vervangen, bestanden teruggezet, de app-lijst wordt getoond om
|
||
apps opnieuw te installeren. Hier is onderhoudsmodus niet nodig omdat er nog niets in gebruik is.
|
||
- **Volledige restore in-place, fase 3:** lastig, omdat de app dan de database vervangt waarop hij
|
||
zelf draait en de webinterface in onderhoudsmodus niet bereikbaar is. Te onderzoeken: een eigen
|
||
"restore-modus" (middleware die alleen de beheerder en de restore-endpoints doorlaat) in plaats
|
||
van onderhoudsmodus. Altijd met `try/finally` en een watchdog die de modus na een time-out opheft.
|
||
|
||
### 3.8 Backup en restore per gebruiker: via user_migration
|
||
|
||
In plaats van zelf te bepalen welke gegevens van een gebruiker meegaan, hergebruiken we
|
||
**user_migration** (officiële Nextcloud-app, actief onderhouden, NC29–36; draait ook op onze eigen productie-instantie).
|
||
Die app werkt met *migrators* die andere apps zelf aanleveren. Op onze productie-instantie zijn dat er al:
|
||
calendar, dav (agenda's en contactpersonen), deck, files_trashbin, mail, settings (profiel,
|
||
account, app-instellingen) en de bestanden zelf. Elke app die later een migrator toevoegt, gaat
|
||
automatisch mee.
|
||
|
||
**Koppeling:** user_migration schrijft normaal een zip naar de map van de gebruiker, via de
|
||
publieke interface `OCP\UserMigration\IExportDestination`. ng_backup levert een **eigen
|
||
`IExportDestination`** die alles direct in de versleutelde, ontdubbelde backupstroom zet (geen
|
||
zip, geen kopie in de map van de gebruiker, geen dubbele ruimte), en een **eigen `IImportSource`**
|
||
die bij restore uit een herstelpunt leest. Het aansturen gaat via `UserMigrationService::export()`
|
||
/ `import()` van user_migration.
|
||
|
||
- `copyFolder()` krijgt een `Folder` mee: daar gebruiken we etag/mtime uit de filecache, zodat
|
||
ongewijzigde bestanden niet opnieuw gelezen of gehasht worden (incrementeel blijft goedkoop).
|
||
- Dezelfde migrators zorgen dat een gebruiker ook naar **een andere Nextcloud** kan worden
|
||
teruggezet (verhuizen), omdat het formaat app-onafhankelijk is.
|
||
- Een gebruikersbackup en de volledige backup delen dezelfde blobs; dubbel opslaan kost niets.
|
||
|
||
**Afhankelijkheid en risico's (uitzoeken in fase 0):**
|
||
- `UserMigrationService` is een interne klasse van user_migration (geen publieke API); net als bij
|
||
files_external via een eigen adapter, met CI op elke NC-versie. Zonder user_migration valt alleen
|
||
de gebruikersbackup weg; de volledige backup werkt dan nog.
|
||
- **Restore over een bestaande gebruiker** is in fase 0 getest en niet schoon (contacten dubbel,
|
||
extra `migrated-*`-agenda's). Daarom twee keuzes in de restore-wizard, nooit importeren over
|
||
een bestaand account heen:
|
||
1. **Vervangen:** eerst automatisch een veiligheidsexport van de huidige stand naar de
|
||
repository, dan het account verwijderen, dan uit de backup opnieuw aanmaken.
|
||
2. **Naast het bestaande account:** de backup terugzetten in een nieuw account
|
||
`<gebruiker>-bak` (of `<gebruiker>-<datum>`), zodat de beheerder kan vergelijken en gericht
|
||
overzetten. (Een gebruikers-id hernoemen kan Nextcloud niet; daarom krijgt de *backup* de
|
||
nieuwe naam, niet het bestaande account.)
|
||
- Agenda's en adresboeken komen terug als `migrated-*` met nieuwe URI's: clients synchroniseren
|
||
opnieuw, delen van die agenda's moeten opnieuw. In de wizard vermelden.
|
||
- **Versies en prullenbak gaan mee:** de FilesMigrator exporteert ook `files_versions` van de
|
||
gebruiker, de trashbin-migrator de prullenbak. Alleen bestanden in de eigen opslag van de
|
||
gebruiker; externe opslag en groepsmappen niet (die gaan wel mee in de volledige backup).
|
||
- Een terugzetting per bestand of map blijft via de eigen bestandsboom gaan (sneller, geen migrators nodig).
|
||
|
||
Niet alle apps hebben een migrator (bijv. Talk-gesprekken); in de restore-wizard tonen welke
|
||
onderdelen wel en niet terugkomen, op basis van de geregistreerde migrators.
|
||
|
||
## 4. Webinterface
|
||
|
||
- **Beheer → Backup:** status (laatste geslaagde backup, volgende run, opslaggebruik), opslagdoelen,
|
||
schema, bewaarbeleid, uitsluitingen, sleutelbeheer (herstelkit opnieuw downloaden na wachtwoordcontrole).
|
||
- **Herstelpunten:** lijst met datum, grootte, status van verificatie; per herstelpunt bladeren door
|
||
gebruikers en mappen, bestanden of mappen terugzetten (naar de oorspronkelijke plek of naar een
|
||
map "Teruggezet <datum>").
|
||
- **Restore-wizard** voor één gebruiker en (fase 2) voor een volledige restore op een verse installatie.
|
||
- **Voortgang** van lopende runs, met logboek; bij fouten een duidelijke melding en wat te doen.
|
||
- **Gebruikers zelf** (optioneel, fase 3): eigen bestanden terugzetten vanuit de Files-app, zoals bij versies.
|
||
|
||
## 5. occ-commando's (`backup:`)
|
||
|
||
| Commando | Doel |
|
||
|---|---|
|
||
| `backup:status` | laatste runs, status per opslagdoel |
|
||
| `backup:run [--target=] [--full]` | backup nu draaien (doorlopend, voor system cron/handmatig) |
|
||
| `backup:list [--target=]` | herstelpunten |
|
||
| `backup:verify <snapshot> [--deep]` | checksums controleren, optioneel alles ontsleutelen |
|
||
| `backup:restore:file <snapshot> <user> <pad> [--to=]` | bestand/map terugzetten |
|
||
| `backup:user:restore <snapshot> <user> [--to-user=]` | gebruiker terugzetten via user_migration (fase 2) |
|
||
| `backup:restore:full <snapshot>` | volledige restore (fase 2, verse installatie) |
|
||
| `backup:target:add/list/remove/test` | opslagdoelen beheren en testen |
|
||
| `backup:key:export / import` | herstelkit |
|
||
| `backup:prune [--dry-run]` | bewaarbeleid toepassen |
|
||
|
||
**Let op:** nextcloud/backup gebruikt ook het voorvoegsel `backup:` (o.a. `backup:point:*`).
|
||
Als beide apps tegelijk aan staan, botsen commando's met dezelfde naam. Onze namen overlappen
|
||
niet met de hunne, maar bij installatie waarschuwen als `backup` ook actief is.
|
||
|
||
## 6. Kwaliteit en tests
|
||
|
||
- PHPUnit-unittests voor formaat, versleuteling, chunking, toestandsmachine, bewaarbeleid.
|
||
- Integratietests met echte databases: dump→restore→vergelijk (rij-voor-rij) op SQLite, MySQL/MariaDB, PostgreSQL.
|
||
- **End-to-end in CI:** NC32–35 opzetten, testdata (gebruikers, bestanden, agenda, contacten),
|
||
backup naar MinIO (S3) en SFTP-container, data beschadigen, restore, alles vergelijken.
|
||
- Foutinjectie: crash halverwege een run/restore → instantie nooit in onderhoudsmodus, run hervat.
|
||
- Geheugen- en schijftest: backup én restore van 20 GB data binnen `memory_limit=256M` op een
|
||
machine met minder dan 1 GB vrije tijdelijke ruimte (tmpfs met limiet); meten dat er nooit meer
|
||
dan één pack lokaal staat.
|
||
- Psalm (niveau streng) tegen `nextcloud/ocp` van elke ondersteunde versie; geen private `OC\`-klassen.
|
||
- Code review per PR, zoals bij occweb.
|
||
|
||
## 7. Fasering en inschatting
|
||
|
||
| Fase | Inhoud | Indicatie |
|
||
|---|---|---|
|
||
| 0 | Onderzoek/prototype: formaat, secretstream, DB-dump via DBAL, S3/SFTP via files_external-klassen, eigen IExportDestination voor user_migration; meten op hosted-achtige limieten | 1–2 weken |
|
||
| 1 (MVP) | Geplande, versleutelde, incrementele backups naar lokaal/S3/SFTP/WebDAV; restore van bestanden/mappen via web en occ; bewaarbeleid; meldingen; CI-rondgang | 4–6 weken |
|
||
| 2 | Backup/restore per gebruiker via user_migration, volledige restore op verse installatie, FTP/SMB, verificatie | 3–4 weken |
|
||
| 3 | In-place volledige restore (restore-modus), zelfbediening voor gebruikers, extra doelen | open |
|
||
|
||
Release in de app store pas na fase 1 en een periode van eigen gebruik op onze productie-instantie (net als occweb):
|
||
een backup-app die niet betrouwbaar is, is erger dan geen backup-app.
|
||
|
||
## 8. Besluiten (2026-10-01)
|
||
|
||
1. **Doelgroep:** alle varianten: AIO, losse installaties én gehoste Nextcloud zonder SSH.
|
||
2. **Licentie:** AGPL-3.0-or-later.
|
||
3. **Repository:** eigen Gitea + GitHub (dillardblom), zoals occweb.
|
||
4. **Naam:** "NG Backup", app-id `ng_backup`, occ-voorvoegsel `backup:`.
|
||
5. **Herstelkit:** verplicht downloaden + expliciete bevestiging door de beheerder (vinkje +
|
||
"Yes, I confirm"), zie 3.2; versleuteld per e-mail als optionele extra kopie.
|
||
6. **Opslag:** files_external wordt door ng_backup aangestuurd zonder koppeling of (verborgen)
|
||
share; de app-status blijft zoals de beheerder hem had (uit blijft uit), zie 3.5.
|
||
7. **Restore per gebruiker:** nooit over een bestaand account heen; óf vervangen (veiligheids-
|
||
export, verwijderen, opnieuw aanmaken), óf terugzetten als `<gebruiker>-bak`, zie 3.8.
|
||
8. **Fase 0 afgerond (2026-10-01),** resultaten in `spikes/RESULTS.md`.
|
||
|
||
## 9. Releases en versies (besluit 2026-10-02)
|
||
|
||
| Versie | Betekenis |
|
||
|---|---|
|
||
| 0.1.0-alpha.N | publiek; meetesten gewaardeerd op een kopie van productie (dev, test, staging) |
|
||
| 0.9.0-beta.N | mag meedraaien op productie, naast de reguliere backup; volledige restore aanwezig en getest |
|
||
| 1.0.0-rc.N | releasekandidaten, alleen bugfixes |
|
||
| 1.0.0 | stabiel: vervanging van de reguliere backup, noodherstel bewezen |
|
||
| 1.2.0 e.v. | functies van fase 2 (restore per gebruiker, FTP/SMB, verificatie, ...) |
|
||
|
||
Volgorde: fase 1 grondig testen, reviewen en debuggen; dan nadenken over 0.1.0-alpha.1. Een
|
||
minimale volledige restore (`occ backup:restore:full` op een verse installatie) komt vóór de beta.
|
||
|
||
**Roadmap-indeling fase 2 (besluit 2026-10-05):** naar v1.2.0 toewerken met in fase 2:
|
||
1. `backup:restore:full` (disaster recovery, verse installatie) — **klaar** (2026-10-05)
|
||
2. FTP/SMB-ondersteuning — **klaar** (2026-10-06): beide liepen al via files_external's generieke
|
||
adapter zonder enige ng_backup-code; alleen testdekking toegevoegd
|
||
(`tests/integration/targets-matrix.sh`) + bevindingen in README ("Storage backend experiences").
|
||
SMB vereist de `smbclient` PECL-extensie (niet standaard in de Nextcloud-image, in CI nu
|
||
on-the-fly gebouwd). NFS is geen eigen backend: lokaal mounten + `local`-backend erop richten.
|
||
3. Verificatie/checksum-audit-commando — **klaar** (2026-10-05)
|
||
4. Retentie/prune voor user-exports — **klaar** (2026-10-06): `occ backup:retention --user-last=N`
|
||
(standaard 3, per installatie, net als de bestaande snapshot-policy), toegepast door
|
||
`occ backup:prune` (geen prullenbak voor user-exports, in tegenstelling tot snapshots — on-demand,
|
||
niet het primaire vangnet).
|
||
5. Automatische user-export in het reguliere schema — **naar backlog** (niet voor deze release; on-demand blijft)
|
||
9. SMB-roundtrip (protocol, niet een specifieke server) — **klaar** (2026-10-06): CI-matrix, 2 GB-roundtrip en
|
||
user-export via SMB; NFS via de OMV-share getest (`local`-backend op een gemounte share, 2026-10-06)
|
||
10. `backup:user:restore` zonder user_migration: vraagt om toestemming om de app te installeren en te activeren (of
|
||
`--install-user-migration`) — **klaar** (2026-10-06); waarschuwing over de settings-allowlist — **klaar**;
|
||
agenda/contacten bij migratie naar een server zonder die gebruiker — **getest** (2026-10-06, naar `migrated-personal`/`migrated-contacts`)
|
||
Backlog: (a) de originele weergavenaam herstellen (het label uit de export is niet getest als
|
||
weergavenaam); (b) de interne `migrated-`-naam hernoemen (vereist directe databasewijziging, niet voor de beta).
|
||
|
||
**Backlog uit de pre-beta review (2026-10-07)** — geen belemmering voor 0.9.0-beta of 1.0.0, staat
|
||
in README "Known limitations":
|
||
1. Geheugen bij grote instanties (>1 TB, miljoenen bestanden): blob-index, prune-set, browse van een
|
||
snapshot in de web-UI, `trashExtras` bij restore en het manifest van een user-export staan volledig
|
||
in het geheugen; streaming/paginering nodig.
|
||
2. Twee servers op één locatie: catalogus kan breken (generatie overschreven, of anchor >200 generaties
|
||
achter). Docs-waarschuwing staat er; mogelijk later een echte put-if-absent per backend.
|
||
3. Sleutelrotatie: een slot verwijderen trekt geen toegang in (master-key blijft gelijk).
|
||
4. Rollback vóór DR niet detecteerbaar: referentie-generatie en -hash in de kit opnemen of tonen.
|
||
5. Kit-velden buiten `wrapped_key` (o.a. `delete_delay_days`) zijn niet geauthenticeerd; nu alleen getoond.
|
||
6. Repository-id in de AD van objecten (formaatwijziging).
|
||
7. Wachtwoorden van locaties via `--option-file` of omgevingsvariabele i.p.v. de commandoregel.
|
||
8. Tijdelijke restore-bestanden niet via de Files-API verwijderen (komen nu in de prullenbak).
|
||
9. DB-restore in foreign-key-volgorde (cascades).
|
||
10. Packs zonder index (index-upload mislukt) opruimen.
|
||
11. `backup:restore:full` in onderhoudsmodus en hervatbaar maken; nu: opnieuw draaien met `--force`.
|
||
12. Config/rename op FTP/SMB-servers die geen rename over een bestaand bestand toestaan.
|
||
|
||
**Plan richting 1.0.0 en 1.1.0 (besluit 2026-10-15/16):**
|
||
- **1.0.0-rc.1:** wat nu op `polish` staat (snelle S3-verify en snapshot-commit, kortere runlijst,
|
||
duur met dagen, job bij elke cron-run, deflate-test, feedbackformulier, datums in de locale van
|
||
de gebruiker, wachtwoordzin min. 16 tekens). Geen nieuwe functies meer.
|
||
- **Vóór 1.0.0 (verplicht):** volledige restore met kit op een tweede server (PVE02), alleen lezend
|
||
of op een kopie van de locatie; plus een user-restore. Zonder geslaagde test geen 1.0.0.
|
||
- **1.0.0 (besluit 2026-10-18):** alleen betrouwbaarheid. rc2 uitgebracht; slaagt de grote
|
||
DR-test op VM 980 (snapshot van 12-10, met `--skip-missing-tables`), dan 1.0.0. Zo niet: fix,
|
||
rc3, en rc3 pas uitbrengen na een geslaagde volledige DR-test.
|
||
- **rc4 (besluit 2026-10-19):** rc3 is niet uitgebracht. De DR-test met rc3 liep ~2,8 MiB/s
|
||
(~3,5 dag); parallel lezen over SFTP haalde vanaf VM 980 ~7,6 MiB/s. Daarom rc4 = rc3 +
|
||
vooruitlezen over SFTP (4 eigen verbindingen). Volgorde: volledige DR-test op VM 980 volgens de
|
||
gids (SFTP, Storage Box) → rc4 uitbrengen en om tests vragen → zelf een volledige DR van een
|
||
productie-instantie van ~250 GB via S3 en via SMB. Drie volledige DR-restores vóór 1.0.0.
|
||
Problemen of meldingen van gebruikers: rc5.
|
||
- **1.0.1:**
|
||
1. (Naar rc4: herstelsnelheid over SFTP.) S3 leest al 16 tegelijk bij index en tree, maar een
|
||
restore leest de packs nog één voor één: ook vooruitlezen via de S3-client.
|
||
2. Achtergrondjobs zonder systeem-cron (issue #13; besluit 2026-10-19). De PR die dit
|
||
oplost zegt "Fixes #13" (bevestigd: #13 was de cron; besluit 2026-10-21):
|
||
a. **Waarschuwen** in de beheerpagina en `backup:status`: modus uit `core backgroundjobs_mode`
|
||
(`ajax`/`webcron`/`cron`) en de laatste run uit `core lastcron`. Niet op `cron`, of de
|
||
laatste run te lang geleden: melding met de cron-regel erbij ("Backups only move while
|
||
someone has Nextcloud open"), en "waiting for background jobs" als een lopende backup lang
|
||
geen stap zette.
|
||
b. **De pagina laat de backup lopen** zolang ze openstaat: in `ajax`/`webcron` voert de
|
||
statusaanroep die de pagina toch al herhaalt ook een korte, begrensde stap uit (zoals de
|
||
korte stappen die AJAX-cron nu al krijgt). "Back up now" werkt dan ook zonder cron zolang
|
||
het tabblad openstaat; de melding zegt dat geplande backups echte cron nodig hebben.
|
||
De melding zegt (zonder werkende systeem-cron): backups hebben systeem-cron nodig, ook de
|
||
gewone nachtelijke; zo stel je cron in (link naar de Nextcloud-documentatie over
|
||
achtergrondjobs voor de versie van de server, nu
|
||
https://docs.nextcloud.com/server/34/admin_manual/configuration_server/background_jobs_configuration.html,
|
||
versienummer uit de server, niet vast); zolang er geen cron is, loopt een backup die je
|
||
met de knop start alleen door terwijl de NG Backup-pagina openstaat.
|
||
c. **Voortgang in de databasefase** (issue #13): nu staat er alleen "running (db)", wat er
|
||
hetzelfde uitziet als vastzitten. Toon "running (db: 47/183 tables)" zoals de bestandsfase
|
||
een percentage toont, en "waiting for background jobs (last ran 12 min ago)" als een run
|
||
een tijd geen stap zette, ook bij een cron die is blijven staan.
|
||
Niet doen: de cron-modus vanuit de app omzetten (zonder echte cron-regel stopt alles, en
|
||
`cron.php` zet de modus zelf op `cron`), en geen `exec php occ backup:run &` vanuit de knop
|
||
(vaak uitgeschakeld, niet op hosted, buiten Nextcloud om lastig te volgen en te stoppen).
|
||
3. Volledige DR-test met een backup die al de tabelstructuur bevat (gemaakt met rc2 of later),
|
||
zonder `--skip-missing-tables`: dat pad is op 1 TB nog niet getest.
|
||
4. **Label bij "Back up now"** (besluit 2026-10-19): een optioneel tekstveld naast de knop; de
|
||
tekst gaat als label van de snapshot mee (nu kan dat alleen met `occ backup:run -l`; de knop
|
||
stuurt een leeg label, geplande runs krijgen "scheduled"). Raakt alleen de UI en een
|
||
bestaande parameter.
|
||
5. **`--salvage`** voor `backup:restore` en `backup:restore:full` (besluit 2026-10-22): terughalen
|
||
wat kan, een bestand dat niet te lezen of te ontsleutelen is overslaan en aan het eind
|
||
opsommen, in plaats van stoppen.
|
||
6. **Tijdelijke bestanden van een afgebroken restore opruimen** (gevonden in de SMB-DR van
|
||
rc6, 2026-10-24): de rc5-poging die stopte liet `VID_0009.mp4.ngb-restore-<id>` staan; de
|
||
geslaagde restore daarna haalde het niet weg, en `files:scan` maakte het zichtbaar in Files.
|
||
Een restore ruimt `*.ngb-restore-*` in de mappen die hij terugzet op.
|
||
7. **Eén bestand terugzetten met `backup:restore`**: een pad naar een bestand zet 0 bestanden
|
||
terug (er komt een `/` achter); nu werkt alleen een map.
|
||
8. **Uitleg (i) bij de prullenbak van herstelpunten** (besluit 2026-10-25): bij "removed by
|
||
retention policy" een infoknop met wat het betekent: het bewaarbeleid houdt voor oudere dagen
|
||
één herstelpunt per dag (het laatste), weken en maanden; de rest gaat naar de prullenbak en
|
||
wordt pas na de ingestelde wachttijd (`delete_delay_days`, standaard 30) echt verwijderd, en is
|
||
tot die datum terug te zetten. Aanleiding: de beheerder zag "19-10 13:12 – after rc4 upgrade
|
||
removed by retention policy" en dacht dat er iets was weggegooid.
|
||
9. **Tijdzone in `occ backup:status`**: de tabel toont de servertijd (UTC), de webinterface de
|
||
tijd van de beheerder; dat leest als een verkeerde starttijd. Zet de zone in de kolomkop
|
||
("started (UTC)") of toon de tijden in de zone van het schema.
|
||
10. **User-restore op gebruikersnaam** (issue #22, 2026-10-27): `backup:user:restore <locatie>
|
||
New_User` gaf alleen "is not a recorded user export"; het argument moet het manifestpad
|
||
zijn, dat alleen `backup:user:backup` eenmalig toonde. Nu: `backup:user:list` toont de
|
||
exports, een kale uid werkt bij precies één export, anders een lijst om uit te kiezen —
|
||
**klaar** (2026-10-27).
|
||
11. **Tabblad "Docs" in de beheerpagina** (besluit 2026-10-27): een derde knop naast "Backup and
|
||
restore" en "Settings", met links naar de HOWTO (`docs/HOWTO.md`), de DR-gids
|
||
(`docs/DISASTER-RECOVERY.md`) en de roadmap (README, `#roadmap`) op GitHub, elk met een
|
||
regel wat erin staat. Nu zijn ze alleen via de README te vinden. Zichtbaar ook zonder
|
||
sleutel, zodat de gids vindbaar is vóór de eerste installatie-stap. "NG Backup is in beta"
|
||
gaat uit de feedbackregel — **klaar** (2026-10-27).
|
||
- **DR-tests over databasegrenzen** (besluit 2026-10-20; productie nc01 draait PostgreSQL): na
|
||
de MariaDB→PostgreSQL-fout (tellers, rc5) elke overstap in de integratietest (`to-pgsql`,
|
||
`to-mysql`, `to-sqlite`, ook in CI). Volle DR-tests met echte data: vóór 1.0.0 de S3-simulatie
|
||
(MariaDB-bron, 138 GB) nogmaals naar een MariaDB-doel en de SMB-simulatie naar een SQLite-doel;
|
||
**na 1.0.0** de 1 TB van nc01 (PostgreSQL) naar een MariaDB-doel.
|
||
- **Polish:** `target:add` toonde bij het eerste indexbestand al "about 642 min left" (S3,
|
||
4.568 bestanden, daarna binnen een minuut klaar): een schatting pas na ~5% of na meer dan
|
||
een paar bestanden.
|
||
- **1.1.0:**
|
||
1. `backup:key:import` zonder kit: de sleutel-slots van de locatie (`config`) lezen, met de kit
|
||
als reserve als `config` ontbreekt of niet opent. Vergt een andere volgorde van
|
||
`target:add` en sleutelimport (het gevoeligste pad), daarom niet in rc1. Daarna de
|
||
DR-test op PVE02 herhalen zonder kit.
|
||
2. Instelling "zware achtergrondjobs pauzeren tijdens een backup" (standaard aan, uit te zetten):
|
||
Memories, files_antivirus BackgroundScanner, Recognize, Preview Generator, fulltextsearch.
|
||
Via de reservering/last_run van die jobs (geen apps uitzetten), crashbestendig, maximaal
|
||
~6 uur achter elkaar, zichtbaar in UI en log. Op nc01 met de hand getest tijdens SMB-run 10.
|
||
3. Snelheid: lezen en uploaden laten overlappen, meerdere bestanden tegelijk lezen, eventueel
|
||
een eigen worker (`occ background-job:worker`). Gemeten op nc01: 55% wachten op de schijf,
|
||
33% op de SMB-upload, ~7% CPU. Vergt extra PHP-processen; botst mogelijk met uitgangspunt 1.
|
||
4. **Schema per locatie** (besluit 2026-10-19): in de instellingen van een locatie "uit",
|
||
"dagelijks om HH:MM" of "elke N uur (vanaf HH:MM)". Nu is er één `schedule_time` voor alle
|
||
locaties, één keer per dag. Bij de update wordt de huidige tijd het schema van elke
|
||
bestaande locatie (voor bestaande gebruikers verandert er niets).
|
||
5. **Locaties na elkaar laten starten** in plaats van tegelijk: twee runs die dezelfde schijven
|
||
lezen zijn samen niet sneller (gezien op nc01); wachtende runs zichtbaar als "queued".
|
||
7. **SMB sneller** (gemeten 2026-10-21): lokaal netwerk zonder vertraging (Samba-container op de
|
||
laptop, 1,8 GB) back-up 24,9 MB/s, restore 27,6 MiB/s; vanaf Canada naar de Storage Box (130 ms)
|
||
2,6 MiB/s, omdat SMB per blok op het antwoord wacht. Grotere schrijfblokken, en vooruitlezen
|
||
bij een restore zoals SFTP (IReadsAhead) of meerdere packs tegelijk. Een NAS met 1 Gbit haalt
|
||
~110 MB/s: daar is winst.
|
||
Ook de index: `target:add` over SMB vanaf Canada haalt ~1 indexbestand per seconde (4.927
|
||
bestanden ~85 min), want SMB krijgt geen `getMany()` met verzoeken tegelijk zoals S3 en SFTP.
|
||
6. **Notitie achteraf bij een restorepunt**, te wijzigen in de lijst. Het label in de snapshot
|
||
is versleuteld en ligt vast; de notitie apart op de locatie bewaren (bijv. in de catalogus),
|
||
zodat een nieuwe server na een DR hem ook ziet.
|
||
8. **Herstelpunt vastpinnen** (besluit 2026-10-25): een herstelpunt dat het bewaarbeleid
|
||
nooit opruimt (bijv. "vóór de upgrade naar NC 35"), in de lijst en met
|
||
`occ backup:pin <snapshot>` / `--unpin`. Op de locatie bewaren (catalogus), zodat de
|
||
opschoning het ziet, ook na een DR; vastgepinde punten tellen niet mee in last/daily/weekly.
|
||
9. **Feedbackvraag weghalen** in 1.1.0 of 1.2.0 (besluit 2026-10-27): de regel "We would like
|
||
to hear how NG Backup works for you ... Share your experience" in de beheerpagina blijft tot
|
||
dan staan (de vermelding "in beta" is er in 1.0.1 al af). Zijn er tegen die
|
||
tijd geen noemenswaardige meldingen, dan werkt het: mensen melden eerder wat niet werkt dan
|
||
wat gewoon werkt. Het formulier (`experience.yml`) mag blijven voor wie het zelf opzoekt.
|
||
- **Bevindingen DR-test op PVE02 (2026-10-17, snapshot van nc01, 1 TB, AIO → gewone NC 34 met
|
||
PostgreSQL 18)** — voor **1.0.0-rc.2**:
|
||
1. Tabellen van apps die bij de backup **niet aanstonden** (restanten: Deck, ONLYOFFICE,
|
||
AppAPI, een archief-app) blokkeren de restore met één tabelnaam per poging. Standaard
|
||
overslaan met één waarschuwing per app; optie om ze toch terug te zetten.
|
||
2. Alle ontbrekende apps en tabellen **vooraf in één lijst**, per app benoemd (nu één per poging,
|
||
ook voor uitgeschakelde standaard-apps zoals `admin_audit`).
|
||
3. `--skip-missing-apps`; `nextcloud-aio` standaard overslaan (bestaat buiten AIO niet), zodat
|
||
AIO → gewone Nextcloud werkt.
|
||
4. `--install-apps`: ontbrekende apps uit de app store installeren.
|
||
5. Apps buiten de app store (eigen builds, `side_menu`, `nextcloud-aio`): documenteren; later
|
||
eventueel `custom_apps` meenemen in de backup.
|
||
6. Tabellen van een app die wél aanstond maar die hun tabel later zelf maakt (Memories:
|
||
`memories_planet_geometry`) netjes afhandelen.
|
||
7. `backup:target:add` zonder sleutel verwijst naar `key:init`; als er al een repository staat,
|
||
moet het naar `key:import <kit>` wijzen.
|
||
8. `backup:target:add` toont geen voortgang bij het ophalen van de index (27.582 bestanden).
|
||
9. `backup:target:remove` laat de index-cache van die locatie in de database staan; opruimen,
|
||
ook bestaande wezen.
|
||
10. Snapshot-meta uitbreiden: ook geïnstalleerde maar uitgeschakelde apps, met per app of hij
|
||
uit de app store komt; `occ backup:apps <locatie> <snapshot>` toont de lijst leesbaar
|
||
(geen platte tekst op de locatie: dat lekt welke apps/versies er draaien).
|
||
11. **Tabelstructuur meedumpen** (kolommen, typen, indexen, primary key), zodat de restore een
|
||
ontbrekende tabel zelf aanmaakt met de gegevens erin. Lost restanten van oude apps op zonder
|
||
ze over te slaan; een later opnieuw geïnstalleerde app vindt zijn oude gegevens terug.
|
||
12. **Foreign keys**: de restore zet tabellen op alfabet terug; `oc_mail_accounts` verwijst naar
|
||
`oc_mail_mailboxes` en PostgreSQL weigert (fk_61a7e87f59a77cdc). Iedereen met Mail loopt hier
|
||
vast. Oplossing: in de juiste volgorde terugzetten (afhankelijkheden eerst), of de controle
|
||
tijdelijk uitzetten en na afloop terugzetten en controleren. In de test: 15 FK's (alle van
|
||
Mail) tijdelijk verwijderd en na de restore teruggezet: alle 15 geldig op de teruggezette
|
||
data, dus "uitzetten, terugzetten, achteraf controleren" werkt.
|
||
13. **SFTP-restore over een lange lijn is traag**: 0,75 MiB/s vanaf Hetzner naar Canada (RTT 122 ms),
|
||
terwijl OpenSSH `sftp` 16,6 MiB/s haalt. De SFTP-stream van files_external (phpseclib) wacht na
|
||
elk klein blok op antwoord. Oplossing: pack-ranges bij SFTP met phpseclib's eigen `get()`
|
||
(veel verzoeken tegelijk) lezen in plaats van via de stream, zoals bij S3. 1 TB duurt nu ~2 weken.
|
||
Na meten: de grootste oorzaak was het netwerk (cubic, zie 14): met BBR gaat de restore van 0,75
|
||
naar 2,3 MiB/s, een hele pack lezen 4-5 MiB/s. Meer verzoeken in één stream (DEPTH 16/32) gaf geen
|
||
duidelijke winst. Verder komen vraagt meerdere verbindingen tegelijk: naar **1.1.0**.
|
||
14. **Uploaden over een lange lijn**: backup naar OVH S3 Warschau vanaf Canada (RTT 114 ms) haalt
|
||
~1 MiB/s met één pack tegelijk; `aws s3 cp` (multipart, parallel) 6 MiB/s. Meerdere packs tegelijk
|
||
uploaden of S3 multipart. Minder dringend dan 13 (restore).
|
||
Oorzaak netwerk (met Sysop): pakketverlies trans-Atlantisch + cubic in de VM; met BBR gaat één
|
||
PUT van 2,4 naar 7,5 MiB/s, ng_backup van ~1 naar ~1,6 MiB/s (rest = serieel lezen/uploaden).
|
||
Tip over BBR in de README gezet. Let op: PVE02 staat in Canada; een restore in de EU gaat sneller.
|
||
15. DR-document: in het Docker-voorbeeld "je containernaam" (`docker ps`) i.p.v. `nextcloud`.
|
||
16. Grote restore (VM 980, snapshot aa7cce8d van de Storage Box) stopte na 22 uur met
|
||
`Not found: packs/26/2642ef39…`. Oorzaak: de wekelijkse opruiming van nc01 (zo 18-10) heeft
|
||
dat pakket tijdens de restore omgepakt (blob staat nu in `389567b7…`); de herstelserver had de
|
||
index van vóór de opruiming. Productie heel: `backup:verify` op nc01 OK (150.639 bestanden,
|
||
316.830 blobs). Opgelost in rc2: bij een mislukte pakketlezing de index herladen en de blob
|
||
lezen waar hij nu staat (test `testRestoreFollowsPacksRepackedByAnotherServer`).
|
||
**Kleine DR-test met rc2 (2026-10-18), volautomatisch geslaagd:** bron VM 981 (NC 34, PostgreSQL,
|
||
51 GB, Mail met gesynct account, Deck uitgeschakeld, Calendar/Contacts, admin_audit) → OVH S3 →
|
||
verse VM 982 met alleen de DR-gids en `--install-apps`: apps vanzelf geïnstalleerd, 10 Deck-tabellen
|
||
aangemaakt, 15 FK's in orde, 23.519/23.519 checksums gelijk, agenda/contact/mail/bord terug.
|
||
Restore 1 u 47 vanuit Warschau naar Canada (met BBR). Lokaal slaagt de test ook op SQLite en MariaDB.
|
||
**Daarna verplicht:** een tweede volledige restore op een verse VM, alleen met de HOWTO en de
|
||
nieuwe opties, zonder handwerk. Pas als die in één keer slaagt: 1.0.0.
|
||
|
||
- **Later (geen versie):** optie om de sleutel niet op de locatie op te slaan (kit is dan de enige
|
||
kopie; uitdrukkelijke waarschuwing). Primaire object storage en External-storage-koppelingen
|
||
meenemen via de opslaglaag (§3.4; nu niet ondersteund, zie README "Known limitations").
|
||
- **Open:** of de bevestigingscode van de kit dubbelop is (nog niet over besloten).
|
||
|
||
Doorgeschoven naar **v1.3.0** (fase 3, niet meer fase 2): in-place volledige restore
|
||
(eigen restore-modus i.p.v. onderhoudsmodus) en zelfbediening voor gebruikers (eigen bestanden
|
||
terugzetten vanuit de Files-app).
|
||
|
||
**Let op (ontdekt 2026-10-05):** Nextcloud's eigen `info.xsd` staat voor `<version>` alleen
|
||
`[0-9]+(\.[0-9]+){0,2}` toe — geen `-alpha`/`-beta`/`-rc`-achtervoegsel. De stabiliteitsaanduiding
|
||
uit deze tabel gaat dus niet in `info.xml` (dat blijft gewoon oplopend, bijv. `0.1.0`), maar in de
|
||
git-tag (`v0.1.0-alpha.1`), de GitHub/Gitea release (gemarkeerd als pre-release) en de CHANGELOG.
|
||
|
||
## Bijlage: lessen uit nextcloud/backup (test NC34, 2026-10-01)
|
||
|
||
- Externe programma's (`pg_dump`) en extensies (`pgsql`, `mysqli`) → onbruikbaar op standaard/hosted.
|
||
- Crash tijdens backup/restore laat de instantie in onderhoudsmodus; occ-commando's van de app
|
||
zijn dan niet beschikbaar.
|
||
- Hele chunks in het geheugen (`ZipArchive::getFromName()` als bestaan-check) → OOM bij restore.
|
||
- Bij een fout eindeloos "opnieuw proberen?" zonder invoer → hangende restore.
|
||
- Backupt de hele webroot + apps → groot en traag; restore alleen via occ in meerdere stappen.
|
||
- Herbruikbaar als idee: checksums + metadata per herstelpunt, AES-GCM/sodium, differentiële backups.
|