diff --git a/Dockerfile b/Dockerfile index a69beac..d7c225d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,8 +60,8 @@ COPY content ./content # Persistent volume for JSONL training data (resolutions survive # redeploys via the Coolify mount). PM2_HOME lives outside it so # supervisor state is local to the container. -RUN mkdir -p /app/data/training /app/.pm2 \ - && chown -R vyndr:vyndr /app/data /app/.pm2 \ +RUN mkdir -p /app/data/training /app/.pm2 /app/backups \ + && chown -R vyndr:vyndr /app/data /app/.pm2 /app/backups \ && chmod +x /app/scripts/docker-entrypoint.sh USER vyndr diff --git a/src/routes/internal.js b/src/routes/internal.js index b95f2d0..8c13501 100644 --- a/src/routes/internal.js +++ b/src/routes/internal.js @@ -281,15 +281,33 @@ router.get('/backup/verify', async (req, res) => { const { latestDump, countRowsInDump } = require('../backupScheduler'); try { const dir = process.env.BACKUP_DIR || '/var/backups/vyndr'; + // Report identity + writability: a mounted-but-unwritable volume is the + // exact failure we hit (Coolify mounts root-owned; the container runs as + // the non-root `vyndr` user), and the fix needs the real uid/gid. + const fs = require('fs'); + let writable = false; + let dirErr = null; + try { + fs.accessSync(dir, fs.constants.W_OK); + writable = true; + } catch (e) { dirErr = e.code || e.message; } + const identity = { + uid: typeof process.getuid === 'function' ? process.getuid() : null, + gid: typeof process.getgid === 'function' ? process.getgid() : null, + backup_dir_writable: writable, + backup_dir_error: dirErr, + }; + const dump = latestDump(dir); if (!dump) { - return res.json({ ok: false, backup_dir: dir, error: 'no dump found in BACKUP_DIR' }); + return res.json({ ok: false, backup_dir: dir, ...identity, error: 'no dump found in BACKUP_DIR' }); } const table = String(req.query.table || 'ledger_entries'); const counted = await countRowsInDump(dump.path, table); return res.json({ ok: counted.ok, backup_dir: dir, + ...identity, dump: dump.file, dump_bytes: dump.size, table,