railiance-platform/scripts/bootstrap_coulomb_recovery_session.mjs
codex 7aa38fcacd Prepare bounded Coulomb API identity bootstrap with private restartable custody
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
2026-10-28 12:43:05 +01:00

100 lines
5.2 KiB
JavaScript

#!/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()<Date.now()+30000)throw new Error('fresh_bootstrap_session_required');
return {session_token:session.token,user_agent:session.jwt.userAgent};
});
const result=await new Promise((resolveResult,reject)=>{
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();
}