Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
100 lines
5.2 KiB
JavaScript
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();
|
|
}
|