#!/usr/bin/env node /** One attended bootstrap; committed helper creates scoped routine API identities. */ import {createHash} from 'node:crypto'; import {execFileSync,spawn} from 'node:child_process'; import {readFileSync,mkdirSync,lstatSync} from 'node:fs'; import {homedir} from 'node:os'; import {dirname,join,resolve} from 'node:path'; import {fileURLToPath,pathToFileURL} from 'node:url'; import {parseArgs} from 'node:util'; import {createInterface} from 'node:readline/promises'; const {values}=parseArgs({options:{'playwright-module':{type:'string'},'preflight-only':{type:'boolean'}}}); const root=resolve(dirname(fileURLToPath(import.meta.url)),'..'); const factory=join(root,'scripts','application_recovery_identities.py'); const directory=join(homedir(),'.config','railiance-platform','coulomb-social-recovery'); const digest=data=>createHash('sha256').update(data).digest('hex'); let browser,reader,child; function sourcePreflight(){ const committed=execFileSync('git',['show','HEAD:scripts/application_recovery_identities.py'],{cwd:root}); if(digest(committed)!==digest(readFileSync(factory)))throw new Error('committed_factory_required'); mkdirSync(directory,{recursive:true,mode:0o700}); const info=lstatSync(directory); if(!info.isDirectory()||info.uid!==process.getuid()||(info.mode&0o077))throw new Error('private_directory_required'); const validation="import importlib.util,sys;from pathlib import Path;"+ "s=importlib.util.spec_from_file_location('recovery_bootstrap',sys.argv[1]);"+ "m=importlib.util.module_from_spec(s);s.loader.exec_module(m);m.private_directory(Path(sys.argv[2]))"; execFileSync('python3',['-c',validation,factory,directory],{stdio:['ignore','pipe','pipe']}); return digest(committed); } async function runFactory(page,retire=false){ const sourceHash=sourcePreflight(); if(new URL(page.url()).origin!=='https://console.scaleway.com')throw new Error('console_page_required'); const auth=await page.evaluate(()=>{ const names=Object.keys(localStorage).filter(n=>n.startsWith('_scw_session_console_scaleway_com_')); if(names.length!==1)throw new Error('unambiguous_session_required'); const session=JSON.parse(localStorage.getItem(names[0])); if(new Date(session.jwt.expiresAt).getTime(){ child=spawn('python3',[factory,'--execute','--directory',directory, ...(retire?['--retire-bootstrap']:[])],{stdio:['pipe','pipe','pipe']}); let output=''; child.stdout.on('data',data=>{if(output.length<65536)output+=data.toString();}); child.stderr.on('data',()=>{}); // Provider/credential diagnostics never enter chat. child.on('error',()=>reject(new Error('bootstrap_child_failed'))); child.on('close',code=>{ child=null; try {resolveResult({exit_code:code,result:JSON.parse(output.trim()),factory_source_sha256:sourceHash});} catch {reject(new Error('redacted_bootstrap_result_required'));} }); child.stdin.end(JSON.stringify(auth)); }); return result; } try{ const sourceHash=sourcePreflight(); const modulePath=resolve(values['playwright-module']||''); const pkg=JSON.parse(readFileSync(join(dirname(modulePath),'package.json'),'utf8')); if(pkg.name!=='playwright'||pkg.version!=='1.62.1')throw new Error('locked_playwright_required'); if(values['preflight-only']){ console.log(JSON.stringify({status:'preflight_passed',factory_source_sha256:sourceHash, private_directory_validated:true,locked_playwright_verified:true,provider_contacted:false})); process.exit(0); } const {chromium}=await import(pathToFileURL(modulePath).href); browser=await chromium.launch({headless:false}); const context=await browser.newContext();const page=await context.newPage(); await page.goto('https://console.scaleway.com/',{waitUntil:'domcontentloaded',timeout:60000}); reader=createInterface({input:process.stdin,output:process.stdout}); console.log('One-time bootstrap browser opened. Sign in privately; no personal state is saved.'); for(;;){ const answer=await reader.question('After Console login: Enter/run = bootstrap; finish = retire temporary key and close; stop = close. '); if(answer.trim()==='stop')break; try{ const retire=answer.trim()==='finish'; const outcome=await runFactory(page,retire); console.log(JSON.stringify(outcome)); if(outcome.exit_code===0&&retire){ try{await page.goto('https://account.scaleway.com/logout',{waitUntil:'domcontentloaded',timeout:30000});}catch{} await context.clearCookies(); break; } if(outcome.exit_code===0){ console.log('Identity stage complete. Use scoped APIs for resource/boundary proofs, then finish. Keep this window for bootstrap only.'); continue; } }catch{console.error('Bootstrap stopped safely. No credential values printed. Keep this window for review.');} console.log('No automatic retry. Review the private journal; keep this browser open until the next reviewed attempt.'); } }catch{ console.error('Bootstrap preflight/session failed; no credential values printed.');process.exitCode=1; }finally{ if(child)child.kill('SIGTERM'); if(reader)reader.close(); if(browser)await browser.close(); }