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
|
|
|
// One s3d per hosted account, each wrapped in the same createPithos() the
|
|
|
|
|
// desktop runs, so the server speaks exactly the /api the Pithos UI already
|
|
|
|
|
// knows. Accounts live under <root>/users/<id>/:
|
|
|
|
|
//
|
|
|
|
|
// account.json who (public key), how they got in (gate), port slot, state
|
|
|
|
|
// s3d.yml loopback-only addresses from the slot, random admin password
|
|
|
|
|
// data/ the s3d data folder moved here from the user's PC
|
|
|
|
|
// rules.json public folders, links and shared keys
|
|
|
|
|
// usage.json bytes served this month
|
|
|
|
|
//
|
|
|
|
|
// An s3d starts on the account's first request and is stopped after
|
|
|
|
|
// `idleMinutes` without one, flushing leftovers to Sia first. At most
|
|
|
|
|
// `maxRunning` run at once; the least recently used idle one makes room.
|
|
|
|
|
|
|
|
|
|
import fs from 'node:fs';
|
|
|
|
|
import path from 'node:path';
|
|
|
|
|
import { createPithos } from '../core/server.js';
|
|
|
|
|
import { makeCli } from '../core/cli.js';
|
|
|
|
|
import { readConfig, writeConfig, randomPassword } from '../core/config.js';
|
|
|
|
|
import { writePrefs } from '../core/prefs.js';
|
|
|
|
|
import { S3Client } from '../core/s3.js';
|
|
|
|
|
import * as admin from '../core/admin.js';
|
|
|
|
|
import { readJson, writeJson } from './store.js';
|
|
|
|
|
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
// reservedKey(akid): true for key ids the gateway itself hands out (grants),
|
|
|
|
|
// which no account's s3d may also use.
|
|
|
|
|
export function createSupervisor(cfg, log = console.log, { reservedKey = () => false } = {}) {
|
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
|
|
|
const usersDir = path.join(cfg.root, 'users');
|
|
|
|
|
const slotsFile = path.join(cfg.root, 'slots.json');
|
|
|
|
|
const keysFile = path.join(cfg.root, 'keys.json');
|
|
|
|
|
const running = new Map(); // id -> instance
|
|
|
|
|
const loading = new Map(); // id -> Promise<instance>
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
const closing = new Map(); // id -> Promise, while an s3d is being stopped
|
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
|
|
|
const keyIndex = readJson(keysFile, {}); // accessKeyId -> account id (owners' s3d keys)
|
|
|
|
|
|
|
|
|
|
const dir = (id) => path.join(usersDir, id);
|
|
|
|
|
const accountFile = (id) => path.join(dir(id), 'account.json');
|
|
|
|
|
const configFile = (id) => path.join(dir(id), 's3d.yml');
|
|
|
|
|
const dataDir = (id) => path.join(dir(id), 'data');
|
|
|
|
|
|
|
|
|
|
function account(id) {
|
|
|
|
|
if (!/^[0-9a-f]{32}$/.test(id)) return null;
|
|
|
|
|
return readJson(accountFile(id), null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function saveAccount(a) { writeJson(accountFile(a.id), a); }
|
|
|
|
|
|
|
|
|
|
function createAccount({ id, pubkey, gate }) {
|
|
|
|
|
const slots = readJson(slotsFile, { next: 0 });
|
|
|
|
|
const slot = slots.next++;
|
|
|
|
|
writeJson(slotsFile, slots);
|
|
|
|
|
const a = { id, pubkey, gate, slot, state: 'empty', createdAt: Date.now() };
|
|
|
|
|
fs.mkdirSync(dataDir(id), { recursive: true });
|
|
|
|
|
const port = cfg.portBase + slot * 2;
|
|
|
|
|
writeConfig(configFile(id), {
|
|
|
|
|
apiAddress: `127.0.0.1:${port}`,
|
|
|
|
|
adminAddress: `127.0.0.1:${port + 1}`,
|
|
|
|
|
adminPassword: randomPassword(),
|
|
|
|
|
directory: dataDir(id),
|
|
|
|
|
'sia.diskUsageLimit': cfg.bufferLimitBytes,
|
|
|
|
|
});
|
|
|
|
|
// Leftovers always go to Sia in hosted mode: an idle s3d gets stopped.
|
|
|
|
|
writePrefs(configFile(id), { autostart: false, autoFlush: true, autoFlushMinutes: 10 });
|
|
|
|
|
saveAccount(a);
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setState(id, state) {
|
|
|
|
|
const a = account(id);
|
|
|
|
|
a.state = state;
|
|
|
|
|
saveAccount(a);
|
|
|
|
|
writePrefs(configFile(id), { autostart: state === 'active' });
|
|
|
|
|
return a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load(id) {
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
// Never start a second s3d on a folder whose first one is still stopping.
|
|
|
|
|
while (closing.has(id)) await closing.get(id);
|
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
|
|
|
if (running.has(id)) return running.get(id);
|
|
|
|
|
if (loading.has(id)) return loading.get(id);
|
|
|
|
|
const p = (async () => {
|
|
|
|
|
await makeRoom();
|
|
|
|
|
const pithos = await createPithos({
|
|
|
|
|
host: '127.0.0.1', port: 0, hostName: 'hosted',
|
|
|
|
|
configFile: configFile(id), binary: cfg.s3dBinary,
|
|
|
|
|
});
|
|
|
|
|
const inst = { id, pithos, cli: makeCli(pithos.daemon), cookie: pithos.internalSession(), lastUsed: Date.now(), busy: 0, buckets: new Map() };
|
|
|
|
|
running.set(id, inst);
|
|
|
|
|
pithos.daemon.on('log', (l) => { if (l.stream !== 'out') log(`[${id.slice(0, 8)}] ${l.line}`); });
|
|
|
|
|
return inst;
|
|
|
|
|
})().finally(() => loading.delete(id));
|
|
|
|
|
loading.set(id, p);
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The instance with s3d up, for anything that talks S3.
|
|
|
|
|
async function ensure(id) {
|
|
|
|
|
const a = account(id);
|
|
|
|
|
if (!a) throw httpError(404, 'no such account');
|
|
|
|
|
if (a.state === 'empty') throw httpError(409, 'nothing is hosted yet: move your drives here from Pithos first');
|
|
|
|
|
if (a.state === 'moving-out') throw httpError(409, 'this account is being moved to its owner\'s computer');
|
|
|
|
|
const inst = await load(id);
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
// The account may have started moving out while it loaded.
|
|
|
|
|
if (account(id).state !== 'active') throw httpError(409, 'this account is being moved');
|
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
|
|
|
inst.lastUsed = Date.now();
|
|
|
|
|
const d = inst.pithos.daemon;
|
|
|
|
|
if (d.state !== 'running') {
|
|
|
|
|
if (d.state !== 'starting') d.start();
|
|
|
|
|
const until = Date.now() + 30_000;
|
|
|
|
|
while (d.state === 'starting' && Date.now() < until) await sleep(200);
|
|
|
|
|
if (d.state !== 'running') throw httpError(503, 'your s3d did not start; try again in a minute');
|
|
|
|
|
await indexKeys(inst);
|
|
|
|
|
}
|
|
|
|
|
return inst;
|
|
|
|
|
}
|
|
|
|
|
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
// Routes S3 requests: access key id -> account. A key id another account
|
|
|
|
|
// (or a grant) already uses is never taken over; that account's traffic
|
|
|
|
|
// would otherwise land in this one's s3d. Such keys are left unrouted and
|
|
|
|
|
// reported in inst.conflicts.
|
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
|
|
|
async function indexKeys(inst) {
|
|
|
|
|
const keys = await inst.cli.listKeys().catch(() => []);
|
|
|
|
|
let changed = false;
|
|
|
|
|
for (const [k, v] of Object.entries(keyIndex)) if (v === inst.id && !keys.some((x) => x.accessKeyId === k)) { delete keyIndex[k]; changed = true; }
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
const conflicts = [];
|
|
|
|
|
for (const k of keys) {
|
|
|
|
|
const owner = Object.hasOwn(keyIndex, k.accessKeyId) ? keyIndex[k.accessKeyId] : null;
|
|
|
|
|
if (owner === inst.id) continue;
|
|
|
|
|
if (owner || reservedKey(k.accessKeyId)) { conflicts.push(k.accessKeyId); continue; }
|
|
|
|
|
keyIndex[k.accessKeyId] = inst.id;
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
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
|
|
|
if (changed) writeJson(keysFile, keyIndex);
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
if (conflicts.length) log(`[${inst.id.slice(0, 8)}] access keys already used elsewhere, not routed: ${conflicts.join(', ')}`);
|
|
|
|
|
inst.keys = keys.filter((k) => !conflicts.includes(k.accessKeyId));
|
|
|
|
|
inst.conflicts = conflicts;
|
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
|
|
|
inst.buckets.clear();
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
return inst.keys;
|
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
|
|
|
}
|
|
|
|
|
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
const keyTaken = (akid, id) => (Object.hasOwn(keyIndex, akid) && keyIndex[akid] !== id) || reservedKey(akid);
|
|
|
|
|
|
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
|
|
|
// Every drive on the account, mapped to a key of the s3d user owning it.
|
|
|
|
|
async function mapBuckets(inst) {
|
|
|
|
|
inst.buckets.clear();
|
|
|
|
|
for (const k of inst.keys || await indexKeys(inst)) {
|
|
|
|
|
const list = await clientFor(inst, k).listBuckets().catch(() => []);
|
|
|
|
|
for (const b of list) if (!inst.buckets.has(b.name)) inst.buckets.set(b.name, k);
|
|
|
|
|
}
|
|
|
|
|
return [...inst.buckets.keys()];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// An owner key that can act on `bucket`.
|
|
|
|
|
async function bucketClient(inst, bucket) {
|
|
|
|
|
if (!inst.buckets.has(bucket)) await mapBuckets(inst);
|
|
|
|
|
const k = inst.buckets.get(bucket);
|
|
|
|
|
return k ? clientFor(inst, k) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clientFor(inst, k) {
|
|
|
|
|
return new S3Client({ endpoint: `http://${readConfig(configFile(inst.id)).apiAddress}`, accessKeyId: k.accessKeyId, secretKey: k.secretKey });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function close(id, reason) {
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
if (closing.has(id)) return closing.get(id);
|
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
|
|
|
const inst = running.get(id);
|
|
|
|
|
if (!inst) return;
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
const p = (async () => {
|
|
|
|
|
log(`[${id.slice(0, 8)}] stopping s3d (${reason})`);
|
|
|
|
|
await inst.pithos.close().catch((e) => log(`[${id.slice(0, 8)}] close failed: ${e.message}`));
|
|
|
|
|
running.delete(id);
|
|
|
|
|
})().finally(() => closing.delete(id));
|
|
|
|
|
closing.set(id, p);
|
|
|
|
|
return p;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function flushQuietly(inst, timeoutMs = 120_000) {
|
|
|
|
|
const c = readConfig(configFile(inst.id));
|
|
|
|
|
try {
|
|
|
|
|
const st = await admin.uploadStats(c);
|
|
|
|
|
if (!st.pendingObjects) return true;
|
|
|
|
|
await Promise.race([admin.flush(c), sleep(timeoutMs).then(() => { throw new Error('timed out'); })]);
|
|
|
|
|
return true;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log(`[${inst.id.slice(0, 8)}] flush before stop failed: ${e.message}`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function makeRoom() {
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
// Loads in flight count too, or parallel first requests overshoot the cap.
|
|
|
|
|
if (running.size + loading.size < cfg.maxRunning) return;
|
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
|
|
|
const idle = [...running.values()].filter((i) => !i.busy).sort((a, b) => a.lastUsed - b.lastUsed);
|
|
|
|
|
if (!idle.length) throw httpError(503, 'the server is busy; try again in a minute');
|
|
|
|
|
await flushQuietly(idle[0]);
|
|
|
|
|
await close(idle[0].id, 'making room');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sweep() {
|
|
|
|
|
const cutoff = Date.now() - cfg.idleMinutes * 60_000;
|
|
|
|
|
for (const inst of [...running.values()]) {
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
if (inst.busy || inst.lastUsed > cutoff || closing.has(inst.id)) continue;
|
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
|
|
|
await flushQuietly(inst);
|
|
|
|
|
if (!inst.busy && inst.lastUsed <= cutoff) await close(inst.id, 'idle');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const sweeper = setInterval(() => { sweep().catch((e) => log('sweep failed:', e.message)); }, 60_000);
|
|
|
|
|
sweeper.unref?.();
|
|
|
|
|
|
|
|
|
|
// Holds an instance "in use" for the duration of a streamed request, so the
|
|
|
|
|
// sweeper does not stop s3d under a long download.
|
|
|
|
|
function hold(inst) {
|
|
|
|
|
inst.busy++;
|
|
|
|
|
let done = false;
|
|
|
|
|
return () => { if (!done) { done = true; inst.busy--; inst.lastUsed = Date.now(); } };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
dir, dataDir, configFile, account, createAccount, saveAccount, setState,
|
Pithos hosting: fixes from an independent review
A server-sent file list could name paths outside the s3d folder (or the key
file in it), and a failed move back deleted every listed path. Transfers now
accept only s3d data paths, check all of them before writing, never
overwrite, and clean up only what they wrote.
A lost reply could lose or fork the drives: move-up and move-down now ask the
server who holds them before touching the local copy, and once the server is
told to release, the local copy is never removed (the release is retried).
The gateway no longer lets one account route another's access key id, refuses
. and .. in shared-key paths, strips presign parameters before re-signing,
counts upload size on disk, stops s3d before it can be restarted mid-move, and
claims sign-up proofs before checking them. The key file is never replaced
when unreadable, a local edit removes the older encrypted copy, listing pages
no longer repeat, and data hashes stream instead of loading whole files.
2026-10-04 03:15:42 +02:00
|
|
|
load, ensure, close, flushQuietly, bucketClient, mapBuckets, indexKeys, hold, keyTaken,
|
|
|
|
|
ownerOfKey: (akid) => (Object.hasOwn(keyIndex, akid) ? keyIndex[akid] : null),
|
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
|
|
|
async closeAll() {
|
|
|
|
|
clearInterval(sweeper);
|
|
|
|
|
for (const id of [...running.keys()]) {
|
|
|
|
|
await flushQuietly(running.get(id), 30_000);
|
|
|
|
|
await close(id, 'shutdown');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
runningCount: () => running.size,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function httpError(status, message) { return Object.assign(new Error(message), { status }); }
|
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|