theseus/bundled-addons/pithos/core/config.js

80 lines
3 KiB
JavaScript
Raw Normal View History

// 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 } 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(),
});
}
export function randomPassword() {
return crypto.randomBytes(18).toString('base64url');
}