Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
792
dashcaddy-installer/src/main/dependency-checker.js
Normal file
792
dashcaddy-installer/src/main/dependency-checker.js
Normal file
@@ -0,0 +1,792 @@
|
||||
const { exec, spawn } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const path = require('path');
|
||||
const platformUtils = require('../shared/platform-utils');
|
||||
const DownloadManager = require('./download-manager');
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
class DependencyChecker {
|
||||
/**
|
||||
* Checks if Docker is installed and running
|
||||
* @returns {Promise<Object>} { installed: boolean, running: boolean, version: string }
|
||||
*/
|
||||
async checkDocker() {
|
||||
try {
|
||||
// Check if Docker is installed
|
||||
const versionResult = await this.executeCommand('docker --version');
|
||||
|
||||
if (!versionResult.success) {
|
||||
return {
|
||||
installed: false,
|
||||
running: false,
|
||||
version: null
|
||||
};
|
||||
}
|
||||
|
||||
// Extract version from output (e.g., "Docker version 24.0.7, build afdd53b")
|
||||
const versionMatch = versionResult.stdout.match(/Docker version ([\d.]+)/);
|
||||
const version = versionMatch ? versionMatch[1] : 'unknown';
|
||||
|
||||
// Check if Docker daemon is running
|
||||
const infoResult = await this.executeCommand('docker info');
|
||||
|
||||
return {
|
||||
installed: true,
|
||||
running: infoResult.success,
|
||||
version: version
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
installed: false,
|
||||
running: false,
|
||||
version: null,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Caddy is installed
|
||||
* @param {string[]} [additionalPaths] - Additional paths to search for caddy binary
|
||||
* @returns {Promise<Object>} { installed: boolean, version: string, path: string }
|
||||
*/
|
||||
async checkCaddy(additionalPaths) {
|
||||
try {
|
||||
// First try caddy in PATH
|
||||
const result = await this.executeCommand('caddy version');
|
||||
|
||||
if (result.success) {
|
||||
const versionMatch = result.stdout.match(/v([\d.]+)/);
|
||||
const version = versionMatch ? versionMatch[1] : 'unknown';
|
||||
|
||||
// Try to get Caddy path
|
||||
let caddyPath = null;
|
||||
if (platformUtils.isWindows()) {
|
||||
const whereResult = await this.executeCommand('where caddy');
|
||||
if (whereResult.success) {
|
||||
caddyPath = whereResult.stdout.trim().split('\n')[0];
|
||||
}
|
||||
} else {
|
||||
const whichResult = await this.executeCommand('which caddy');
|
||||
if (whichResult.success) {
|
||||
caddyPath = whichResult.stdout.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return { installed: true, version, path: caddyPath };
|
||||
}
|
||||
|
||||
// Not in PATH - search common locations
|
||||
const searchResult = await this.findCaddyBinary(additionalPaths);
|
||||
if (searchResult) {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
return { installed: false, version: null, path: null };
|
||||
} catch (error) {
|
||||
return {
|
||||
installed: false,
|
||||
version: null,
|
||||
path: null,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches common locations for the Caddy binary
|
||||
* @param {string[]} [additionalPaths] - Additional paths to search
|
||||
* @returns {Promise<Object|null>} Caddy info if found, null otherwise
|
||||
*/
|
||||
async findCaddyBinary(additionalPaths) {
|
||||
const fs = require('fs');
|
||||
const binaryName = platformUtils.isWindows() ? 'caddy.exe' : 'caddy';
|
||||
|
||||
// Build list of candidate paths
|
||||
const searchPaths = [];
|
||||
|
||||
if (platformUtils.isWindows()) {
|
||||
searchPaths.push(
|
||||
'C:\\caddy',
|
||||
'C:\\Program Files\\Caddy',
|
||||
'C:\\Program Files (x86)\\Caddy',
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Caddy'),
|
||||
path.join(process.env.USERPROFILE || '', 'caddy'),
|
||||
'C:\\DashCaddy\\caddy'
|
||||
);
|
||||
} else {
|
||||
searchPaths.push(
|
||||
'/usr/local/bin',
|
||||
'/usr/bin',
|
||||
'/opt/caddy',
|
||||
'/opt/dashcaddy/caddy',
|
||||
path.join(process.env.HOME || '', '.local/bin')
|
||||
);
|
||||
}
|
||||
|
||||
// Add any user-specified paths
|
||||
if (additionalPaths) {
|
||||
searchPaths.push(...additionalPaths);
|
||||
}
|
||||
|
||||
for (const dir of searchPaths) {
|
||||
if (!dir) continue;
|
||||
const fullPath = path.join(dir, binaryName);
|
||||
try {
|
||||
fs.accessSync(fullPath, fs.constants.X_OK);
|
||||
// Found the binary - verify it works
|
||||
const result = await this.executeCommand(`"${fullPath}" version`);
|
||||
if (result.success) {
|
||||
const versionMatch = result.stdout.match(/v([\d.]+)/);
|
||||
const version = versionMatch ? versionMatch[1] : 'unknown';
|
||||
return { installed: true, version, path: fullPath };
|
||||
}
|
||||
} catch {
|
||||
// Not found here, continue searching
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks platform information
|
||||
* @returns {Promise<Object>} Platform details
|
||||
*/
|
||||
async checkPlatform() {
|
||||
return platformUtils.getPlatformInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides installation instructions for Docker
|
||||
* @param {string} platform - Platform identifier (windows, macos, linux)
|
||||
* @returns {Object} Installation instructions
|
||||
*/
|
||||
getDockerInstallInstructions(platform) {
|
||||
const instructions = {
|
||||
windows: {
|
||||
title: 'Install Docker Desktop for Windows',
|
||||
steps: [
|
||||
'Download Docker Desktop from https://www.docker.com/products/docker-desktop',
|
||||
'Run the installer and follow the setup wizard',
|
||||
'Restart your computer if prompted',
|
||||
'Launch Docker Desktop from the Start menu',
|
||||
'Wait for Docker to start (you\'ll see the whale icon in the system tray)',
|
||||
'Click "Retry" in the installer once Docker is running'
|
||||
],
|
||||
url: 'https://docs.docker.com/desktop/install/windows-install/',
|
||||
requiresWSL2: true,
|
||||
wsl2Instructions: [
|
||||
'Docker Desktop requires WSL 2',
|
||||
'The installer will guide you through enabling WSL 2 if needed',
|
||||
'You may need to enable virtualization in your BIOS'
|
||||
]
|
||||
},
|
||||
macos: {
|
||||
title: 'Install Docker Desktop for Mac',
|
||||
steps: [
|
||||
'Download Docker Desktop from https://www.docker.com/products/docker-desktop',
|
||||
'Open the .dmg file and drag Docker to Applications',
|
||||
'Launch Docker from Applications',
|
||||
'Grant necessary permissions when prompted',
|
||||
'Wait for Docker to start (you\'ll see the whale icon in the menu bar)',
|
||||
'Click "Retry" in the installer once Docker is running'
|
||||
],
|
||||
url: 'https://docs.docker.com/desktop/install/mac-install/',
|
||||
requiresWSL2: false
|
||||
},
|
||||
linux: {
|
||||
title: 'Install Docker Engine for Linux',
|
||||
steps: [
|
||||
'Open a terminal',
|
||||
'Run: curl -fsSL https://get.docker.com -o get-docker.sh',
|
||||
'Run: sudo sh get-docker.sh',
|
||||
'Run: sudo systemctl start docker',
|
||||
'Run: sudo systemctl enable docker',
|
||||
'Run: sudo usermod -aG docker $USER',
|
||||
'Log out and back in for group changes to take effect',
|
||||
'Click "Retry" in the installer'
|
||||
],
|
||||
url: 'https://docs.docker.com/engine/install/',
|
||||
requiresWSL2: false,
|
||||
packageManagers: {
|
||||
ubuntu: 'sudo apt-get update && sudo apt-get install docker.io',
|
||||
fedora: 'sudo dnf install docker',
|
||||
arch: 'sudo pacman -S docker'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return instructions[platform] || instructions.linux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides installation instructions for Caddy
|
||||
* @param {string} platform - Platform identifier (windows, macos, linux)
|
||||
* @returns {Object} Installation instructions
|
||||
*/
|
||||
getCaddyInstallInstructions(platform) {
|
||||
const instructions = {
|
||||
windows: {
|
||||
title: 'Install Caddy for Windows',
|
||||
steps: [
|
||||
'Download Caddy from https://caddyserver.com/download',
|
||||
'Extract the caddy.exe file',
|
||||
'Move caddy.exe to C:\\Windows\\System32 (requires admin)',
|
||||
'Or add the Caddy folder to your PATH environment variable',
|
||||
'Open a new PowerShell window',
|
||||
'Run: caddy version',
|
||||
'Click "Retry" in the installer'
|
||||
],
|
||||
url: 'https://caddyserver.com/docs/install#windows',
|
||||
automated: false
|
||||
},
|
||||
macos: {
|
||||
title: 'Install Caddy for macOS',
|
||||
steps: [
|
||||
'Open Terminal',
|
||||
'Install Homebrew if not already installed: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
||||
'Run: brew install caddy',
|
||||
'Run: caddy version',
|
||||
'Click "Retry" in the installer'
|
||||
],
|
||||
url: 'https://caddyserver.com/docs/install#mac',
|
||||
automated: true,
|
||||
command: 'brew install caddy'
|
||||
},
|
||||
linux: {
|
||||
title: 'Install Caddy for Linux',
|
||||
steps: [
|
||||
'Open a terminal',
|
||||
'Run the appropriate command for your distribution (see below)',
|
||||
'Run: caddy version',
|
||||
'Click "Retry" in the installer'
|
||||
],
|
||||
url: 'https://caddyserver.com/docs/install#debian-ubuntu-raspbian',
|
||||
automated: true,
|
||||
packageManagers: {
|
||||
ubuntu: 'sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https && curl -1sLf "https://dl.cloudsmith.io/public/caddy/stable/gpg.key" | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && curl -1sLf "https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt" | sudo tee /etc/apt/sources.list.d/caddy-stable.list && sudo apt update && sudo apt install caddy',
|
||||
fedora: 'dnf install "dnf-command(copr)" && dnf copr enable @caddy/caddy && dnf install caddy',
|
||||
arch: 'pacman -S caddy'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return instructions[platform] || instructions.linux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts automated installation of Docker (platform-specific)
|
||||
* @param {string} platform - Platform identifier
|
||||
* @param {Function} progressCallback - Progress callback
|
||||
* @returns {Promise<Object>} Installation result
|
||||
*/
|
||||
async installDocker(platform, progressCallback) {
|
||||
const downloadManager = new DownloadManager();
|
||||
const platformInfo = platformUtils.getPlatformInfo();
|
||||
|
||||
try {
|
||||
// Download Docker Desktop
|
||||
progressCallback?.({ status: 'downloading', message: 'Downloading Docker Desktop...' });
|
||||
|
||||
const downloadResult = await downloadManager.downloadDocker(
|
||||
platform,
|
||||
platformInfo.arch,
|
||||
progressCallback
|
||||
);
|
||||
|
||||
if (!downloadResult.success) {
|
||||
// Fallback to instructions if download fails
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: downloadResult.message || 'Download failed',
|
||||
instructions: this.getDockerInstallInstructions(platform)
|
||||
};
|
||||
}
|
||||
|
||||
// Run the installer
|
||||
progressCallback?.({ status: 'installing', message: 'Running Docker installer...' });
|
||||
|
||||
if (platform === 'windows') {
|
||||
// Run Docker Desktop installer silently
|
||||
const installerPath = downloadResult.path;
|
||||
|
||||
try {
|
||||
// The Docker Desktop installer supports silent install
|
||||
await this.runInstallerWindows(installerPath);
|
||||
|
||||
// Wait for Docker to be ready
|
||||
progressCallback?.({ status: 'waiting', message: 'Waiting for Docker to start...' });
|
||||
await this.waitForDocker(120000); // 2 minute timeout
|
||||
|
||||
// Verify installation
|
||||
const verification = await this.checkDocker();
|
||||
|
||||
if (verification.installed) {
|
||||
await downloadManager.cleanup();
|
||||
return {
|
||||
success: true,
|
||||
automated: true,
|
||||
message: 'Docker Desktop installed successfully',
|
||||
version: verification.version
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
// Installation might require user interaction or admin
|
||||
return {
|
||||
success: false,
|
||||
automated: true,
|
||||
message: 'Docker installer launched. Please complete the installation and restart.',
|
||||
instructions: this.getDockerInstallInstructions(platform)
|
||||
};
|
||||
}
|
||||
} else if (platform === 'macos') {
|
||||
// Mount and install DMG
|
||||
const dmgPath = downloadResult.path;
|
||||
|
||||
try {
|
||||
await this.runInstallerMacOS(dmgPath);
|
||||
|
||||
progressCallback?.({ status: 'waiting', message: 'Waiting for Docker to start...' });
|
||||
await this.waitForDocker(120000);
|
||||
|
||||
const verification = await this.checkDocker();
|
||||
|
||||
if (verification.installed) {
|
||||
await downloadManager.cleanup();
|
||||
return {
|
||||
success: true,
|
||||
automated: true,
|
||||
message: 'Docker Desktop installed successfully',
|
||||
version: verification.version
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
automated: true,
|
||||
message: 'Docker installation started. Please complete any prompts.',
|
||||
instructions: this.getDockerInstallInstructions(platform)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Linux - use package manager
|
||||
return await this.installDockerLinux(progressCallback);
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: 'Docker installation requires manual completion',
|
||||
instructions: this.getDockerInstallInstructions(platform)
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: error.message,
|
||||
instructions: this.getDockerInstallInstructions(platform)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Docker installer on Windows
|
||||
*/
|
||||
async runInstallerWindows(installerPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Start installer with install flag
|
||||
const installer = spawn(installerPath, ['install', '--quiet', '--accept-license'], {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
});
|
||||
|
||||
installer.unref();
|
||||
|
||||
// Give the installer time to start
|
||||
setTimeout(() => resolve(), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Docker installer on macOS
|
||||
*/
|
||||
async runInstallerMacOS(dmgPath) {
|
||||
// Mount the DMG
|
||||
await this.executeCommand(`hdiutil attach "${dmgPath}" -nobrowse`);
|
||||
|
||||
// Copy to Applications
|
||||
await this.executeCommand('cp -R "/Volumes/Docker/Docker.app" /Applications/');
|
||||
|
||||
// Unmount
|
||||
await this.executeCommand('hdiutil detach "/Volumes/Docker"');
|
||||
|
||||
// Launch Docker
|
||||
await this.executeCommand('open /Applications/Docker.app');
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Docker on Linux using package manager
|
||||
*/
|
||||
async installDockerLinux(progressCallback) {
|
||||
const distro = await this.detectLinuxDistro();
|
||||
|
||||
progressCallback?.({ status: 'installing', message: `Installing Docker for ${distro}...` });
|
||||
|
||||
try {
|
||||
if (distro === 'ubuntu' || distro === 'debian') {
|
||||
await this.executeCommand('curl -fsSL https://get.docker.com -o /tmp/get-docker.sh');
|
||||
await this.executeCommand('sudo sh /tmp/get-docker.sh');
|
||||
await this.executeCommand('sudo systemctl enable docker');
|
||||
await this.executeCommand('sudo systemctl start docker');
|
||||
} else if (distro === 'fedora') {
|
||||
await this.executeCommand('sudo dnf install -y docker-ce docker-ce-cli containerd.io');
|
||||
await this.executeCommand('sudo systemctl enable docker');
|
||||
await this.executeCommand('sudo systemctl start docker');
|
||||
} else if (distro === 'arch') {
|
||||
await this.executeCommand('sudo pacman -S --noconfirm docker');
|
||||
await this.executeCommand('sudo systemctl enable docker');
|
||||
await this.executeCommand('sudo systemctl start docker');
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: 'Unsupported Linux distribution',
|
||||
instructions: this.getDockerInstallInstructions('linux')
|
||||
};
|
||||
}
|
||||
|
||||
// Add user to docker group
|
||||
await this.executeCommand('sudo usermod -aG docker $USER');
|
||||
|
||||
const verification = await this.checkDocker();
|
||||
|
||||
return {
|
||||
success: verification.installed,
|
||||
automated: true,
|
||||
message: verification.installed ?
|
||||
'Docker installed successfully. You may need to log out and back in for group changes.' :
|
||||
'Docker installed but may require restart',
|
||||
version: verification.version
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
automated: true,
|
||||
message: error.message,
|
||||
instructions: this.getDockerInstallInstructions('linux')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for Docker to become available
|
||||
*/
|
||||
async waitForDocker(timeout = 60000) {
|
||||
const startTime = Date.now();
|
||||
const pollInterval = 3000;
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
const status = await this.checkDocker();
|
||||
if (status.installed && status.running) {
|
||||
return true;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
||||
}
|
||||
|
||||
throw new Error('Docker did not become available within timeout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts automated installation of Caddy (platform-specific)
|
||||
* @param {string} platform - Platform identifier
|
||||
* @param {Function} progressCallback - Progress callback
|
||||
* @param {string} targetPath - Optional target path for Caddy binary
|
||||
* @returns {Promise<Object>} Installation result
|
||||
*/
|
||||
async installCaddy(platform, progressCallback, targetPath) {
|
||||
const downloadManager = new DownloadManager();
|
||||
const platformInfo = platformUtils.getPlatformInfo();
|
||||
const instructions = this.getCaddyInstallInstructions(platform);
|
||||
|
||||
try {
|
||||
// Download Caddy from GitHub
|
||||
progressCallback?.({ status: 'downloading', message: 'Downloading Caddy...' });
|
||||
|
||||
const downloadResult = await downloadManager.downloadCaddy(
|
||||
platform,
|
||||
platformInfo.arch,
|
||||
progressCallback
|
||||
);
|
||||
|
||||
if (!downloadResult.success) {
|
||||
// Try package manager as fallback
|
||||
return await this.installCaddyWithPackageManager(platform, progressCallback);
|
||||
}
|
||||
|
||||
// Extract the archive
|
||||
progressCallback?.({ status: 'extracting', message: 'Extracting Caddy...' });
|
||||
|
||||
// Determine target directory
|
||||
let extractTarget;
|
||||
if (targetPath) {
|
||||
extractTarget = targetPath;
|
||||
} else if (platform === 'windows') {
|
||||
extractTarget = 'C:\\Program Files\\Caddy';
|
||||
} else {
|
||||
extractTarget = '/usr/local/bin';
|
||||
}
|
||||
|
||||
const extractResult = await downloadManager.extractCaddy(
|
||||
downloadResult.path,
|
||||
extractTarget,
|
||||
platform
|
||||
);
|
||||
|
||||
if (!extractResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
automated: true,
|
||||
message: 'Failed to extract Caddy',
|
||||
error: extractResult.error,
|
||||
instructions
|
||||
};
|
||||
}
|
||||
|
||||
// On Windows, add to PATH if needed
|
||||
if (platform === 'windows') {
|
||||
progressCallback?.({ status: 'configuring', message: 'Adding Caddy to PATH...' });
|
||||
await this.addToWindowsPath(extractTarget);
|
||||
}
|
||||
|
||||
// Verify installation
|
||||
progressCallback?.({ status: 'verifying', message: 'Verifying installation...' });
|
||||
|
||||
// Give a moment for PATH changes to take effect
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const verification = await this.checkCaddy([extractTarget]);
|
||||
|
||||
await downloadManager.cleanup();
|
||||
|
||||
if (verification.installed) {
|
||||
return {
|
||||
success: true,
|
||||
automated: true,
|
||||
message: 'Caddy installed successfully',
|
||||
version: downloadResult.version || verification.version,
|
||||
path: verification.path || extractResult.binaryPath
|
||||
};
|
||||
} else {
|
||||
// Binary exists but not in PATH yet
|
||||
return {
|
||||
success: true,
|
||||
automated: true,
|
||||
message: 'Caddy installed. Restart terminal for PATH changes.',
|
||||
version: downloadResult.version,
|
||||
path: extractResult.binaryPath
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback to package manager
|
||||
return await this.installCaddyWithPackageManager(platform, progressCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Caddy using system package manager
|
||||
*/
|
||||
async installCaddyWithPackageManager(platform, progressCallback) {
|
||||
const instructions = this.getCaddyInstallInstructions(platform);
|
||||
|
||||
try {
|
||||
let command;
|
||||
|
||||
if (platform === 'macos') {
|
||||
// Check if Homebrew is available
|
||||
const brewCheck = await this.executeCommand('which brew');
|
||||
if (brewCheck.success) {
|
||||
progressCallback?.({ status: 'installing', message: 'Installing Caddy via Homebrew...' });
|
||||
command = 'brew install caddy';
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: 'Homebrew not found. Please install Homebrew first.',
|
||||
instructions
|
||||
};
|
||||
}
|
||||
} else if (platform === 'linux') {
|
||||
const distro = await this.detectLinuxDistro();
|
||||
progressCallback?.({ status: 'installing', message: `Installing Caddy for ${distro}...` });
|
||||
|
||||
if (distro === 'ubuntu' || distro === 'debian') {
|
||||
// Add Caddy repository and install
|
||||
await this.executeCommand('sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https');
|
||||
await this.executeCommand('curl -1sLf "https://dl.cloudsmith.io/public/caddy/stable/gpg.key" | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg');
|
||||
await this.executeCommand('curl -1sLf "https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt" | sudo tee /etc/apt/sources.list.d/caddy-stable.list');
|
||||
await this.executeCommand('sudo apt update');
|
||||
command = 'sudo apt install -y caddy';
|
||||
} else if (distro === 'fedora') {
|
||||
await this.executeCommand('sudo dnf install -y "dnf-command(copr)"');
|
||||
await this.executeCommand('sudo dnf copr enable -y @caddy/caddy');
|
||||
command = 'sudo dnf install -y caddy';
|
||||
} else if (distro === 'arch') {
|
||||
command = 'sudo pacman -S --noconfirm caddy';
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: 'Unsupported Linux distribution',
|
||||
instructions
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
automated: false,
|
||||
message: 'Automated installation not supported',
|
||||
instructions
|
||||
};
|
||||
}
|
||||
|
||||
const result = await this.executeCommand(command);
|
||||
|
||||
if (result.success) {
|
||||
const verification = await this.checkCaddy();
|
||||
|
||||
return {
|
||||
success: verification.installed,
|
||||
automated: true,
|
||||
message: verification.installed ? 'Caddy installed successfully' : 'Installation completed but verification failed',
|
||||
version: verification.version
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
automated: true,
|
||||
message: 'Installation command failed',
|
||||
error: result.stderr,
|
||||
instructions
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
automated: true,
|
||||
message: 'Installation failed',
|
||||
error: error.message,
|
||||
instructions
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add directory to Windows PATH
|
||||
*/
|
||||
async addToWindowsPath(directory) {
|
||||
try {
|
||||
// Add to user PATH using PowerShell
|
||||
const script = `
|
||||
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
if ($currentPath -notlike '*${directory.replace(/\\/g, '\\\\')}*') {
|
||||
[Environment]::SetEnvironmentVariable('Path', "$currentPath;${directory.replace(/\\/g, '\\\\')}", 'User')
|
||||
}
|
||||
`;
|
||||
|
||||
await this.executeCommand(`powershell -Command "${script}"`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects Linux distribution
|
||||
* @returns {Promise<string>} Distribution name (ubuntu, fedora, arch, etc.)
|
||||
*/
|
||||
async detectLinuxDistro() {
|
||||
try {
|
||||
const result = await this.executeCommand('cat /etc/os-release');
|
||||
|
||||
if (result.success) {
|
||||
const output = result.stdout.toLowerCase();
|
||||
|
||||
if (output.includes('ubuntu') || output.includes('debian')) {
|
||||
return 'ubuntu';
|
||||
} else if (output.includes('fedora') || output.includes('rhel') || output.includes('centos')) {
|
||||
return 'fedora';
|
||||
} else if (output.includes('arch')) {
|
||||
return 'arch';
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
} catch (error) {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a shell command
|
||||
* @param {string} command - Command to execute
|
||||
* @returns {Promise<Object>} { success: boolean, stdout: string, stderr: string }
|
||||
*/
|
||||
async executeCommand(command) {
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(command, {
|
||||
timeout: 30000, // 30 second timeout
|
||||
maxBuffer: 1024 * 1024 // 1MB buffer
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
stdout: stdout,
|
||||
stderr: stderr
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
stdout: error.stdout || '',
|
||||
stderr: error.stderr || error.message,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if WSL2 is available on Windows
|
||||
* @returns {Promise<Object>} WSL2 status
|
||||
*/
|
||||
async checkWSL2() {
|
||||
if (!platformUtils.isWindows()) {
|
||||
return {
|
||||
available: false,
|
||||
required: false,
|
||||
message: 'WSL2 is only relevant on Windows'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.executeCommand('wsl --status');
|
||||
|
||||
return {
|
||||
available: result.success,
|
||||
required: true,
|
||||
message: result.success ? 'WSL2 is available' : 'WSL2 is not installed',
|
||||
details: result.stdout
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
required: true,
|
||||
message: 'WSL2 is not installed or not accessible',
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DependencyChecker;
|
||||
Reference in New Issue
Block a user