@aion0/bastion 0.1.7 → 0.1.10

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 (36) hide show
  1. package/dist/api.d.ts +72 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +121 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/cli/commands/openclaw.d.ts.map +1 -1
  6. package/dist/cli/commands/openclaw.js +268 -63
  7. package/dist/cli/commands/openclaw.js.map +1 -1
  8. package/dist/cli/daemon.d.ts.map +1 -1
  9. package/dist/cli/daemon.js +6 -1
  10. package/dist/cli/daemon.js.map +1 -1
  11. package/dist/core/bootstrap.d.ts +30 -0
  12. package/dist/core/bootstrap.d.ts.map +1 -0
  13. package/dist/core/bootstrap.js +221 -0
  14. package/dist/core/bootstrap.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +14 -175
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugins/builtin/dlp-scanner.d.ts +1 -1
  20. package/dist/plugins/builtin/dlp-scanner.d.ts.map +1 -1
  21. package/dist/plugins/builtin/dlp-scanner.js +25 -1
  22. package/dist/plugins/builtin/dlp-scanner.js.map +1 -1
  23. package/dist/plugins/builtin/tool-guard.d.ts +1 -1
  24. package/dist/plugins/builtin/tool-guard.d.ts.map +1 -1
  25. package/dist/plugins/builtin/tool-guard.js +11 -1
  26. package/dist/plugins/builtin/tool-guard.js.map +1 -1
  27. package/dist/plugins/index.d.ts +3 -1
  28. package/dist/plugins/index.d.ts.map +1 -1
  29. package/dist/plugins/index.js +17 -1
  30. package/dist/plugins/index.js.map +1 -1
  31. package/integration/openclaw-docker/fix-issues/not-install-brew/Dockerfile +155 -0
  32. package/integration/openclaw-docker/fix-issues/not-install-brew/Dockerfile.upstream +133 -0
  33. package/integration/openclaw-docker/fix-issues/not-install-brew/docker-setup.sh +578 -0
  34. package/integration/openclaw-docker/fix-issues/not-install-brew/docker-setup.sh.upstream +570 -0
  35. package/integration/openclaw-docker/fix-issues/not-install-brew/release_notes.md +53 -0
  36. package/package.json +14 -4
package/dist/api.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ import type { BastionConfig } from './config/schema.js';
2
+ import type { ConfigManager } from './config/manager.js';
3
+ import type { PluginManager } from './plugins/index.js';
4
+ export interface BastionServerOptions {
5
+ configPath?: string;
6
+ config?: Partial<BastionConfig>;
7
+ port?: number;
8
+ host?: string;
9
+ silent?: boolean;
10
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
11
+ dbPath?: string;
12
+ skipPidFile?: boolean;
13
+ skipRetention?: boolean;
14
+ }
15
+ export interface DlpFindingEvent {
16
+ requestId: string;
17
+ patternName: string;
18
+ patternCategory: string;
19
+ action: string;
20
+ matchCount: number;
21
+ direction: string;
22
+ }
23
+ export interface ToolGuardAlertEvent {
24
+ requestId: string;
25
+ toolName: string;
26
+ ruleId: string;
27
+ ruleName: string;
28
+ severity: string;
29
+ category: string;
30
+ action: string;
31
+ matchedText: string;
32
+ }
33
+ export interface RequestCompleteEvent {
34
+ requestId: string;
35
+ statusCode: number;
36
+ latencyMs: number;
37
+ isStreaming: boolean;
38
+ provider: string;
39
+ model: string;
40
+ usage: {
41
+ inputTokens: number;
42
+ outputTokens: number;
43
+ cacheCreationTokens: number;
44
+ cacheReadTokens: number;
45
+ };
46
+ }
47
+ export interface CertificateInfo {
48
+ certPath: string;
49
+ exists: boolean;
50
+ }
51
+ export interface BastionServer {
52
+ readonly port: number;
53
+ readonly host: string;
54
+ readonly url: string;
55
+ readonly caCertPath: string;
56
+ readonly dashboardUrl: string;
57
+ readonly authToken: string | undefined;
58
+ readonly config: BastionConfig;
59
+ readonly plugins: PluginManager;
60
+ readonly configManager: ConfigManager;
61
+ on(event: 'dlp:finding', handler: (data: DlpFindingEvent) => void): BastionServer;
62
+ on(event: 'toolguard:alert', handler: (data: ToolGuardAlertEvent) => void): BastionServer;
63
+ on(event: 'request:complete', handler: (data: RequestCompleteEvent) => void): BastionServer;
64
+ on(event: 'error', handler: (error: Error) => void): BastionServer;
65
+ on(event: 'close', handler: () => void): BastionServer;
66
+ off(event: string, handler: (...args: unknown[]) => void): BastionServer;
67
+ removeAllListeners(event?: string): BastionServer;
68
+ close(): Promise<void>;
69
+ }
70
+ export declare function createServer(options?: BastionServerOptions): Promise<BastionServer>;
71
+ export declare function ensureCertificate(): Promise<CertificateInfo>;
72
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js ADDED
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createServer = createServer;
4
+ exports.ensureCertificate = ensureCertificate;
5
+ const node_events_1 = require("node:events");
6
+ const bootstrap_js_1 = require("./core/bootstrap.js");
7
+ const certs_js_1 = require("./proxy/certs.js");
8
+ const database_js_1 = require("./storage/database.js");
9
+ const logger_js_1 = require("./utils/logger.js");
10
+ const log = (0, logger_js_1.createLogger)('api');
11
+ // ── Implementation ──
12
+ class BastionServerImpl extends node_events_1.EventEmitter {
13
+ result;
14
+ closed = false;
15
+ constructor(result) {
16
+ super();
17
+ this.result = result;
18
+ this.bridgeEvents(result.eventBus);
19
+ }
20
+ get port() {
21
+ const addr = this.result.server.address();
22
+ return addr?.port ?? this.result.config.server.port;
23
+ }
24
+ get host() {
25
+ return this.result.config.server.host;
26
+ }
27
+ get url() {
28
+ return `http://${this.host}:${this.port}`;
29
+ }
30
+ get caCertPath() {
31
+ return (0, certs_js_1.getCACertPath)();
32
+ }
33
+ get dashboardUrl() {
34
+ return `${this.url}/dashboard`;
35
+ }
36
+ get authToken() {
37
+ return this.result.authToken;
38
+ }
39
+ get config() {
40
+ return this.result.configManager.get();
41
+ }
42
+ get plugins() {
43
+ return this.result.pluginManager;
44
+ }
45
+ get configManager() {
46
+ return this.result.configManager;
47
+ }
48
+ async close() {
49
+ if (this.closed)
50
+ return;
51
+ this.closed = true;
52
+ log.info('Closing BastionServer');
53
+ // Stop purge interval
54
+ if (this.result.purgeInterval) {
55
+ clearInterval(this.result.purgeInterval);
56
+ }
57
+ // Close HTTP server
58
+ await new Promise((resolve) => {
59
+ this.result.server.close(() => resolve());
60
+ });
61
+ // Run destroy callbacks (external plugins)
62
+ for (const cb of this.result.destroyCallbacks) {
63
+ try {
64
+ await cb();
65
+ }
66
+ catch (err) {
67
+ log.warn('Destroy callback failed', { error: err.message });
68
+ }
69
+ }
70
+ // Clean up event bus
71
+ this.result.eventBus.removeAllListeners();
72
+ // Close database
73
+ (0, database_js_1.closeDatabase)();
74
+ this.emit('close');
75
+ this.removeAllListeners();
76
+ log.info('BastionServer closed');
77
+ }
78
+ bridgeEvents(eventBus) {
79
+ eventBus.on('dlp:finding', (data) => {
80
+ this.emit('dlp:finding', data);
81
+ });
82
+ eventBus.on('toolguard:alert', (data) => {
83
+ this.emit('toolguard:alert', data);
84
+ });
85
+ eventBus.on('request:complete', (data) => {
86
+ this.emit('request:complete', data);
87
+ });
88
+ eventBus.on('request:blocked', (data) => {
89
+ this.emit('request:blocked', data);
90
+ });
91
+ }
92
+ }
93
+ // ── Public API ──
94
+ async function createServer(options) {
95
+ const result = await (0, bootstrap_js_1.bootstrap)({
96
+ configPath: options?.configPath,
97
+ configOverrides: options?.config,
98
+ silent: options?.silent ?? true,
99
+ logLevel: options?.logLevel,
100
+ port: options?.port,
101
+ host: options?.host,
102
+ dbPath: options?.dbPath,
103
+ skipPidFile: options?.skipPidFile ?? true,
104
+ skipRetention: options?.skipRetention,
105
+ });
106
+ const server = new BastionServerImpl(result);
107
+ log.info('BastionServer created', {
108
+ port: server.port,
109
+ host: server.host,
110
+ url: server.url,
111
+ });
112
+ return server;
113
+ }
114
+ async function ensureCertificate() {
115
+ (0, certs_js_1.ensureCA)();
116
+ return {
117
+ certPath: (0, certs_js_1.getCACertPath)(),
118
+ exists: true,
119
+ };
120
+ }
121
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"openclaw.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/openclaw.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+dzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgsB9D"}
1
+ {"version":3,"file":"openclaw.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/openclaw.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6hBzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA41B9D"}
@@ -135,6 +135,57 @@ if (proxyUrl) {
135
135
  } else { L('no proxy URL found, skipping'); }
136
136
  `;
137
137
  // ── shared helpers ───────────────────────────────────────────────────────────
138
+ /**
139
+ * 3-way merge a fix file into the upstream source using `git merge-file`.
140
+ * - srcFile: current upstream file (in the OpenClaw source directory)
141
+ * - baseFile: the upstream version our fixes were originally based on (.upstream)
142
+ * - oursFile: our modified version with fixes
143
+ * Falls back to overwrite if git is unavailable or conflicts are unresolvable.
144
+ */
145
+ function mergeFixFile(srcFile, baseFile, oursFile, label) {
146
+ if (!(0, node_fs_1.existsSync)(baseFile)) {
147
+ // No merge base — just overwrite
148
+ (0, node_fs_1.copyFileSync)(oursFile, srcFile);
149
+ console.log(` Patched ${label} (no merge base, overwrite)`);
150
+ return;
151
+ }
152
+ // Work on a temp copy so merge failures don't corrupt the original
153
+ const tmpFile = srcFile + '.bastion-merge';
154
+ (0, node_fs_1.copyFileSync)(srcFile, tmpFile);
155
+ try {
156
+ (0, node_child_process_1.execSync)(`git merge-file "${tmpFile}" "${baseFile}" "${oursFile}"`, {
157
+ stdio: 'pipe',
158
+ });
159
+ // Exit 0 — clean merge
160
+ (0, node_fs_1.copyFileSync)(tmpFile, srcFile);
161
+ console.log(` Merged ${label}`);
162
+ }
163
+ catch (err) {
164
+ const exitCode = err.status;
165
+ if (exitCode !== undefined && exitCode > 0) {
166
+ // Conflicts exist — check if they contain real conflict markers
167
+ const merged = (0, node_fs_1.readFileSync)(tmpFile, 'utf-8');
168
+ if (merged.includes('<<<<<<<')) {
169
+ console.log(` WARNING: ${label} merge has conflicts, using our version.`);
170
+ (0, node_fs_1.copyFileSync)(oursFile, srcFile);
171
+ }
172
+ else {
173
+ // Some changes but no conflict markers — safe to use
174
+ (0, node_fs_1.copyFileSync)(tmpFile, srcFile);
175
+ console.log(` Merged ${label}`);
176
+ }
177
+ }
178
+ else {
179
+ // git merge-file not available or other error
180
+ console.log(` WARNING: git merge-file failed for ${label}, using our version.`);
181
+ (0, node_fs_1.copyFileSync)(oursFile, srcFile);
182
+ }
183
+ }
184
+ finally {
185
+ if ((0, node_fs_1.existsSync)(tmpFile))
186
+ (0, node_fs_1.unlinkSync)(tmpFile);
187
+ }
188
+ }
138
189
  function generateToken() {
139
190
  return (0, node_crypto_1.randomBytes)(32).toString('hex');
140
191
  }
@@ -185,6 +236,16 @@ function envVal(name, key) {
185
236
  const match = content.match(new RegExp(`^${key}=(.*)$`, 'm'));
186
237
  return match?.[1] ?? '';
187
238
  }
239
+ /** Check if a Docker image has real Homebrew installed (not the brew-shim). */
240
+ function imageHasRealBrew(image) {
241
+ try {
242
+ const output = (0, node_child_process_1.execSync)(`docker run --rm --entrypoint sh ${image} -c "test -x /home/linuxbrew/.linuxbrew/bin/brew && echo yes || echo no"`, { stdio: 'pipe', timeout: 15_000 }).toString().trim();
243
+ return output === 'yes';
244
+ }
245
+ catch {
246
+ return false;
247
+ }
248
+ }
188
249
  function writeEnvFile(dir, vars) {
189
250
  const content = Object.entries(vars)
190
251
  .map(([k, v]) => `${k}=${v}`)
@@ -241,10 +302,13 @@ function generateComposeFile(image) {
241
302
  `;
242
303
  }
243
304
  /** Bastion proxy overlay — merged via docker compose -f ... -f ... */
244
- function generateBastionOverride(caPath) {
305
+ function generateBastionOverride(caPath, options) {
306
+ const brewMount = options?.skipBrewShim ? '' : '\n - ./brew:/usr/local/bin/brew:ro';
307
+ // Only run as root when brew-shim is needed (apt-get requires root).
308
+ // Real Homebrew refuses to run as root, so we must keep the default user (node).
309
+ const userLine = options?.skipBrewShim ? '' : '\n user: root';
245
310
  return `services:
246
- openclaw-gateway:
247
- user: root
311
+ openclaw-gateway:${userLine}
248
312
  environment:
249
313
  HOME: /home/node
250
314
  HTTPS_PROXY: "http://openclaw-gw@host.docker.internal:\${BASTION_PORT:-8420}"
@@ -253,11 +317,9 @@ function generateBastionOverride(caPath) {
253
317
  NODE_OPTIONS: "--import /opt/bastion/proxy-bootstrap.mjs"
254
318
  volumes:
255
319
  - ${caPath}:/etc/ssl/certs/bastion-ca.crt:ro
256
- - ./proxy-bootstrap.mjs:/opt/bastion/proxy-bootstrap.mjs:ro
257
- - ./brew:/usr/local/bin/brew:ro
320
+ - ./proxy-bootstrap.mjs:/opt/bastion/proxy-bootstrap.mjs:ro${brewMount}
258
321
 
259
- openclaw-cli:
260
- user: root
322
+ openclaw-cli:${userLine}
261
323
  environment:
262
324
  HOME: /home/node
263
325
  HTTPS_PROXY: "http://openclaw-cli@host.docker.internal:\${BASTION_PORT:-8420}"
@@ -266,8 +328,7 @@ function generateBastionOverride(caPath) {
266
328
  NODE_OPTIONS: "--import /opt/bastion/proxy-bootstrap.mjs"
267
329
  volumes:
268
330
  - ${caPath}:/etc/ssl/certs/bastion-ca.crt:ro
269
- - ./proxy-bootstrap.mjs:/opt/bastion/proxy-bootstrap.mjs:ro
270
- - ./brew:/usr/local/bin/brew:ro
331
+ - ./proxy-bootstrap.mjs:/opt/bastion/proxy-bootstrap.mjs:ro${brewMount}
271
332
  `;
272
333
  }
273
334
  /** Build the list of compose file args for an instance. */
@@ -539,10 +600,17 @@ function registerOpenclawCommand(program) {
539
600
  // If instance already exists, update overlay + bootstrap and start
540
601
  if ((0, node_fs_1.existsSync)(dir)) {
541
602
  console.log(`Instance '${name}' exists, starting...`);
542
- // Always update proxy bootstrap + brew shim + Bastion override (idempotent)
603
+ const instanceImage = envVal(name, 'OPENCLAW_IMAGE') || options.image;
604
+ const hasRealBrew = imageHasRealBrew(instanceImage);
605
+ if (hasRealBrew) {
606
+ console.log(' Image has real Homebrew installed — skipping brew-shim.');
607
+ }
608
+ // Always update proxy bootstrap + Bastion override (idempotent)
543
609
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'proxy-bootstrap.mjs'), PROXY_BOOTSTRAP_CONTENT, 'utf-8');
544
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'brew'), BREW_SHIM_CONTENT, { mode: 0o755 });
545
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'docker-compose.bastion.yml'), generateBastionOverride(caPath), 'utf-8');
610
+ if (!hasRealBrew) {
611
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'brew'), BREW_SHIM_CONTENT, { mode: 0o755 });
612
+ }
613
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'docker-compose.bastion.yml'), generateBastionOverride(caPath, { skipBrewShim: hasRealBrew }), 'utf-8');
546
614
  // Migrate old-style compose that had Bastion proxy baked in
547
615
  const existingCompose = (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, 'docker-compose.yml'), 'utf-8');
548
616
  if (existingCompose.includes('NODE_EXTRA_CA_CERTS')) {
@@ -563,73 +631,80 @@ function registerOpenclawCommand(program) {
563
631
  console.log(`Dashboard: http://127.0.0.1:${finalPort}/?token=${finalToken}`);
564
632
  return;
565
633
  }
566
- // Create new instance
634
+ // Create new instance — delegate to docker-setup.sh for onboarding,
635
+ // permissions, gateway config, sandbox setup, etc.
567
636
  const configDir = options.configDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), `.openclaw-${name}`);
568
637
  const workspaceDir = options.workspace ?? (0, node_path_1.join)((0, node_os_1.homedir)(), `openclaw-${name}`, 'workspace');
569
638
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
570
- (0, node_fs_1.mkdirSync)(configDir, { recursive: true });
571
- (0, node_fs_1.mkdirSync)(workspaceDir, { recursive: true });
572
- (0, node_fs_1.mkdirSync)((0, node_path_1.join)(configDir, 'devices'), { recursive: true });
573
- const token = generateToken();
574
- writeEnvFile(dir, {
575
- OPENCLAW_IMAGE: options.image,
576
- OPENCLAW_CONFIG_DIR: configDir,
577
- OPENCLAW_WORKSPACE_DIR: workspaceDir,
578
- OPENCLAW_GATEWAY_TOKEN: token,
579
- OPENCLAW_GATEWAY_PORT: String(port),
580
- OPENCLAW_BRIDGE_PORT: String(bridgePort),
581
- OPENCLAW_GATEWAY_BIND: 'lan',
582
- BASTION_PORT: String(bastionPort),
583
- CLAUDE_AI_SESSION_KEY: '',
584
- CLAUDE_WEB_SESSION_KEY: '',
585
- CLAUDE_WEB_COOKIE: '',
586
- });
639
+ // Generate docker-compose.yml (docker-setup.sh expects it in ROOT_DIR)
587
640
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'docker-compose.yml'), generateComposeFile(options.image), 'utf-8');
588
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'docker-compose.bastion.yml'), generateBastionOverride(caPath), 'utf-8');
641
+ // Copy docker-setup.sh from fix-issues
642
+ const fixesDir = (0, node_path_1.resolve)((0, node_path_1.join)(__dirname, '..', '..', '..', 'integration', 'openclaw-docker', 'fix-issues', 'not-install-brew'));
643
+ const setupScript = (0, node_path_1.join)(fixesDir, 'docker-setup.sh');
644
+ if (!(0, node_fs_1.existsSync)(setupScript)) {
645
+ console.error('docker-setup.sh not found. Bastion installation may be incomplete.');
646
+ process.exit(1);
647
+ }
648
+ (0, node_fs_1.copyFileSync)(setupScript, (0, node_path_1.join)(dir, 'docker-setup.sh'));
649
+ // Run docker-setup.sh — it handles: .env, permissions, onboarding,
650
+ // gateway config, controlUi origin, sandbox setup, etc.
651
+ console.log(`==> Running OpenClaw Docker setup (gateway: ${port}, bridge: ${bridgePort})...`);
652
+ const setupCode = await new Promise((resolve) => {
653
+ const child = (0, node_child_process_1.spawn)('bash', [(0, node_path_1.join)(dir, 'docker-setup.sh')], {
654
+ stdio: 'inherit',
655
+ env: {
656
+ ...process.env,
657
+ OPENCLAW_SKIP_BUILD: '1',
658
+ OPENCLAW_IMAGE: options.image,
659
+ OPENCLAW_CONFIG_DIR: configDir,
660
+ OPENCLAW_WORKSPACE_DIR: workspaceDir,
661
+ OPENCLAW_GATEWAY_PORT: String(port),
662
+ OPENCLAW_BRIDGE_PORT: String(bridgePort),
663
+ OPENCLAW_GATEWAY_BIND: 'lan',
664
+ // Match project name used by dc() so later commands find the containers
665
+ COMPOSE_PROJECT_NAME: `openclaw-${name}`,
666
+ },
667
+ });
668
+ child.on('close', (c) => resolve(c ?? 1));
669
+ child.on('error', (err) => {
670
+ console.error(`docker-setup.sh error: ${err.message}`);
671
+ resolve(1);
672
+ });
673
+ });
674
+ if (setupCode !== 0) {
675
+ console.error('Docker setup failed.');
676
+ process.exit(setupCode);
677
+ }
678
+ // Append Bastion-specific env vars to .env created by docker-setup.sh
679
+ const envFile = (0, node_path_1.join)(dir, '.env');
680
+ let envContent = (0, node_fs_1.existsSync)(envFile) ? (0, node_fs_1.readFileSync)(envFile, 'utf-8') : '';
681
+ if (!envContent.includes('BASTION_PORT=')) {
682
+ envContent += `BASTION_PORT=${bastionPort}\n`;
683
+ (0, node_fs_1.writeFileSync)(envFile, envContent, 'utf-8');
684
+ }
685
+ // Add Bastion proxy overlay on top
686
+ const hasRealBrew = imageHasRealBrew(options.image);
687
+ if (hasRealBrew) {
688
+ console.log(' Image has real Homebrew installed — skipping brew-shim.');
689
+ }
589
690
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'proxy-bootstrap.mjs'), PROXY_BOOTSTRAP_CONTENT, 'utf-8');
590
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'brew'), BREW_SHIM_CONTENT, { mode: 0o755 });
591
- console.log(`==> Instance '${name}' created (gateway: ${port}, bridge: ${bridgePort})`);
592
- console.log('==> Initializing gateway config...');
593
- let code = await dc(name, ['run', '--rm', 'openclaw-cli', 'config', 'set', 'gateway.mode', 'local']);
594
- if (code !== 0) {
595
- console.error('Failed to initialize gateway config');
596
- process.exit(code);
691
+ if (!hasRealBrew) {
692
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'brew'), BREW_SHIM_CONTENT, { mode: 0o755 });
597
693
  }
598
- // Patch config before first start (controlUi origin, bind=lan)
599
- fixBind(name);
600
- console.log('==> Starting gateway...');
601
- code = await dc(name, ['up', '-d', 'openclaw-gateway']);
694
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, 'docker-compose.bastion.yml'), generateBastionOverride(caPath, { skipBrewShim: hasRealBrew }), 'utf-8');
695
+ // Restart gateway with Bastion proxy overlay
696
+ console.log('==> Restarting gateway with Bastion proxy overlay...');
697
+ const code = await dc(name, ['up', '-d', '--force-recreate', 'openclaw-gateway']);
602
698
  if (code !== 0)
603
699
  process.exit(code);
604
700
  await sleep(3000);
605
- console.log('');
606
- console.log('==> Running interactive onboarding...');
607
- console.log(` When prompted for gateway token, use: ${token}`);
608
- console.log('');
609
- code = await dc(name, ['exec', 'openclaw-gateway', 'node', 'dist/index.js', 'onboard', '--no-install-daemon']);
610
- if (code !== 0) {
611
- console.error('Onboarding failed');
612
- process.exit(code);
613
- }
614
- console.log('');
615
- console.log('==> Applying post-onboard fixes...');
616
- syncToken(name);
617
- fixBind(name);
618
- console.log('==> Restarting gateway...');
619
- await dc(name, ['restart', 'openclaw-gateway']);
620
- await sleep(3000);
621
701
  approveDevices(name);
622
- await dc(name, ['restart', 'openclaw-gateway']);
623
- await sleep(2000);
624
702
  const finalToken = envVal(name, 'OPENCLAW_GATEWAY_TOKEN');
625
703
  console.log('');
626
704
  console.log(`==> Instance '${name}' is ready!`);
627
705
  console.log('');
628
706
  console.log(` Dashboard: http://127.0.0.1:${port}/?token=${finalToken}`);
629
707
  console.log('');
630
- console.log(' Open the URL above in your browser.');
631
- console.log(' If prompted to pair, refresh the page — devices are auto-approved.');
632
- console.log('');
633
708
  });
634
709
  // bastion openclaw docker run
635
710
  docker
@@ -1058,5 +1133,135 @@ function registerOpenclawCommand(program) {
1058
1133
  process.exitCode = code ?? 0;
1059
1134
  });
1060
1135
  });
1136
+ // ════════════════════════════════════════════════════════════════════════════
1137
+ // bastion openclaw build ...
1138
+ // ════════════════════════════════════════════════════════════════════════════
1139
+ openclaw
1140
+ .command('build')
1141
+ .description('Build OpenClaw Docker image from source')
1142
+ .option('--tag <tag>', 'Git branch/tag to build', 'main')
1143
+ .option('--brew', 'Install Homebrew (~500MB) for brew-based skills')
1144
+ .option('--browser', 'Install Chromium + Xvfb (~300MB) for browser automation')
1145
+ .option('--docker-cli', 'Install Docker CLI (~50MB) for sandbox container management')
1146
+ .option('--apt-packages <packages>', 'Extra apt packages to install (comma-separated)')
1147
+ .option('--image <name>', 'Docker image name', DEFAULT_IMAGE)
1148
+ .option('--src <path>', 'Path to existing OpenClaw source (skip clone)')
1149
+ .action(async (options) => {
1150
+ checkDocker();
1151
+ const srcDir = options.src ? (0, node_path_1.resolve)(options.src) : (0, node_path_1.join)(OPENCLAW_DIR, 'src');
1152
+ // Clone or update source
1153
+ if (options.src) {
1154
+ if (!(0, node_fs_1.existsSync)(srcDir)) {
1155
+ console.error(`Source directory not found: ${srcDir}`);
1156
+ process.exit(1);
1157
+ }
1158
+ console.log(`==> Using existing source: ${srcDir}`);
1159
+ }
1160
+ else {
1161
+ console.log(`==> Preparing source (branch/tag: ${options.tag})...`);
1162
+ if ((0, node_fs_1.existsSync)(srcDir)) {
1163
+ console.log(' Source directory exists, fetching latest...');
1164
+ (0, node_child_process_1.execSync)(`git -C "${srcDir}" fetch --all --tags`, { stdio: 'inherit' });
1165
+ (0, node_child_process_1.execSync)(`git -C "${srcDir}" checkout "${options.tag}"`, { stdio: 'inherit' });
1166
+ try {
1167
+ (0, node_child_process_1.execSync)(`git -C "${srcDir}" pull --ff-only`, { stdio: 'pipe' });
1168
+ }
1169
+ catch { /* ignore */ }
1170
+ }
1171
+ else {
1172
+ console.log(' Cloning openclaw repository...');
1173
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(OPENCLAW_DIR), { recursive: true });
1174
+ (0, node_child_process_1.execSync)(`git clone --branch "${options.tag}" https://github.com/openclaw/openclaw.git "${srcDir}"`, { stdio: 'inherit' });
1175
+ }
1176
+ }
1177
+ // Apply Bastion fixes — merge into upstream source via 3-way merge
1178
+ const fixesDir = (0, node_path_1.resolve)((0, node_path_1.join)(__dirname, '..', '..', '..', 'integration', 'openclaw-docker', 'fix-issues', 'not-install-brew'));
1179
+ if ((0, node_fs_1.existsSync)(fixesDir)) {
1180
+ console.log('==> Applying Bastion integration fixes...');
1181
+ // Dockerfile: 3-way merge (preserves upstream changes)
1182
+ const upstreamDockerfile = (0, node_path_1.join)(srcDir, 'Dockerfile');
1183
+ const baseDockerfile = (0, node_path_1.join)(fixesDir, 'Dockerfile.upstream');
1184
+ const oursDockerfile = (0, node_path_1.join)(fixesDir, 'Dockerfile');
1185
+ if ((0, node_fs_1.existsSync)(upstreamDockerfile) && (0, node_fs_1.existsSync)(oursDockerfile)) {
1186
+ mergeFixFile(upstreamDockerfile, baseDockerfile, oursDockerfile, 'Dockerfile');
1187
+ }
1188
+ // docker-setup.sh: 3-way merge (preserves upstream changes)
1189
+ const upstreamSetup = (0, node_path_1.join)(srcDir, 'docker-setup.sh');
1190
+ const baseSetup = (0, node_path_1.join)(fixesDir, 'docker-setup.sh.upstream');
1191
+ const oursSetup = (0, node_path_1.join)(fixesDir, 'docker-setup.sh');
1192
+ if ((0, node_fs_1.existsSync)(oursSetup)) {
1193
+ if ((0, node_fs_1.existsSync)(upstreamSetup)) {
1194
+ mergeFixFile(upstreamSetup, baseSetup, oursSetup, 'docker-setup.sh');
1195
+ }
1196
+ else {
1197
+ (0, node_fs_1.copyFileSync)(oursSetup, upstreamSetup);
1198
+ console.log(' Copied docker-setup.sh (upstream has none)');
1199
+ }
1200
+ }
1201
+ }
1202
+ else {
1203
+ console.log(' WARNING: Fix files not found, building with original Dockerfile.');
1204
+ }
1205
+ // Build Docker image
1206
+ console.log(`==> Building Docker image '${options.image}'...`);
1207
+ const buildArgs = ['build'];
1208
+ const extras = [];
1209
+ if (options.brew) {
1210
+ buildArgs.push('--build-arg', 'OPENCLAW_INSTALL_BREW=1');
1211
+ extras.push('Homebrew (~500MB)');
1212
+ }
1213
+ if (options.browser) {
1214
+ buildArgs.push('--build-arg', 'OPENCLAW_INSTALL_BROWSER=1');
1215
+ extras.push('Chromium + Xvfb (~300MB)');
1216
+ }
1217
+ if (options.dockerCli) {
1218
+ buildArgs.push('--build-arg', 'OPENCLAW_INSTALL_DOCKER_CLI=1');
1219
+ extras.push('Docker CLI (~50MB)');
1220
+ }
1221
+ if (options.aptPackages) {
1222
+ buildArgs.push('--build-arg', `OPENCLAW_DOCKER_APT_PACKAGES=${options.aptPackages}`);
1223
+ extras.push(`apt: ${options.aptPackages}`);
1224
+ }
1225
+ if (extras.length > 0) {
1226
+ console.log(` Optional components: ${extras.join(', ')}`);
1227
+ }
1228
+ // Show hints for unused optional components
1229
+ const hints = [];
1230
+ if (!options.brew) {
1231
+ hints.push('--brew : Homebrew for brew-based skills (1password-cli, signal-cli, etc.)');
1232
+ }
1233
+ if (!options.browser) {
1234
+ hints.push('--browser : Chromium for browser automation');
1235
+ }
1236
+ if (!options.dockerCli) {
1237
+ hints.push('--docker-cli : Docker CLI for sandbox container management');
1238
+ }
1239
+ if (hints.length > 0) {
1240
+ console.log('');
1241
+ console.log(' TIP: Optional build flags available:');
1242
+ for (const hint of hints) {
1243
+ console.log(` ${hint}`);
1244
+ }
1245
+ console.log(` Example: bastion openclaw build --brew --browser --docker-cli`);
1246
+ console.log('');
1247
+ }
1248
+ buildArgs.push('-t', options.image, '-f', (0, node_path_1.join)(srcDir, 'Dockerfile'), srcDir);
1249
+ const code = await new Promise((resolve) => {
1250
+ const child = (0, node_child_process_1.spawn)('docker', buildArgs, { stdio: 'inherit' });
1251
+ child.on('close', (c) => resolve(c ?? 1));
1252
+ child.on('error', (err) => {
1253
+ console.error(`docker build error: ${err.message}`);
1254
+ resolve(1);
1255
+ });
1256
+ });
1257
+ if (code !== 0) {
1258
+ console.error('Docker build failed.');
1259
+ process.exit(code);
1260
+ }
1261
+ console.log(`==> Build complete: ${options.image}`);
1262
+ if (options.brew) {
1263
+ console.log(' Homebrew installed — brew-shim will NOT be mounted for instances using this image.');
1264
+ }
1265
+ });
1061
1266
  }
1062
1267
  //# sourceMappingURL=openclaw.js.map