@aerocorp/cli
Version:
AeroCorp CLI 5.1.0 - Future-Proofed Enterprise Infrastructure with Live Preview, Tunneling & Advanced DevOps
325 lines (276 loc) ⢠10.5 kB
text/typescript
/**
* AeroCorp CLI 5.0.0 - WSL Integration Service
* Handles WSL-based SSH connections and Docker operations
*/
import { spawn, ChildProcess } from 'child_process';
import chalk from 'chalk';
import ora from 'ora';
import { ConfigService } from './config';
export interface SSHConfig {
host: string;
user: string;
port?: number;
keyPath?: string;
}
export interface DockerLogOptions {
container?: string;
applicationId?: string;
lines?: number;
follow?: boolean;
}
export class WSLService {
private configService: ConfigService;
private defaultSSHConfig: SSHConfig;
constructor() {
this.configService = new ConfigService();
this.defaultSSHConfig = {
host: this.configService.get('server_ip') || '128.140.35.238',
user: 'root',
port: 22,
keyPath: '~/.ssh/id_ed25519'
};
}
/**
* Check if WSL is available and properly configured
*/
async checkWSLAvailability(): Promise<boolean> {
const spinner = ora('Checking WSL availability...').start();
try {
const result = await this.executeWSLCommand('echo "WSL is available"', { timeout: 5000 });
if (result.success) {
spinner.succeed('WSL is available and configured');
return true;
} else {
spinner.fail('WSL is not properly configured');
console.log(chalk.yellow('š” Install WSL: https://docs.microsoft.com/en-us/windows/wsl/install'));
return false;
}
} catch (error) {
spinner.fail('WSL check failed');
console.error(chalk.red('ā Error:'), error.message);
return false;
}
}
/**
* Setup SSH keys for Coolify server access
*/
async setupSSHKeys(): Promise<boolean> {
const spinner = ora('Setting up SSH keys...').start();
try {
console.log(chalk.blue('\nš SSH Key Setup for Coolify Server'));
console.log(chalk.gray('This will create SSH keys and copy them to the server'));
// Check if SSH key already exists
const keyCheckResult = await this.executeWSLCommand('test -f ~/.ssh/id_ed25519');
if (!keyCheckResult.success) {
spinner.text = 'Generating SSH key...';
// Generate SSH key
const keyGenResult = await this.executeWSLCommand(
'ssh-keygen -t ed25519 -C "aerocorp-cli@aerocorp" -f ~/.ssh/id_ed25519 -N ""'
);
if (!keyGenResult.success) {
throw new Error('Failed to generate SSH key');
}
console.log(chalk.green('ā
SSH key generated'));
} else {
console.log(chalk.blue('š SSH key already exists'));
}
spinner.text = 'Copying SSH key to server...';
// Copy SSH key to server
const copyKeyResult = await this.executeWSLCommand(
`ssh-copy-id -i ~/.ssh/id_ed25519.pub ${this.defaultSSHConfig.user}@${this.defaultSSHConfig.host}`
);
if (copyKeyResult.success) {
spinner.succeed('SSH keys configured successfully');
console.log(chalk.green('ā
SSH access to Coolify server is ready'));
return true;
} else {
spinner.fail('Failed to copy SSH key to server');
console.log(chalk.yellow('š” You may need to manually copy the key or check server access'));
return false;
}
} catch (error) {
spinner.fail('SSH setup failed');
console.error(chalk.red('ā Error:'), error.message);
return false;
}
}
/**
* Test SSH connection to Coolify server
*/
async testSSHConnection(): Promise<boolean> {
const spinner = ora('Testing SSH connection...').start();
try {
const result = await this.executeWSLCommand(
`ssh -o ConnectTimeout=10 -o BatchMode=yes ${this.defaultSSHConfig.user}@${this.defaultSSHConfig.host} "echo 'SSH connection successful'"`
);
if (result.success) {
spinner.succeed('SSH connection test passed');
console.log(chalk.green(`ā
Connected to ${this.defaultSSHConfig.host}`));
return true;
} else {
spinner.fail('SSH connection test failed');
console.log(chalk.red('ā Cannot connect to Coolify server via SSH'));
console.log(chalk.yellow('š” Run: aerocorp wsl setup-ssh'));
return false;
}
} catch (error) {
spinner.fail('SSH test failed');
console.error(chalk.red('ā Error:'), error.message);
return false;
}
}
/**
* Get Docker logs via SSH
*/
async getDockerLogs(options: DockerLogOptions): Promise<void> {
const spinner = ora('Fetching Docker logs via SSH...').start();
try {
let dockerCommand: string;
if (options.applicationId) {
// Find container by Coolify application ID
dockerCommand = `docker logs ${options.follow ? '-f' : ''} --tail ${options.lines || 100} $(docker ps --filter "label=coolify.applicationId=${options.applicationId}" --format "{{.ID}}" | head -1)`;
} else if (options.container) {
// Use specific container name/ID
dockerCommand = `docker logs ${options.follow ? '-f' : ''} --tail ${options.lines || 100} ${options.container}`;
} else {
throw new Error('Either applicationId or container must be specified');
}
spinner.stop();
console.log(chalk.cyan(`\nš Docker Logs ${options.follow ? '(following)' : ''}:`));
console.log(chalk.gray('ā'.repeat(80)));
// Execute SSH command with live output
const sshProcess = spawn('wsl', [
'ssh',
`${this.defaultSSHConfig.user}@${this.defaultSSHConfig.host}`,
dockerCommand
], {
stdio: 'inherit'
});
if (options.follow) {
console.log(chalk.gray('Press Ctrl+C to stop following logs'));
}
return new Promise((resolve, reject) => {
sshProcess.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`SSH command failed with code ${code}`));
}
});
sshProcess.on('error', (error) => {
reject(new Error(`SSH process error: ${error.message}`));
});
});
} catch (error) {
spinner.fail('Failed to fetch Docker logs');
throw error;
}
}
/**
* Execute a command in WSL
*/
private async executeWSLCommand(command: string, options: { timeout?: number } = {}): Promise<{ success: boolean; output: string; error?: string }> {
return new Promise((resolve) => {
const process = spawn('wsl', ['bash', '-c', command], {
stdio: ['pipe', 'pipe', 'pipe']
});
let output = '';
let error = '';
process.stdout?.on('data', (data) => {
output += data.toString();
});
process.stderr?.on('data', (data) => {
error += data.toString();
});
const timeout = options.timeout || 30000;
const timer = setTimeout(() => {
process.kill();
resolve({ success: false, output, error: 'Command timed out' });
}, timeout);
process.on('close', (code) => {
clearTimeout(timer);
resolve({
success: code === 0,
output: output.trim(),
error: error.trim()
});
});
process.on('error', (err) => {
clearTimeout(timer);
resolve({
success: false,
output,
error: err.message
});
});
});
}
/**
* Get WSL distribution info
*/
async getWSLInfo(): Promise<void> {
console.log(chalk.cyan('\nš§ WSL Environment Information'));
console.log(chalk.gray('ā'.repeat(50)));
try {
const distroResult = await this.executeWSLCommand('cat /etc/os-release | grep PRETTY_NAME');
if (distroResult.success) {
const distroName = distroResult.output.split('=')[1]?.replace(/"/g, '') || 'Unknown';
console.log(chalk.white(`š Distribution: ${distroName}`));
}
const kernelResult = await this.executeWSLCommand('uname -r');
if (kernelResult.success) {
console.log(chalk.white(`š§ Kernel: ${kernelResult.output}`));
}
const sshResult = await this.executeWSLCommand('which ssh');
if (sshResult.success) {
console.log(chalk.green('ā
SSH client available'));
} else {
console.log(chalk.red('ā SSH client not found'));
console.log(chalk.yellow('š” Install: sudo apt update && sudo apt install openssh-client'));
}
const dockerResult = await this.executeWSLCommand('which docker');
if (dockerResult.success) {
console.log(chalk.green('ā
Docker client available'));
} else {
console.log(chalk.yellow('ā ļø Docker client not found (optional)'));
}
} catch (error) {
console.error(chalk.red('ā Failed to get WSL info:'), error.message);
}
}
/**
* Interactive SSH key setup with user guidance
*/
async interactiveSSHSetup(): Promise<void> {
console.log(chalk.cyan.bold('\nš Interactive SSH Setup for Coolify'));
console.log(chalk.white('This will configure SSH access to your Coolify server'));
console.log(chalk.gray('ā'.repeat(60)));
// Check WSL first
const wslAvailable = await this.checkWSLAvailability();
if (!wslAvailable) {
console.log(chalk.red('ā WSL is required for SSH functionality'));
console.log(chalk.yellow('š” Install WSL and try again'));
return;
}
// Setup SSH keys
console.log(chalk.blue('\nš Step 1: SSH Key Generation'));
const sshSetup = await this.setupSSHKeys();
if (sshSetup) {
console.log(chalk.blue('\nš Step 2: Testing Connection'));
const connectionTest = await this.testSSHConnection();
if (connectionTest) {
console.log(chalk.green.bold('\nš SSH setup completed successfully!'));
console.log(chalk.white('You can now use SSH-based commands like:'));
console.log(chalk.gray(' ⢠aerocorp logs --app <uuid> --ssh'));
console.log(chalk.gray(' ⢠aerocorp wsl info'));
console.log(chalk.gray(' ⢠aerocorp wsl test'));
} else {
console.log(chalk.yellow('\nā ļø SSH setup completed but connection test failed'));
console.log(chalk.white('Manual steps may be required:'));
console.log(chalk.gray(' 1. Check server firewall settings'));
console.log(chalk.gray(' 2. Verify SSH service is running'));
console.log(chalk.gray(' 3. Check SSH key permissions'));
}
}
}
}