@@ -14,6 +14,7 @@ import { html } from '@elysiajs/html'
1414// Import page components
1515import { LoginPage } from './views/LoginPage.js'
1616import { DashboardPage } from './views/DashboardPage.js'
17+ import { OnboardingPage } from './views/OnboardingPage.js'
1718
1819// Session Management
1920const sessions = new Map ( )
@@ -93,6 +94,19 @@ export const r2Backup = initialConfig => app => {
9394 let config = { ...initialConfig , ...savedConfig }
9495 let backupJob = null
9596
97+ // Helper to check if config.json exists and has required fields
98+ const hasValidConfig = ( ) => {
99+ if ( ! existsSync ( configPath ) ) return false
100+ try {
101+ const content = readFileSync ( configPath , 'utf-8' )
102+ const parsed = JSON . parse ( content )
103+ // Check minimum required fields for system to work
104+ return ! ! ( parsed . bucket && parsed . endpoint && parsed . accessKeyId && parsed . secretAccessKey && parsed . auth ?. username && parsed . auth ?. password )
105+ } catch {
106+ return false
107+ }
108+ }
109+
96110 const getS3Client = ( ) => {
97111 console . log ( 'S3 Config:' , {
98112 bucket : config . bucket ,
@@ -275,14 +289,27 @@ export const r2Backup = initialConfig => app => {
275289 return app . use ( html ( ) ) . group ( '/backup' , app => {
276290 // Authentication Middleware
277291 const authMiddleware = context => {
292+ // Skip auth entirely if no valid config (needs onboarding)
293+ if ( ! hasValidConfig ( ) ) {
294+ return
295+ }
296+
278297 if ( ! config . auth || ! config . auth . username || ! config . auth . password ) {
279298 return
280299 }
281300
282301 const path = context . path
283302
284- // Skip auth for login, logout, and static assets
285- if ( path === '/backup/login' || path === '/backup/auth/login' || path === '/backup/auth/logout' || path === '/backup/favicon.ico' || path === '/backup/logo.png' ) {
303+ // Skip auth for login, logout, onboarding, and static assets
304+ if (
305+ path === '/backup/login' ||
306+ path === '/backup/auth/login' ||
307+ path === '/backup/auth/logout' ||
308+ path === '/backup/onboarding' ||
309+ path === '/backup/api/onboarding' ||
310+ path === '/backup/favicon.ico' ||
311+ path === '/backup/logo.png'
312+ ) {
286313 return
287314 }
288315
@@ -620,8 +647,95 @@ export const r2Backup = initialConfig => app => {
620647 } )
621648 } )
622649
650+ // ONBOARDING: Setup Page
651+ . get ( '/onboarding' , ( { set } ) => {
652+ // If already configured, redirect to dashboard
653+ if ( hasValidConfig ( ) ) {
654+ set . status = 302
655+ set . headers [ 'Location' ] = '/backup'
656+ return
657+ }
658+ return OnboardingPage ( { sourceDir : config . sourceDir } )
659+ } )
660+
661+ // ONBOARDING: Save Initial Config
662+ . post (
663+ '/api/onboarding' ,
664+ async ( { body, set } ) => {
665+ // Don't allow if already configured
666+ if ( hasValidConfig ( ) ) {
667+ set . status = 403
668+ return { status : 'error' , message : 'System is already configured' }
669+ }
670+
671+ const { endpoint, bucket, prefix, accessKeyId, secretAccessKey, extensions, cronSchedule, cronEnabled, username, password } = body
672+
673+ // Parse extensions
674+ let parsedExtensions = [ ]
675+ if ( extensions ) {
676+ parsedExtensions = extensions
677+ . split ( ',' )
678+ . map ( e => e . trim ( ) )
679+ . filter ( Boolean )
680+ }
681+
682+ // Build initial config
683+ const initialConfigData = {
684+ endpoint,
685+ bucket,
686+ prefix : prefix || '' ,
687+ accessKeyId,
688+ secretAccessKey,
689+ extensions : parsedExtensions ,
690+ cronSchedule : cronSchedule || '0 0 * * *' ,
691+ cronEnabled : cronEnabled !== false ,
692+ auth : {
693+ username,
694+ password,
695+ } ,
696+ }
697+
698+ try {
699+ await writeFile ( configPath , JSON . stringify ( initialConfigData , null , 2 ) )
700+
701+ // Update runtime config
702+ config = { ...config , ...initialConfigData }
703+
704+ // Setup cron if enabled
705+ setupCron ( )
706+
707+ return { status : 'success' , message : 'Configuration saved successfully' }
708+ } catch ( e ) {
709+ console . error ( 'Failed to save onboarding config:' , e )
710+ set . status = 500
711+ return { status : 'error' , message : 'Failed to save configuration' }
712+ }
713+ } ,
714+ {
715+ body : t . Object ( {
716+ endpoint : t . String ( ) ,
717+ bucket : t . String ( ) ,
718+ prefix : t . Optional ( t . String ( ) ) ,
719+ accessKeyId : t . String ( ) ,
720+ secretAccessKey : t . String ( ) ,
721+ extensions : t . Optional ( t . String ( ) ) ,
722+ cronSchedule : t . Optional ( t . String ( ) ) ,
723+ cronEnabled : t . Optional ( t . Boolean ( ) ) ,
724+ username : t . String ( ) ,
725+ password : t . String ( ) ,
726+ } ) ,
727+ }
728+ )
729+
623730 // UI: Dashboard
624- . get ( '/' , ( ) => {
731+ . get ( '/' , ( { set } ) => {
732+ // Redirect to onboarding if no valid config
733+ if ( ! hasValidConfig ( ) ) {
734+ set . status = 302
735+ set . headers [ 'Location' ] = '/backup/onboarding'
736+ return
737+ }
738+
625739 const jobStatus = getJobStatus ( )
626740 const hasAuth = ! ! ( config . auth && config . auth . username && config . auth . password )
627741 return DashboardPage ( { config, jobStatus, hasAuth } )
0 commit comments