Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 209520165e | |||
| 9554e774e5 | |||
| 8b2dca28e3 | |||
| 5742f77253 | |||
| 550249570b | |||
| 0eb23f56e7 | |||
| a0c1f1cd58 | |||
| 26cc9a897e |
+65
-3
@@ -2,9 +2,69 @@
|
|||||||
|
|
||||||
All notable changes to NG Backup are documented here. The format follows
|
All notable changes to NG Backup are documented here. The format follows
|
||||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows the stages in
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows the stages in
|
||||||
`PLAN.md` section 9. The `<version>` in `appinfo/info.xml` is plain SemVer-numeric (Nextcloud's
|
`PLAN.md` section 9. The `<version>` in `appinfo/info.xml` is plain SemVer-numeric on purpose:
|
||||||
own `info.xsd` doesn't allow a `-alpha`/`-beta`/`-rc` suffix there); the stability stage is instead
|
the app store accepts a `-beta` suffix, but Nextcloud servers then only offer the release on the
|
||||||
carried by the git tag, the release, and this file.
|
beta/daily update channel, which would hide the app from most admins. The stability stage is
|
||||||
|
instead carried by the app description, the git tag, the release, and this file.
|
||||||
|
|
||||||
|
## [0.9.1-beta.2] - 2026-10-07
|
||||||
|
|
||||||
|
Second beta, the first release in the Nextcloud app store (signed).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- App store listing: screenshots, website, repository and documentation link in `appinfo/info.xml`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Restore: the "new folder" option showed `"Restored "` without the date pattern.
|
||||||
|
- Restore: the restore-point dropdown cut off the time.
|
||||||
|
- Settings: the passphrase actions are on one line instead of stacked.
|
||||||
|
- `docs/HOWTO.md`: the SFTP example used a non-existent auth mechanism (`sftp::password`
|
||||||
|
instead of `password::password`).
|
||||||
|
|
||||||
|
## [0.9.0-beta.1] - 2026-10-07
|
||||||
|
|
||||||
|
First beta. Tested thoroughly and usable **alongside** your current backup, but **not yet a
|
||||||
|
drop-in replacement** for it. We test what we can ourselves (Nextcloud 32–35, SQLite, MariaDB and
|
||||||
|
PostgreSQL, S3, SFTP, FTP, SMB and NFS), but we need field experience from other setups: please
|
||||||
|
report what works and what doesn't, especially on large instances and on storage providers not
|
||||||
|
listed in the README.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Disaster recovery: `occ backup:key:import` and `occ backup:restore:full` restore the database,
|
||||||
|
data directory and config onto a fresh installation of the same major version.
|
||||||
|
- Per-user backup and restore via user_migration: `occ backup:user:backup` and
|
||||||
|
`occ backup:user:restore` (replace the account after a safety export, or restore as
|
||||||
|
`<uid>-bak`), also onto a server where the user does not exist yet. The commands ask before
|
||||||
|
installing user_migration (or take `--install-user-migration`).
|
||||||
|
- Checksum audit: `occ backup:verify`, shallow or `--deep`, for snapshots and per-user exports.
|
||||||
|
- Retention for per-user exports: `occ backup:retention --user-last=N`.
|
||||||
|
- FTP and SMB covered by the integration tests; NFS through the `local` backend on a mounted share.
|
||||||
|
- `docs/HOWTO.md`: step by step from setup to a tested restore.
|
||||||
|
- Warnings when a backup finishes with unreadable files or folders, or with files that kept
|
||||||
|
changing while being read (log, `occ`, notification, snapshot).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `backup:restore:full` refuses on an installation with more than one user and asks first;
|
||||||
|
`backup:user:restore --mode=replace` asks before deleting an existing account (`--force` for
|
||||||
|
scripts in both cases).
|
||||||
|
- A failed scheduled backup is retried at most three times a day, an hour apart.
|
||||||
|
- The recovery kit carries the deletion delay; the kit file is created owner-only.
|
||||||
|
- Adding a location through the web API needs password confirmation.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Exports of users whose id contains `.part-` could be deleted by cleanup on remote locations.
|
||||||
|
- Cleanup no longer frees the data of a per-user export whose manifest is missing.
|
||||||
|
- Crash safety of moving snapshots to and from the trash, of catalog writes by concurrent
|
||||||
|
backups and per-user exports, and of short uploads.
|
||||||
|
- A file that keeps changing no longer makes a backup restart forever.
|
||||||
|
- Long restores and exports keep their lock on the location alive.
|
||||||
|
- Floating-point database columns are dumped exactly.
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
See "Known limitations" in the README: memory use on very large instances, never two servers
|
||||||
|
writing to one location, removing a passphrase slot does not revoke access, no rollback detection
|
||||||
|
before a disaster recovery, and a few smaller points. None of them block the road to 1.0.0; they
|
||||||
|
are on the backlog in `PLAN.md`.
|
||||||
|
|
||||||
## [0.1.0-alpha.1] - 2026-10-05
|
## [0.1.0-alpha.1] - 2026-10-05
|
||||||
|
|
||||||
@@ -43,4 +103,6 @@ and not implemented yet.
|
|||||||
- See the "Storage backend experiences" section of the README for provider-specific quirks found
|
- See the "Storage backend experiences" section of the README for provider-specific quirks found
|
||||||
so far (e.g. Hetzner Storage Box sub-accounts).
|
so far (e.g. Hetzner Storage Box sub-accounts).
|
||||||
|
|
||||||
|
[0.9.1-beta.2]: https://github.com/dillardblom/ng_backup/releases/tag/v0.9.1-beta.2
|
||||||
|
[0.9.0-beta.1]: https://github.com/dillardblom/ng_backup/releases/tag/v0.9.0-beta.1
|
||||||
[0.1.0-alpha.1]: https://github.com/dillardblom/ng_backup/releases/tag/v0.1.0-alpha.1
|
[0.1.0-alpha.1]: https://github.com/dillardblom/ng_backup/releases/tag/v0.1.0-alpha.1
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ installations and hosted Nextcloud. (Choosing SMB as a storage backend is the on
|
|||||||
see "Storage backend experiences" below — that requirement comes from files_external, not from
|
see "Storage backend experiences" below — that requirement comes from files_external, not from
|
||||||
ng_backup, and applies to any Nextcloud app that writes to an SMB share.)
|
ng_backup, and applies to any Nextcloud app that writes to an SMB share.)
|
||||||
|
|
||||||
**Status: beta (0.9.0).** Tested thoroughly, usable alongside your current backup, but not yet a
|
**Status: beta (0.9.1).** Tested thoroughly, usable alongside your current backup, but not yet a
|
||||||
drop-in replacement for it. See "Known limitations" below, `PLAN.md` for the design and
|
drop-in replacement for it. See "Known limitations" below, `PLAN.md` for the design and
|
||||||
`docs/HOWTO.md` for a step-by-step guide from setup to a tested restore.
|
`docs/HOWTO.md` for a step-by-step guide from setup to a tested restore.
|
||||||
|
|
||||||
@@ -63,7 +63,8 @@ All backups of an installation are encrypted with one master key.
|
|||||||
a single slot is possible, but then that one passphrase is a single point of failure.
|
a single slot is possible, but then that one passphrase is a single point of failure.
|
||||||
- When a holder leaves, give their slot a new passphrase (`occ backup:key:slot:replace`), download
|
- When a holder leaves, give their slot a new passphrase (`occ backup:key:slot:replace`), download
|
||||||
the new kit and confirm it. Note: an old kit together with the old passphrase still opens the
|
the new kit and confirm it. Note: an old kit together with the old passphrase still opens the
|
||||||
same master key; to revoke it completely, start new locations with a new key.
|
same master key; to revoke it completely, start new locations with a new key. This is noted on
|
||||||
|
the backlog and will be addressed in a future version (key rotation).
|
||||||
- Downloading the kit, and every change to the slots, requires the administrator's password, is
|
- Downloading the kit, and every change to the slots, requires the administrator's password, is
|
||||||
written to the audit log and is announced to all administrators.
|
written to the audit log and is announced to all administrators.
|
||||||
|
|
||||||
|
|||||||
+14
-6
@@ -4,17 +4,25 @@
|
|||||||
<id>ng_backup</id>
|
<id>ng_backup</id>
|
||||||
<name>NG Backup</name>
|
<name>NG Backup</name>
|
||||||
<summary>Encrypted, incremental backups of your Nextcloud with restore from the web interface</summary>
|
<summary>Encrypted, incremental backups of your Nextcloud with restore from the web interface</summary>
|
||||||
<description><![CDATA[Alpha: feature-complete for scheduled, encrypted, incremental backups to
|
<description><![CDATA[Beta: scheduled, encrypted, incremental backups of the database, config and data
|
||||||
S3/SFTP/WebDAV/local, with restore of files and folders from the web interface and occ. Meant for
|
directory to S3, SFTP, WebDAV, FTP, SMB or a local path, with restore of files and folders, of
|
||||||
testing on a copy of production (dev, test, staging) -- not yet for production use, and full
|
single users (via user_migration) and of the whole server onto a fresh installation. Tested
|
||||||
disaster recovery is not implemented yet. See the recovery kit section of the README before
|
thoroughly and usable alongside your current backup, but not yet a drop-in replacement for it.
|
||||||
relying on this for anything.]]></description>
|
See the README for known limitations and the recovery kit before relying on it.]]></description>
|
||||||
<version>0.1.0</version>
|
<version>0.9.1</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author>Dillard Blom</author>
|
<author>Dillard Blom</author>
|
||||||
<namespace>NgBackup</namespace>
|
<namespace>NgBackup</namespace>
|
||||||
|
<documentation>
|
||||||
|
<admin>https://github.com/dillardblom/ng_backup/blob/main/docs/HOWTO.md</admin>
|
||||||
|
</documentation>
|
||||||
<category>tools</category>
|
<category>tools</category>
|
||||||
|
<website>https://apps.nextcloud.com/apps/ng_backup</website>
|
||||||
<bugs>https://github.com/dillardblom/ng_backup/issues</bugs>
|
<bugs>https://github.com/dillardblom/ng_backup/issues</bugs>
|
||||||
|
<repository type="git">https://github.com/dillardblom/ng_backup</repository>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/dillardblom/ng_backup/main/screenshots/1-backups.png</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/dillardblom/ng_backup/main/screenshots/2-restore.png</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/dillardblom/ng_backup/main/screenshots/3-settings.png</screenshot>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<php min-version="8.1"/>
|
<php min-version="8.1"/>
|
||||||
<lib>sodium</lib>
|
<lib>sodium</lib>
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ Examples:
|
|||||||
|
|
||||||
```
|
```
|
||||||
occ backup:target:add offsite amazons3 -a amazons3::accesskey -o bucket=<bucket> -o key=<key> -o secret=<secret>
|
occ backup:target:add offsite amazons3 -a amazons3::accesskey -o bucket=<bucket> -o key=<key> -o secret=<secret>
|
||||||
occ backup:target:add nas sftp -a sftp::password -o host=<host> -o user=<user> -o password=<pw> -o root=home
|
occ backup:target:add nas sftp -a password::password -o host=<host> -o user=<user> -o password=<pw> -o root=home
|
||||||
occ backup:target:add local local -a null::null -o datadir=/mnt/backup
|
occ backup:target:add local local -a null::null -o datadir=/mnt/backup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 238 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 226 KiB |
@@ -36,7 +36,7 @@
|
|||||||
<td>{{ t('ng_backup', 'Slot {n}', { n: slot.slot }) }}</td>
|
<td>{{ t('ng_backup', 'Slot {n}', { n: slot.slot }) }}</td>
|
||||||
<td><strong>{{ slot.label }}</strong></td>
|
<td><strong>{{ slot.label }}</strong></td>
|
||||||
<td class="ngb-muted">{{ slot.created ? new Date(slot.created * 1000).toLocaleDateString() : '' }}</td>
|
<td class="ngb-muted">{{ slot.created ? new Date(slot.created * 1000).toLocaleDateString() : '' }}</td>
|
||||||
<td>
|
<td class="ngb-actions">
|
||||||
<NcButton variant="tertiary" @click="openSlotForm(slot)">{{ t('ng_backup', 'New passphrase') }}</NcButton>
|
<NcButton variant="tertiary" @click="openSlotForm(slot)">{{ t('ng_backup', 'New passphrase') }}</NcButton>
|
||||||
<NcButton v-if="status.key.slots.length > 1" variant="tertiary" @click="removeSlot(slot)">{{ t('ng_backup', 'Remove') }}</NcButton>
|
<NcButton v-if="status.key.slots.length > 1" variant="tertiary" @click="removeSlot(slot)">{{ t('ng_backup', 'Remove') }}</NcButton>
|
||||||
</td>
|
</td>
|
||||||
@@ -162,5 +162,6 @@ const confirm = () => guarded(async () => {
|
|||||||
.ngb-form, .ngb-confirm { display: flex; flex-direction: column; gap: 12px; max-width: 600px; margin: 8px 0; }
|
.ngb-form, .ngb-confirm { display: flex; flex-direction: column; gap: 12px; max-width: 600px; margin: 8px 0; }
|
||||||
.ngb-row { display: flex; gap: 8px; }
|
.ngb-row { display: flex; gap: 8px; }
|
||||||
.ngb-table td { padding: 2px 12px 2px 0; }
|
.ngb-table td { padding: 2px 12px 2px 0; }
|
||||||
|
.ngb-actions { display: flex; gap: 4px; }
|
||||||
h3 { margin-top: 16px; font-weight: bold; }
|
h3 { margin-top: 16px; font-weight: bold; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
:description="t('ng_backup', 'Pick a restore point and a user folder or file. Restores run in the background and go through Nextcloud, so overwritten files keep their old content as a version and removed files go to the trash.')">
|
:description="t('ng_backup', 'Pick a restore point and a user folder or file. Restores run in the background and go through Nextcloud, so overwritten files keep their old content as a version and removed files go to the trash.')">
|
||||||
<div class="ngb-row">
|
<div class="ngb-row">
|
||||||
<NcSelect v-model="target" :options="status.targets" label="name" :input-label="t('ng_backup', 'Location')" :clearable="false" />
|
<NcSelect v-model="target" :options="status.targets" label="name" :input-label="t('ng_backup', 'Location')" :clearable="false" />
|
||||||
<NcSelect v-if="target" v-model="snapshot" :options="snapshots" label="displayLabel" :input-label="t('ng_backup', 'Restore point')" :clearable="false" />
|
<NcSelect v-if="target" v-model="snapshot" :options="snapshots" label="displayLabel" class="ngb-snapshot" :input-label="t('ng_backup', 'Restore point')" :clearable="false" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="snapshot">
|
<template v-if="snapshot">
|
||||||
@@ -69,7 +69,7 @@ const busy = ref(false)
|
|||||||
const error = ref('')
|
const error = ref('')
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
const modes = [
|
const modes = [
|
||||||
{ id: 'new-folder', label: t('ng_backup', 'Into a new folder "Restored <date>" (nothing existing is changed)') },
|
{ id: 'new-folder', label: t('ng_backup', 'Into a new folder "Restored {date}" (nothing existing is changed)', { date: 'YYYY-MM-DD HH.MM' }) },
|
||||||
{ id: 'merge', label: t('ng_backup', 'Into the original place, overwrite (old content kept as a version)') },
|
{ id: 'merge', label: t('ng_backup', 'Into the original place, overwrite (old content kept as a version)') },
|
||||||
{ id: 'replace', label: t('ng_backup', 'Into the original place, overwrite and move files that are not in the backup to the trash') },
|
{ id: 'replace', label: t('ng_backup', 'Into the original place, overwrite and move files that are not in the backup to the trash') },
|
||||||
]
|
]
|
||||||
@@ -124,6 +124,7 @@ async function restore() {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.ngb-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
.ngb-row { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||||
|
.ngb-row :deep(.ngb-snapshot) { min-width: min(360px, 100%); }
|
||||||
.ngb-crumbs { display: flex; align-items: center; flex-wrap: wrap; }
|
.ngb-crumbs { display: flex; align-items: center; flex-wrap: wrap; }
|
||||||
.ngb-entries li { display: flex; align-items: center; gap: 12px; min-height: 36px; }
|
.ngb-entries li { display: flex; align-items: center; gap: 12px; min-height: 36px; }
|
||||||
.ngb-entries li.selected { background: var(--color-primary-element-light); border-radius: var(--border-radius); }
|
.ngb-entries li.selected { background: var(--color-primary-element-light); border-radius: var(--border-radius); }
|
||||||
|
|||||||
Reference in New Issue
Block a user