Add systemd service support for Linux (#426)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: hewigovens <360470+hewigovens@users.noreply.github.com>
This commit is contained in:
Peter Steinberger 2025-07-20 11:48:29 +02:00 committed by GitHub
parent 337ef43b00
commit 12e6c6d61c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 921 additions and 25 deletions

View file

@ -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"
]
},
{

View file

@ -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

383
web/docs/systemd.md Normal file
View file

@ -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

View file

@ -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 <session-id> <command> 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<void> {
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<void> {
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<void> {
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);
});
}

View file

@ -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);
}
}