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
// "On Silent Mode" inside the control server. In hosted mode the UI talks to
// this server exactly as before; this module answers by:
// - forwarding daemon, users, keys, stats and status calls to the gateway,
// - giving the object routes an S3 client that encrypts and decrypts here,
// - turning "Access" and "Share link" into gateway rules (and converting the
// files they cover to readable copies, since visitors have no key),
// - moving the s3d data folder to the server and back.
import fs from 'node:fs' ;
import path from 'node:path' ;
import * as admin from './admin.js' ;
import * as accounts from './accounts.js' ;
import { readPrefs , writePrefs } from './prefs.js' ;
import { ensureConfig } from './config.js' ;
import {
fileSecrets , signIn , gateway , HostedError , EncryptingS3Client , plainCovered ,
dataFiles , uploadData , downloadData ,
} from './hosted.js' ;
export const DEFAULT _HOSTED _URL = 'https://s3.silentmode.st' ;
// Calls forwarded as they are. The gateway allows the same set.
const FORWARD = [
[ 'GET' , /^\/api\/status$/ ] , [ 'GET' , /^\/api\/logs$/ ] , [ 'GET' , /^\/api\/stats$/ ] ,
[ 'POST' , /^\/api\/flush$/ ] , [ 'GET' , /^\/api\/autoflush$/ ] ,
[ 'POST' , /^\/api\/daemon\/(start|restart)$/ ] ,
[ 'GET' , /^\/api\/users$/ ] , [ 'POST' , /^\/api\/users$/ ] , [ 'DELETE' , /^\/api\/users\/[^/]+$/ ] ,
[ 'GET' , /^\/api\/keys$/ ] , [ 'POST' , /^\/api\/keys$/ ] , [ 'DELETE' , /^\/api\/keys\/[^/]+$/ ] ,
[ 'GET' , /^\/api\/account$/ ] , [ 'PUT' , /^\/api\/account\/label$/ ] ,
2026-10-04 04:01:36 +02:00
[ 'GET' , /^\/api\/space$/ ] ,
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
] ;
// Things that act on this computer's s3d and make no sense while drives live
// on the server.
const LOCAL _ONLY = [
[ /^\/api\/binary\/install$/ , 'There is nothing to install while your drives are on Silent Mode.' ] ,
[ /^\/api\/login/ , 'Your drives are on Silent Mode, already connected to your Sia account.' ] ,
[ /^\/api\/account\/(switch|restore)$/ , 'Move your drives back to this computer before switching Sia accounts.' ] ,
[ /^\/api\/daemon\/stop$/ , 'On Silent Mode, s3d stops by itself when nobody uses it.' ] ,
] ;
export function installHosted ( ctx ) {
const { route , configFile , cfg , daemon , broadcast , registration , resetRegistration , HttpError } = ctx ;
const secrets = ctx . secrets || fileSecrets ( configFile ) ;
const prefs = ( ) => readPrefs ( configFile ) ;
const hp = ( ) => prefs ( ) . hosted || null ;
const isHosted = ( ) => prefs ( ) . mode === 'hosted' && ! ! hp ( ) ? . token ;
const gw = ( ) => {
const h = hp ( ) ;
if ( ! h ? . token ) throw new HttpError ( 401 , 'sign in to Silent Mode first' ) ;
return gateway ( h . url , h . token ) ;
} ;
const wrap = async ( fn ) => {
try { return await fn ( ) ; } catch ( e ) {
if ( e instanceof HostedError ) {
if ( e . status === 401 ) { writePrefs ( configFile , { hosted : { ... hp ( ) , token : null } } ) ; }
throw new HttpError ( e . status , e . message ) ;
}
throw e ;
}
} ;
// ---- rules, cached briefly ----
let rulesCache = null ;
async function rules ( ) {
if ( ! isHosted ( ) ) return [ ] ;
if ( rulesCache && Date . now ( ) - rulesCache . at < 30_000 ) return rulesCache . list ;
const list = await wrap ( ( ) => gw ( ) . call ( 'GET' , '/hosted/v1/rules' ) ) ;
rulesCache = { at : Date . now ( ) , list } ;
return list ;
}
const dropRules = ( ) => { rulesCache = null ; } ;
// ---- the S3 client for object routes ----
const keyCache = new Map ( ) ;
async function hostedKeys ( user ) {
const c = keyCache . get ( user ) ;
if ( c && Date . now ( ) - c . at < 60_000 ) return c . keys ;
const keys = await wrap ( ( ) => gw ( ) . call ( 'GET' , ` /api/keys?user= ${ encodeURIComponent ( user ) } ` ) ) ;
keyCache . set ( user , { at : Date . now ( ) , keys } ) ;
return keys ;
}
async function s3For ( user , localKeys ) {
if ( isHosted ( ) ) {
const keys = await hostedKeys ( user ) ;
if ( ! keys . length ) throw new HttpError ( 409 , ` user " ${ user } " has no access keys yet - create one first ` ) ;
return new EncryptingS3Client ( {
endpoint : hp ( ) . url , accessKeyId : keys [ 0 ] . accessKeyId , secretKey : keys [ 0 ] . secretKey ,
master : ( ) => secrets . encryptionKey ( ) , rules ,
} ) ;
}
// Back on this computer, files encrypted while hosted still decrypt.
if ( prefs ( ) . encryptedData && localKeys ? . length ) {
return new EncryptingS3Client ( {
endpoint : ` http:// ${ cfg ( ) . apiAddress } ` , accessKeyId : localKeys [ 0 ] . accessKeyId , secretKey : localKeys [ 0 ] . secretKey ,
master : ( ) => secrets . encryptionKey ( ) , encryptWrites : false ,
} ) ;
}
return null ;
}
// ---- status, logs and events from the server ----
let remote = null ; // last /api/status from the gateway
async function hostedStatus ( local ) {
const h = hp ( ) ;
try {
remote = await wrap ( ( ) => gw ( ) . call ( 'GET' , '/api/status' ) ) ;
return { ... remote , host : local . host , mode : 'hosted' , login : local . login , hosted : { ... remote . hosted , url : h . url } } ;
} catch ( e ) {
return { ... local , mode : 'hosted' , daemon : { state : 'stopped' , error : e . message } , hosted : { url : h . url , error : e . message } , registration : { registered : true } } ;
}
}
let lastSeq = 0 ;
let lastDaemon = '' ;
async function pollRemote ( ) {
if ( ! isHosted ( ) || ! ctx . hasSubscribers ( ) ) return ;
try {
const st = await gw ( ) . call ( 'GET' , '/api/status' ) ;
remote = st ;
const d = JSON . stringify ( st . daemon ) ;
if ( d !== lastDaemon ) { lastDaemon = d ; broadcast ( 'status' , st . daemon ) ; }
const logs = await gw ( ) . call ( 'GET' , ` /api/logs?since= ${ lastSeq } ` ) ;
for ( const l of logs || [ ] ) { lastSeq = Math . max ( lastSeq , l . seq ) ; broadcast ( 'log' , l ) ; }
} catch { /* the next poll tries again */ }
}
const poller = setInterval ( ( ) => { pollRemote ( ) ; } , 5000 ) ;
poller . unref ? . ( ) ;
// ---- intercept: runs before the local routes in hosted mode ----
async function intercept ( req , res , url , send ) {
if ( ! isHosted ( ) ) return false ;
const p = url . pathname ;
for ( const [ re , msg ] of LOCAL _ONLY ) if ( re . test ( p ) ) throw new HttpError ( 409 , msg ) ;
if ( p === '/api/config' && req . method === 'PUT' ) throw new HttpError ( 409 , 'Server settings are managed by Silent Mode while your drives are there.' ) ;
if ( p === '/api/status' ) return false ; // answered by the local route, which asks us
if ( ! FORWARD . some ( ( [ m , re ] ) => m === req . method && re . test ( p ) ) ) return false ;
const body = req . method === 'GET' ? undefined : await readJson ( req ) ;
const out = await wrap ( ( ) => gw ( ) . call ( req . method , p + url . search , { body } ) ) ;
if ( /^\/api\/(users|keys)/ . test ( p ) && req . method !== 'GET' ) keyCache . clear ( ) ;
send ( out ) ;
return true ;
}
// ---- routes ----
route ( 'GET' , '/api/hosted' , async ( ) => {
const h = hp ( ) ;
const out = { mode : isHosted ( ) ? 'hosted' : 'local' , url : h ? . url || DEFAULT _HOSTED _URL , id : h ? . id || null , signedIn : ! ! h ? . token , secrets : secrets . kind || 'vault' } ;
if ( h ? . token ) {
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
try { const warn = await settleMoves ( ) ; if ( warn ) out . warning = warn ; } catch { }
out . mode = isHosted ( ) ? 'hosted' : 'local' ;
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
try { out . me = await gw ( ) . call ( 'GET' , '/hosted/v1/me' ) ; } catch ( e ) { out . error = e . message ; if ( e . status === 401 ) out . signedIn = false ; }
}
if ( ! out . signedIn ) {
try { out . info = await gateway ( out . url ) . call ( 'GET' , '/hosted/v1/info' ) ; } catch ( e ) { out . error = ` Silent Mode is not reachable: ${ e . message } ` ; }
}
return out ;
} ) ;
route ( 'POST' , '/api/hosted/signin' , async ( req ) => {
const { url = DEFAULT _HOSTED _URL , signup } = await readJson ( req ) ;
if ( ! /^https:\/\/[^\s/]+$|^http:\/\/(127\.0\.0\.1|localhost)(:\d+)?$/ . test ( url . replace ( /\/$/ , '' ) ) ) throw new HttpError ( 400 , 'the server address must be https' ) ;
const r = await wrap ( ( ) => signIn ( url . replace ( /\/$/ , '' ) , secrets , signup ) ) ;
if ( r . needsSignup ) return r ;
writePrefs ( configFile , { hosted : { url : url . replace ( /\/$/ , '' ) , id : r . id , token : r . token } } ) ;
return { ok : true , id : r . id , state : r . state } ;
} ) ;
route ( 'POST' , '/api/hosted/signout' , async ( ) => {
const h = hp ( ) ;
if ( h ? . token ) await gw ( ) . call ( 'DELETE' , '/hosted/v1/session' ) . catch ( ( ) => { } ) ;
writePrefs ( configFile , { hosted : h ? { ... h , token : null } : null , mode : 'local' } ) ;
dropRules ( ) ;
keyCache . clear ( ) ;
return { ok : true } ;
} ) ;
// Use drives already on Silent Mode from this computer (a second device).
route ( 'POST' , '/api/hosted/mode' , async ( req ) => {
const { mode } = await readJson ( req ) ;
if ( mode === 'local' ) { writePrefs ( configFile , { mode : 'local' } ) ; return { mode : 'local' } ; }
if ( mode !== 'hosted' ) throw new HttpError ( 400 , 'mode is local or hosted' ) ;
const me = await wrap ( ( ) => gw ( ) . call ( 'GET' , '/hosted/v1/me' ) ) ;
if ( me . state !== 'active' ) throw new HttpError ( 409 , 'Silent Mode has no drives for you yet. Move them there first.' ) ;
writePrefs ( configFile , { mode : 'hosted' , encryptedData : true } ) ;
return { mode : 'hosted' } ;
} ) ;
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
// Asks the server who holds the drives, a few times. null if it cannot tell.
async function serverState ( g ) {
for ( let i = 0 ; i < 3 ; i ++ ) {
try { return ( await g . call ( 'GET' , '/hosted/v1/me' ) ) . state ; } catch { await new Promise ( ( r ) => setTimeout ( r , 1000 * ( i + 1 ) ) ) ; }
}
return null ;
}
// The server has the drives: set the local copy aside so it can never run
// next to the server's (that would fork the account).
function adoptHosted ( reg , users ) {
const c = cfg ( ) ;
if ( fs . existsSync ( path . join ( c . directory , 's3d.db' ) ) ) {
accounts . archiveCurrent ( c . directory , { indexerUrl : reg ? . indexerUrl || null , users : users || [ ] , movedTo : hp ( ) . url } ) ;
}
resetRegistration ( ) ;
writePrefs ( configFile , { mode : 'hosted' , encryptedData : true , autostart : false , moveUncertain : null } ) ;
keyCache . clear ( ) ;
dropRules ( ) ;
}
// Finishes a move whose last answer was lost (see move-up and move-down).
async function settleMoves ( ) {
const p = prefs ( ) ;
if ( ! p . moveUncertain && ! p . pendingRelease ) return null ;
const g = gw ( ) ;
const state = await serverState ( g ) ;
if ( state == null ) return 'Silent Mode is not reachable, so an earlier move is not finished yet.' ;
if ( p . moveUncertain ) {
if ( state === 'active' ) adoptHosted ( p . moveUncertain . reg , p . moveUncertain . users ) ;
else writePrefs ( configFile , { moveUncertain : null } ) ; // it never arrived; the local copy is the account
}
if ( p . pendingRelease ) {
if ( state === 'moving-out' ) await g . call ( 'POST' , '/hosted/v1/data/release' ) . catch ( ( ) => { } ) ;
if ( ( await serverState ( g ) ) !== 'moving-out' ) writePrefs ( configFile , { pendingRelease : null } ) ;
}
return 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
// Local -> Silent Mode. Nothing is re-uploaded to Sia: s3d's database and
// any objects still waiting for Sia move, then s3d runs on the server.
route ( 'POST' , '/api/hosted/move-up' , async ( req ) => {
const { force } = await readJson ( req ) ;
const g = gw ( ) ;
const progress = ( p ) => broadcast ( 'move' , { direction : 'up' , ... p } ) ;
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 ( prefs ( ) . moveUncertain ) throw new HttpError ( 409 , 'An earlier move is not settled yet; open Settings again in a minute.' ) ;
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 me = await wrap ( ( ) => g . call ( 'GET' , '/hosted/v1/me' ) ) ;
if ( me . state !== 'empty' ) throw new HttpError ( 409 , 'Silent Mode already holds drives for you. Use them from here, or move them back first.' ) ;
const c = cfg ( ) ;
const reg = await registration ( ) ;
if ( ! reg ? . registered ) throw new HttpError ( 409 , 'Connect this computer to your Sia account first; then move the connection to Silent Mode.' ) ;
const wasRunning = ! ! daemon . child ;
if ( wasRunning ) {
progress ( { phase : 'flush' } ) ;
try { await admin . flush ( c ) ; } catch ( e ) {
if ( ! force ) throw new HttpError ( 409 , ` Some uploads could not reach Sia yet ( ${ e . message } ). Try again later, or move anyway: they will finish on the server. ` ) ;
}
}
progress ( { phase : 'stop' } ) ;
await daemon . stop ( ) ;
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 restartLocal = ( ) => { if ( wasRunning ) { try { ensureConfig ( configFile ) ; daemon . start ( ) ; } catch { } } } ;
let files ;
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
try {
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
files = await dataFiles ( c . directory ) ;
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 ( ! files . some ( ( f ) => f . path === 's3d.db' ) ) throw new HttpError ( 409 , 'there is no s3d database to move' ) ;
await uploadData ( g , c . directory , files , progress ) ;
} catch ( e ) {
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
// Nothing was committed: the server discards what arrived, s3d runs here again.
await g . call ( 'POST' , '/hosted/v1/data/abort' ) . catch ( ( ) => { } ) ;
restartLocal ( ) ;
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
progress ( { phase : 'failed' , error : e . message } ) ;
throw e ;
}
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
progress ( { phase : 'start' } ) ;
let r = null ;
let commitError = null ;
try { r = await wrap ( ( ) => g . call ( 'POST' , '/hosted/v1/data/commit' , { body : { files } } ) ) ; } catch ( e ) { commitError = e ; }
// From here an answer can be lost in either direction, so the server's
// state decides which copy is the account.
const state = r ? 'active' : await serverState ( g ) ;
if ( state === 'active' ) {
adoptHosted ( reg , r ? . users ) ;
progress ( { phase : 'done' } ) ;
return { ok : true , registered : true , users : r ? . users || [ ] } ;
}
if ( state === 'empty' ) {
restartLocal ( ) ;
progress ( { phase : 'failed' , error : commitError ? . message } ) ;
throw commitError || new HttpError ( 502 , 'the server did not take the drives' ) ;
}
// Unknown: keep s3d stopped here until the server can be asked again.
writePrefs ( configFile , { moveUncertain : { at : Date . now ( ) , reg : { indexerUrl : reg . indexerUrl } , users : [ ] } } ) ;
progress ( { phase : 'failed' , error : 'no answer from Silent Mode' } ) ;
throw new HttpError ( 502 , 'Silent Mode stopped answering during the move. s3d stays stopped here until Pithos can check which side has your drives; open Settings again in a minute.' ) ;
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
} ) ;
// Silent Mode -> local. The server flushes and stops its s3d, the folder
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
// comes down, the local s3d starts on it, and only then is the server copy
// released. Once release is sent the local copy is never removed.
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
route ( 'POST' , '/api/hosted/move-down' , async ( req ) => {
const { force } = await readJson ( req ) ;
const g = gw ( ) ;
const progress = ( p ) => broadcast ( 'move' , { direction : 'down' , ... p } ) ;
const c = cfg ( ) ;
if ( daemon . child ) await daemon . stop ( ) ;
let archived = null ;
if ( fs . existsSync ( path . join ( c . directory , 's3d.db' ) ) ) {
archived = accounts . archiveCurrent ( c . directory , { note : 'set aside to bring drives back from Silent Mode' } ) . name ;
}
progress ( { phase : 'flush' } ) ;
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
let man ;
try { man = await wrap ( ( ) => g . call ( 'POST' , ` /hosted/v1/data/checkout ${ force ? '?force=1' : '' } ` ) ) ; } catch ( e ) {
if ( archived ) { try { accounts . restoreArchive ( c . directory , archived , { } ) ; } catch { } }
throw e ;
}
const written = [ ] ;
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
try {
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
await downloadData ( g , c . directory , man . files , progress , written ) ;
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
progress ( { phase : 'start' } ) ;
ensureConfig ( configFile ) ;
resetRegistration ( ) ;
daemon . start ( ) ;
const until = Date . now ( ) + 30_000 ;
while ( daemon . state === 'starting' && Date . now ( ) < until ) await new Promise ( ( r ) => setTimeout ( r , 200 ) ) ;
if ( daemon . state !== 'running' ) throw new Error ( 's3d did not start on the moved folder' ) ;
const reg = await registration ( ) ;
if ( ! reg ? . registered ) throw new Error ( 'the moved folder has no Sia connection' ) ;
} catch ( e ) {
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
// Only what this move wrote is removed; the server keeps its copy.
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 daemon . stop ( ) . catch ( ( ) => { } ) ;
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
for ( const f of written ) fs . rmSync ( f , { force : 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 ( archived ) { try { accounts . restoreArchive ( c . directory , archived , { } ) ; } catch { } }
writePrefs ( configFile , { mode : 'hosted' } ) ;
await g . call ( 'POST' , '/hosted/v1/data/cancel-checkout' ) . catch ( ( ) => { } ) ;
progress ( { phase : 'failed' , error : e . message } ) ;
throw new HttpError ( 502 , ` Moving back failed and nothing changed: ${ e . message } ` ) ;
}
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 drives now run here. Whatever happens to the release, this copy stays.
writePrefs ( configFile , { mode : 'local' , autostart : true , encryptedData : true , pendingRelease : true } ) ;
keyCache . clear ( ) ;
dropRules ( ) ;
try {
await g . call ( 'POST' , '/hosted/v1/data/release' ) ;
writePrefs ( configFile , { pendingRelease : null } ) ;
} catch { /* settleMoves() retries; the server copy stays parked meanwhile */ }
progress ( { phase : 'done' } ) ;
return { ok : 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
} ) ;
// Rules: public folders, shared keys, links. Files they cover become
// readable copies; removing a rule encrypts them again.
route ( 'GET' , '/api/hosted/rules' , async ( ) => rules ( ) ) ;
route ( 'POST' , '/api/hosted/rules' , async ( req ) => {
const body = await readJson ( req ) ;
const client = await s3For ( body . user ) ;
if ( ! client ) throw new HttpError ( 409 , 'not on Silent Mode' ) ;
const rule = await wrap ( ( ) => gw ( ) . call ( 'POST' , '/hosted/v1/rules' , { body } ) ) ;
dropRules ( ) ;
await client . convert ( rule . bucket , rule . prefix , { toPlain : true , onProgress : ( p ) => broadcast ( 'convert' , { rule : rule . id , ... p } ) } ) ;
return rule ;
} ) ;
route ( 'DELETE' , '/api/hosted/rules' , async ( req , url ) => {
const id = url . searchParams . get ( 'id' ) ;
const user = url . searchParams . get ( 'user' ) ;
const rule = ( await rules ( ) ) . find ( ( r ) => r . id === id ) ;
if ( ! rule ) throw new HttpError ( 404 , 'no such rule' ) ;
await wrap ( ( ) => gw ( ) . call ( 'DELETE' , ` /hosted/v1/rules?id= ${ encodeURIComponent ( id ) } ` ) ) ;
dropRules ( ) ;
const remaining = await rules ( ) ;
const client = await s3For ( user ) ;
if ( client ) {
await client . convert ( rule . bucket , rule . prefix , {
toPlain : false ,
skip : ( key ) => plainCovered ( remaining , rule . bucket , key ) ,
onProgress : ( p ) => broadcast ( 'convert' , { rule : rule . id , ... p } ) ,
} ) ;
}
return { ok : true } ;
} ) ;
2026-10-04 01:42:55 +02:00
// Files from before hosting (or from another S3 client) are stored as they
// came. This encrypts every one that nothing shares.
route ( 'POST' , '/api/hosted/encrypt' , async ( req ) => {
const { user , bucket } = await readJson ( req ) ;
const client = await s3For ( user ) ;
if ( ! isHosted ( ) || ! client ) throw new HttpError ( 409 , 'not on Silent Mode' ) ;
const current = await rules ( ) ;
return client . convert ( bucket , '' , {
toPlain : false ,
skip : ( key ) => plainCovered ( current , bucket , key ) ,
onProgress : ( p ) => broadcast ( 'convert' , 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
// The whole-drive "Access" dialog, answered with a public rule on "".
async function drivePolicy ( bucket ) {
const r = ( await rules ( ) ) . find ( ( x ) => x . type === 'public' && x . bucket === bucket && x . prefix === '' ) ;
return { policy : null , public : r ? r . rights : 'private' , hosted : true } ;
}
async function setDrivePolicy ( user , bucket , mode ) {
const current = ( await rules ( ) ) . filter ( ( x ) => x . type === 'public' && x . bucket === bucket && x . prefix === '' ) ;
const client = await s3For ( user ) ;
for ( const r of current ) await wrap ( ( ) => gw ( ) . call ( 'DELETE' , ` /hosted/v1/rules?id= ${ encodeURIComponent ( r . id ) } ` ) ) ;
dropRules ( ) ;
if ( mode === 'private' ) {
const remaining = await rules ( ) ;
await client . convert ( bucket , '' , { toPlain : false , skip : ( key ) => plainCovered ( remaining , bucket , key ) , onProgress : ( p ) => broadcast ( 'convert' , p ) } ) ;
return ;
}
await wrap ( ( ) => gw ( ) . call ( 'POST' , '/hosted/v1/rules' , { body : { type : 'public' , bucket , prefix : '' , rights : mode } } ) ) ;
dropRules ( ) ;
await client . convert ( bucket , '' , { toPlain : true , onProgress : ( p ) => broadcast ( 'convert' , p ) } ) ;
}
// "Share link" for one file: a link rule, and the file stored readable.
async function shareLink ( user , bucket , key , seconds ) {
const client = await s3For ( user ) ;
const expires = Date . now ( ) + Math . min ( Math . max ( 60 , seconds | 0 ) , 30 * 86400 ) * 1000 ;
const rule = await wrap ( ( ) => gw ( ) . call ( 'POST' , '/hosted/v1/rules' , { body : { type : 'link' , bucket , prefix : key , expires } } ) ) ;
dropRules ( ) ;
await client . convert ( bucket , key , { toPlain : true , skip : ( k ) => k !== key } ) ;
const links = ( prefs ( ) . hostedLinks || [ ] ) . filter ( ( l ) => l . expires > Date . now ( ) ) ;
links . push ( { user , bucket , key , expires } ) ;
writePrefs ( configFile , { hostedLinks : links } ) ;
return ` ${ hp ( ) . url } /l/ ${ rule . token } ` ;
}
// Files shared by a link that has run out go back to being encrypted.
async function sweepLinks ( ) {
if ( ! isHosted ( ) ) return ;
const all = prefs ( ) . hostedLinks || [ ] ;
const expired = all . filter ( ( l ) => l . expires <= Date . now ( ) ) ;
if ( ! expired . length ) return ;
dropRules ( ) ;
const remaining = await rules ( ) ;
for ( const l of expired ) {
if ( plainCovered ( remaining , l . bucket , l . key ) ) continue ;
const client = await s3For ( l . user ) . catch ( ( ) => null ) ;
await client ? . convert ( l . bucket , l . key , { toPlain : false , skip : ( k ) => k !== l . key } ) . catch ( ( ) => { } ) ;
}
writePrefs ( configFile , { hostedLinks : all . filter ( ( l ) => l . expires > Date . now ( ) ) } ) ;
}
const sweeper = setInterval ( ( ) => { sweepLinks ( ) . catch ( ( ) => { } ) ; } , 10 * 60_000 ) ;
sweeper . unref ? . ( ) ;
// Key file export/import for hosts without a vault.
route ( 'GET' , '/api/hosted/secret' , ( ) => {
if ( ! secrets . exportSecret ) throw new HttpError ( 400 , 'your keys come from the Theseus vault; back up the vault instead' ) ;
return secrets . exportSecret ( ) ;
} ) ;
route ( 'POST' , '/api/hosted/secret' , async ( req ) => {
if ( ! secrets . importSecret ) throw new HttpError ( 400 , 'your keys come from the Theseus vault' ) ;
if ( isHosted ( ) || hp ( ) ? . token ) throw new HttpError ( 409 , 'sign out of Silent Mode before replacing your key' ) ;
secrets . importSecret ( await readJson ( req ) ) ;
return { ok : true } ;
} ) ;
return {
isHosted , intercept , s3For , hostedStatus , drivePolicy , setDrivePolicy , shareLink ,
2026-10-04 03:32:04 +02:00
userNames : async ( ) => ( await wrap ( ( ) => gw ( ) . call ( 'GET' , '/api/users' ) ) ) . map ( ( u ) => u . name ) ,
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
remoteDaemon : ( ) => remote ? . daemon || { state : 'stopped' } ,
close ( ) { clearInterval ( poller ) ; clearInterval ( sweeper ) ; } ,
} ;
}
async function readJson ( req ) {
const parts = [ ] ;
for await ( const c of req ) parts . push ( c ) ;
const text = Buffer . concat ( parts ) . toString ( 'utf8' ) ;
if ( ! text ) return { } ;
try { return JSON . parse ( text ) ; } catch { return { } ; }
}