Ships Pithos (the s3d control panel) as a built-in extension: the dock menu opens it in a tab served from a loopback port, and "Stop s3d" shuts the gateway down. Generated by Pithos/scripts/build-theseus-addon.mjs. yaml is vendored under vendor/yaml/lib rather than node_modules/ or dist/, because Theseus git-ignores both and a clean-worktree release build would otherwise ship the add-on without its only dependency.
93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
// Typed wrappers over s3d's user/key subcommands. s3d has no HTTP API for
|
|
// these; the CLI opens the same SQLite file the daemon uses (WAL mode, so it
|
|
// is safe while the daemon is running).
|
|
|
|
import { spawn } from 'node:child_process';
|
|
|
|
export class CliError extends Error {}
|
|
|
|
function check({ code, stdout, stderr }) {
|
|
if (code !== 0) throw new CliError((stderr || stdout).trim() || `s3d exited with code ${code}`);
|
|
return stdout;
|
|
}
|
|
|
|
export function makeCli(daemon) {
|
|
return {
|
|
// Whether this s3d holds an app key. `s3d login` reports an existing
|
|
// registration before it reads any input; with stdin closed an
|
|
// unregistered instance hits EOF at the first prompt and exits.
|
|
async registration() {
|
|
const { stdout } = await new Promise((resolve, reject) => {
|
|
const child = spawn(daemon.binary, ['login'], {
|
|
cwd: daemon.cwd(), env: daemon.env(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
});
|
|
let out = '';
|
|
child.stdout.on('data', (d) => { out += d; });
|
|
child.stderr.on('data', (d) => { out += d; });
|
|
const timer = setTimeout(() => child.kill(), 15_000);
|
|
child.on('error', reject);
|
|
child.on('exit', () => { clearTimeout(timer); resolve({ stdout: out.replace(/\x1b\[[0-9;]*m/g, '') }); });
|
|
});
|
|
const m = stdout.match(/already registered with (\S+?)\.?\s*$/m);
|
|
return m ? { registered: true, indexerUrl: m[1] } : { registered: false };
|
|
},
|
|
|
|
async version() {
|
|
const out = check(await daemon.run(['version']));
|
|
const m = out.match(/s3d v?(\S+)/);
|
|
const commit = out.match(/Commit:\s*(\S+)/);
|
|
return { version: m ? m[1] : null, commit: commit ? commit[1] : null, raw: out.trim() };
|
|
},
|
|
|
|
async listUsers() {
|
|
const out = check(await daemon.run(['users', 'list']));
|
|
if (/^No users found/m.test(out)) return [];
|
|
return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
},
|
|
|
|
async createUser(name) {
|
|
validateName(name);
|
|
check(await daemon.run(['users', 'create', name]));
|
|
},
|
|
|
|
async deleteUser(name) {
|
|
validateName(name);
|
|
check(await daemon.run(['users', 'delete', name]));
|
|
},
|
|
|
|
// [{ accessKeyId, secretKey, user }]
|
|
async listKeys(user) {
|
|
const out = check(await daemon.run(user ? ['keys', 'list', user] : ['keys', 'list']));
|
|
if (/^No access keys found/m.test(out)) return [];
|
|
return out.split(/\r?\n/).filter(Boolean).map((line) => {
|
|
const [accessKeyId, secretKey, ...rest] = line.split('\t');
|
|
return { accessKeyId, secretKey, user: rest.join('\t') };
|
|
});
|
|
},
|
|
|
|
async createKey(user, { accessKey, secretKey } = {}) {
|
|
validateName(user);
|
|
const args = ['keys', 'create'];
|
|
if (accessKey || secretKey) args.push('--access-key', accessKey || '', '--secret-key', secretKey || '');
|
|
args.push(user);
|
|
const out = check(await daemon.run(args));
|
|
const ak = out.match(/Access Key:\s*(\S+)/);
|
|
const sk = out.match(/Secret Key:\s*(\S+)/);
|
|
if (!ak || !sk) throw new CliError(`unexpected output from s3d keys create: ${out.trim()}`);
|
|
return { accessKeyId: ak[1], secretKey: sk[1], user };
|
|
},
|
|
|
|
async deleteKey(accessKeyId) {
|
|
if (!/^[\x21-\x7e]{1,128}$/.test(accessKeyId)) throw new CliError('invalid access key id');
|
|
check(await daemon.run(['keys', 'delete', accessKeyId]));
|
|
},
|
|
};
|
|
}
|
|
|
|
// Users are passed as argv, never through a shell, but a leading '-' would be
|
|
// read as a flag by s3d's parser.
|
|
function validateName(name) {
|
|
if (typeof name !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._@-]{0,63}$/.test(name)) {
|
|
throw new CliError('user names are 1-64 characters: letters, digits, . _ @ -, not starting with a symbol');
|
|
}
|
|
}
|