theseus/bundled-addons/pithos/core/config.js
Local Dev 4ff75d312a Pithos 0.3.18: JSON key export, Linux desktop builds
Export JSON… gives scripts and agents one file with endpoint, key, secret
and drive. The desktop app now ships for Linux too (AppImage, .deb,
tar.gz), using per-user s3d paths there.
2026-10-04 22:49:48 +02:00

81 lines
3.2 KiB
JavaScript

// s3d.yml read/write. Edits go through the yaml Document API so keys the GUI
// does not know about (and the operator's comments) survive a save.
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import YAML from '../vendor/yaml/lib/index.js';
import { defaultDataDir, linuxUserMode } from './paths.js';
// Fields the settings form exposes, as dotted yaml paths. Anything else in the
// file is left untouched.
export const EDITABLE = [
'apiAddress', 'apiHTTPSAddress', 'adminAddress', 'adminPassword', 'directory',
'sia.diskUsageLimit', 'sia.uploadThreads', 'sia.dataShards', 'sia.parityShards',
's3.hostBases',
'log.stdout.enabled', 'log.stdout.level', 'log.file.enabled', 'log.file.level',
];
export const DEFAULTS = {
apiAddress: '127.0.0.1:8000',
adminAddress: '127.0.0.1:8001',
};
export function readConfig(file) {
const exists = fs.existsSync(file);
const doc = exists ? YAML.parseDocument(fs.readFileSync(file, 'utf8')) : new YAML.Document({});
if (doc.errors.length) throw new Error(`${file}: ${doc.errors[0].message}`);
const data = doc.toJS() || {};
return {
file,
exists,
data,
// Effective values the daemon will use, for things the GUI needs to reach it.
apiAddress: data.apiAddress || DEFAULTS.apiAddress,
adminAddress: data.adminAddress || DEFAULTS.adminAddress,
adminPassword: data.adminPassword || '',
directory: process.env.S3D_DATA_DIR || data.directory || defaultDataDir(),
};
}
// patch: { 'sia.uploadThreads': 8, 'apiHTTPSAddress': null, ... }; null/'' deletes the key.
export function writeConfig(file, patch) {
const exists = fs.existsSync(file);
const doc = exists ? YAML.parseDocument(fs.readFileSync(file, 'utf8')) : new YAML.Document({});
if (doc.contents == null) doc.contents = doc.createNode({});
for (const [key, value] of Object.entries(patch)) {
if (!EDITABLE.includes(key)) throw new Error(`not an editable setting: ${key}`);
const p = key.split('.');
if (value === null || value === '' || (Array.isArray(value) && value.length === 0)) doc.deleteIn(p);
else doc.setIn(p, value);
}
fs.mkdirSync(path.dirname(file), { recursive: true });
const tmp = file + '.tmp';
fs.writeFileSync(tmp, doc.toString(), { mode: 0o600 });
fs.renameSync(tmp, file);
return readConfig(file);
}
// A fresh config the daemon can start with: admin API on, random password.
export function ensureConfig(file) {
if (fs.existsSync(file)) {
const cfg = readConfig(file);
if (cfg.data.adminAddress && cfg.data.adminPassword) return cfg;
// s3d refuses to start without both, and the GUI needs them for stats.
return writeConfig(file, {
adminAddress: cfg.data.adminAddress || DEFAULTS.adminAddress,
adminPassword: cfg.data.adminPassword || randomPassword(),
});
}
return writeConfig(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() } : {}),
});
}
export function randomPassword() {
return crypto.randomBytes(18).toString('base64url');
}