@_xtribe/cli 1.0.0-beta.19 → 1.0.0-beta.25
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.
- package/README.md +30 -7
- package/install-tribe.js +675 -127
- package/package.json +3 -5
- package/lima-guestagent.Linux-aarch64.gz +0 -0
- package/tribe +0 -0
- package/tribe-deployment.yaml +0 -455
package/install-tribe.js
CHANGED
|
@@ -8,9 +8,13 @@ const { execSync, spawn } = require('child_process');
|
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const ora = require('ora');
|
|
10
10
|
const which = require('which');
|
|
11
|
+
const fetch = require('node-fetch');
|
|
12
|
+
const crypto = require('crypto'); // Added for checksum verification
|
|
11
13
|
|
|
12
14
|
const platform = os.platform();
|
|
13
|
-
|
|
15
|
+
// Map Node.js arch to standard naming (x64 -> amd64, etc.)
|
|
16
|
+
const nodeArch = os.arch();
|
|
17
|
+
const arch = nodeArch === 'x64' ? 'amd64' : (nodeArch === 'arm64' || nodeArch === 'aarch64' ? 'arm64' : nodeArch);
|
|
14
18
|
const homeDir = os.homedir();
|
|
15
19
|
const binDir = path.join(homeDir, 'bin');
|
|
16
20
|
const tribeDir = path.join(homeDir, '.tribe');
|
|
@@ -83,8 +87,25 @@ async function downloadFile(url, dest) {
|
|
|
83
87
|
return;
|
|
84
88
|
}
|
|
85
89
|
response.pipe(file);
|
|
86
|
-
file.on('finish', () => {
|
|
90
|
+
file.on('finish', async () => {
|
|
87
91
|
file.close();
|
|
92
|
+
|
|
93
|
+
// Check if this might be a pointer file
|
|
94
|
+
const stats = fs.statSync(dest);
|
|
95
|
+
if (stats.size < 100) {
|
|
96
|
+
const content = fs.readFileSync(dest, 'utf8').trim();
|
|
97
|
+
// Check if content looks like a path (e.g., "main-121/tribe-linux-amd64")
|
|
98
|
+
if (content.match(/^[\w\-\/]+$/) && content.includes('/')) {
|
|
99
|
+
console.log(`📎 Following pointer to: ${content}`);
|
|
100
|
+
const baseUrl = url.substring(0, url.lastIndexOf('/'));
|
|
101
|
+
const actualBinaryUrl = `${baseUrl}/${content}`;
|
|
102
|
+
|
|
103
|
+
// Download the actual binary
|
|
104
|
+
fs.unlinkSync(dest); // Remove pointer file
|
|
105
|
+
await downloadFile(actualBinaryUrl, dest);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
resolve();
|
|
89
110
|
});
|
|
90
111
|
}).on('error', reject);
|
|
@@ -93,6 +114,96 @@ async function downloadFile(url, dest) {
|
|
|
93
114
|
|
|
94
115
|
// Docker installation removed - Colima provides Docker runtime
|
|
95
116
|
|
|
117
|
+
async function installK3s() {
|
|
118
|
+
const spinner = ora('Installing K3s (lightweight Kubernetes)...').start();
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// Check if K3s is already installed
|
|
122
|
+
try {
|
|
123
|
+
execSync('which k3s', { stdio: 'ignore' });
|
|
124
|
+
spinner.succeed('K3s already installed');
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
// Not installed, continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check if we have permission to install
|
|
131
|
+
const needsSudo = process.getuid && process.getuid() !== 0;
|
|
132
|
+
|
|
133
|
+
// Check if we're in a container or CI environment
|
|
134
|
+
const isContainer = process.env.container === 'docker' ||
|
|
135
|
+
fs.existsSync('/.dockerenv') ||
|
|
136
|
+
!fs.existsSync('/run/systemd/system') ||
|
|
137
|
+
process.env.CI === 'true';
|
|
138
|
+
|
|
139
|
+
if (isContainer) {
|
|
140
|
+
spinner.warn('Running in container/CI - K3s installation skipped');
|
|
141
|
+
log.info('K3s requires systemd and privileged access');
|
|
142
|
+
log.info('For containers, consider using KIND or external cluster');
|
|
143
|
+
return true; // Don't fail in containers
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Download and install K3s
|
|
147
|
+
spinner.text = 'Downloading K3s installer...';
|
|
148
|
+
|
|
149
|
+
const installCommand = needsSudo ?
|
|
150
|
+
'curl -sfL https://get.k3s.io | sudo sh -s - --write-kubeconfig-mode 644' :
|
|
151
|
+
'curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644';
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
execSync(installCommand, {
|
|
155
|
+
stdio: 'pipe',
|
|
156
|
+
env: { ...process.env, INSTALL_K3S_SKIP_START: 'false' }
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
spinner.succeed('K3s installed successfully');
|
|
160
|
+
|
|
161
|
+
// Wait for K3s to be ready
|
|
162
|
+
spinner.text = 'Waiting for K3s to start...';
|
|
163
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
164
|
+
|
|
165
|
+
// Configure kubectl to use K3s
|
|
166
|
+
const k3sConfig = '/etc/rancher/k3s/k3s.yaml';
|
|
167
|
+
const userConfig = path.join(os.homedir(), '.kube', 'config');
|
|
168
|
+
|
|
169
|
+
if (fs.existsSync(k3sConfig)) {
|
|
170
|
+
fs.mkdirSync(path.dirname(userConfig), { recursive: true });
|
|
171
|
+
|
|
172
|
+
if (needsSudo) {
|
|
173
|
+
execSync(`sudo cp ${k3sConfig} ${userConfig} && sudo chown $(id -u):$(id -g) ${userConfig}`, { stdio: 'ignore' });
|
|
174
|
+
} else {
|
|
175
|
+
fs.copyFileSync(k3sConfig, userConfig);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Update server address to use localhost
|
|
179
|
+
let configContent = fs.readFileSync(userConfig, 'utf8');
|
|
180
|
+
configContent = configContent.replace(/server: https:\/\/127\.0\.0\.1:6443/, 'server: https://localhost:6443');
|
|
181
|
+
fs.writeFileSync(userConfig, configContent);
|
|
182
|
+
|
|
183
|
+
spinner.succeed('K3s configured');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error.message.includes('permission denied') || error.message.includes('sudo')) {
|
|
189
|
+
spinner.fail('K3s installation requires sudo permission');
|
|
190
|
+
log.info('Please run the installer with sudo or install K3s manually:');
|
|
191
|
+
log.info(' curl -sfL https://get.k3s.io | sudo sh -');
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
} catch (error) {
|
|
198
|
+
spinner.fail('K3s installation failed');
|
|
199
|
+
log.error(error.message);
|
|
200
|
+
log.info('Manual K3s installation:');
|
|
201
|
+
log.info(' curl -sfL https://get.k3s.io | sudo sh -');
|
|
202
|
+
log.info(' sudo systemctl enable --now k3s');
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
96
207
|
async function installColima() {
|
|
97
208
|
if (platform !== 'darwin') {
|
|
98
209
|
log.info('Colima is only needed on macOS - skipping');
|
|
@@ -149,6 +260,23 @@ async function installColima() {
|
|
|
149
260
|
// KIND installation removed - using Colima's built-in Kubernetes
|
|
150
261
|
|
|
151
262
|
async function installKubectl() {
|
|
263
|
+
// Linux with K3s already has kubectl
|
|
264
|
+
if (platform === 'linux') {
|
|
265
|
+
try {
|
|
266
|
+
execSync('k3s kubectl version --client', { stdio: 'ignore' });
|
|
267
|
+
log.success('kubectl available via k3s');
|
|
268
|
+
// Create a symlink for convenience
|
|
269
|
+
try {
|
|
270
|
+
execSync('sudo ln -sf /usr/local/bin/k3s /usr/local/bin/kubectl', { stdio: 'ignore' });
|
|
271
|
+
} catch {
|
|
272
|
+
// Link might already exist
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
} catch {
|
|
276
|
+
// Continue with regular kubectl installation
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
152
280
|
if (await checkCommand('kubectl')) {
|
|
153
281
|
log.success('kubectl already installed');
|
|
154
282
|
return true;
|
|
@@ -174,141 +302,286 @@ async function installKubectl() {
|
|
|
174
302
|
}
|
|
175
303
|
}
|
|
176
304
|
|
|
177
|
-
async function
|
|
305
|
+
async function installTribeCLI() {
|
|
178
306
|
const spinner = ora('Installing TRIBE CLI...').start();
|
|
179
307
|
|
|
180
308
|
try {
|
|
181
309
|
const tribeDest = path.join(binDir, 'tribe');
|
|
182
310
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
if (fs.existsSync(bundledBinary)) {
|
|
186
|
-
fs.copyFileSync(bundledBinary, tribeDest);
|
|
187
|
-
fs.chmodSync(tribeDest, '755');
|
|
188
|
-
spinner.succeed('TRIBE CLI installed from bundled binary');
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Check if we have local source
|
|
193
|
-
const sourceFile = path.join(__dirname, 'cluster-cli.go');
|
|
194
|
-
if (fs.existsSync(sourceFile)) {
|
|
195
|
-
// Build from source
|
|
311
|
+
// Skip if already installed and working
|
|
312
|
+
if (fs.existsSync(tribeDest)) {
|
|
196
313
|
try {
|
|
197
|
-
execSync(
|
|
198
|
-
|
|
199
|
-
fs.copyFileSync(path.join(__dirname, 'tribe'), tribeDest);
|
|
200
|
-
fs.chmodSync(tribeDest, '755');
|
|
201
|
-
spinner.succeed('TRIBE CLI built from source');
|
|
314
|
+
execSync(`${tribeDest} version`, { stdio: 'ignore' });
|
|
315
|
+
spinner.succeed('TRIBE CLI already installed');
|
|
202
316
|
return true;
|
|
203
317
|
} catch {
|
|
204
|
-
|
|
318
|
+
// Binary exists but doesn't work, remove it
|
|
319
|
+
fs.unlinkSync(tribeDest);
|
|
205
320
|
}
|
|
206
321
|
}
|
|
207
322
|
|
|
208
|
-
//
|
|
209
|
-
|
|
323
|
+
// Download pre-built binary from GitHub
|
|
324
|
+
// Proper architecture detection for all platforms
|
|
325
|
+
let arch;
|
|
326
|
+
if (process.arch === 'x64' || process.arch === 'x86_64') {
|
|
327
|
+
arch = 'amd64';
|
|
328
|
+
} else if (process.arch === 'arm64' || process.arch === 'aarch64') {
|
|
329
|
+
arch = 'arm64';
|
|
330
|
+
} else {
|
|
331
|
+
arch = process.arch; // fallback
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const platform = os.platform();
|
|
335
|
+
|
|
336
|
+
// Multiple sources for reliability
|
|
337
|
+
// For testing, use a direct URL to our releases folder
|
|
338
|
+
const isTestEnv = process.env.TRIBE_TEST_BINARY_URL;
|
|
339
|
+
const githubRepo = process.env.TRIBE_INSTALLER_REPO || 'TRIBE-INC/releases';
|
|
210
340
|
|
|
341
|
+
let downloadUrl = '';
|
|
342
|
+
|
|
211
343
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
344
|
+
spinner.text = 'Fetching latest release information from GitHub...';
|
|
345
|
+
const response = await fetch(`https://api.github.com/repos/${githubRepo}/releases`);
|
|
346
|
+
if (!response.ok) {
|
|
347
|
+
throw new Error(`GitHub API returned ${response.status}`);
|
|
348
|
+
}
|
|
349
|
+
const releases = await response.json();
|
|
350
|
+
if (!releases || releases.length === 0) {
|
|
351
|
+
throw new Error('No releases found');
|
|
352
|
+
}
|
|
353
|
+
const latestRelease = releases[0];
|
|
354
|
+
const assetName = `tribe-${platform}-${arch}`;
|
|
355
|
+
const asset = latestRelease.assets.find(a => a.name === assetName);
|
|
356
|
+
|
|
357
|
+
if (!asset) {
|
|
358
|
+
throw new Error(`No binary found for ${platform}-${arch} in release ${latestRelease.tag_name}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
downloadUrl = asset.browser_download_url;
|
|
362
|
+
spinner.text = `Found latest release: ${latestRelease.tag_name}`;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
spinner.fail(`Failed to get release info: ${error.message}`);
|
|
365
|
+
// Fallback to other methods if the API fails
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const sources = isTestEnv ? [isTestEnv] : [
|
|
369
|
+
downloadUrl,
|
|
370
|
+
// Public releases repository (primary source)
|
|
371
|
+
`https://raw.githubusercontent.com/${githubRepo}/main/cli/latest-tribe-${platform}-${arch}`,
|
|
372
|
+
// GitHub releases (fallback)
|
|
373
|
+
`https://github.com/${githubRepo}/releases/latest/download/tribe-${platform}-${arch}`,
|
|
374
|
+
// Try jsDelivr CDN (for better availability)
|
|
375
|
+
`https://cdn.jsdelivr.net/gh/${githubRepo}@main/cli/latest-tribe-${platform}-${arch}`,
|
|
376
|
+
].filter(Boolean); // Filter out empty downloadUrl if API failed
|
|
377
|
+
|
|
378
|
+
let downloaded = false;
|
|
379
|
+
let lastError;
|
|
380
|
+
|
|
381
|
+
for (const url of sources) {
|
|
382
|
+
try {
|
|
383
|
+
let downloadUrl = url;
|
|
384
|
+
|
|
385
|
+
// Check if this is the GitHub API endpoint
|
|
386
|
+
if (url.includes('/api.github.com/')) {
|
|
387
|
+
spinner.text = 'Fetching latest release information from GitHub...';
|
|
388
|
+
|
|
389
|
+
// Fetch release data
|
|
390
|
+
const response = await fetch(url);
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
throw new Error(`GitHub API returned ${response.status}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const releaseData = await response.json();
|
|
396
|
+
|
|
397
|
+
// Handle both single release and array of releases
|
|
398
|
+
const release = Array.isArray(releaseData) ? releaseData[0] : releaseData;
|
|
399
|
+
|
|
400
|
+
if (!release || !release.assets) {
|
|
401
|
+
throw new Error('No release found');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const assetName = `tribe-${platform}-${arch}`;
|
|
405
|
+
const asset = release.assets.find(a => a.name === assetName);
|
|
406
|
+
|
|
407
|
+
if (!asset) {
|
|
408
|
+
throw new Error(`No binary found for ${platform}-${arch}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
downloadUrl = asset.browser_download_url;
|
|
412
|
+
spinner.text = `Found ${release.prerelease ? 'pre-release' : 'release'}: ${release.tag_name}`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
spinner.text = `Downloading TRIBE CLI from ${new URL(downloadUrl).hostname}...`;
|
|
416
|
+
await downloadFile(downloadUrl, tribeDest);
|
|
417
|
+
|
|
418
|
+
// Check if file is not empty
|
|
419
|
+
const stats = fs.statSync(tribeDest);
|
|
420
|
+
if (stats.size === 0) {
|
|
421
|
+
throw new Error('Downloaded file is empty');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fs.chmodSync(tribeDest, '755');
|
|
425
|
+
|
|
426
|
+
// Verify the binary works
|
|
427
|
+
try {
|
|
428
|
+
execSync(`${tribeDest} version`, { stdio: 'ignore' });
|
|
429
|
+
} catch (versionError) {
|
|
430
|
+
// If version fails, still consider it downloaded if file is valid
|
|
431
|
+
log.warning('Binary downloaded but version check failed - may need different architecture');
|
|
231
432
|
}
|
|
433
|
+
|
|
434
|
+
spinner.succeed('TRIBE CLI installed successfully');
|
|
435
|
+
downloaded = true;
|
|
436
|
+
break;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
lastError = error;
|
|
439
|
+
// Clean up failed download
|
|
440
|
+
if (fs.existsSync(tribeDest)) {
|
|
441
|
+
fs.unlinkSync(tribeDest);
|
|
442
|
+
}
|
|
443
|
+
log.warning(`Failed: ${error.message}. Trying next source...`);
|
|
232
444
|
}
|
|
445
|
+
}
|
|
233
446
|
|
|
234
|
-
|
|
447
|
+
if (!downloaded) {
|
|
448
|
+
throw new Error(`Failed to download TRIBE CLI. Please check your internet connection and try again.
|
|
449
|
+
|
|
450
|
+
If this persists, the binaries may not be available for your platform (${platform}-${arch}).
|
|
451
|
+
Please visit https://tribecode.ai/support for assistance.`);
|
|
235
452
|
}
|
|
453
|
+
|
|
454
|
+
return true;
|
|
236
455
|
} catch (error) {
|
|
237
456
|
spinner.fail(`TRIBE CLI installation failed: ${error.message}`);
|
|
457
|
+
|
|
458
|
+
// Show helpful support information
|
|
459
|
+
console.log('');
|
|
460
|
+
console.log(chalk.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
461
|
+
console.log(chalk.yellow('Need help? Visit:'));
|
|
462
|
+
console.log(chalk.cyan.bold(' https://tribecode.ai/support'));
|
|
463
|
+
console.log('');
|
|
464
|
+
console.log(chalk.gray('You can also:'));
|
|
465
|
+
console.log(chalk.gray(' • Check system requirements'));
|
|
466
|
+
console.log(chalk.gray(' • View troubleshooting guides'));
|
|
467
|
+
console.log(chalk.gray(' • Contact our support team'));
|
|
468
|
+
console.log(chalk.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
469
|
+
console.log('');
|
|
470
|
+
|
|
238
471
|
return false;
|
|
239
472
|
}
|
|
240
473
|
}
|
|
241
474
|
|
|
242
475
|
async function startContainerRuntime() {
|
|
243
|
-
|
|
244
|
-
return true; // Linux uses Docker daemon directly
|
|
245
|
-
}
|
|
476
|
+
const spinner = ora('Starting container runtime...').start();
|
|
246
477
|
|
|
247
|
-
// Check if container runtime is already working
|
|
248
478
|
try {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
} catch {
|
|
253
|
-
// Try to start Colima with different approaches
|
|
254
|
-
if (await checkCommand('colima')) {
|
|
255
|
-
const spinner = ora('Starting Colima container runtime...').start();
|
|
256
|
-
|
|
479
|
+
// Platform-specific container runtime handling
|
|
480
|
+
if (platform === 'darwin') {
|
|
481
|
+
// macOS - Check if Colima is running
|
|
257
482
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
483
|
+
const colimaStatus = execSync('colima status 2>&1', { encoding: 'utf8' });
|
|
484
|
+
if (colimaStatus.includes('is running')) {
|
|
485
|
+
spinner.succeed('Colima is already running');
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
} catch {
|
|
489
|
+
// Colima not running, need to start it
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Try to start Colima
|
|
493
|
+
if (await checkCommand('colima')) {
|
|
494
|
+
const spinner = ora('Starting Colima container runtime...').start();
|
|
270
495
|
|
|
271
|
-
} catch (error) {
|
|
272
|
-
// Strategy 2: Start in background with Kubernetes
|
|
273
496
|
try {
|
|
274
|
-
|
|
497
|
+
// Strategy 1: Quick start with minimal resources and Kubernetes
|
|
498
|
+
spinner.text = 'Starting Colima with Kubernetes...';
|
|
275
499
|
const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
|
|
500
|
+
execSync(`${colimaPath} start --cpu 2 --memory 4 --disk 10 --kubernetes --vm-type=vz`, {
|
|
501
|
+
stdio: 'pipe',
|
|
502
|
+
timeout: 60000 // 60 second timeout for K8s
|
|
280
503
|
});
|
|
281
|
-
child.unref(); // Don't wait for completion
|
|
282
504
|
|
|
283
|
-
spinner.succeed('Colima
|
|
284
|
-
log.info('Colima is starting in the background');
|
|
285
|
-
log.info('Run "colima status" to check progress');
|
|
286
|
-
log.info('Run "docker info" to test when ready');
|
|
505
|
+
spinner.succeed('Colima started successfully');
|
|
287
506
|
return true;
|
|
288
507
|
|
|
289
|
-
} catch (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
508
|
+
} catch (error) {
|
|
509
|
+
// Strategy 2: Start in background with Kubernetes
|
|
510
|
+
try {
|
|
511
|
+
spinner.text = 'Starting Colima with Kubernetes in background...';
|
|
512
|
+
const colimaPath = await findCommand('colima') || path.join(binDir, 'colima');
|
|
513
|
+
const child = spawn(colimaPath, ['start', '--cpu', '2', '--memory', '4', '--disk', '10', '--kubernetes'], {
|
|
514
|
+
detached: true,
|
|
515
|
+
stdio: 'ignore',
|
|
516
|
+
env: { ...process.env, PATH: `${binDir}:${process.env.PATH}` }
|
|
517
|
+
});
|
|
518
|
+
child.unref(); // Don't wait for completion
|
|
519
|
+
|
|
520
|
+
spinner.succeed('Colima startup initiated (background)');
|
|
521
|
+
log.info('Colima is starting in the background');
|
|
522
|
+
log.info('Run "colima status" to check progress');
|
|
523
|
+
return true;
|
|
524
|
+
|
|
525
|
+
} catch (bgError) {
|
|
526
|
+
spinner.fail('Failed to start Colima');
|
|
527
|
+
log.warning('Container runtime startup failed (likely due to macOS system restrictions)');
|
|
528
|
+
log.info('Options to fix:');
|
|
529
|
+
log.info('');
|
|
530
|
+
log.info('Option 1 - Manual Colima start:');
|
|
531
|
+
log.info(' colima start --cpu 2 --memory 4 --kubernetes');
|
|
532
|
+
log.info(' # This downloads a 344MB disk image (may take time)');
|
|
533
|
+
log.info('');
|
|
534
|
+
log.info('Option 2 - Use Homebrew (recommended):');
|
|
535
|
+
log.info(' brew install colima');
|
|
536
|
+
log.info(' colima start --kubernetes');
|
|
537
|
+
log.info('');
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
307
540
|
}
|
|
308
541
|
}
|
|
542
|
+
} else if (platform === 'linux') {
|
|
543
|
+
// Linux - K3s should already be running
|
|
544
|
+
spinner.text = 'Checking K3s status...';
|
|
545
|
+
|
|
546
|
+
// Check if we're in a container (no systemd)
|
|
547
|
+
const isContainer = process.env.container === 'docker' || fs.existsSync('/.dockerenv') || !fs.existsSync('/run/systemd/system');
|
|
548
|
+
|
|
549
|
+
if (isContainer) {
|
|
550
|
+
spinner.info('Running in container - K3s requires systemd');
|
|
551
|
+
log.info('For containers, use KIND or connect to external cluster');
|
|
552
|
+
return true; // Don't fail
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
execSync('sudo systemctl is-active --quiet k3s', { stdio: 'ignore' });
|
|
557
|
+
spinner.succeed('K3s is running');
|
|
558
|
+
return true;
|
|
559
|
+
} catch {
|
|
560
|
+
// Try to start K3s
|
|
561
|
+
spinner.text = 'Starting K3s...';
|
|
562
|
+
try {
|
|
563
|
+
execSync('sudo systemctl start k3s', { stdio: 'ignore' });
|
|
564
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
565
|
+
spinner.succeed('K3s started');
|
|
566
|
+
return true;
|
|
567
|
+
} catch (error) {
|
|
568
|
+
spinner.warn('K3s not available - requires systemd');
|
|
569
|
+
log.info('K3s requires systemd. Install K3s manually or use KIND/external cluster');
|
|
570
|
+
return true; // Don't fail
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} else if (platform === 'win32') {
|
|
574
|
+
spinner.fail('Windows support coming soon!');
|
|
575
|
+
log.info('For Windows, please use WSL2 with Ubuntu and run this installer inside WSL2');
|
|
576
|
+
return false;
|
|
577
|
+
} else {
|
|
578
|
+
spinner.fail(`Unsupported platform: ${platform}`);
|
|
579
|
+
return false;
|
|
309
580
|
}
|
|
581
|
+
} catch (error) {
|
|
582
|
+
spinner.fail(`Container runtime error: ${error.message}`);
|
|
583
|
+
return false;
|
|
310
584
|
}
|
|
311
|
-
return false;
|
|
312
585
|
}
|
|
313
586
|
|
|
314
587
|
async function updatePath() {
|
|
@@ -332,10 +605,98 @@ async function updatePath() {
|
|
|
332
605
|
process.env.PATH = `${binDir}:${process.env.PATH}`;
|
|
333
606
|
}
|
|
334
607
|
|
|
608
|
+
async function updateShellConfig() {
|
|
609
|
+
const homeDir = os.homedir();
|
|
610
|
+
const pathExport = `export PATH="$HOME/bin:$PATH"`;
|
|
611
|
+
|
|
612
|
+
// Get current shell
|
|
613
|
+
const currentShell = process.env.SHELL || '/bin/sh';
|
|
614
|
+
const shellName = path.basename(currentShell);
|
|
615
|
+
|
|
616
|
+
// Platform-specific shell config files
|
|
617
|
+
const shellConfigs = {
|
|
618
|
+
'zsh': ['.zshrc'],
|
|
619
|
+
'bash': ['.bashrc', '.bash_profile'],
|
|
620
|
+
'sh': ['.profile'],
|
|
621
|
+
'fish': ['.config/fish/config.fish'],
|
|
622
|
+
'ksh': ['.kshrc'],
|
|
623
|
+
'tcsh': ['.tcshrc', '.cshrc']
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
// On Linux, also update .profile for login shells
|
|
627
|
+
if (platform === 'linux') {
|
|
628
|
+
shellConfigs['bash'].push('.profile');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Get config files for current shell
|
|
632
|
+
const configFiles = shellConfigs[shellName] || ['.profile'];
|
|
633
|
+
|
|
634
|
+
let updated = false;
|
|
635
|
+
|
|
636
|
+
for (const configFile of configFiles) {
|
|
637
|
+
const configPath = path.join(homeDir, configFile);
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
// Create config file if it doesn't exist
|
|
641
|
+
if (!fs.existsSync(configPath)) {
|
|
642
|
+
// Handle fish shell special syntax
|
|
643
|
+
if (configFile.includes('fish')) {
|
|
644
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
645
|
+
fs.writeFileSync(configPath, `# Created by TRIBE installer\nset -gx PATH $HOME/bin $PATH\n`);
|
|
646
|
+
} else {
|
|
647
|
+
fs.writeFileSync(configPath, `# Created by TRIBE installer\n${pathExport}\n`);
|
|
648
|
+
}
|
|
649
|
+
updated = true;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Check if PATH is already configured
|
|
654
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
655
|
+
if (content.includes('$HOME/bin') || content.includes('~/bin')) {
|
|
656
|
+
continue; // Already configured
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Add PATH export
|
|
660
|
+
if (configFile.includes('fish')) {
|
|
661
|
+
fs.appendFileSync(configPath, `\n# Added by TRIBE installer\nset -gx PATH $HOME/bin $PATH\n`);
|
|
662
|
+
} else {
|
|
663
|
+
fs.appendFileSync(configPath, `\n# Added by TRIBE installer\n${pathExport}\n`);
|
|
664
|
+
}
|
|
665
|
+
updated = true;
|
|
666
|
+
} catch (error) {
|
|
667
|
+
log.warning(`Could not update ${configFile}: ${error.message}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (updated) {
|
|
672
|
+
log.success(`Updated shell configuration`);
|
|
673
|
+
|
|
674
|
+
// Linux-specific: also update /etc/environment if we have permission
|
|
675
|
+
if (platform === 'linux' && process.getuid && process.getuid() === 0) {
|
|
676
|
+
try {
|
|
677
|
+
const envPath = '/etc/environment';
|
|
678
|
+
if (fs.existsSync(envPath)) {
|
|
679
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
680
|
+
if (!envContent.includes('/usr/local/bin')) {
|
|
681
|
+
// Update system PATH
|
|
682
|
+
const newPath = envContent.replace(/PATH="([^"]*)"/, 'PATH="/usr/local/bin:$1"');
|
|
683
|
+
fs.writeFileSync(envPath, newPath);
|
|
684
|
+
log.success('Updated system PATH in /etc/environment');
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
} catch (error) {
|
|
688
|
+
// Ignore permission errors
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
log.info(`Run "source ~/${configFiles[0]}" or restart your terminal`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
335
696
|
async function verifyInstallation() {
|
|
336
697
|
const spinner = ora('Verifying installation...').start();
|
|
337
698
|
|
|
338
|
-
const tools = ['
|
|
699
|
+
const tools = ['kubectl', 'tribe', 'colima'];
|
|
339
700
|
const results = {};
|
|
340
701
|
|
|
341
702
|
for (const tool of tools) {
|
|
@@ -344,11 +705,27 @@ async function verifyInstallation() {
|
|
|
344
705
|
|
|
345
706
|
// Special check for container runtime
|
|
346
707
|
let containerWorking = false;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
708
|
+
if (platform === 'darwin') {
|
|
709
|
+
try {
|
|
710
|
+
const colimaStatus = execSync('colima status 2>&1', { encoding: 'utf8' });
|
|
711
|
+
containerWorking = colimaStatus.includes('is running');
|
|
712
|
+
} catch {
|
|
713
|
+
containerWorking = false;
|
|
714
|
+
}
|
|
715
|
+
} else if (platform === 'linux') {
|
|
716
|
+
try {
|
|
717
|
+
// Check if k3s is running
|
|
718
|
+
execSync('systemctl is-active k3s', { stdio: 'ignore' });
|
|
719
|
+
containerWorking = true;
|
|
720
|
+
} catch {
|
|
721
|
+
// Fallback to kubectl
|
|
722
|
+
try {
|
|
723
|
+
execSync('kubectl cluster-info', { stdio: 'ignore' });
|
|
724
|
+
containerWorking = true;
|
|
725
|
+
} catch {
|
|
726
|
+
containerWorking = false;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
352
729
|
}
|
|
353
730
|
|
|
354
731
|
spinner.stop();
|
|
@@ -412,7 +789,14 @@ async function startColimaWithKubernetes() {
|
|
|
412
789
|
// Check if it has Kubernetes enabled
|
|
413
790
|
if (await checkColimaHasKubernetes()) {
|
|
414
791
|
spinner.succeed('Colima is already running with Kubernetes');
|
|
415
|
-
|
|
792
|
+
|
|
793
|
+
// Verify kubectl works
|
|
794
|
+
try {
|
|
795
|
+
execSync('kubectl cluster-info', { stdio: 'ignore' });
|
|
796
|
+
return true;
|
|
797
|
+
} catch {
|
|
798
|
+
spinner.info('Colima is running but kubectl not connected, continuing...');
|
|
799
|
+
}
|
|
416
800
|
} else {
|
|
417
801
|
spinner.text = 'Colima is running without Kubernetes. Restarting with Kubernetes...';
|
|
418
802
|
|
|
@@ -488,6 +872,32 @@ async function deployTribeCluster() {
|
|
|
488
872
|
if (fs.existsSync(sourceYaml)) {
|
|
489
873
|
fs.copyFileSync(sourceYaml, destYaml);
|
|
490
874
|
log.info('Copied deployment configuration');
|
|
875
|
+
} else {
|
|
876
|
+
// Download deployment YAML if not bundled
|
|
877
|
+
spinner.text = 'Downloading deployment configuration...';
|
|
878
|
+
const yamlSources = [
|
|
879
|
+
// Direct from GitHub (primary)
|
|
880
|
+
'https://raw.githubusercontent.com/TRIBE-INC/0zen/main/sdk/cmd/cli-gui/package/tribe-deployment.yaml',
|
|
881
|
+
// jsDelivr CDN as fallback
|
|
882
|
+
'https://cdn.jsdelivr.net/gh/TRIBE-INC/0zen@main/sdk/cmd/cli-gui/package/tribe-deployment.yaml'
|
|
883
|
+
];
|
|
884
|
+
|
|
885
|
+
let yamlDownloaded = false;
|
|
886
|
+
for (const yamlUrl of yamlSources) {
|
|
887
|
+
try {
|
|
888
|
+
await downloadFile(yamlUrl, destYaml);
|
|
889
|
+
log.info('Downloaded deployment configuration');
|
|
890
|
+
yamlDownloaded = true;
|
|
891
|
+
break;
|
|
892
|
+
} catch (downloadError) {
|
|
893
|
+
// Try next source
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (!yamlDownloaded) {
|
|
898
|
+
spinner.fail('Failed to get deployment configuration');
|
|
899
|
+
throw new Error('Could not download deployment YAML from any source');
|
|
900
|
+
}
|
|
491
901
|
}
|
|
492
902
|
|
|
493
903
|
// Run tribe start command with deployment YAML path
|
|
@@ -626,19 +1036,26 @@ async function deployTribeCluster() {
|
|
|
626
1036
|
log.info(' colima start --kubernetes');
|
|
627
1037
|
log.info('3. Then run: tribe start');
|
|
628
1038
|
} else {
|
|
629
|
-
|
|
1039
|
+
log.info('You can manually deploy later with: tribe start');
|
|
630
1040
|
}
|
|
631
1041
|
return false;
|
|
632
1042
|
}
|
|
633
1043
|
}
|
|
634
1044
|
|
|
635
1045
|
async function promptForClusterSetup() {
|
|
1046
|
+
// Check for auto-approve in CI environments
|
|
1047
|
+
if (process.env.CI === 'true' || process.env.TRIBE_AUTO_APPROVE === 'true') {
|
|
1048
|
+
console.log('\n' + chalk.bold('🚀 TRIBE Cluster Setup'));
|
|
1049
|
+
console.log(chalk.green('Auto-approving cluster setup (CI mode)'));
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
636
1053
|
// Simple prompt without external dependencies
|
|
637
1054
|
return new Promise((resolve) => {
|
|
638
1055
|
console.log('\n' + chalk.bold('🚀 TRIBE Cluster Setup'));
|
|
639
1056
|
console.log('\nWould you like to set up the TRIBE cluster now?');
|
|
640
1057
|
console.log('This will:');
|
|
641
|
-
console.log(' • Start Colima with Kubernetes');
|
|
1058
|
+
console.log(' • Start ' + (platform === 'darwin' ? 'Colima' : 'K3s') + ' with Kubernetes');
|
|
642
1059
|
console.log(' • Deploy all TRIBE services');
|
|
643
1060
|
console.log(' • Set up port forwarding');
|
|
644
1061
|
console.log('\n' + chalk.yellow('Note: This requires ~2GB disk space and may take a few minutes'));
|
|
@@ -655,20 +1072,124 @@ async function promptForClusterSetup() {
|
|
|
655
1072
|
});
|
|
656
1073
|
}
|
|
657
1074
|
|
|
1075
|
+
async function cleanupOldInstallations() {
|
|
1076
|
+
// Clean up old aliases
|
|
1077
|
+
const rcFiles = ['.zshrc', '.bashrc'];
|
|
1078
|
+
rcFiles.forEach(file => {
|
|
1079
|
+
const rcPath = path.join(homeDir, file);
|
|
1080
|
+
if (fs.existsSync(rcPath)) {
|
|
1081
|
+
try {
|
|
1082
|
+
let content = fs.readFileSync(rcPath, 'utf8');
|
|
1083
|
+
const originalContent = content;
|
|
1084
|
+
|
|
1085
|
+
// Remove old tribe-cli aliases
|
|
1086
|
+
content = content.replace(/^alias tribe=['"]tribe-cli['"]\s*$/gm, '# $& # Removed by TRIBE installer');
|
|
1087
|
+
content = content.replace(/^alias t=['"]tribe-cli['"]\s*$/gm, '# $& # Removed by TRIBE installer');
|
|
1088
|
+
|
|
1089
|
+
if (content !== originalContent) {
|
|
1090
|
+
fs.writeFileSync(rcPath, content);
|
|
1091
|
+
log.info(`Cleaned up old aliases in ${file}`);
|
|
1092
|
+
}
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
log.warning(`Could not clean up ${file}: ${error.message}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
658
1100
|
async function main() {
|
|
659
1101
|
console.log(chalk.bold.blue('\n🚀 TRIBE CLI Complete Installer\n'));
|
|
660
1102
|
|
|
1103
|
+
// Detect and display platform info
|
|
1104
|
+
const displayArch = process.arch === 'x64' ? 'amd64' : process.arch;
|
|
1105
|
+
console.log(chalk.cyan(`Platform: ${platform} (${displayArch})`));
|
|
1106
|
+
console.log(chalk.cyan(`Node: ${process.version}`));
|
|
1107
|
+
console.log('');
|
|
1108
|
+
|
|
1109
|
+
// Check for unsupported platforms early
|
|
1110
|
+
if (platform === 'win32') {
|
|
1111
|
+
console.log(chalk.yellow('\n⚠️ Windows is not yet supported'));
|
|
1112
|
+
console.log(chalk.yellow('Please use WSL2 with Ubuntu and run this installer inside WSL2'));
|
|
1113
|
+
console.log(chalk.yellow('Instructions: https://docs.microsoft.com/en-us/windows/wsl/install\n'));
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// Edge case detection
|
|
1118
|
+
// 1. Check for unsupported architectures
|
|
1119
|
+
const supportedArchitectures = ['x64', 'x86_64', 'arm64', 'aarch64'];
|
|
1120
|
+
if (!supportedArchitectures.includes(nodeArch)) {
|
|
1121
|
+
console.log(chalk.red('\n❌ Unsupported Architecture'));
|
|
1122
|
+
console.log(chalk.red(`Your system architecture (${nodeArch}) is not supported.`));
|
|
1123
|
+
console.log(chalk.yellow('TRIBE only provides binaries for x64 (AMD64) and ARM64.\n'));
|
|
1124
|
+
process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// 2. WSL2 detection
|
|
1128
|
+
if (fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop') || process.env.WSL_DISTRO_NAME) {
|
|
1129
|
+
console.log(chalk.yellow('\n⚠️ WSL2 Environment Detected'));
|
|
1130
|
+
console.log(chalk.yellow('K3s may have networking limitations in WSL2.'));
|
|
1131
|
+
console.log(chalk.yellow('Consider using Docker Desktop with WSL2 backend instead.\n'));
|
|
1132
|
+
// Don't exit, just warn
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// 3. Proxy detection
|
|
1136
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
1137
|
+
if (proxyUrl) {
|
|
1138
|
+
console.log(chalk.blue('ℹ️ Proxy detected: ' + proxyUrl));
|
|
1139
|
+
console.log(chalk.blue('Downloads will use system proxy settings.\n'));
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// 4. Check for existing Kubernetes installations
|
|
1143
|
+
const k8sTools = [];
|
|
1144
|
+
const checkTools = ['minikube', 'microk8s', 'kind', 'k3d', 'kubectl'];
|
|
1145
|
+
for (const tool of checkTools) {
|
|
1146
|
+
try {
|
|
1147
|
+
execSync(`which ${tool}`, { stdio: 'ignore' });
|
|
1148
|
+
k8sTools.push(tool);
|
|
1149
|
+
} catch {}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (k8sTools.length > 0) {
|
|
1153
|
+
console.log(chalk.blue('\n📦 Existing Kubernetes tools detected: ' + k8sTools.join(', ')));
|
|
1154
|
+
console.log(chalk.blue('You can use --skip-cluster to skip K3s installation.\n'));
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// 5. Disk space check
|
|
1158
|
+
try {
|
|
1159
|
+
const stats = fs.statfsSync(homeDir);
|
|
1160
|
+
const freeGB = (stats.bavail * stats.bsize) / (1024 ** 3);
|
|
1161
|
+
if (freeGB < 3) {
|
|
1162
|
+
console.log(chalk.yellow(`\n⚠️ Low disk space: ${freeGB.toFixed(1)}GB free`));
|
|
1163
|
+
console.log(chalk.yellow('K3s installation requires at least 3GB of free space.\n'));
|
|
1164
|
+
}
|
|
1165
|
+
} catch {}
|
|
1166
|
+
|
|
1167
|
+
// Clean up old installations
|
|
1168
|
+
await cleanupOldInstallations();
|
|
1169
|
+
|
|
661
1170
|
log.info(`Detected: ${platform} (${arch})`);
|
|
662
1171
|
log.info(`Installing to: ${binDir}`);
|
|
663
1172
|
|
|
664
1173
|
// Update PATH first
|
|
665
1174
|
await updatePath();
|
|
666
1175
|
|
|
667
|
-
|
|
668
|
-
|
|
1176
|
+
// Update shell configuration files
|
|
1177
|
+
await updateShellConfig();
|
|
1178
|
+
|
|
1179
|
+
const tasks = [];
|
|
1180
|
+
|
|
1181
|
+
// Platform-specific Kubernetes installation
|
|
1182
|
+
if (platform === 'darwin') {
|
|
1183
|
+
tasks.push({ name: 'Colima', fn: installColima });
|
|
1184
|
+
} else if (platform === 'linux') {
|
|
1185
|
+
tasks.push({ name: 'K3s', fn: installK3s });
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Common tools for all platforms
|
|
1189
|
+
tasks.push(
|
|
669
1190
|
{ name: 'kubectl', fn: installKubectl },
|
|
670
|
-
{ name: 'TRIBE CLI', fn:
|
|
671
|
-
|
|
1191
|
+
{ name: 'TRIBE CLI', fn: installTribeCLI }
|
|
1192
|
+
);
|
|
672
1193
|
|
|
673
1194
|
let allSuccess = true;
|
|
674
1195
|
|
|
@@ -688,7 +1209,7 @@ async function main() {
|
|
|
688
1209
|
// Check if cluster already exists
|
|
689
1210
|
const clusterExists = await checkClusterExists();
|
|
690
1211
|
|
|
691
|
-
if (!clusterExists) {
|
|
1212
|
+
if (!clusterExists && !global.skipCluster) {
|
|
692
1213
|
// Prompt for cluster setup
|
|
693
1214
|
const shouldSetup = await promptForClusterSetup();
|
|
694
1215
|
|
|
@@ -764,17 +1285,30 @@ async function main() {
|
|
|
764
1285
|
console.log('');
|
|
765
1286
|
// Check container runtime for guidance
|
|
766
1287
|
let runtimeWorking = false;
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1288
|
+
if (platform === 'darwin') {
|
|
1289
|
+
try {
|
|
1290
|
+
const colimaStatus = execSync('colima status 2>&1', { encoding: 'utf8' });
|
|
1291
|
+
runtimeWorking = colimaStatus.includes('is running');
|
|
1292
|
+
} catch {
|
|
1293
|
+
runtimeWorking = false;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (!runtimeWorking) {
|
|
1297
|
+
log.info('Start container runtime:');
|
|
1298
|
+
console.log(' colima start --kubernetes # Start Colima with Kubernetes');
|
|
1299
|
+
}
|
|
1300
|
+
} else if (platform === 'linux') {
|
|
1301
|
+
try {
|
|
1302
|
+
execSync('systemctl is-active k3s', { stdio: 'ignore' });
|
|
1303
|
+
runtimeWorking = true;
|
|
1304
|
+
} catch {
|
|
1305
|
+
runtimeWorking = false;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (!runtimeWorking) {
|
|
1309
|
+
log.info('Start container runtime:');
|
|
1310
|
+
console.log(' sudo systemctl start k3s # Start k3s');
|
|
1311
|
+
}
|
|
778
1312
|
}
|
|
779
1313
|
console.log('');
|
|
780
1314
|
log.info('Restart your shell or run: source ~/.zshrc');
|
|
@@ -792,13 +1326,24 @@ if (require.main === module) {
|
|
|
792
1326
|
|
|
793
1327
|
if (args.includes('--help') || args.includes('-h')) {
|
|
794
1328
|
console.log(chalk.bold.blue('TRIBE CLI Installer\n'));
|
|
795
|
-
console.log('Usage: npx
|
|
1329
|
+
console.log('Usage: npx @_xtribe/cli [options]\n');
|
|
796
1330
|
console.log('Options:');
|
|
797
1331
|
console.log(' --help, -h Show this help message');
|
|
798
1332
|
console.log(' --verify Only verify existing installation');
|
|
799
1333
|
console.log(' --dry-run Show what would be installed');
|
|
800
|
-
|
|
801
|
-
console.log('
|
|
1334
|
+
console.log(' --skip-cluster Skip cluster deployment (for testing)');
|
|
1335
|
+
console.log('\nThis installer sets up the complete TRIBE development environment:');
|
|
1336
|
+
|
|
1337
|
+
if (platform === 'darwin') {
|
|
1338
|
+
console.log('• Colima (Container runtime with Kubernetes)');
|
|
1339
|
+
} else if (platform === 'linux') {
|
|
1340
|
+
console.log('• K3s (Lightweight Kubernetes, one-line install)');
|
|
1341
|
+
} else if (platform === 'win32') {
|
|
1342
|
+
console.log('\n⚠️ Windows support coming soon!');
|
|
1343
|
+
console.log('Please use WSL2 with Ubuntu and run this installer inside WSL2');
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
802
1347
|
console.log('• kubectl (Kubernetes CLI)');
|
|
803
1348
|
console.log('• TRIBE CLI (Multi-agent orchestration)');
|
|
804
1349
|
console.log('\nAfter installation:');
|
|
@@ -826,6 +1371,9 @@ if (require.main === module) {
|
|
|
826
1371
|
process.exit(0);
|
|
827
1372
|
}
|
|
828
1373
|
|
|
1374
|
+
// Store skip-cluster flag
|
|
1375
|
+
global.skipCluster = args.includes('--skip-cluster');
|
|
1376
|
+
|
|
829
1377
|
main().catch(error => {
|
|
830
1378
|
console.error(chalk.red('Installation failed:'), error.message);
|
|
831
1379
|
process.exit(1);
|