@@ -4,7 +4,8 @@ import { readFile } from 'fs/promises';
44import { join , dirname } from 'path' ;
55import { fileURLToPath } from 'url' ;
66import { handleGet , handleHead , handlePut , handleDelete , handleOptions , handlePatch } from './handlers/resource.js' ;
7- import { handlePost , handleCreatePod } from './handlers/container.js' ;
7+ import { handlePost , handleCreatePod , createPodStructure } from './handlers/container.js' ;
8+ import * as storage from './storage/filesystem.js' ;
89import { getCorsHeaders } from './ldp/headers.js' ;
910import { authorize , handleUnauthorized } from './auth/middleware.js' ;
1011import { notificationsPlugin } from './notifications/index.js' ;
@@ -71,6 +72,9 @@ export function createServer(options = {}) {
7172 const apNostrPubkey = options . apNostrPubkey ?? null ;
7273 // Invite-only registration is OFF by default - open registration
7374 const inviteOnly = options . inviteOnly ?? false ;
75+ // Single-user mode - creates pod on startup, disables registration
76+ const singleUser = options . singleUser ?? false ;
77+ const singleUserName = options . singleUserName ?? 'me' ;
7478 // Default storage quota per pod (50MB default, 0 = unlimited)
7579 const defaultQuota = options . defaultQuota ?? 50 * 1024 * 1024 ;
7680 // WebID-TLS client certificate authentication is OFF by default
@@ -165,7 +169,7 @@ export function createServer(options = {}) {
165169
166170 // Register Identity Provider plugin if enabled
167171 if ( idpEnabled ) {
168- fastify . register ( idpPlugin , { issuer : idpIssuer , inviteOnly } ) ;
172+ fastify . register ( idpPlugin , { issuer : idpIssuer , inviteOnly, singleUser } ) ;
169173 }
170174
171175 // Register Nostr relay if enabled
@@ -447,6 +451,91 @@ export function createServer(options = {}) {
447451 fastify . options ( '/' , handleOptions ) ;
448452 fastify . post ( '/' , writeRateLimit , handlePost ) ;
449453
454+ // Single-user mode: create pod on startup if it doesn't exist
455+ if ( singleUser ) {
456+ fastify . addHook ( 'onReady' , async ( ) => {
457+ // Determine base URL for pod URIs
458+ const protocol = options . ssl ? 'https' : 'http' ;
459+ const host = options . host === '0.0.0.0' ? 'localhost' : ( options . host || 'localhost' ) ;
460+ const port = options . port || 3000 ;
461+ const baseUrl = idpIssuer ?. replace ( / \/ $ / , '' ) || `${ protocol } ://${ host } :${ port } ` ;
462+ const issuer = idpIssuer || `${ baseUrl } /` ;
463+
464+ // Root-level pod (empty or '/' name) vs named pod
465+ const isRootPod = ! singleUserName || singleUserName === '/' ;
466+ const podPath = isRootPod ? '/' : `/${ singleUserName } /` ;
467+ const podUri = isRootPod ? `${ baseUrl } /` : `${ baseUrl } /${ singleUserName } /` ;
468+ const webId = `${ podUri } profile/card#me` ;
469+ const displayName = isRootPod ? 'me' : singleUserName ;
470+
471+ // Check if pod already exists (profile/card is the indicator)
472+ const profileExists = await storage . exists ( `${ podPath } profile/card` ) ;
473+
474+ if ( ! profileExists ) {
475+ fastify . log . info ( `Creating single-user pod at ${ podUri } ...` ) ;
476+
477+ if ( isRootPod ) {
478+ // Root-level pod - create structure directly at /
479+ await createRootPodStructure ( webId , podUri , issuer , displayName ) ;
480+ } else {
481+ // Named pod at /{name}/
482+ await createPodStructure ( singleUserName , webId , podUri , issuer , defaultQuota ) ;
483+ }
484+ fastify . log . info ( `Single-user pod created at ${ podUri } ` ) ;
485+ }
486+ } ) ;
487+ }
488+
489+ /**
490+ * Create root-level pod structure (for single-user mode with pod at /)
491+ */
492+ async function createRootPodStructure ( webId , podUri , issuer , displayName ) {
493+ const { generateProfile, generatePreferences, generateTypeIndex, serialize } = await import ( './webid/profile.js' ) ;
494+ const { generateOwnerAcl, generatePrivateAcl, generateInboxAcl, generatePublicFolderAcl, serializeAcl } = await import ( './wac/parser.js' ) ;
495+
496+ // Create directories at root
497+ await storage . createContainer ( '/inbox/' ) ;
498+ await storage . createContainer ( '/public/' ) ;
499+ await storage . createContainer ( '/private/' ) ;
500+ await storage . createContainer ( '/Settings/' ) ;
501+ await storage . createContainer ( '/profile/' ) ;
502+
503+ // Generate profile
504+ const profileHtml = generateProfile ( { webId, name : displayName , podUri, issuer } ) ;
505+ await storage . write ( '/profile/card' , profileHtml ) ;
506+
507+ // Preferences and type indexes
508+ const prefs = generatePreferences ( { webId, podUri } ) ;
509+ await storage . write ( '/Settings/Preferences.ttl' , serialize ( prefs ) ) ;
510+
511+ const publicTypeIndex = generateTypeIndex ( `${ podUri } Settings/publicTypeIndex.ttl` ) ;
512+ await storage . write ( '/Settings/publicTypeIndex.ttl' , serialize ( publicTypeIndex ) ) ;
513+
514+ const privateTypeIndex = generateTypeIndex ( `${ podUri } Settings/privateTypeIndex.ttl` ) ;
515+ await storage . write ( '/Settings/privateTypeIndex.ttl' , serialize ( privateTypeIndex ) ) ;
516+
517+ // ACL files
518+ const rootAcl = generateOwnerAcl ( podUri , webId , true ) ;
519+ await storage . write ( '/.acl' , serializeAcl ( rootAcl ) ) ;
520+
521+ const privateAcl = generatePrivateAcl ( `${ podUri } private/` , webId ) ;
522+ await storage . write ( '/private/.acl' , serializeAcl ( privateAcl ) ) ;
523+
524+ const settingsAcl = generatePrivateAcl ( `${ podUri } Settings/` , webId ) ;
525+ await storage . write ( '/Settings/.acl' , serializeAcl ( settingsAcl ) ) ;
526+
527+ const inboxAcl = generateInboxAcl ( `${ podUri } inbox/` , webId ) ;
528+ await storage . write ( '/inbox/.acl' , serializeAcl ( inboxAcl ) ) ;
529+
530+ const publicAcl = generatePublicFolderAcl ( `${ podUri } public/` , webId ) ;
531+ await storage . write ( '/public/.acl' , serializeAcl ( publicAcl ) ) ;
532+
533+ const profileAcl = generatePublicFolderAcl ( `${ podUri } profile/` , webId ) ;
534+ await storage . write ( '/profile/.acl' , serializeAcl ( profileAcl ) ) ;
535+
536+ // Note: Quota not initialized for root-level pods (no user directory)
537+ }
538+
450539 return fastify ;
451540}
452541
0 commit comments