@ours.network/install 1.2.1-nightly.4 → 1.2.1-nightly.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/GATEWAY_SETUP.md +178 -0
  2. package/README.md +471 -9
  3. package/ROOTLESS_PODMAN_VALIDATION.md +210 -0
  4. package/assets/Dockerfile +14 -12
  5. package/assets/Dockerfile.gateway +5 -0
  6. package/assets/docker-compose.yaml +8 -1
  7. package/assets/fleet-settings.example.json +48 -0
  8. package/assets/gateway-entrypoint.sh +37 -0
  9. package/assets/release-lock.json +1177 -4508
  10. package/assets/release.json +37 -19
  11. package/assets/scripts/build/build-common.mjs +2 -1
  12. package/assets/scripts/build/build-sdk.mjs +6 -1
  13. package/assets/scripts/maintenance/build-context.mjs +1 -1
  14. package/assets/scripts/maintenance/daemon-owner.mjs +15 -0
  15. package/assets/scripts/maintenance/docker-layout-conversion.mjs +7 -3
  16. package/assets/scripts/maintenance/recover-notifications.mjs +26 -0
  17. package/assets/scripts/maintenance/release-graph.mjs +18 -1
  18. package/assets/scripts/maintenance/state-operation.mjs +62 -5
  19. package/assets/scripts/runtime/README.md +7 -0
  20. package/assets/scripts/runtime/client-setup.mjs +80 -6
  21. package/assets/scripts/runtime/dependency-ready.mjs +13 -0
  22. package/assets/scripts/runtime/diagnostics.mjs +72 -0
  23. package/assets/scripts/runtime/entrypoint.sh +5 -1
  24. package/assets/scripts/runtime/health-messenger.mjs +7 -6
  25. package/assets/scripts/runtime/notifications-gateway.mjs +90 -0
  26. package/assets/scripts/runtime/recover.mjs +165 -0
  27. package/assets/scripts/runtime/start-cowork.sh +5 -0
  28. package/assets/scripts/runtime/start-messenger.sh +29 -4
  29. package/assets/scripts/runtime/start-telegram.sh +5 -0
  30. package/assets/scripts/runtime/supervise.mjs +35 -0
  31. package/assets/sources.json +54 -28
  32. package/install.mjs +7 -2
  33. package/lib/boot-readiness.mjs +43 -0
  34. package/lib/build-transition.mjs +16 -0
  35. package/lib/client-cli.mjs +127 -0
  36. package/lib/container-engine.mjs +127 -0
  37. package/lib/diagnostics.mjs +1 -0
  38. package/lib/docker-conversion-runtime.mjs +3 -2
  39. package/lib/docker-layout-installation.mjs +2 -1
  40. package/lib/docker-recovery.mjs +55 -0
  41. package/lib/docker-runtime-repair.mjs +160 -0
  42. package/lib/effects.mjs +357 -133
  43. package/lib/gateway-transition.mjs +87 -0
  44. package/lib/gateway.mjs +158 -0
  45. package/lib/legacy-migration.mjs +13 -2
  46. package/lib/orchestrate-uninstall.mjs +18 -6
  47. package/lib/orchestrate.mjs +193 -43
  48. package/lib/plan.mjs +27 -11
  49. package/lib/podman-preflight.mjs +40 -0
  50. package/lib/server-onboarding.mjs +164 -16
  51. package/lib/server-ports.mjs +17 -0
  52. package/lib/setup-options.mjs +99 -28
  53. package/lib/setup.mjs +35 -17
  54. package/lib/target.mjs +24 -8
  55. package/lib/uninstall.mjs +2 -2
  56. package/lib/usage.mjs +21 -7
  57. package/lib/workspace-setup.mjs +83 -0
  58. package/package.json +4 -2
@@ -0,0 +1,83 @@
1
+ import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join,resolve } from 'node:path';
3
+ import {parseSetupArgs} from './setup-options.mjs';
4
+ /** Base64 is transport, not encryption. Broad provider credentials are never accepted. */
5
+ export function decodeWorkspacePayload(encoded,now=Date.now()) {
6
+ if(typeof encoded!=='string' || encoded.length>32768 || !/^[A-Za-z0-9_-]+$/.test(encoded.trim()))throw Error('Invalid private workspace payload');
7
+ let p;try{p=JSON.parse(Buffer.from(encoded.trim(),'base64url'));}catch{throw Error('Invalid private workspace payload');}
8
+ const allowed=['version','appOrigin','hostname','rootName','name','surname','connectorToken','invitation','serverCid','challenge'];
9
+ if(!p || Object.keys(p).some(k=>!allowed.includes(k)) || p.version!==1 || !['https://app.ours.network','https://app.ours-tunnel.com'].includes(p.appOrigin) || !/^[a-z0-9][a-z0-9-]{2,60}\.ours-tunnel\.com$/.test(p.hostname) || !/^[a-z0-9-]{2,30}@[a-z0-9-]{2,30}$/.test(p.rootName) || !/^[a-f0-9]{64}$/i.test(p.serverCid))throw Error('Invalid workspace payload boundary');
10
+ for(const key of ['name','surname','connectorToken','invitation'])if(typeof p[key]!=='string' || !p[key].trim() || p[key].length>8192 || /[\x00-\x1f\x7f]/.test(p[key]))throw Error('Invalid private workspace payload');
11
+ if(!p.challenge || !['nonce','accountId','workspaceId'].every(k=>/^[\w-]{43}$/.test(p.challenge[k])) || !Number.isFinite(p.challenge.expiresAt) || p.challenge.expiresAt<=now || p.challenge.expiresAt>now+16*60000)throw Error('Workspace challenge expired or invalid');
12
+ return p;
13
+ }
14
+ export function extractWorkspaceArgs(args){
15
+ const flags=['--setup-workspace','--setup-workspace-file','--workspace-fleet-bin','--fleet-settings'];const values={},rest=[];
16
+ for(let i=0;i<args.length;i++){const at=args[i].indexOf('='),flag=at<0?args[i]:args[i].slice(0,at);if(!flags.includes(flag)){rest.push(args[i]);continue;}if(values[flag]!==undefined)throw Error('Duplicate workspace flag');const value=at<0?args[++i]:args[i].slice(at+1);if(!value || value.startsWith('--'))throw Error('Workspace flag requires a value');values[flag]=value;}
17
+ if(values['--setup-workspace'] && values['--setup-workspace-file'])throw Error('Choose one private payload transport');
18
+ return {values,rest};
19
+ }
20
+ export async function runWorkspaceSetup(args,effects,runSetup){
21
+ const migrateAppOrigin=args.includes('--workspace-migrate-app-origin');
22
+ if(args.filter(arg=>arg==='--workspace-migrate-app-origin').length>1)throw Error('Duplicate workspace migration flag');
23
+ const {values,rest}=extractWorkspaceArgs(args.filter(arg=>arg!=='--workspace-migrate-app-origin'));let encoded;
24
+ if(values['--setup-workspace-file']){const path=resolve(values['--setup-workspace-file']),stat=lstatSync(path);if(!stat.isFile() || stat.isSymbolicLink() || stat.nlink!==1 || stat.uid!==process.getuid?.() || stat.size>32768 || (stat.mode&0o077)!==0)throw Error('Setup file must be owned and private (chmod 600)');encoded=readFileSync(path,'utf8').trim();}
25
+ else if(values['--setup-workspace']==='-'){const chunks=[];let bytes=0;for await(const chunk of process.stdin){bytes+=chunk.length;if(bytes>32768)throw Error('Private payload too large');chunks.push(chunk);}encoded=Buffer.concat(chunks).toString().trim();}
26
+ else encoded=values['--setup-workspace'];
27
+ const p=decodeWorkspacePayload(encoded),fleetBin=values['--workspace-fleet-bin'] || 'ours-fleet';
28
+ const parsed=parseSetupArgs(rest,{home:effects.home,validate:false});
29
+ if(parsed.scope==='server')throw Error('Workspace setup requires all or client scope');
30
+ if(parsed.identityName!==undefined || parsed.integrations!==undefined)throw Error('Workspace setup supplies identity and client integration selection');
31
+ // Check feature availability before changing an installation. Task acceptance uses
32
+ // an exact task-built binary; @latest cannot contain an unmerged feature.
33
+ const root=parsed.stateDir || join(effects.home,'.ours-network','workspace'),fleetSettings=values['--fleet-settings'];
34
+ // The saved gateway profile selects the actual host, including non-default server state.
35
+ // Reuse it directly: server/client reinstall would also rewrite or downgrade retained setup.
36
+ const managed=effects.readManagedClientProfile?.();
37
+ const managedPath=join(effects.home,'.ours-client','profile.json');
38
+ const existingProfile=parsed.config || effects.env?.OURS_CONFIG || (managed?managedPath:undefined);
39
+ const existing=Boolean(existingProfile);
40
+ const marker=join(effects.home,'.ours-client','workspace-setup-pending.json');
41
+ let pending;
42
+ if(existsSync(marker)){
43
+ const stat=lstatSync(marker);
44
+ if(!stat.isFile() || stat.isSymbolicLink() || stat.nlink!==1 || stat.uid!==process.getuid?.() || (stat.mode&0o077)!==0 || stat.size>4096)throw Error('Workspace retry marker must be owned and private');
45
+ pending=JSON.parse(readFileSync(marker,'utf8'));
46
+ if(pending.version!==1 || pending.accountId!==p.challenge.accountId || pending.workspaceId!==p.challenge.workspaceId || pending.appOrigin!==p.appOrigin)throw Error('Pending fresh installation belongs to another workspace; identity was not changed. Resume the original workspace, or back up the host and remove only ~/.ours-client/workspace-setup-pending.json to treat it as existing (preserving its profile)');
47
+ }
48
+ const preserveProfile=existing && !pending;
49
+ if(existing && parsed.scope && parsed.scope!=='client')throw Error('Existing workspace setup requires client scope; root identity was not changed');
50
+ if(!existing && !pending && (existsSync(join(root,'installation.json')) || existsSync(join(effects.home,'fleet.yaml'))))throw Error('Existing host requires its gateway client profile; no new root was created');
51
+ const options=[...rest];
52
+ if(!parsed.scope)options.push('--scope',existing?'client':'all');
53
+ if(existing && !parsed.config)options.push('--config',existingProfile);
54
+ if(!existing){if(!parsed.mode)options.push('--mode','docker');if(!parsed.stateDir)options.push('--state-dir',root);options.push('--identity-name',p.rootName);}
55
+ options.push('--integrations','none');
56
+ parseSetupArgs(options,{home:effects.home});
57
+ if(preserveProfile){
58
+ const version=await effects.run(fleetBin,['version','--json'],{sensitive:true});
59
+ let info;try{info=JSON.parse(version.stdout);}catch{}
60
+ if(!version.ok || !info?.capabilities?.includes('workspace.enroll.preserve-profile-v1'))throw Error('Existing-host setup requires a profile-preserving Fleet release; nothing was changed');
61
+ }
62
+ const help=await effects.run(fleetBin,['workspace-enroll','--help'],{sensitive:true});if(!help.ok)throw Error('Fleet workspace enrollment is unavailable');
63
+ const tunnel=await effects.run('cloudflared',['--version'],{sensitive:true});if(!tunnel.ok)throw Error('Tunnel connector is unavailable');
64
+ if(options.includes('--dry-run')){if(!existing)return runSetup(options,effects);return 0;}
65
+ if(!existing){
66
+ if(!pending){mkdirSync(join(effects.home,'.ours-client'),{recursive:true,mode:0o700});writeFileSync(marker,JSON.stringify({version:1,accountId:p.challenge.accountId,workspaceId:p.challenge.workspaceId,appOrigin:p.appOrigin})+'\n',{mode:0o600,flag:'wx'});}
67
+ const result=await runSetup(options,effects);if(result!==0)return result;
68
+ }
69
+ const selected=existingProfile || effects.readManagedClientProfile();if(!selected)throw Error('Workspace client profile was not installed');
70
+ const env={OURS_CONFIG:existingProfile || managedPath};
71
+ for(const key of ['OURS_PORT','OURS_STATE_DIR','OURS_API_TOKEN','OURS_DAEMON_ID','OURS_DAEMON_URL','OURS_DAEMON_CREDENTIAL_PATH'])env[key]=undefined;
72
+ const fleetHome=effects.env?.OURS_FLEET_HOME || effects.home,fleetConfig=join(fleetHome,'fleet.yaml');
73
+ if(!existsSync(fleetConfig)){
74
+ const initArgs=['init',...(fleetSettings?['--settings',fleetSettings]:[])];
75
+ const initialized=fleetSettings?await effects.run(fleetBin,initArgs,{env}):await effects.runInteractive(fleetBin,initArgs,{env});if(!initialized.ok)throw Error('Fleet configuration incomplete');
76
+ }
77
+ mkdirSync(root,{recursive:true,mode:0o700});const temporary=mkdtempSync(join(root,'.workspace-payload-'));chmodSync(temporary,0o700);
78
+ const payloadFile=join(temporary,'payload');writeFileSync(payloadFile,encoded,{mode:0o600,flag:'wx'});
79
+ try{const enrolled=await effects.run(fleetBin,['workspace-enroll','--file',payloadFile,...(preserveProfile?['--preserve-profile']:[]),...(migrateAppOrigin?['--migrate-app-origin']:[])],{env,stream:true,sensitive:true});if(!enrolled.ok)throw Error('Workspace enrollment incomplete; existing identity and profile were retained');}
80
+ finally{rmSync(temporary,{recursive:true,force:true});}
81
+ if(pending || !existing)rmSync(marker);
82
+ effects.out('Workspace proof submitted. Check verified binding/tunnel status in your account, then scan or paste the private device code.');return 0;
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/install",
3
- "version": "1.2.1-nightly.4",
3
+ "version": "1.2.1-nightly.41",
4
4
  "private": false,
5
5
  "description": "The all-in-one ours.network installer: one shared daemon, MCP, cowork, Telegram, Fleet initialization, harness plugins, Human identity, progress UI, and guided next steps.",
6
6
  "type": "module",
@@ -15,7 +15,9 @@
15
15
  "uninstall.mjs",
16
16
  "uninstall.sh",
17
17
  "README.md",
18
- "LICENSE"
18
+ "ROOTLESS_PODMAN_VALIDATION.md",
19
+ "LICENSE",
20
+ "GATEWAY_SETUP.md"
19
21
  ],
20
22
  "license": "FSL-1.1-Apache-2.0",
21
23
  "author": "Adapt Toolkit",