diff --git a/docs.json b/docs.json index 0dcaca11..71d3318e 100644 --- a/docs.json +++ b/docs.json @@ -147,7 +147,8 @@ "web/docs/performance", "web/docs/playwright-testing", "web/src/test/playwright/SEQUENTIAL_OPTIMIZATIONS", - "web/docs/npm" + "web/docs/npm", + "web/docs/systemd" ] }, { diff --git a/web/README.md b/web/README.md index 45826528..0d7501c2 100644 --- a/web/README.md +++ b/web/README.md @@ -165,6 +165,8 @@ vibetunnel fwd --session-id abc123 npm test vibetunnel fwd --session-id abc123 python script.py ``` +Linux users can install VibeTunnel as a systemd service with `vibetunnel systemd` for automatic startup and process management - see [detailed systemd documentation](docs/systemd.md). + ### Environment Variables VibeTunnel respects the following environment variables: @@ -197,6 +199,7 @@ This npm package includes: - Native PTY support for terminal emulation - Web interface with xterm.js - Session management and forwarding +- Built-in systemd service management for Linux ## Platform Support diff --git a/web/docs/systemd.md b/web/docs/systemd.md new file mode 100644 index 00000000..6980b6a4 --- /dev/null +++ b/web/docs/systemd.md @@ -0,0 +1,383 @@ +# VibeTunnel Systemd Service Guide + +This guide covers installing and managing VibeTunnel as a systemd service on Linux systems. + +## Overview + +VibeTunnel includes built-in systemd integration that allows you to run it as a persistent service on Linux. The service runs as a **user-level systemd service** under your account (not system-wide), providing automatic startup, restart on failure, and proper resource management. + +## Quick Start + +```bash +# Install the systemd service (run as regular user, NOT root) +vibetunnel systemd + +# Start the service +systemctl --user start vibetunnel + +# Enable auto-start on boot +systemctl --user enable vibetunnel + +# Check status +systemctl --user status vibetunnel +``` + +## Installation + +### Prerequisites + +- Linux system with systemd (most modern distributions) +- VibeTunnel installed globally via npm (`npm install -g vibetunnel`) +- Regular user account (do not run as root) + +### Install Command + +```bash +vibetunnel systemd +``` + +This command will: +1. Verify VibeTunnel is installed and accessible +2. Create a wrapper script at `~/.local/bin/vibetunnel-systemd` +3. Install the service file at `~/.config/systemd/user/vibetunnel.service` +4. Enable the service for automatic startup +5. Configure user lingering for boot startup + +## Service Management + +### Basic Commands + +```bash +# Start the service +systemctl --user start vibetunnel + +# Stop the service +systemctl --user stop vibetunnel + +# Restart the service +systemctl --user restart vibetunnel + +# Check service status +systemctl --user status vibetunnel + +# Enable auto-start +systemctl --user enable vibetunnel + +# Disable auto-start +systemctl --user disable vibetunnel + +# Check VibeTunnel's systemd status +vibetunnel systemd status +``` + +### Viewing Logs + +```bash +# Follow logs in real-time +journalctl --user -u vibetunnel -f + +# View all logs +journalctl --user -u vibetunnel + +# View logs from the last hour +journalctl --user -u vibetunnel --since "1 hour ago" + +# View only error messages +journalctl --user -u vibetunnel -p err +``` + +## Configuration + +### Default Settings + +The service runs with these defaults: +- **Port**: 4020 +- **Bind Address**: 0.0.0.0 (all interfaces) +- **Working Directory**: Your home directory +- **Restart Policy**: Always restart on failure +- **Restart Delay**: 10 seconds +- **Memory Limit**: 512MB soft, 1GB hard +- **File Descriptor Limit**: 65536 +- **Environment**: `NODE_ENV=production`, `VIBETUNNEL_LOG_LEVEL=info` + +### Service File Location + +The service configuration is stored at: +``` +~/.config/systemd/user/vibetunnel.service +``` + +### Customizing the Service + +To modify service settings: + +1. Edit the service file: + ```bash + nano ~/.config/systemd/user/vibetunnel.service + ``` + +2. Common customizations: + ```ini + # Change port + ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 8080 --bind 0.0.0.0 + + # Add authentication + ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4020 --bind 0.0.0.0 --auth system + + # Change log level + Environment=VIBETUNNEL_LOG_LEVEL=debug + + # Adjust memory limits + MemoryHigh=1G + MemoryMax=2G + + # Add custom environment variables + Environment=MY_CUSTOM_VAR=value + ``` + +3. Reload and restart: + ```bash + systemctl --user daemon-reload + systemctl --user restart vibetunnel + ``` + +## Architecture + +### Why User-Level Service? + +VibeTunnel uses user-level systemd services for several reasons: + +1. **Security**: Runs with user privileges, not root +2. **Node.js Compatibility**: Works with user-installed Node.js version managers (nvm, fnm) +3. **User Data Access**: Natural access to your projects and Git repositories +4. **Simplicity**: No sudo required for management +5. **Isolation**: Each user can run their own instance + +### The Wrapper Script + +The installer creates a wrapper script at `~/.local/bin/vibetunnel-systemd` that: +- Searches for VibeTunnel in multiple locations +- Handles nvm and fnm installations +- Falls back to system-wide Node.js if needed +- Provides detailed logging for troubleshooting + +### User Lingering + +The installer enables "user lingering" which allows your user services to run even when you're not logged in: + +```bash +# This is done automatically during installation +loginctl enable-linger $USER + +# To check lingering status +loginctl show-user $USER | grep Linger + +# To disable lingering (if desired) +loginctl disable-linger $USER +``` + +## Troubleshooting + +### Service Won't Start + +1. Check if VibeTunnel is installed: + ```bash + which vibetunnel + ``` + +2. Check service logs: + ```bash + journalctl --user -u vibetunnel -n 50 + ``` + +3. Verify the wrapper script exists: + ```bash + ls -la ~/.local/bin/vibetunnel-systemd + ``` + +4. Test the wrapper script directly: + ```bash + ~/.local/bin/vibetunnel-systemd --version + ``` + +### Port Already in Use + +If port 4020 is already in use: + +1. Find what's using the port: + ```bash + lsof -i :4020 + ``` + +2. Either stop the conflicting service or change VibeTunnel's port in the service file + +### Node.js Version Manager Issues + +If using nvm or fnm, ensure they're properly initialized: + +1. Check your shell configuration: + ```bash + # For nvm + echo $NVM_DIR + + # For fnm + echo $FNM_DIR + ``` + +2. The wrapper script searches these locations: + - nvm: `~/.nvm` + - fnm: `~/.local/share/fnm` + - Global npm: `/usr/local/bin/npm`, `/usr/bin/npm` + +### Permission Denied + +If you get permission errors: + +1. Ensure you're NOT running as root +2. Check file permissions: + ```bash + ls -la ~/.config/systemd/user/ + ls -la ~/.local/bin/vibetunnel-systemd + ``` + +3. Fix permissions if needed: + ```bash + chmod 755 ~/.local/bin/vibetunnel-systemd + chmod 644 ~/.config/systemd/user/vibetunnel.service + ``` + +## Uninstallation + +To completely remove the systemd service: + +```bash +# Stop and disable the service +systemctl --user stop vibetunnel +systemctl --user disable vibetunnel + +# Remove service files +vibetunnel systemd uninstall + +# Optional: Disable user lingering +loginctl disable-linger $USER +``` + +This will: +- Stop the running service +- Disable automatic startup +- Remove the service file +- Remove the wrapper script +- Reload systemd configuration + +## Advanced Usage + +### Multiple Instances + +To run multiple VibeTunnel instances: + +1. Copy the service file with a new name: + ```bash + cp ~/.config/systemd/user/vibetunnel.service ~/.config/systemd/user/vibetunnel-dev.service + ``` + +2. Edit the new service file to use a different port: + ```ini + ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4021 --bind 0.0.0.0 + ``` + +3. Manage the new instance: + ```bash + systemctl --user daemon-reload + systemctl --user start vibetunnel-dev + ``` + +### Environment-Specific Configuration + +Create environment-specific service overrides: + +```bash +# Create override directory +mkdir -p ~/.config/systemd/user/vibetunnel.service.d/ + +# Create override file +cat > ~/.config/systemd/user/vibetunnel.service.d/override.conf << EOF +[Service] +Environment=NODE_ENV=development +Environment=VIBETUNNEL_LOG_LEVEL=debug +ExecStart= +ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4020 --bind 127.0.0.1 +EOF + +# Reload and restart +systemctl --user daemon-reload +systemctl --user restart vibetunnel +``` + +### Integration with Other Services + +To make VibeTunnel depend on other services: + +```ini +[Unit] +After=network-online.target postgresql.service +Wants=network-online.target + +[Service] +# ... rest of configuration +``` + +## Security Considerations + +### Firewall Configuration + +If binding to 0.0.0.0, ensure your firewall is properly configured: + +```bash +# UFW example +sudo ufw allow 4020/tcp + +# firewalld example +sudo firewall-cmd --add-port=4020/tcp --permanent +sudo firewall-cmd --reload +``` + +### Restricting Access + +To limit access to localhost only, modify the service: + +```ini +ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4020 --bind 127.0.0.1 +``` + +### Resource Limits + +The service includes resource limits for stability: +- Memory: 512MB soft limit, 1GB hard limit +- File descriptors: 65536 +- Automatic restart with 10-second delay + +Adjust these based on your needs and system resources. + +## FAQ + +**Q: Why doesn't the service run as root?** +A: VibeTunnel doesn't require root privileges and running as a regular user is more secure. It also ensures compatibility with user-installed Node.js version managers. + +**Q: Can I run this on a server without a GUI?** +A: Yes, the systemd service works perfectly on headless servers. User lingering ensures it starts at boot. + +**Q: How do I run VibeTunnel on a different port?** +A: Edit the service file and change the `--port` parameter in the `ExecStart` line, then reload and restart. + +**Q: What if I use a custom Node.js installation?** +A: The wrapper script searches common locations. If your installation isn't found, you can modify the wrapper script at `~/.local/bin/vibetunnel-systemd`. + +**Q: Can multiple users run VibeTunnel on the same system?** +A: Yes, each user can install their own service. Just ensure they use different ports. + +## Support + +For issues specific to the systemd service: +1. Check the logs with `journalctl --user -u vibetunnel` +2. Verify the installation with `vibetunnel systemd status` +3. Report issues at https://github.com/amantus-ai/vibetunnel/issues \ No newline at end of file diff --git a/web/src/cli.ts b/web/src/cli.ts index dc96f513..2e611c66 100644 --- a/web/src/cli.ts +++ b/web/src/cli.ts @@ -54,30 +54,134 @@ process.on('unhandledRejection', (reason, promise) => { process.exit(1); }); -// Only execute if this is the main module (or in SEA/bundled context where require.main is undefined) -// In bundled builds, both module.parent and require.main are undefined -// In npm package context, check if we're the actual CLI entry point -const isMainModule = - !module.parent && - (require.main === module || - require.main === undefined || - (require.main?.filename?.endsWith('/vibetunnel-cli') ?? false)); +/** + * Print help message with version and usage information + */ +function printHelp(): void { + console.log(`VibeTunnel Server v${VERSION}`); + console.log(''); + console.log('Usage:'); + console.log(' vibetunnel [options] Start VibeTunnel server'); + console.log(' vibetunnel fwd Forward command to session'); + console.log(' vibetunnel systemd [action] Manage systemd service (Linux)'); + console.log(' vibetunnel version Show version'); + console.log(' vibetunnel help Show this help'); + console.log(''); + console.log('Systemd Service Actions:'); + console.log(' install - Install VibeTunnel as systemd service (default)'); + console.log(' uninstall - Remove VibeTunnel systemd service'); + console.log(' status - Check systemd service status'); + console.log(''); + console.log('Examples:'); + console.log(' vibetunnel --port 8080 --no-auth'); + console.log(' vibetunnel fwd abc123 "ls -la"'); + console.log(' vibetunnel systemd'); + console.log(' vibetunnel systemd uninstall'); + console.log(''); + console.log('For more options, run: vibetunnel --help'); +} -if (isMainModule) { - if (process.argv[2] === 'version') { - console.log(`VibeTunnel Server v${VERSION}`); - process.exit(0); - } else if (process.argv[2] === 'fwd') { - startVibeTunnelForward(process.argv.slice(3)).catch((error) => { - logger.error('Fatal error:', error); - closeLogger(); - process.exit(1); - }); - } else { - // Show startup message at INFO level or when debug is enabled - if (verbosityLevel !== undefined && verbosityLevel >= VerbosityLevel.INFO) { - logger.log('Starting VibeTunnel server...'); - } - startVibeTunnelServer(); +/** + * Print version information + */ +function printVersion(): void { + console.log(`VibeTunnel Server v${VERSION}`); +} + +/** + * Handle command forwarding to a session + */ +async function handleForwardCommand(): Promise { + try { + await startVibeTunnelForward(process.argv.slice(3)); + } catch (error) { + logger.error('Fatal error:', error); + closeLogger(); + process.exit(1); } } + +/** + * Handle systemd service installation and management + */ +async function handleSystemdService(): Promise { + try { + // Import systemd installer dynamically to avoid loading it on every startup + const { installSystemdService } = await import('./server/services/systemd-installer.js'); + const action = process.argv[3] || 'install'; + installSystemdService(action); + } catch (error) { + logger.error('Failed to load systemd installer:', error); + closeLogger(); + process.exit(1); + } +} + +/** + * Start the VibeTunnel server with optional startup logging + */ +function handleStartServer(): void { + // Show startup message at INFO level or when debug is enabled + if (verbosityLevel !== undefined && verbosityLevel >= VerbosityLevel.INFO) { + logger.log('Starting VibeTunnel server...'); + } + startVibeTunnelServer(); +} + +/** + * Parse command line arguments and execute appropriate action + */ +async function parseCommandAndExecute(): Promise { + const command = process.argv[2]; + + switch (command) { + case 'version': + printVersion(); + process.exit(0); + break; + + case 'help': + case '--help': + case '-h': + printHelp(); + process.exit(0); + break; + + case 'fwd': + await handleForwardCommand(); + break; + + case 'systemd': + await handleSystemdService(); + break; + + default: + // No command provided - start the server + handleStartServer(); + break; + } +} + +/** + * Check if this module is being run directly (not imported) + */ +function isMainModule(): boolean { + return ( + !module.parent && + (require.main === module || + require.main === undefined || + (require.main?.filename?.endsWith('/vibetunnel-cli') ?? false)) + ); +} + +// Main execution +if (isMainModule()) { + parseCommandAndExecute().catch((error) => { + logger.error('Unhandled error in main execution:', error); + if (error instanceof Error) { + logger.error('Stack trace:', error.stack); + } + closeLogger(); + process.exit(1); + }); +} diff --git a/web/src/server/services/systemd-installer.ts b/web/src/server/services/systemd-installer.ts new file mode 100644 index 00000000..ab8f14df --- /dev/null +++ b/web/src/server/services/systemd-installer.ts @@ -0,0 +1,405 @@ +#!/usr/bin/env node + +import { execSync } from 'node:child_process'; +import { chmodSync, existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +// Colors for output +const RED = '\x1b[0;31m'; +const GREEN = '\x1b[0;32m'; +const BLUE = '\x1b[0;34m'; +const NC = '\x1b[0m'; // No Color + +// Configuration +const SERVICE_NAME = 'vibetunnel'; +const SERVICE_FILE = 'vibetunnel.service'; + +// Get the current user (regular user only, no sudo/root) +function getCurrentUser(): { username: string; home: string } { + const username = process.env.USER || 'unknown'; + const home = process.env.HOME || `/home/${username}`; + + return { username, home }; +} + +// Print colored output +function printInfo(message: string): void { + console.log(`${BLUE}[INFO]${NC} ${message}`); +} + +function printSuccess(message: string): void { + console.log(`${GREEN}[SUCCESS]${NC} ${message}`); +} + +function printError(message: string): void { + console.log(`${RED}[ERROR]${NC} ${message}`); +} + +// Create a stable wrapper script that can find vibetunnel regardless of node version manager +function createVibetunnelWrapper(): string { + const { username, home } = getCurrentUser(); + const wrapperPath = `${home}/.local/bin/vibetunnel-systemd`; + const wrapperContent = `#!/bin/bash +# VibeTunnel Systemd Wrapper Script +# This script finds and executes vibetunnel for user: ${username} + +# Function to log messages +log_info() { + echo "[INFO] $1" >&2 +} + +log_error() { + echo "[ERROR] $1" >&2 +} + +# Set up environment for user ${username} +export HOME="${home}" +export USER="${username}" + +# Try to find vibetunnel in various ways +find_vibetunnel() { + # Method 1: Check if vibetunnel is in PATH + if command -v vibetunnel >/dev/null 2>&1; then + log_info "Found vibetunnel in PATH" + vibetunnel "$@" + return $? + fi + + # Method 2: Check for nvm installations + if [ -d "${home}/.nvm" ]; then + log_info "Checking nvm installation for user ${username}" + export NVM_DIR="${home}/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + if command -v vibetunnel >/dev/null 2>&1; then + log_info "Found vibetunnel via nvm" + vibetunnel "$@" + return $? + fi + fi + + # Method 3: Check for fnm installations + if [ -d "${home}/.local/share/fnm" ] && [ -x "${home}/.local/share/fnm/fnm" ]; then + log_info "Checking fnm installation for user ${username}" + export FNM_DIR="${home}/.local/share/fnm" + export PATH="${home}/.local/share/fnm:$PATH" + export SHELL="/bin/bash" # Force shell for fnm + # Initialize fnm with explicit shell and use the default node version + eval "$("${home}/.local/share/fnm/fnm" env --shell bash)" 2>/dev/null || true + # Try to use the default node version or current version + "${home}/.local/share/fnm/fnm" use default >/dev/null 2>&1 || "${home}/.local/share/fnm/fnm" use current >/dev/null 2>&1 || true + if command -v vibetunnel >/dev/null 2>&1; then + log_info "Found vibetunnel via fnm" + vibetunnel "$@" + return $? + fi + fi + + # Method 4: Check common global npm locations + for npm_bin in "/usr/local/bin/npm" "/usr/bin/npm" "/opt/homebrew/bin/npm"; do + if [ -x "$npm_bin" ]; then + log_info "Trying npm global with $npm_bin" + NPM_PREFIX=$("$npm_bin" config get prefix 2>/dev/null) + if [ -n "$NPM_PREFIX" ] && [ -x "$NPM_PREFIX/bin/vibetunnel" ]; then + log_info "Found vibetunnel via npm global: $NPM_PREFIX/bin/vibetunnel" + "$NPM_PREFIX/bin/vibetunnel" "$@" + return $? + fi + fi + done + + # Method 5: Try to run with node directly using global npm package + for node_bin in "/usr/local/bin/node" "/usr/bin/node" "/opt/homebrew/bin/node"; do + if [ -x "$node_bin" ]; then + for script_path in "/usr/local/lib/node_modules/vibetunnel/dist/cli.js" "/usr/lib/node_modules/vibetunnel/dist/cli.js"; do + if [ -f "$script_path" ]; then + log_info "Running vibetunnel via node: $node_bin $script_path" + "$node_bin" "$script_path" "$@" + return $? + fi + done + fi + done + + log_error "Could not find vibetunnel installation for user ${username}" + log_error "Please ensure vibetunnel is installed globally: npm install -g vibetunnel" + return 1 +} + +# Execute the function with all arguments +find_vibetunnel "$@" +`; + + try { + // Ensure ~/.local/bin directory exists + const localBinDir = `${home}/.local/bin`; + if (!existsSync(localBinDir)) { + mkdirSync(localBinDir, { recursive: true }); + printInfo(`Created directory: ${localBinDir}`); + } + + // Create the wrapper script + writeFileSync(wrapperPath, wrapperContent); + chmodSync(wrapperPath, 0o755); + + printSuccess(`Created wrapper script at ${wrapperPath}`); + return wrapperPath; + } catch (error) { + printError(`Failed to create wrapper script: ${error}`); + process.exit(1); + } +} + +// Verify that vibetunnel is accessible and return wrapper path +function checkVibetunnelAndCreateWrapper(): string { + // First, verify that vibetunnel is actually installed somewhere + try { + const vibetunnelPath = execSync('which vibetunnel', { encoding: 'utf8', stdio: 'pipe' }).trim(); + printInfo(`Found VibeTunnel at: ${vibetunnelPath}`); + } catch (_error) { + printError('VibeTunnel is not installed or not accessible. Please install it first:'); + console.log(' npm install -g vibetunnel'); + process.exit(1); + } + + // Create and return the wrapper script path + return createVibetunnelWrapper(); +} + +// Remove wrapper script during uninstall +function removeVibetunnelWrapper(): void { + const { home } = getCurrentUser(); + const wrapperPath = `${home}/.local/bin/vibetunnel-systemd`; + try { + if (existsSync(wrapperPath)) { + unlinkSync(wrapperPath); + printInfo('Removed wrapper script'); + } + } catch (_error) { + // Ignore errors when removing wrapper + } +} + +// No need to create users or directories - using current user + +// Get the systemd service template +function getServiceTemplate(vibetunnelPath: string): string { + const { home } = getCurrentUser(); + + return `[Unit] +Description=VibeTunnel - Terminal sharing server with web interface +Documentation=https://github.com/amantus-ai/vibetunnel +After=network.target +Wants=network.target + +[Service] +Type=simple +WorkingDirectory=${home} +ExecStart=${vibetunnelPath} --port 4020 --bind 0.0.0.0 +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=${SERVICE_NAME} + +# Environment - preserve user environment for node version managers +Environment=NODE_ENV=production +Environment=VIBETUNNEL_LOG_LEVEL=info +Environment=HOME=%h +Environment=USER=%i + +# Resource limits +LimitNOFILE=65536 +MemoryHigh=512M +MemoryMax=1G + +[Install] +WantedBy=default.target`; +} + +// Install systemd service +function installService(vibetunnelPath: string): void { + printInfo('Installing user systemd service...'); + + const { home } = getCurrentUser(); + const systemdDir = `${home}/.config/systemd/user`; + const serviceContent = getServiceTemplate(vibetunnelPath); + const servicePath = join(systemdDir, SERVICE_FILE); + + try { + // Create user systemd directory if it doesn't exist + mkdirSync(systemdDir, { recursive: true }); + + writeFileSync(servicePath, serviceContent); + chmodSync(servicePath, 0o644); + + // Reload user systemd + execSync('systemctl --user daemon-reload', { stdio: 'pipe' }); + printSuccess('User systemd service installed'); + } catch (error) { + printError(`Failed to install service: ${error}`); + process.exit(1); + } +} + +// Configure service +function configureService(): void { + printInfo('Configuring service...'); + + try { + // Enable the user service + execSync(`systemctl --user enable ${SERVICE_NAME}`, { stdio: 'pipe' }); + printSuccess('User service enabled for automatic startup'); + + // Enable lingering so service starts on boot even when user not logged in + try { + const { username } = getCurrentUser(); + execSync(`loginctl enable-linger ${username}`, { stdio: 'pipe' }); + printSuccess('User lingering enabled - service will start on boot'); + } catch (error) { + printError(`Failed to enable lingering: ${error}`); + printError('Service will only start when user logs in'); + } + } catch (error) { + printError(`Failed to configure service: ${error}`); + process.exit(1); + } +} + +// Display usage instructions +function showUsage(): void { + const { username, home } = getCurrentUser(); + + printSuccess('VibeTunnel systemd service installation completed!'); + console.log(''); + console.log('Usage:'); + console.log(` systemctl --user start ${SERVICE_NAME} # Start the service`); + console.log(` systemctl --user stop ${SERVICE_NAME} # Stop the service`); + console.log(` systemctl --user restart ${SERVICE_NAME} # Restart the service`); + console.log(` systemctl --user status ${SERVICE_NAME} # Check service status`); + console.log(` systemctl --user enable ${SERVICE_NAME} # Enable auto-start (already done)`); + console.log(` systemctl --user disable ${SERVICE_NAME} # Disable auto-start`); + console.log(''); + console.log('Logs:'); + console.log(` journalctl --user -u ${SERVICE_NAME} -f # Follow logs in real-time`); + console.log(` journalctl --user -u ${SERVICE_NAME} # View all logs`); + console.log(''); + console.log('Configuration:'); + console.log(' Service runs on port 4020 by default'); + console.log(' Web interface: http://localhost:4020'); + console.log(` Service runs as user: ${username}`); + console.log(` Working directory: ${home}`); + console.log(` Wrapper script: ${home}/.local/bin/vibetunnel-systemd`); + console.log(''); + console.log(`To customize the service, edit: ${home}/.config/systemd/user/${SERVICE_FILE}`); + console.log( + `Then run: systemctl --user daemon-reload && systemctl --user restart ${SERVICE_NAME}` + ); +} + +// Uninstall function +function uninstallService(): void { + printInfo('Uninstalling VibeTunnel user systemd service...'); + + try { + // Stop and disable user service + try { + execSync(`systemctl --user is-active ${SERVICE_NAME}`, { stdio: 'pipe' }); + execSync(`systemctl --user stop ${SERVICE_NAME}`, { stdio: 'pipe' }); + printInfo('User service stopped'); + } catch (_error) { + // Service not running + } + + try { + execSync(`systemctl --user is-enabled ${SERVICE_NAME}`, { stdio: 'pipe' }); + execSync(`systemctl --user disable ${SERVICE_NAME}`, { stdio: 'pipe' }); + printInfo('User service disabled'); + } catch (_error) { + // Service not enabled + } + + // Remove service file + const { home } = getCurrentUser(); + const systemdDir = `${home}/.config/systemd/user`; + const servicePath = join(systemdDir, SERVICE_FILE); + if (existsSync(servicePath)) { + unlinkSync(servicePath); + printInfo('Service file removed'); + } + + // Reload user systemd + execSync('systemctl --user daemon-reload', { stdio: 'pipe' }); + + // Remove wrapper script + removeVibetunnelWrapper(); + + // Optionally disable lingering (ask user) + const { username } = getCurrentUser(); + printInfo('Note: User lingering is still enabled. To disable:'); + console.log(` loginctl disable-linger ${username}`); + + printSuccess('VibeTunnel user systemd service uninstalled'); + } catch (error) { + printError(`Failed to uninstall service: ${error}`); + process.exit(1); + } +} + +// Check service status +function checkServiceStatus(): void { + try { + const status = execSync(`systemctl --user status ${SERVICE_NAME}`, { encoding: 'utf8' }); + console.log(status); + } catch (error) { + // systemctl status returns non-zero for inactive services, which is normal + if (error instanceof Error && 'stdout' in error) { + console.log(error.stdout); + } else { + printError(`Failed to get service status: ${error}`); + } + } +} + +// Check if running as root and prevent execution +function checkNotRoot(): void { + if (process.getuid && process.getuid() === 0) { + printError('This installer must NOT be run as root!'); + printError('VibeTunnel systemd service should run as a regular user for security.'); + printError('Please run this command as a regular user (without sudo).'); + process.exit(1); + } +} + +// Main installation function +export function installSystemdService(action: string = 'install'): void { + // Prevent running as root for security + checkNotRoot(); + + switch (action) { + case 'install': { + printInfo('Installing VibeTunnel user systemd service...'); + + const wrapperPath = checkVibetunnelAndCreateWrapper(); + installService(wrapperPath); + configureService(); + showUsage(); + break; + } + + case 'uninstall': { + uninstallService(); + break; + } + + case 'status': + checkServiceStatus(); + break; + + default: + console.log('Usage: vibetunnel systemd [install|uninstall|status]'); + console.log(' install - Install VibeTunnel user systemd service (default)'); + console.log(' uninstall - Remove VibeTunnel user systemd service'); + console.log(' status - Check service status'); + process.exit(1); + } +}