Pithos 0.3.4: show the Sia account, label it, switch and restore accounts
"Acting as" read like a choice of Sia account; it picks an S3 user on
this s3d, and every user shares the one Sia account the s3d is connected
to. So:
- The picker is "S3 user", with a tooltip saying all users share one Sia
account; Buckets explains what a bucket is.
- Buckets and the Overview show "Sia account: <label> (indexer)". Pithos
cannot see the account's email, so the Sia connection page lets the
user label it (kept in pithos.json beside the s3d data).
- "Switch Sia account" stops s3d and moves its data (connection, users,
keys, buckets, upload buffer) into previous-account-<stamp>/ next to
s3d.yml, never deleting it; the guide then connects another account.
It warns about uploads still waiting for Sia and offers a flush, and
refuses while another s3d serves the config.
- "Earlier Sia accounts" lists those folders with Restore, which sets the
current account aside the same way and swaps the old one back.
Also: a fresh connect now records which indexer it used, and a "not
connected" check is re-run after 30 s so a login made outside Pithos
shows up.
2026-10-03 22:57:46 +02:00
|
|
|
// Switching the Sia account an s3d is connected to. s3d has no "log out":
|
|
|
|
|
// its connection (the app key), users, keys, buckets and upload buffer all
|
|
|
|
|
// live in its data folder. A switch moves that folder's contents into
|
|
|
|
|
// previous-account-<stamp>/ beside it (never deleted), so s3d starts fresh
|
|
|
|
|
// and can be connected to another account; a restore swaps one back.
|
|
|
|
|
//
|
|
|
|
|
// s3d.yml stays where it is: it holds addresses and the admin password, not
|
|
|
|
|
// the account. pithos.json (the user's label for the account) moves with the
|
|
|
|
|
// data, because the label belongs to that account.
|
|
|
|
|
|
|
|
|
|
import fs from 'node:fs';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
|
|
|
|
|
const ARCHIVE_PREFIX = 'previous-account-';
|
|
|
|
|
const META = 'pithos-archive.json';
|
|
|
|
|
const LABEL_FILE = 'pithos.json';
|
|
|
|
|
|
Pithos: run your drives on Silent Mode with your own Sia account
s3d binds one Sia account per process, so the server runs one s3d per
person (pithos-hosted), started on demand and flushed and stopped when idle.
Only the connected s3d data folder moves between PC and server; the Sia
recovery phrase never leaves the PC and nothing is re-uploaded to Sia.
Because the server holds the connection key, local Pithos encrypts names and
contents before they leave the machine, with keys from the Theseus vault (or
a local key file elsewhere). What s3d cannot do on its own, the gateway now
enforces: public folders, expiring share links and S3 keys for other people
limited to one folder with read, add-only or edit rights. Files those reach
are stored readable, since visitors have no key, and go back to encrypted
when the rule is removed. Sign-up needs a BNS name signature, a deposit or
an invite, with a per-IP limit.
2026-10-04 01:33:20 +02:00
|
|
|
// Files that belong to this computer, not to a Sia account. The hosted key
|
|
|
|
|
// file above all: moving or deleting it would make encrypted files unreadable.
|
|
|
|
|
const LOCAL = new Set(['s3d.yml', 'pithos-prefs.json', 'pithos-hosted-secret.json']);
|
|
|
|
|
const keep = (name) => LOCAL.has(name) || name.startsWith(ARCHIVE_PREFIX);
|
Pithos 0.3.4: show the Sia account, label it, switch and restore accounts
"Acting as" read like a choice of Sia account; it picks an S3 user on
this s3d, and every user shares the one Sia account the s3d is connected
to. So:
- The picker is "S3 user", with a tooltip saying all users share one Sia
account; Buckets explains what a bucket is.
- Buckets and the Overview show "Sia account: <label> (indexer)". Pithos
cannot see the account's email, so the Sia connection page lets the
user label it (kept in pithos.json beside the s3d data).
- "Switch Sia account" stops s3d and moves its data (connection, users,
keys, buckets, upload buffer) into previous-account-<stamp>/ next to
s3d.yml, never deleting it; the guide then connects another account.
It warns about uploads still waiting for Sia and offers a flush, and
refuses while another s3d serves the config.
- "Earlier Sia accounts" lists those folders with Restore, which sets the
current account aside the same way and swaps the old one back.
Also: a fresh connect now records which indexer it used, and a "not
connected" check is re-run after 30 s so a login made outside Pithos
shows up.
2026-10-03 22:57:46 +02:00
|
|
|
|
|
|
|
|
function stamp(d = new Date()) {
|
|
|
|
|
const p = (n) => String(n).padStart(2, '0');
|
|
|
|
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function readLabel(dataDir) {
|
|
|
|
|
try { return String(JSON.parse(fs.readFileSync(path.join(dataDir, LABEL_FILE), 'utf8')).accountLabel || ''); } catch { return ''; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function writeLabel(dataDir, label) {
|
|
|
|
|
const clean = String(label || '').trim().slice(0, 120);
|
|
|
|
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
|
|
|
let cur = {};
|
|
|
|
|
try { cur = JSON.parse(fs.readFileSync(path.join(dataDir, LABEL_FILE), 'utf8')); } catch {}
|
|
|
|
|
fs.writeFileSync(path.join(dataDir, LABEL_FILE), JSON.stringify({ ...cur, accountLabel: clean }, null, 2));
|
|
|
|
|
return clean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function listArchives(dataDir) {
|
|
|
|
|
let names = [];
|
|
|
|
|
try { names = fs.readdirSync(dataDir).filter((n) => n.startsWith(ARCHIVE_PREFIX)); } catch { return []; }
|
|
|
|
|
return names.sort().reverse().map((name) => {
|
|
|
|
|
const dir = path.join(dataDir, name);
|
|
|
|
|
let meta = {};
|
|
|
|
|
try { meta = JSON.parse(fs.readFileSync(path.join(dir, META), 'utf8')); } catch {}
|
|
|
|
|
return {
|
|
|
|
|
name,
|
|
|
|
|
archivedAt: meta.archivedAt || null,
|
|
|
|
|
indexerUrl: meta.indexerUrl || null,
|
|
|
|
|
label: meta.label || readLabel(dir),
|
|
|
|
|
users: meta.users || [],
|
Pithos: run your drives on Silent Mode with your own Sia account
s3d binds one Sia account per process, so the server runs one s3d per
person (pithos-hosted), started on demand and flushed and stopped when idle.
Only the connected s3d data folder moves between PC and server; the Sia
recovery phrase never leaves the PC and nothing is re-uploaded to Sia.
Because the server holds the connection key, local Pithos encrypts names and
contents before they leave the machine, with keys from the Theseus vault (or
a local key file elsewhere). What s3d cannot do on its own, the gateway now
enforces: public folders, expiring share links and S3 keys for other people
limited to one folder with read, add-only or edit rights. Files those reach
are stored readable, since visitors have no key, and go back to encrypted
when the rule is removed. Sign-up needs a BNS name signature, a deposit or
an invite, with a per-IP limit.
2026-10-04 01:33:20 +02:00
|
|
|
movedTo: meta.movedTo || null,
|
Pithos 0.3.4: show the Sia account, label it, switch and restore accounts
"Acting as" read like a choice of Sia account; it picks an S3 user on
this s3d, and every user shares the one Sia account the s3d is connected
to. So:
- The picker is "S3 user", with a tooltip saying all users share one Sia
account; Buckets explains what a bucket is.
- Buckets and the Overview show "Sia account: <label> (indexer)". Pithos
cannot see the account's email, so the Sia connection page lets the
user label it (kept in pithos.json beside the s3d data).
- "Switch Sia account" stops s3d and moves its data (connection, users,
keys, buckets, upload buffer) into previous-account-<stamp>/ next to
s3d.yml, never deleting it; the guide then connects another account.
It warns about uploads still waiting for Sia and offers a flush, and
refuses while another s3d serves the config.
- "Earlier Sia accounts" lists those folders with Restore, which sets the
current account aside the same way and swaps the old one back.
Also: a fresh connect now records which indexer it used, and a "not
connected" check is re-run after 30 s so a login made outside Pithos
shows up.
2026-10-03 22:57:46 +02:00
|
|
|
hasData: fs.existsSync(path.join(dir, 's3d.db')),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Moves the current account's data aside. `meta` is stored with it for the
|
|
|
|
|
// restore list: { indexerUrl, users }.
|
|
|
|
|
export function archiveCurrent(dataDir, meta = {}) {
|
|
|
|
|
if (!fs.existsSync(path.join(dataDir, 's3d.db'))) throw new Error('there is no s3d data to move aside');
|
|
|
|
|
let name = ARCHIVE_PREFIX + stamp();
|
|
|
|
|
while (fs.existsSync(path.join(dataDir, name))) name += '-1';
|
|
|
|
|
const dest = path.join(dataDir, name);
|
|
|
|
|
fs.mkdirSync(dest);
|
|
|
|
|
const label = readLabel(dataDir);
|
|
|
|
|
for (const entry of fs.readdirSync(dataDir)) {
|
|
|
|
|
if (keep(entry) || entry === name) continue;
|
|
|
|
|
fs.renameSync(path.join(dataDir, entry), path.join(dest, entry));
|
|
|
|
|
}
|
|
|
|
|
fs.writeFileSync(path.join(dest, META), JSON.stringify({ archivedAt: new Date().toISOString(), label, ...meta }, null, 2));
|
|
|
|
|
return { name, path: dest };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Swaps an archived account back in. Whatever is current is archived first
|
|
|
|
|
// (when there is anything), so a restore never loses data either.
|
|
|
|
|
export function restoreArchive(dataDir, name, metaForCurrent = {}) {
|
|
|
|
|
if (!name.startsWith(ARCHIVE_PREFIX) || name.includes('/') || name.includes('\\') || name.includes('..')) throw new Error('not an account archive');
|
|
|
|
|
const src = path.join(dataDir, name);
|
|
|
|
|
if (!fs.existsSync(path.join(src, 's3d.db'))) throw new Error('that archive has no s3d data');
|
Pithos: run your drives on Silent Mode with your own Sia account
s3d binds one Sia account per process, so the server runs one s3d per
person (pithos-hosted), started on demand and flushed and stopped when idle.
Only the connected s3d data folder moves between PC and server; the Sia
recovery phrase never leaves the PC and nothing is re-uploaded to Sia.
Because the server holds the connection key, local Pithos encrypts names and
contents before they leave the machine, with keys from the Theseus vault (or
a local key file elsewhere). What s3d cannot do on its own, the gateway now
enforces: public folders, expiring share links and S3 keys for other people
limited to one folder with read, add-only or edit rights. Files those reach
are stored readable, since visitors have no key, and go back to encrypted
when the rule is removed. Sign-up needs a BNS name signature, a deposit or
an invite, with a per-IP limit.
2026-10-04 01:33:20 +02:00
|
|
|
let meta = {};
|
|
|
|
|
try { meta = JSON.parse(fs.readFileSync(path.join(src, META), 'utf8')); } catch {}
|
|
|
|
|
// The copy left behind when drives moved to Silent Mode is out of date the
|
|
|
|
|
// moment the server s3d writes anything; running it would fork the account.
|
|
|
|
|
if (meta.movedTo) throw new Error(`these drives now live on ${meta.movedTo}; move them back from Settings instead`);
|
Pithos 0.3.4: show the Sia account, label it, switch and restore accounts
"Acting as" read like a choice of Sia account; it picks an S3 user on
this s3d, and every user shares the one Sia account the s3d is connected
to. So:
- The picker is "S3 user", with a tooltip saying all users share one Sia
account; Buckets explains what a bucket is.
- Buckets and the Overview show "Sia account: <label> (indexer)". Pithos
cannot see the account's email, so the Sia connection page lets the
user label it (kept in pithos.json beside the s3d data).
- "Switch Sia account" stops s3d and moves its data (connection, users,
keys, buckets, upload buffer) into previous-account-<stamp>/ next to
s3d.yml, never deleting it; the guide then connects another account.
It warns about uploads still waiting for Sia and offers a flush, and
refuses while another s3d serves the config.
- "Earlier Sia accounts" lists those folders with Restore, which sets the
current account aside the same way and swaps the old one back.
Also: a fresh connect now records which indexer it used, and a "not
connected" check is re-run after 30 s so a login made outside Pithos
shows up.
2026-10-03 22:57:46 +02:00
|
|
|
const archivedCurrent = fs.existsSync(path.join(dataDir, 's3d.db')) ? archiveCurrent(dataDir, metaForCurrent).name : null;
|
|
|
|
|
// Anything left (tmp folders from a fresh, unconnected s3d) is clutter.
|
|
|
|
|
for (const entry of fs.readdirSync(dataDir)) {
|
|
|
|
|
if (keep(entry)) continue;
|
|
|
|
|
fs.rmSync(path.join(dataDir, entry), { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
for (const entry of fs.readdirSync(src)) {
|
|
|
|
|
if (entry === META) continue;
|
|
|
|
|
fs.renameSync(path.join(src, entry), path.join(dataDir, entry));
|
|
|
|
|
}
|
|
|
|
|
fs.rmSync(src, { recursive: true, force: true });
|
|
|
|
|
return { restored: name, archivedCurrent };
|
|
|
|
|
}
|