From f79573b317456d493741da31c894b71be980aaa7 Mon Sep 17 00:00:00 2001 From: SITO Date: Wed, 19 Aug 2026 00:45:40 +0200 Subject: [PATCH] ssb: base de multicuenta, igual que en la version de movil Se traen los mismos cambios: la cuenta sale de OASIS_ACCOUNT con el nombre validado, statePath se separa por cuenta, y los doce sitios que construian la ruta como os.homedir() + '/.ssb' pasan a usar la de la cuenta activa. El mas importante sigue siendo panicmode_model: con varias identidades habria borrado la que no era. Tambien entra el gate que faltaba en renderMobileTopbar, sin el cual la cabecera de movil se dibujaba encima de la de upstream en escritorio. La configuracion de escritorio no se toca: sigue con tema Dark-SNH y todos los modulos. Verificado arrancando sin OASIS_MOBILE: ocho rutas OK, incluidas /peers y /stats que son las que leen gossip.json, y cero rastro de la interfaz movil. --- src/backend/backend.js | 6 +++--- src/models/exportmode_model.js | 4 ++-- src/models/legacy_model.js | 8 ++++---- src/models/main_models.js | 6 +++--- src/models/panicmode_model.js | 4 ++-- src/models/stats_model.js | 10 +++++----- src/server/ssb_config.js | 22 ++++++++++++++++++++-- 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/backend/backend.js b/src/backend/backend.js index f11d2d7..a8f44fe 100644 --- a/src/backend/backend.js +++ b/src/backend/backend.js @@ -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 (_) {} diff --git a/src/models/exportmode_model.js b/src/models/exportmode_model.js index cd535d5..52b20cb 100644 --- a/src/models/exportmode_model.js +++ b/src/models/exportmode_model.js @@ -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 } diff --git a/src/models/legacy_model.js b/src/models/legacy_model.js index c2f44dc..b612890 100644 --- a/src/models/legacy_model.js +++ b/src/models/legacy_model.js @@ -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)) { diff --git a/src/models/main_models.js b/src/models/main_models.js index 1456649..a92f365 100644 --- a/src/models/main_models.js +++ b/src/models/main_models.js @@ -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')); diff --git a/src/models/panicmode_model.js b/src/models/panicmode_model.js index 95933fc..0c11a0d 100644 --- a/src/models/panicmode_model.js +++ b/src/models/panicmode_model.js @@ -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); diff --git a/src/models/stats_model.js b/src/models/stats_model.js index 0af631c..ddbb322 100644 --- a/src/models/stats_model.js +++ b/src/models/stats_model.js @@ -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; diff --git a/src/server/ssb_config.js b/src/server/ssb_config.js index f3e4be6..b3aca81 100644 --- a/src/server/ssb_config.js +++ b/src/server/ssb_config.js @@ -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 (~/.) 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; };