diff --git a/README.md b/README.md index edfdd9f..c33b3aa 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,27 @@ If the app shows "No Archives Found" and logs `EACCES: permission denied`: chmod -R 755 /path/to/archives ``` 2. **SELinux (Fedora/RHEL/CentOS)**: Use the `:z` flag in your volume mount as shown above. -3. **User Mapping**: You can force the container to run as your host user: +3. **User Mapping**: The container runs as the non-root `node` user (UID 1000). + If your archives are readable only by another account, run as that user + instead — the container needs to *list* the archive directory, so `--x` + (traverse-only) permissions are not enough: ```bash - docker run --user $(id -u):$(id -g) ... + docker run --user $(stat -c '%u:%g' /path/to/archives) ... ``` + In Compose: + ```yaml + services: + instaarchive: + user: "1234:1234" # a UID that can read your archives + ``` + +### Archive Index + +On first start the server walks the archive root once and caches the result, +keyed by directory mtime. This matters on network storage: for a 110k-file +archive root, listing went from ~52s per request to ~0.1s. Mount a volume at +`/cache` (or set `CACHE_DIR`) so the index survives restarts, otherwise it is +rebuilt on every start. ## Supported Archive Structure diff --git a/server.ts b/server.ts index 7ff8424..5192ccb 100644 --- a/server.ts +++ b/server.ts @@ -16,7 +16,23 @@ const PORT = process.env.PORT || 3001; const ARCHIVES_DIR = path.resolve(process.env.ARCHIVES_DIR || path.join(__dirname, '_sample-archives')); console.log(`[Server] Initializing...`); -console.log(`[Server] Running as user: ${os.userInfo().username} (UID: ${os.userInfo().uid}, GID: ${os.userInfo().gid})`); +/** + * Describe the running user without assuming it exists in /etc/passwd. + * + * `os.userInfo()` throws ENOENT for a UID with no passwd entry, which is + * exactly what happens when the container is started with `--user 1234:1234` + * (as the deployment docs suggest) — previously crashing the server at boot. + */ +const describeUser = () => { + try { + const info = os.userInfo(); + return `${info.username} (UID: ${info.uid}, GID: ${info.gid})`; + } catch { + return `UID: ${typeof process.getuid === 'function' ? process.getuid() : '?'}, GID: ${typeof process.getgid === 'function' ? process.getgid() : '?'} (no passwd entry)`; + } +}; + +console.log(`[Server] Running as user: ${describeUser()}`); console.log(`[Server] Environment ARCHIVES_DIR: ${process.env.ARCHIVES_DIR}`); console.log(`[Server] Resolved ARCHIVES_DIR: ${ARCHIVES_DIR}`); @@ -114,7 +130,7 @@ app.get('/api/archives', (req, res) => { res.json(archives); } catch (err: any) { if (err.code === 'EACCES') { - console.error(`[API] Permission Denied! The server (UID ${os.userInfo().uid}) cannot read ${ARCHIVES_DIR}.`); + console.error(`[API] Permission Denied! The server (${describeUser()}) cannot read ${ARCHIVES_DIR}.`); console.error(`[API] Hint: If using Linux/Docker, check folder permissions (chmod 755) or SELinux context (append :z to your volume mount).`); } else { console.error('[API] Error listing archives:', err);