80 lines
3 KiB
JavaScript
80 lines
3 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 } 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');
|
||
|
|
}
|