ssb: base de multicuenta y arreglo de las rutas fijas a ~/.ssb

ssb_config pasa a derivar la cuenta de OASIS_ACCOUNT. Como ssb-config construye la
ruta desde el appname y gui.js saca el socket de config.path, cambiar ese literal
arrastra de forma coherente el almacen, la base de datos, los blobs, el secret y el
socket. Sin la variable se usa 'ssb', asi que las instalaciones existentes siguen en
~/.ssb y no notan nada.

El nombre se valida contra [a-zA-Z0-9][a-zA-Z0-9_-]{0,31} y cae a 'ssb' si no encaja.
No es de adorno: acaba en rutas de fichero y hay operaciones destructivas detras.
Comprobado que OASIS_ACCOUNT=../../etc no sale de sitio.

statePath separa tambien por cuenta cuando se usa OASIS_STATE_DIR. Sin eso, dos
identidades compartirian el estado que 0.9.5 empezo a guardar ahi, que es justo el
camino a partir el registro.

Y el arreglo que importa: doce sitios construian la ruta como os.homedir() + '/.ssb'
en vez de usar la de la cuenta. El peor era panicmode_model, que con varias
identidades habria borrado la equivocada; tambien exportmode y legacy_model, que
habrian exportado o restaurado la identidad de otra cuenta. Los de lectura
—main_models, stats_model y backend.js con gossip.json y ebt— habrian mirado siempre
la cuenta por defecto.

Falta la parte visible: el selector de identidad y el reinicio del proceso al
cambiar. Esta base es la que hacia falta para que eso no sea peligroso.

Verificado: con OASIS_ACCOUNT sin definir la ruta sigue siendo ~/.ssb; con 'trabajo'
pasa a ~/.trabajo; con un valor con .. o espacios se rechaza. Diez rutas OK con la
app en marcha, incluidas /peers y /stats, que son las que leen esos ficheros.
This commit is contained in:
SITO 2026-08-19 00:44:23 +02:00
parent f80b2e04f0
commit f354069af7
7 changed files with 39 additions and 21 deletions

View file

@ -3507,7 +3507,7 @@ router
});
};
try {
const gossipPathLocal = path.join(os.homedir(), '.ssb', 'gossip.json');
const gossipPathLocal = path.join(require('../server/ssb_config').path, 'gossip.json');
let gossip = [];
try { gossip = JSON.parse(await promisesFs.readFile(gossipPathLocal, 'utf8')); } catch (_) {}
if (Array.isArray(gossip)) {
@ -8770,9 +8770,9 @@ router
if (pubKey) {
try {
const os = require('os'), fsx = require('fs'), px = require('path');
const gossip = JSON.parse(fsx.readFileSync(px.join(os.homedir(), '.ssb', 'gossip.json'), 'utf8') || '[]');
const gossip = JSON.parse(fsx.readFileSync(px.join(require('../server/ssb_config').path, 'gossip.json'), 'utf8') || '[]');
let unfollowed = [];
try { unfollowed = JSON.parse(fsx.readFileSync(px.join(os.homedir(), '.ssb', 'gossip_unfollowed.json'), 'utf8') || '[]'); } catch (_) {}
try { unfollowed = JSON.parse(fsx.readFileSync(px.join(require('../server/ssb_config').path, 'gossip_unfollowed.json'), 'utf8') || '[]'); } catch (_) {}
const activePub = Array.isArray(gossip) && gossip.some(p => p && p.key === pubKey) && !unfollowed.some(u => u && u.key === pubKey);
if (activePub) { ctx.redirect('/invites?flash=alreadyFederated'); return; }
} catch (_) {}

View file

@ -6,8 +6,8 @@ const archiver = require('../server/node_modules/archiver');
module.exports = {
exportSSB: async (outputPath) => {
try {
const homeDir = os.homedir();
const ssbPath = path.join(homeDir, '.ssb');
// se exporta la cuenta activa, no siempre la de por defecto
const ssbPath = require('../server/ssb_config').path;
const output = fs.createWriteStream(outputPath);
const archive = archiver('zip', {
zlib: { level: 9 }

View file

@ -49,8 +49,8 @@ module.exports = {
exportData: async (password) => {
try {
const pw = normalizePassword(password);
const homeDir = os.homedir();
const secretFilePath = path.join(homeDir, '.ssb', 'secret');
// identidad de la cuenta activa
const secretFilePath = path.join(require('../server/ssb_config').path, 'secret');
if (!fs.existsSync(secretFilePath)) {
throw new Error(".ssb/secret file doesn't exist");
}
@ -78,8 +78,8 @@ module.exports = {
} catch (_) {
throw new Error('Wrong password or corrupt backup file.');
}
const homeDir = os.homedir();
const ssbDir = path.join(homeDir, '.ssb');
// identidad de la cuenta activa
const ssbDir = require('../server/ssb_config').path;
fs.mkdirSync(ssbDir, { recursive: true });
const secretPath = path.join(ssbDir, 'secret');
if (fs.existsSync(secretPath)) {

View file

@ -66,8 +66,8 @@ const configure = (...customOptions) =>
Object.assign({}, defaultOptions, ...customOptions);
// PEERS
const ebtDir = path.join(os.homedir(), '.ssb', 'ebt');
const unfollowedPath = path.join(os.homedir(), '.ssb', 'gossip_unfollowed.json');
const ebtDir = path.join(require('../server/ssb_config').path, 'ebt');
const unfollowedPath = path.join(require('../server/ssb_config').path, 'gossip_unfollowed.json');
async function loadPeersFromEbt() {
let result = [];
@ -789,7 +789,7 @@ models.meta = {
discovered: async () => {
const ssb = await cooler.open();
const snapshot = await ssb.conn.dbPeers();
const gossipPath = path.join(os.homedir(), '.ssb', 'gossip.json');
const gossipPath = path.join(require('../server/ssb_config').path, 'gossip.json');
let gossipMap = new Map();
try {
const gossipData = JSON.parse(await fs.readFile(gossipPath, 'utf8'));

View file

@ -5,8 +5,8 @@ const path = require('path');
module.exports = {
removeSSB: async () => {
try {
const homeDir = os.homedir();
const ssbPath = path.join(homeDir, '.ssb');
// ruta de la cuenta activa: con varias identidades, ~/.ssb fijo borraria otra
const ssbPath = require('../server/ssb_config').path;
await fs.promises.rm(ssbPath, { recursive: true, force: true });
} catch (error) {
throw new Error("Error deleting data: " + error.message);

View file

@ -22,7 +22,7 @@ function readAddrMap() {
const listPubsFromEbt = () => {
try {
const ebtDir = path.join(os.homedir(), '.ssb', 'ebt');
const ebtDir = path.join(require('../server/ssb_config').path, 'ebt');
const files = fs.readdirSync(ebtDir);
return files.filter(f => f.endsWith('.ed25519'));
} catch {
@ -428,12 +428,12 @@ module.exports = ({ cooler, tribeCrypto, tribesModel }) => {
const inhabitants = new Set(allMsgs.map(m => m.value.author)).size;
const secretStat = fs.statSync(`${os.homedir()}/.ssb/secret`);
const secretStat = fs.statSync((require('../server/ssb_config').path + '/secret'));
const createdAt = secretStat.birthtime.toLocaleString();
const folderSize = getFolderSize(`${os.homedir()}/.ssb`);
const flumeSize = getFolderSize(`${os.homedir()}/.ssb/flume`);
const blobsSize = getFolderSize(`${os.homedir()}/.ssb/blobs`);
const folderSize = getFolderSize((require('../server/ssb_config').path + ''));
const flumeSize = getFolderSize((require('../server/ssb_config').path + '/flume'));
const blobsSize = getFolderSize((require('../server/ssb_config').path + '/blobs'));
const allTs = scopedMsgs.map(m => m.value.timestamp || 0).filter(Boolean);
const lastTs = allTs.length ? Math.max(...allTs) : 0;

View file

@ -11,7 +11,21 @@ const i = argv.indexOf('--');
const conf = argv.slice(i + 1);
const cliArgs = ~i ? argv.slice(0, i) : argv;
let config = Config('ssb', minimist(conf));
// Nombre de la cuenta: define donde vive todo (~/.<cuenta>) y de ahi cuelgan el
// secret, la base de datos, los blobs y el socket. Sin la variable se usa 'ssb',
// que es lo de siempre, asi que las instalaciones existentes no notan nada.
//
// Se valida a proposito: este nombre acaba en rutas de fichero y hay operaciones
// destructivas (el modo panico borra el directorio de la cuenta) que no deben poder
// apuntar fuera de sitio.
const ACCOUNT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/;
const rawAccount = process.env.OASIS_ACCOUNT || 'ssb';
const account = ACCOUNT_RE.test(rawAccount) ? rawAccount : 'ssb';
if (account !== rawAccount) {
console.error(`[oasis] OASIS_ACCOUNT no valido (${rawAccount}); se usa 'ssb'`);
}
let config = Config(account, minimist(conf));
config = { ...config, ...configData };
const debug = process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true';
@ -23,8 +37,12 @@ const megabyte = Math.pow(2, 20);
config.blobs = config.blobs || {};
config.blobs.max = 50 * megabyte;
config.account = account;
config.statePath = (name) => {
if (process.env.OASIS_STATE_DIR) return path.join(process.env.OASIS_STATE_DIR, name);
// El directorio externo se separa por cuenta: sin esto dos identidades
// compartirian estado, que es justo lo que hay que evitar.
if (process.env.OASIS_STATE_DIR) return path.join(process.env.OASIS_STATE_DIR, account, name);
return config.path ? path.join(config.path, name) : null;
};