From 5267dab2360be92dc339d103a1f766e3053aa59a Mon Sep 17 00:00:00 2001 From: ergosteur <1992147+ergosteur@users.noreply.github.com> Date: Sat, 7 Mar 2026 03:02:02 -0500 Subject: [PATCH] fix: address Docker EACCES errors with better logging and SELinux hints --- README.md | 21 +++++++++++++----- compose.yml | 5 ++--- package.json | 2 +- server.ts | 61 +++++++++++++++++++++++++++++++++------------------- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index bedcf2f..32c0c0d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ docker run -d \ ghcr.io/ergosteur/instaarchive-viewer:latest ``` +> **Note for Linux/SELinux users:** If you see "Permission Denied" in the logs, append `,z` to your volume mount: `-v /path/to/archives:/archives:ro,z` + ### Docker Compose Create a `compose.yml` file: @@ -36,13 +38,22 @@ services: ports: - "3000:3000" volumes: - - ./archives:/archives:ro + - ./archives:/archives:ro,z # ,z handles SELinux permissions ``` -Run with: -```bash -docker compose up -d -``` +### Troubleshooting Permissions + +If the app shows "No Archives Found" and logs `EACCES: permission denied`: + +1. **Check Directory Permissions**: Ensure the archive folder is world-readable: + ```bash + 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: + ```bash + docker run --user $(id -u):$(id -g) ... + ``` ## Supported Archive Structure diff --git a/compose.yml b/compose.yml index ec3ddab..698cd29 100644 --- a/compose.yml +++ b/compose.yml @@ -1,11 +1,10 @@ services: instaarchive: - image: ghcr.io/${GITHUB_REPOSITORY:-ergosteur/instaarchive-viewer}:latest - build: . + image: ghcr.io/ergosteur/instaarchive-viewer:latest ports: - "3000:3000" volumes: - - ./archives:/archives:ro + - ./archives:/archives:ro,z environment: - PORT=3000 - ARCHIVES_DIR=/archives diff --git a/package.json b/package.json index f701a65..f2ad7c1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-example", "private": true, - "version": "1.1.1", + "version": "1.1.2", "type": "module", "scripts": { "dev": "vite --port=3000 --host=0.0.0.0", diff --git a/server.ts b/server.ts index 5cfdd3c..12181d4 100644 --- a/server.ts +++ b/server.ts @@ -3,6 +3,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import dotenv from 'dotenv'; +import os from 'os'; dotenv.config(); @@ -14,13 +15,18 @@ 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})`); console.log(`[Server] Environment ARCHIVES_DIR: ${process.env.ARCHIVES_DIR}`); console.log(`[Server] Resolved ARCHIVES_DIR: ${ARCHIVES_DIR}`); // Ensure archives directory exists if (!fs.existsSync(ARCHIVES_DIR)) { console.warn(`[Server] Warning: Archives directory not found at ${ARCHIVES_DIR}. Creating it...`); - fs.mkdirSync(ARCHIVES_DIR, { recursive: true }); + try { + fs.mkdirSync(ARCHIVES_DIR, { recursive: true }); + } catch (err) { + console.error(`[Server] Failed to create archives directory:`, err); + } } else { console.log(`[Server] Archives directory exists.`); } @@ -43,31 +49,42 @@ app.get('/api/archives', (req, res) => { .map(item => { // Try to find a profile pic or first image for the thumbnail const archivePath = path.join(ARCHIVES_DIR, item.name); - const files = fs.readdirSync(archivePath); - console.log(`[API] Found archive: ${item.name} (${files.length} files)`); - - let thumbnail = ''; - const profilePic = files.find(f => f.toLowerCase().includes('_profile_pic.jpg') || f.toLowerCase() === `${item.name.toLowerCase()}.jpg`); - if (profilePic) { - thumbnail = `/archives/${item.name}/${profilePic}`; - } else { - const firstImage = files.find(f => /\.(jpg|jpeg|png|webp)$/i.test(f)); - if (firstImage) thumbnail = `/archives/${item.name}/${firstImage}`; - } + try { + const files = fs.readdirSync(archivePath); + console.log(`[API] Found archive: ${item.name} (${files.length} files)`); + + let thumbnail = ''; + const profilePic = files.find(f => f.toLowerCase().includes('_profile_pic.jpg') || f.toLowerCase() === `${item.name.toLowerCase()}.jpg`); + if (profilePic) { + thumbnail = `/archives/${item.name}/${profilePic}`; + } else { + const firstImage = files.find(f => /\.(jpg|jpeg|png|webp)$/i.test(f)); + if (firstImage) thumbnail = `/archives/${item.name}/${firstImage}`; + } - return { - name: item.name, - thumbnail, - path: item.name, - fileCount: files.length - }; - }); + return { + name: item.name, + thumbnail, + path: item.name, + fileCount: files.length + }; + } catch (e) { + console.error(`[API] Could not read subdirectory ${item.name}:`, e); + return null; + } + }) + .filter(Boolean); console.log(`[API] Returning ${archives.length} validated archives.`); res.json(archives); - } catch (err) { - console.error('[API] Error listing archives:', err); - res.status(500).json({ error: 'Failed to list 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] 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); + } + res.status(500).json({ error: 'Permission denied or failed to list archives' }); } });