Backup: probe the target with a real write instead of access(W_OK)
On the server the backup folder is a Synology NFS share with "map all users to admin": access(W_OK) returns EACCES for uid 1000 although writes succeed, so the first scheduled run failed with "not usable". Create and remove a probe file instead; test that it leaves nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
086f6353f8
commit
f8f21ab42a
3 files changed
+18
-2
No files matched your search
@@ -169,7 +169,11 @@ class BackupService {
|
|||||||
try {
|
try {
|
||||||
const st = await fsp.stat(this.backupRoot);
|
const st = await fsp.stat(this.backupRoot);
|
||||||
if (!st.isDirectory()) throw new Error('not a directory');
|
if (!st.isDirectory()) throw new Error('not a directory');
|
||||||
await fsp.access(this.backupRoot, fs.constants.W_OK);
|
// Probe with a real write: access(W_OK) is unreliable on NFS with squashed ids (Synology
|
||||||
|
// "map all users to admin" denies it although writes succeed).
|
||||||
|
const probe = path.join(this.backupRoot, `.circus-backup-probe-${process.pid}`);
|
||||||
|
await fsp.writeFile(probe, '');
|
||||||
|
await fsp.rm(probe, { force: true });
|
||||||
const names = (await fsp.readdir(this.backupRoot)).filter(name => ARCHIVE.test(name)).sort();
|
const names = (await fsp.readdir(this.backupRoot)).filter(name => ARCHIVE.test(name)).sort();
|
||||||
this.archives = await Promise.all(names.map(async name => ({ name, size: (await fsp.stat(path.join(this.backupRoot, name))).size, at: archiveTime(name) })));
|
this.archives = await Promise.all(names.map(async name => ({ name, size: (await fsp.stat(path.join(this.backupRoot, name))).size, at: archiveTime(name) })));
|
||||||
this.targetError = null;
|
this.targetError = null;
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@ holds it). Implementation: [`backup.js`](../backup.js); tests: `test/backup.test
|
|||||||
|
|
||||||
The folder must already exist and be writable by uid 1000 (`create_host_path: false`:
|
The folder must already exist and be writable by uid 1000 (`create_host_path: false`:
|
||||||
a missing folder makes `docker compose up` fail instead of silently creating an empty
|
a missing folder makes `docker compose up` fail instead of silently creating an empty
|
||||||
one on the wrong disk). Use another disk or a NAS share, never a folder inside `DATA`.
|
one on the wrong disk). Use another disk or a NAS share, never a folder inside `DATA`. Writability is checked by
|
||||||
|
creating and removing a probe file (`.circus-backup-probe-<pid>`), not with `access()`, which
|
||||||
|
NFS shares with squashed ids (e.g. Synology "map all users to admin") answer wrongly.
|
||||||
Without `BACKUP_PATH`, Compose still mounts the unused default `./backup`, so that
|
Without `BACKUP_PATH`, Compose still mounts the unused default `./backup`, so that
|
||||||
folder must exist too (`node tools/prepare-compose.js` creates it). Upgrading an
|
folder must exist too (`node tools/prepare-compose.js` creates it). Upgrading an
|
||||||
existing deployment therefore needs either `BACKUP_PATH` in `.env` or an empty
|
existing deployment therefore needs either `BACKUP_PATH` in `.env` or an empty
|
||||||
|
|||||||
@@ -168,6 +168,16 @@ test('backups are off without BACKUP, and a missing folder is reported, never cr
|
|||||||
} finally { f.cleanup(); }
|
} finally { f.cleanup(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the writability probe leaves nothing behind in the backup folder', async () => {
|
||||||
|
const f = fixture();
|
||||||
|
try {
|
||||||
|
const backup = service(f);
|
||||||
|
await backup.refreshTarget();
|
||||||
|
assert.equal(backup.publicState().targetError, null);
|
||||||
|
assert.deepEqual(fs.readdirSync(f.target).filter(name => name.startsWith('.circus-backup-probe')), []);
|
||||||
|
} finally { f.cleanup(); }
|
||||||
|
});
|
||||||
|
|
||||||
test('only one run at a time', async () => {
|
test('only one run at a time', async () => {
|
||||||
const f = fixture();
|
const f = fixture();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user