#!/bin/sh set -e # Function to handle errors handle_error() { echo "❌ Error: $1" exit 1 } # Function to detect init system detect_init_system() { if command -v systemctl >/dev/null 2>&1 && [ -d /etc/systemd/system ]; then echo "systemd" elif [ -f /sbin/init ] && /sbin/init --version 2>/dev/null | grep -q upstart; then echo "upstart" elif command -v openrc >/dev/null 2>&1 || [ -f /sbin/openrc ]; then echo "openrc" elif [ -d /etc/s6 ] || command -v s6-svc >/dev/null 2>&1; then echo "s6" elif command -v runit >/dev/null 2>&1 || [ -d /etc/runit ]; then echo "runit" elif [ -d /etc/init.d ]; then echo "sysvinit" else echo "unknown" fi } # Function to detect if we're in a container or embedded environment detect_environment() { if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then echo "container" elif [ -f /proc/device-tree/model ] && grep -qi "raspberry\|beagle\|odroid" /proc/device-tree/model 2>/dev/null; then echo "embedded" elif [ "$(uname -m)" = "armv7l" ] || [ "$(uname -m)" = "aarch64" ]; then echo "arm" else echo "standard" fi } # Function to check for Docker prerequisite check_docker_prerequisite() { echo "🔍 Checking for Docker prerequisite conflicts..." # Check if Docker command exists if command -v docker >/dev/null 2>&1; then handle_error "Docker is installed on this system. Please remove Docker before installing KubeSolo as it can interfere with KubeSolo networking. See: https://docs.kubesolo.io/prerequisites" fi # Check if Docker daemon is running if [ -S /var/run/docker.sock ]; then handle_error "Docker daemon appears to be running (socket found at /var/run/docker.sock). Please stop and remove Docker before installing KubeSolo." fi # Check for Docker systemd service if command -v systemctl >/dev/null 2>&1; then if systemctl is-active --quiet docker 2>/dev/null; then handle_error "Docker service is active. Please stop and remove Docker before installing KubeSolo." fi fi echo "✅ No Docker installation detected" } # Function to check hostname RFC 1123 compliance check_hostname_compliance() { echo "🔍 Checking hostname RFC 1123 compliance..." # Get the hostname CURRENT_HOSTNAME=$(hostname 2>/dev/null || echo "") if [ -z "$CURRENT_HOSTNAME" ]; then handle_error "Could not determine hostname. Please ensure hostname is properly configured." fi # Check if hostname contains uppercase letters if echo "$CURRENT_HOSTNAME" | grep -q '[A-Z]'; then handle_error "Hostname '$CURRENT_HOSTNAME' contains uppercase letters. RFC 1123 requires lowercase only. Please change hostname to lowercase." fi # Check RFC 1123 subdomain compliance using grep # Pattern: must start and end with alphanumeric, contain only lowercase alphanumeric, '-', or '.' if ! echo "$CURRENT_HOSTNAME" | grep -qE '^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$'; then handle_error "Hostname '$CURRENT_HOSTNAME' is not RFC 1123 compliant. Must contain only lowercase letters, numbers, hyphens, and dots, and must start and end with an alphanumeric character." fi # Additional check for length (RFC 1123 limit is 63 characters per label) if [ ${#CURRENT_HOSTNAME} -gt 253 ]; then handle_error "Hostname '$CURRENT_HOSTNAME' is too long (${#CURRENT_HOSTNAME} characters). Maximum allowed is 253 characters." fi echo "✅ Hostname '$CURRENT_HOSTNAME' is RFC 1123 compliant" } # Function to check iptables xt_comment module support check_iptables_comment_module() { echo "🔍 Checking iptables xt_comment module support..." # Check if iptables command exists if ! command -v iptables >/dev/null 2>&1; then handle_error "iptables command not found. Please install iptables before proceeding." fi # Test iptables comment module by trying to create a test rule # Use a unique comment to avoid conflicts TEST_COMMENT="kubesolo-test-$$-$(date +%s)" # Try to add a test rule with comment (to a non-existent chain to avoid side effects) if ! iptables -t filter -A KUBESOLO_TEST_CHAIN -m comment --comment "$TEST_COMMENT" -j ACCEPT 2>/dev/null; then # Try to check if the module is available in a different way if [ -d /proc/sys/net ] && [ -r /proc/modules ]; then if ! grep -q "xt_comment" /proc/modules 2>/dev/null && ! modprobe xt_comment 2>/dev/null; then handle_error "iptables xt_comment module is not available. This module is required for KubeSolo networking. Please ensure your kernel has xt_comment support or install iptables-mod-extra package." fi else # Final fallback test - try to list rules with verbose output if ! iptables -t filter -L -v >/dev/null 2>&1; then handle_error "iptables does not appear to be working properly. Please check iptables installation and kernel module support." fi echo "âš ī¸ Could not verify xt_comment module support directly, but iptables appears functional" fi else # Clean up test rule if it was somehow added (shouldn't happen with non-existent chain) iptables -t filter -D KUBESOLO_TEST_CHAIN -m comment --comment "$TEST_COMMENT" -j ACCEPT 2>/dev/null || true fi echo "✅ iptables with xt_comment module support verified" } # Function to stop running KubeSolo processes stop_running_processes() { echo "🔍 Checking for running KubeSolo processes..." # Try to stop service first (graceful shutdown) if command -v systemctl >/dev/null 2>&1; then if systemctl is-active --quiet kubesolo 2>/dev/null; then echo "🛑 Stopping KubeSolo service (systemd)..." systemctl stop kubesolo 2>/dev/null || true sleep 2 fi elif [ -f "/etc/init.d/kubesolo" ]; then if command -v service >/dev/null 2>&1; then if service kubesolo status >/dev/null 2>&1; then echo "🛑 Stopping KubeSolo service (init.d)..." service kubesolo stop 2>/dev/null || true sleep 2 fi fi fi # Find all remaining processes with kubesolo in the command line local pids pids=$(pgrep -f "kubesolo" 2>/dev/null || true) if [ -n "$pids" ]; then echo "🛑 Stopping remaining KubeSolo processes..." for pid in $pids; do if kill -0 "$pid" 2>/dev/null; then if [ -f "/proc/$pid/cmdline" ]; then local cmdline cmdline=$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ' || echo "unknown") echo " Stopping PID $pid: $cmdline" else echo " Stopping PID $pid" fi kill -TERM "$pid" 2>/dev/null || true fi done # Wait a bit for graceful shutdown sleep 2 # Force kill any that are still running pids=$(pgrep -f "kubesolo" 2>/dev/null || true) if [ -n "$pids" ]; then echo "🛑 Force stopping remaining processes..." for pid in $pids; do kill -KILL "$pid" 2>/dev/null || true done sleep 1 fi echo "✅ KubeSolo processes stopped" else echo "✅ No running KubeSolo processes found" fi } # Function to stop processes holding KubeSolo ports stop_port_processes() { echo "🔍 Checking for processes holding KubeSolo ports..." # KubeSolo ports: 2379 (Kine), 6443 (API Server), 10443 (Webhook), 6060 (pprof) local ports="2379 6443 10443 6060" local found_processes=false for port in $ports; do local pids="" local port_name="" case $port in 2379) port_name="Kine (etcd replacement)" ;; 6443) port_name="API Server" ;; 10443) port_name="Webhook" ;; 6060) port_name="pprof server" ;; esac # Try lsof first (more reliable) if command -v lsof >/dev/null 2>&1; then pids=$(lsof -ti ":$port" 2>/dev/null || true) # Fallback to ss elif command -v ss >/dev/null 2>&1; then pids=$(ss -lptn "sport = :$port" 2>/dev/null | grep -oE 'pid=[0-9]+' | cut -d= -f2 | sort -u || true) # Fallback to netstat elif command -v netstat >/dev/null 2>&1; then pids=$(netstat -tlnp 2>/dev/null | grep ":$port " | awk '{print $7}' | cut -d'/' -f1 | grep -E '^[0-9]+$' | sort -u || true) fi if [ -n "$pids" ]; then found_processes=true echo "🛑 Stopping processes holding port $port ($port_name)..." for pid in $pids; do # Check if it's actually a kubesolo-related process local cmdline="" local procname="" local is_kubesolo=false if [ -f "/proc/$pid/cmdline" ]; then cmdline=$(cat "/proc/$pid/cmdline" 2>/dev/null | tr '\0' ' ' || echo "") fi if [ -f "/proc/$pid/comm" ]; then procname=$(cat "/proc/$pid/comm" 2>/dev/null || echo "") fi # Check if it's a kubesolo process if echo "$cmdline" | grep -q "kubesolo" || echo "$procname" | grep -qi "kubesolo"; then is_kubesolo=true fi # For KubeSolo-specific ports, be more aggressive if we can't determine the process # Port 2379 is Kine (KubeSolo-specific), so if something is holding it, it's likely leftover if [ "$is_kubesolo" = "true" ] || ([ "$port" = "2379" ] && [ -z "$cmdline" ]); then echo " Stopping PID $pid" kill -TERM "$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true else echo "âš ī¸ Process $pid is holding port $port but doesn't appear to be KubeSolo-related (skipping)" fi done fi done if [ "$found_processes" = "true" ]; then echo "âŗ Waiting for ports to be released..." sleep 2 echo "✅ Port processes stopped" else echo "✅ No processes found holding KubeSolo ports" fi } # Function to clean up file conflicts cleanup_file_conflicts() { echo "🔍 Checking for file conflicts..." # Use CONFIG_PATH which is set earlier in the script local install_path="/usr/local/bin/kubesolo" local config_path="$CONFIG_PATH" local cleanup_needed=false # Check if binary exists and is in use if [ -f "$install_path" ]; then # Check if file is locked or in use (only if lsof is available) if command -v lsof >/dev/null 2>&1; then local binary_pids binary_pids=$(lsof -t "$install_path" 2>/dev/null || true) if [ -n "$binary_pids" ]; then cleanup_needed=true echo "🛑 Stopping processes using binary $install_path..." for pid in $binary_pids; do kill -TERM "$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true done sleep 1 else echo "â„šī¸ Binary $install_path exists (will be replaced during installation)" fi else # If lsof is not available, just note that binary exists echo "â„šī¸ Binary $install_path exists (will be replaced during installation)" fi fi # Check for socket files that might be in use local socket_files=" $config_path/containerd/containerd.sock $config_path/kine/socket /run/containerd/containerd.sock " for socket_file in $socket_files; do if [ -S "$socket_file" ]; then if command -v lsof >/dev/null 2>&1; then local socket_pids socket_pids=$(lsof -t "$socket_file" 2>/dev/null || true) if [ -n "$socket_pids" ]; then cleanup_needed=true echo "🛑 Stopping processes using socket $socket_file..." for pid in $socket_pids; do kill -TERM "$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true done sleep 1 fi else # If lsof is not available, try to remove socket (it will be recreated) echo "â„šī¸ Socket file $socket_file exists (will be cleaned up)" rm -f "$socket_file" 2>/dev/null || true fi fi done # Clean up PID file local pidfile="/var/run/kubesolo.pid" if [ -f "$pidfile" ]; then local pid pid=$(cat "$pidfile" 2>/dev/null || echo "") if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then cleanup_needed=true echo "🛑 Stopping process from PID file $pidfile (PID: $pid)..." kill -TERM "$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true sleep 1 fi # Remove stale PID file rm -f "$pidfile" 2>/dev/null || true echo "â„šī¸ Cleaned up PID file" fi if [ "$cleanup_needed" = "true" ]; then echo "âŗ Waiting for file handles to be released..." sleep 2 echo "✅ File conflicts resolved" else echo "✅ No file conflicts detected" fi } # Detect OS and architecture OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) case $ARCH in x86_64) ARCH="amd64" ;; aarch64) ARCH="arm64" ;; armv7l) ARCH="arm" ;; riscv64) ARCH="riscv64" ;; *) handle_error "Unsupported architecture: $ARCH" ;; esac # Detect libc type (glibc vs musl) LIBC_SUFFIX="" if [ -f /lib/ld-musl-*.so.1 ] || [ -f /usr/lib/ld-musl-*.so.1 ]; then # Check if musl builds are available for this architecture if [ "$ARCH" = "amd64" ] || [ "$ARCH" = "arm64" ]; then LIBC_SUFFIX="-musl" echo "🔍 Detected musl libc system - will download musl-compatible binary" else handle_error "musl libc detected but musl builds are only available for amd64 and arm64 architectures. Current architecture: $ARCH" fi else echo "🔍 Detected glibc system - will download standard binary" fi # Detect init system and environment INIT_SYSTEM=$(detect_init_system) ENVIRONMENT=$(detect_environment) echo "🔍 Detected init system: $INIT_SYSTEM" echo "🔍 Detected environment: $ENVIRONMENT" # Default configuration from environment variables KUBESOLO_VERSION="${KUBESOLO_VERSION:-v1.0.0}" CONFIG_PATH="${KUBESOLO_PATH:-/var/lib/kubesolo}" APISERVER_EXTRA_SANS="${KUBESOLO_APISERVER_EXTRA_SANS:-}" PORTAINER_EDGE_ID="${KUBESOLO_PORTAINER_EDGE_ID:-}" PORTAINER_EDGE_KEY="${KUBESOLO_PORTAINER_EDGE_KEY:-}" PORTAINER_EDGE_ASYNC="${KUBESOLO_PORTAINER_EDGE_ASYNC:-false}" LOCAL_STORAGE="${KUBESOLO_LOCAL_STORAGE:-false}" DEBUG="${KUBESOLO_DEBUG:-false}" PPROF_SERVER="${KUBESOLO_PPROF_SERVER:-false}" RUN_MODE="${KUBESOLO_RUN_MODE:-service}" # service, foreground, or daemon PROXY="${KUBESOLO_PROXY:-}" # Parse command line arguments for arg in "$@"; do case $arg in --version=*) KUBESOLO_VERSION="${arg#*=}" ;; --path=*) CONFIG_PATH="${arg#*=}" ;; --apiserver-extra-sans=*) APISERVER_EXTRA_SANS="${arg#*=}" ;; --portainer-edge-id=*) PORTAINER_EDGE_ID="${arg#*=}" ;; --portainer-edge-key=*) PORTAINER_EDGE_KEY="${arg#*=}" ;; --portainer-edge-async=*) PORTAINER_EDGE_ASYNC="${arg#*=}" ;; --local-storage=*) LOCAL_STORAGE="${arg#*=}" ;; --debug=*) DEBUG="${arg#*=}" ;; --pprof-server=*) PPROF_SERVER="${arg#*=}" ;; --run-mode=*) RUN_MODE="${arg#*=}" ;; --proxy=*) PROXY="${arg#*=}" ;; --help) echo "Usage: $0 [options]" echo "Options:" echo " --version=VERSION Set KubeSolo version (default: $KUBESOLO_VERSION)" echo " --path=PATH Set configuration path (default: $CONFIG_PATH)" echo " --apiserver-extra-sans=SANS Set additional Subject Alternative Names for the API server" echo " --portainer-edge-id=ID Set Portainer Edge ID" echo " --portainer-edge-key=KEY Set Portainer Edge Key" echo " --portainer-edge-async=true|false Enable Portainer Edge Async (default: $PORTAINER_EDGE_ASYNC)" echo " --local-storage=true|false Enable local storage (default: $LOCAL_STORAGE)" echo " --debug=true|false Enable debug logging (default: $DEBUG)" echo " --pprof-server=true|false Enable pprof server (default: $PPROF_SERVER)" echo " --run-mode=MODE Run mode: service, foreground, or daemon (default: $RUN_MODE)" echo " --proxy=URL Set proxy for HTTP/HTTPS requests" echo " --help Show this help message" echo "" echo "Supported Init Systems: systemd, sysvinit, s6, runit, openrc, upstart" echo "Fallback modes: foreground (manual start), daemon (background process)" exit 0 ;; esac done # Function to check for Docker prerequisite check_docker_prerequisite # Function to check hostname RFC 1123 compliance check_hostname_compliance # Function to check iptables xt_comment module support check_iptables_comment_module # Function to stop running KubeSolo processes stop_running_processes # Function to stop processes holding KubeSolo ports stop_port_processes # Function to clean up file conflicts cleanup_file_conflicts # Service configuration APP_NAME="kubesolo" BIN_URL="https://github.com/portainer/kubesolo/releases/download/$KUBESOLO_VERSION/kubesolo-$KUBESOLO_VERSION-$OS-$ARCH$LIBC_SUFFIX.tar.gz" INSTALL_PATH="/usr/local/bin/$APP_NAME" echo "🔄 Installing $APP_NAME $KUBESOLO_VERSION for $INIT_SYSTEM init system..." # Download and extract the archive TEMP_DIR=$(mktemp -d -p $HOME) || handle_error "Failed to create temporary directory" echo "đŸ“Ĩ Downloading $APP_NAME $KUBESOLO_VERSION..." curl -sfL "$BIN_URL" -o "$TEMP_DIR/kubesolo.tar.gz" || handle_error "Failed to download $APP_NAME from $BIN_URL" echo "đŸ“Ļ Extracting $APP_NAME..." tar -xzf "$TEMP_DIR/kubesolo.tar.gz" -C "$TEMP_DIR" || handle_error "Failed to extract $APP_NAME archive" echo "📝 Installing binary..." mv "$TEMP_DIR/kubesolo" "$INSTALL_PATH" || handle_error "Failed to move binary to $INSTALL_PATH" rm -rf "$TEMP_DIR" chmod +x "$INSTALL_PATH" || handle_error "Failed to set executable permissions on $INSTALL_PATH" # Handle SELinux file contexts if SELinux tools are available if command -v restorecon >/dev/null 2>&1; then echo "🔒 Restoring SELinux contexts for installed binary..." restorecon -v "$INSTALL_PATH" || echo "âš ī¸ Could not restore SELinux context (this may be normal)" fi # Construct command arguments CMD_ARGS="--path=$CONFIG_PATH" if [ -n "$APISERVER_EXTRA_SANS" ]; then CMD_ARGS="$CMD_ARGS --apiserver-extra-sans=$APISERVER_EXTRA_SANS" fi if [ -n "$PORTAINER_EDGE_ID" ]; then CMD_ARGS="$CMD_ARGS --portainer-edge-id=$PORTAINER_EDGE_ID" fi if [ -n "$PORTAINER_EDGE_KEY" ]; then CMD_ARGS="$CMD_ARGS --portainer-edge-key=$PORTAINER_EDGE_KEY" fi if [ "$LOCAL_STORAGE" = "true" ]; then CMD_ARGS="$CMD_ARGS --local-storage=true" fi if [ "$DEBUG" = "true" ]; then CMD_ARGS="$CMD_ARGS --debug=$DEBUG" fi if [ "$PPROF_SERVER" = "true" ]; then CMD_ARGS="$CMD_ARGS --pprof-server=$PPROF_SERVER" fi # Function to generate proxy environment variables generate_proxy_env() { if [ -n "$PROXY" ]; then echo "Environment=\"HTTP_PROXY=$PROXY\"" echo "Environment=\"HTTPS_PROXY=$PROXY\"" echo "Environment=\"NO_PROXY=localhost,127.0.0.1\"" fi } # Function to generate proxy exports for shell scripts generate_proxy_exports() { if [ -n "$PROXY" ]; then echo "export HTTP_PROXY=\"$PROXY\"" echo "export HTTPS_PROXY=\"$PROXY\"" echo "export NO_PROXY=\"localhost,127.0.0.1\"" fi } # Function to generate proxy environment variables for upstart generate_proxy_env_upstart() { if [ -n "$PROXY" ]; then echo "env HTTP_PROXY=\"$PROXY\"" echo "env HTTPS_PROXY=\"$PROXY\"" echo "env NO_PROXY=\"localhost,127.0.0.1\"" fi } # Function to create systemd service create_systemd_service() { SERVICE_PATH="/etc/systemd/system/$APP_NAME.service" cat < "$SERVICE_PATH" || handle_error "Failed to create systemd service file" [Unit] Description=$APP_NAME Service After=network.target [Service] ExecStart=$INSTALL_PATH $CMD_ARGS Restart=always RestartSec=3 OOMScoreAdjust=-500 LimitNOFILE=65535 StandardOutput=journal StandardError=journal $(generate_proxy_env) [Install] WantedBy=multi-user.target EOF systemctl daemon-reexec || handle_error "Failed to reexecute systemd daemon" systemctl daemon-reload || handle_error "Failed to reload systemd daemon" systemctl enable "$APP_NAME" || handle_error "Failed to enable $APP_NAME service" systemctl restart "$APP_NAME" || handle_error "Failed to start $APP_NAME service" echo "✅ $APP_NAME service created and started with systemd" } # Function to create SysV init script create_sysvinit_service() { SERVICE_PATH="/etc/init.d/$APP_NAME" cat < "$SERVICE_PATH" || handle_error "Failed to create SysV init script" #!/bin/sh ### BEGIN INIT INFO # Provides: $APP_NAME # Required-Start: \$network \$local_fs # Required-Stop: \$network \$local_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: $APP_NAME service # Description: KubeSolo single-node Kubernetes distribution ### END INIT INFO $(generate_proxy_exports) DAEMON="$INSTALL_PATH" DAEMON_ARGS="$CMD_ARGS" PIDFILE="/var/run/$APP_NAME.pid" USER="root" . /lib/lsb/init-functions case "\$1" in start) log_daemon_msg "Starting $APP_NAME" start-stop-daemon --start --quiet --pidfile \$PIDFILE --make-pidfile --background --chuid \$USER --exec \$DAEMON -- \$DAEMON_ARGS log_end_msg \$? ;; stop) log_daemon_msg "Stopping $APP_NAME" start-stop-daemon --stop --quiet --pidfile \$PIDFILE RETVAL=\$? [ \$RETVAL -eq 0 ] && rm -f \$PIDFILE log_end_msg \$RETVAL ;; restart) \$0 stop \$0 start ;; status) status_of_proc -p \$PIDFILE \$DAEMON "$APP_NAME" && exit 0 || exit \$? ;; *) echo "Usage: \$0 {start|stop|restart|status}" exit 1 ;; esac exit 0 EOF chmod +x "$SERVICE_PATH" || handle_error "Failed to make SysV init script executable" # Enable service for different runlevels if command -v update-rc.d >/dev/null 2>&1; then update-rc.d "$APP_NAME" defaults || handle_error "Failed to enable $APP_NAME service" elif command -v chkconfig >/dev/null 2>&1; then chkconfig --add "$APP_NAME" || handle_error "Failed to add $APP_NAME service" chkconfig "$APP_NAME" on || handle_error "Failed to enable $APP_NAME service" fi # Start the service - try service command first, fall back to direct init script if command -v service >/dev/null 2>&1; then service "$APP_NAME" start || handle_error "Failed to start $APP_NAME service" else "/etc/init.d/$APP_NAME" start || handle_error "Failed to start $APP_NAME service" fi echo "✅ $APP_NAME service created and started with SysV init" } # Function to create OpenRC service create_openrc_service() { SERVICE_PATH="/etc/init.d/$APP_NAME" cat < "$SERVICE_PATH" || handle_error "Failed to create OpenRC service script" #!/sbin/openrc-run $(generate_proxy_exports) name="$APP_NAME" description="KubeSolo single-node Kubernetes distribution" command="$INSTALL_PATH" command_args="$CMD_ARGS" command_background=true pidfile="/var/run/\${RC_SVCNAME}.pid" command_user="root" depend() { need net after firewall } EOF chmod +x "$SERVICE_PATH" || handle_error "Failed to make OpenRC service script executable" rc-update add "$APP_NAME" default || handle_error "Failed to enable $APP_NAME service" rc-service "$APP_NAME" start || handle_error "Failed to start $APP_NAME service" echo "✅ $APP_NAME service created and started with OpenRC" } # Function to create s6 service create_s6_service() { S6_SERVICE_DIR="/etc/s6/sv/$APP_NAME" mkdir -p "$S6_SERVICE_DIR" || handle_error "Failed to create s6 service directory" cat < "$S6_SERVICE_DIR/run" || handle_error "Failed to create s6 run script" #!/bin/sh $(generate_proxy_exports) exec $INSTALL_PATH $CMD_ARGS EOF chmod +x "$S6_SERVICE_DIR/run" || handle_error "Failed to make s6 run script executable" # Create finish script for proper cleanup cat < "$S6_SERVICE_DIR/finish" #!/bin/sh echo "$APP_NAME service finished" EOF chmod +x "$S6_SERVICE_DIR/finish" # Enable and start service if [ -d /etc/s6/adminsv/default ]; then ln -sf "$S6_SERVICE_DIR" "/etc/s6/adminsv/default/$APP_NAME" 2>/dev/null || true fi if command -v s6-svc >/dev/null 2>&1; then s6-svc -u "$S6_SERVICE_DIR" || handle_error "Failed to start $APP_NAME with s6" fi echo "✅ $APP_NAME service created and started with s6" } # Function to create runit service create_runit_service() { RUNIT_SERVICE_DIR="/etc/runit/sv/$APP_NAME" mkdir -p "$RUNIT_SERVICE_DIR" || handle_error "Failed to create runit service directory" cat < "$RUNIT_SERVICE_DIR/run" || handle_error "Failed to create runit run script" #!/bin/sh $(generate_proxy_exports) exec $INSTALL_PATH $CMD_ARGS EOF chmod +x "$RUNIT_SERVICE_DIR/run" || handle_error "Failed to make runit run script executable" # Enable service if [ -d /var/service ]; then ln -sf "$RUNIT_SERVICE_DIR" "/var/service/$APP_NAME" || handle_error "Failed to enable $APP_NAME service" elif [ -d /etc/runit/runsvdir/default ]; then ln -sf "$RUNIT_SERVICE_DIR" "/etc/runit/runsvdir/default/$APP_NAME" || handle_error "Failed to enable $APP_NAME service" fi echo "✅ $APP_NAME service created and started with runit" } # Function to create upstart service create_upstart_service() { SERVICE_PATH="/etc/init/$APP_NAME.conf" cat < "$SERVICE_PATH" || handle_error "Failed to create upstart service file" description "$APP_NAME service" author "KubeSolo" start on runlevel [2345] stop on runlevel [!2345] respawn respawn limit 10 5 $(generate_proxy_env_upstart) exec $INSTALL_PATH $CMD_ARGS EOF initctl reload-configuration || handle_error "Failed to reload upstart configuration" initctl start "$APP_NAME" || handle_error "Failed to start $APP_NAME service" echo "✅ $APP_NAME service created and started with upstart" } # Function to run in foreground mode run_foreground() { echo "🚀 Starting $APP_NAME in foreground mode..." echo "📝 Command: $INSTALL_PATH $CMD_ARGS" echo "âš ī¸ Press Ctrl+C to stop the service" echo "💡 To run in background, use: nohup $INSTALL_PATH $CMD_ARGS > /var/log/$APP_NAME.log 2>&1 &" # Set proxy environment variables if configured if [ -n "$PROXY" ]; then export HTTP_PROXY="$PROXY" export HTTPS_PROXY="$PROXY" export NO_PROXY="localhost,127.0.0.1" fi eval "exec \"$INSTALL_PATH\" $CMD_ARGS" } # Function to run as daemon run_daemon() { PIDFILE="/var/run/$APP_NAME.pid" LOGFILE="/var/log/$APP_NAME.log" echo "🚀 Starting $APP_NAME as daemon..." # Create log directory if it doesn't exist mkdir -p "$(dirname "$LOGFILE")" || handle_error "Failed to create log directory" # Set proxy environment variables if configured if [ -n "$PROXY" ]; then export HTTP_PROXY="$PROXY" export HTTPS_PROXY="$PROXY" export NO_PROXY="localhost,127.0.0.1" fi # Start daemon - use eval to properly handle arguments eval "nohup \"$INSTALL_PATH\" $CMD_ARGS > \"$LOGFILE\" 2>&1 &" echo $! > "$PIDFILE" || handle_error "Failed to write PID file" echo "✅ $APP_NAME started as daemon (PID: $(cat "$PIDFILE"))" echo "📋 Logs: tail -f $LOGFILE" echo "🛑 Stop: kill \$(cat $PIDFILE)" } # Main service creation logic echo "📝 Setting up $APP_NAME service..." case "$RUN_MODE" in "foreground") run_foreground ;; "daemon") run_daemon ;; "service"|*) case "$INIT_SYSTEM" in "systemd") create_systemd_service ;; "sysvinit") create_sysvinit_service ;; "openrc") create_openrc_service ;; "s6") create_s6_service ;; "runit") create_runit_service ;; "upstart") create_upstart_service ;; "unknown"|*) echo "âš ī¸ Unknown or unsupported init system: $INIT_SYSTEM" echo "🔄 Falling back to daemon mode..." run_daemon ;; esac ;; esac # Wait for kubesolo to start and generate kubeconfig if [ "$RUN_MODE" != "foreground" ]; then echo "📋 Service status and logs:" case "$INIT_SYSTEM" in "systemd") echo " Status: systemctl status $APP_NAME" echo " Logs: journalctl -u $APP_NAME -f" ;; "sysvinit") if command -v service >/dev/null 2>&1; then echo " Status: service $APP_NAME status" else echo " Status: /etc/init.d/$APP_NAME status" fi echo " Logs: tail -f /var/log/syslog | grep $APP_NAME" ;; "openrc") echo " Status: rc-service $APP_NAME status" echo " Logs: tail -f /var/log/messages | grep $APP_NAME" ;; *) echo " Logs: tail -f /var/log/$APP_NAME.log" ;; esac fi # Check for kubectl and merge kubeconfig (same as original) KUBECTL_PATH="" if command -v kubectl >/dev/null 2>&1; then KUBECTL_PATH=$(command -v kubectl) fi if [ -n "$KUBECTL_PATH" ] && [ -x "$KUBECTL_PATH" ] && [ "$RUN_MODE" != "foreground" ]; then echo "🔍 Detected kubectl installation at $KUBECTL_PATH" # Detect real user when running under sudo REAL_USER="$USER" REAL_HOME="$HOME" REAL_UID="" REAL_GID="" if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then REAL_USER="$SUDO_USER" REAL_UID="$SUDO_UID" REAL_GID="$SUDO_GID" # Get the real user's home directory REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6) if [ -z "$REAL_HOME" ]; then REAL_HOME="/home/$SUDO_USER" fi echo "🔍 Detected sudo usage - using real user: $REAL_USER (home: $REAL_HOME)" fi # Wait for kubesolo to generate the kubeconfig echo "âŗ Waiting for kubesolo to generate kubeconfig..." i=1 while [ $i -le 30 ]; do if [ -f "$CONFIG_PATH/pki/admin/admin.kubeconfig" ]; then break fi if [ $i -eq 30 ]; then echo "âš ī¸ Kubeconfig not found in $CONFIG_PATH/pki/admin/admin.kubeconfig after waiting" exit 0 fi sleep 1 i=$((i + 1)) done if [ -f "$CONFIG_PATH/pki/admin/admin.kubeconfig" ]; then echo "🔄 Merging kubeconfig..." # Create backup of existing kubeconfig if [ -f "$REAL_HOME/.kube/config" ]; then cp "$REAL_HOME/.kube/config" "$REAL_HOME/.kube/config.backup-$(date +%Y%m%d%H%M%S)" || handle_error "Failed to backup existing kubeconfig" fi # Create .kube directory if it doesn't exist mkdir -p "$REAL_HOME/.kube" || handle_error "Failed to create .kube directory" # Merge the configs export KUBECONFIG="$REAL_HOME/.kube/config:$CONFIG_PATH/pki/admin/admin.kubeconfig" if "$KUBECTL_PATH" config view --flatten > "$REAL_HOME/.kube/config.tmp" 2>/dev/null; then mv "$REAL_HOME/.kube/config.tmp" "$REAL_HOME/.kube/config" || handle_error "Failed to update kubeconfig" echo "✅ Kubeconfig merged successfully" echo "📝 Your existing kubeconfig has been backed up with timestamp" else echo "âš ī¸ Failed to merge kubeconfig, copying KubeSolo config as default" cp "$CONFIG_PATH/pki/admin/admin.kubeconfig" "$REAL_HOME/.kube/config" || handle_error "Failed to copy kubeconfig" fi unset KUBECONFIG # Fix ownership if we're running under sudo if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ] && [ -n "$REAL_UID" ] && [ -n "$REAL_GID" ]; then echo "🔧 Fixing kubeconfig ownership for user $REAL_USER..." chown -R "$REAL_UID:$REAL_GID" "$REAL_HOME/.kube" || echo "âš ī¸ Could not fix kubeconfig ownership (this may be normal)" fi echo "📁 Kubeconfig location: $REAL_HOME/.kube/config" fi else if [ "$RUN_MODE" != "foreground" ]; then echo "â„šī¸ kubectl not found, skipping kubeconfig merge" echo "💡 To use kubectl, please install it first: https://kubernetes.io/docs/tasks/tools/install-kubectl/" echo "📁 Kubeconfig location: $CONFIG_PATH/pki/admin/admin.kubeconfig" fi fi echo "✅ $APP_NAME installation completed!" # Exit with success code exit 0