From 3d863b5fe6e415351ce646ff06f47914fff1bb52 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 4 Oct 2026 22:41:10 +0200 Subject: [PATCH] Pithos on Linux desktops: per-user s3d paths s3d's Linux defaults (/etc/s3d/s3d.yml, /var/lib/s3d) are for a system service; a desktop user cannot write them, so the desktop app could never create its config. When no system config exists and those places are not writable, Pithos uses ~/.config/s3d/s3d.yml and ~/.local/share/s3d (XDG), and writes the directory into the new config so s3d itself, started with S3D_CONFIG_FILE, uses the same place. --- core/config.js | 4 +++- core/paths.js | 28 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/core/config.js b/core/config.js index 472917c..da76676 100644 --- a/core/config.js +++ b/core/config.js @@ -5,7 +5,7 @@ import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import YAML from 'yaml'; -import { defaultDataDir } from './paths.js'; +import { defaultDataDir, linuxUserMode } from './paths.js'; // Fields the settings form exposes, as dotted yaml paths. Anything else in the // file is left untouched. @@ -71,6 +71,8 @@ export function ensureConfig(file) { apiAddress: DEFAULTS.apiAddress, adminAddress: DEFAULTS.adminAddress, adminPassword: randomPassword(), + // s3d itself would fall back to /var/lib/s3d, which this user cannot write + ...(linuxUserMode() ? { directory: defaultDataDir() } : {}), }); } diff --git a/core/paths.js b/core/paths.js index f9fa995..2717072 100644 --- a/core/paths.js +++ b/core/paths.js @@ -6,11 +6,33 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; +// s3d's Linux defaults (/etc/s3d, /var/lib/s3d) suit a system service. A +// desktop user who cannot write there, and has no system config, gets the +// XDG per-user places instead; new configs then name that directory +// explicitly, so a hand-run s3d with S3D_CONFIG_FILE agrees. +const xdg = (envName, fallback) => process.env[envName] || path.join(os.homedir(), ...fallback); +const userConfigFile = () => path.join(xdg('XDG_CONFIG_HOME', ['.config']), 's3d', 's3d.yml'); +const userDataDir = () => path.join(xdg('XDG_DATA_HOME', ['.local', 'share']), 's3d'); + +function writable(p) { + // the nearest existing folder decides whether p could be created + for (let d = p; ; d = path.dirname(d)) { + if (fs.existsSync(d)) { try { fs.accessSync(d, fs.constants.W_OK); return true; } catch { return false; } } + if (path.dirname(d) === d) return false; + } +} + +export function linuxUserMode() { + if (process.platform === 'win32' || process.platform === 'darwin') return false; + if (['/etc/s3d/s3d.yml', '/var/lib/s3d/s3d.yml'].some((p) => fs.existsSync(p))) return false; + return !(writable('/etc/s3d') && writable('/var/lib/s3d')); +} + export function defaultDataDir() { switch (process.platform) { case 'win32': return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 's3d'); case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support', 's3d'); - default: return '/var/lib/s3d'; + default: return linuxUserMode() ? userDataDir() : '/var/lib/s3d'; } } @@ -20,7 +42,9 @@ export function configSearchPaths() { switch (process.platform) { case 'win32': paths.push(path.join(defaultDataDir(), 's3d.yml')); break; case 'darwin': paths.push(path.join(defaultDataDir(), 's3d.yml')); break; - default: paths.push('/etc/s3d/s3d.yml', '/var/lib/s3d/s3d.yml'); + default: + if (linuxUserMode()) paths.push(userConfigFile()); + paths.push('/etc/s3d/s3d.yml', '/var/lib/s3d/s3d.yml'); } return paths; }