diff --git a/ansible/inventory.ini b/ansible/inventory.ini index af5c765..c6fefdd 100644 --- a/ansible/inventory.ini +++ b/ansible/inventory.ini @@ -5,4 +5,7 @@ mennos-desktop ansible_connection=local [servers] mennos-vps ansible_connection=local mennos-server ansible_connection=local -mennos-rtlsdr-pc ansible_connection=local \ No newline at end of file +mennos-rtlsdr-pc ansible_connection=local + +[wsl] +mennos-desktopw ansible_connection=local diff --git a/ansible/playbook.yml b/ansible/playbook.yml index e8b405e..23954fb 100644 --- a/ansible/playbook.yml +++ b/ansible/playbook.yml @@ -2,18 +2,18 @@ - name: Configure all hosts hosts: all handlers: - - name: Import handler tasks - ansible.builtin.import_tasks: handlers/main.yml + - name: Import handler tasks + ansible.builtin.import_tasks: handlers/main.yml gather_facts: true tasks: - - name: Include global tasks - ansible.builtin.import_tasks: tasks/global/global.yml + - name: Include global tasks + ansible.builtin.import_tasks: tasks/global/global.yml - - name: Include workstation tasks - ansible.builtin.import_tasks: tasks/workstations/workstation.yml - when: inventory_hostname in ['mennos-laptop', 'mennos-desktop'] + - name: Include workstation tasks + ansible.builtin.import_tasks: tasks/workstations/workstation.yml + when: inventory_hostname in ['mennos-laptop', 'mennos-desktop'] - - name: Include server tasks - ansible.builtin.import_tasks: tasks/servers/server.yml - when: inventory_hostname in ['mennos-vps', 'mennos-server', 'mennos-rtlsdr-pc'] + - name: Include server tasks + ansible.builtin.import_tasks: tasks/servers/server.yml + when: inventory_hostname in ['mennos-vps', 'mennos-server', 'mennos-rtlsdr-pc', 'mennos-desktopw'] diff --git a/ansible/tasks/global/utils/smart-ssh/smart-ssh.go b/ansible/tasks/global/utils/smart-ssh/smart-ssh.go index 2a85e70..7999642 100644 --- a/ansible/tasks/global/utils/smart-ssh/smart-ssh.go +++ b/ansible/tasks/global/utils/smart-ssh/smart-ssh.go @@ -30,10 +30,10 @@ type LoggingConfig struct { // SmartAlias represents a smart SSH alias configuration type SmartAlias struct { - Primary string `yaml:"primary"` // SSH config host to use when local - Fallback string `yaml:"fallback"` // SSH config host to use when remote + Primary string `yaml:"primary"` // SSH config host to use when local + Fallback string `yaml:"fallback"` // SSH config host to use when remote CheckHost string `yaml:"check_host"` // IP to ping for connectivity test - Timeout string `yaml:"timeout"` // Ping timeout (default: "2s") + Timeout string `yaml:"timeout"` // Ping timeout (default: "2s") } // TunnelDefinition represents a tunnel configuration @@ -47,36 +47,39 @@ type TunnelDefinition struct { // TunnelState represents runtime state of an active tunnel type TunnelState struct { - Name string `json:"name"` - Source string `json:"source"` // "config" or "adhoc" - Type string `json:"type"` // local, remote, dynamic - LocalPort int `json:"local_port"` - RemoteHost string `json:"remote_host"` - RemotePort int `json:"remote_port"` - SSHHost string `json:"ssh_host"` - SSHHostResolved string `json:"ssh_host_resolved"` // After smart alias resolution - PID int `json:"pid"` - Status string `json:"status"` - StartedAt time.Time `json:"started_at"` - LastSeen time.Time `json:"last_seen"` - CommandLine string `json:"command_line"` + Name string `json:"name"` + Source string `json:"source"` // "config" or "adhoc" + Type string `json:"type"` // local, remote, dynamic + LocalPort int `json:"local_port"` + RemoteHost string `json:"remote_host"` + RemotePort int `json:"remote_port"` + SSHHost string `json:"ssh_host"` + SSHHostResolved string `json:"ssh_host_resolved"` // After smart alias resolution + PID int `json:"pid"` + Status string `json:"status"` + StartedAt time.Time `json:"started_at"` + LastSeen time.Time `json:"last_seen"` + CommandLine string `json:"command_line"` } // Config represents the YAML configuration structure type Config struct { - Logging LoggingConfig `yaml:"logging"` - SmartAliases map[string]SmartAlias `yaml:"smart_aliases"` - Tunnels map[string]TunnelDefinition `yaml:"tunnels"` + Logging LoggingConfig `yaml:"logging"` + SmartAliases map[string]SmartAlias `yaml:"smart_aliases"` + Tunnels map[string]TunnelDefinition `yaml:"tunnels"` } const ( - realSSHPath = "/usr/bin/ssh" + defaultSSHPath = "/usr/bin/ssh" + wslSSHPath = "ssh.exe" + wslDetectPath = "/mnt/c/Windows/System32/cmd.exe" ) var ( - configDir string - tunnelsDir string - config *Config + configDir string + tunnelsDir string + config *Config + sshPath string // Will be set based on WSL2 detection // Global flags tunnelMode bool @@ -92,10 +95,10 @@ var ( ) var rootCmd = &cobra.Command{ - Use: "ssh", - Short: "Smart SSH utility with tunnel management", - Long: "A transparent SSH wrapper that provides smart alias resolution and background tunnel management", - Run: handleSSH, + Use: "ssh", + Short: "Smart SSH utility with tunnel management", + Long: "A transparent SSH wrapper that provides smart alias resolution and background tunnel management", + Run: handleSSH, DisableFlagParsing: true, } @@ -103,13 +106,16 @@ var tunnelCmd = &cobra.Command{ Use: "tunnel [tunnel-name]", Short: "Manage background SSH tunnels", Long: "Create, list, and manage persistent SSH tunnels in the background", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { handleTunnelManual(append([]string{"--tunnel"}, args...)) }, - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(1), } func init() { + // Detect and set SSH path based on environment (WSL2 vs native Linux) + sshPath = detectSSHPath() + // Initialize config directory homeDir, err := os.UserHomeDir() if err != nil { @@ -141,6 +147,13 @@ func init() { // Initialize logging initLogging(config.Logging) + // Log SSH path detection (after logging is initialized) + if isWSL2() { + log.Debug().Str("ssh_path", sshPath).Msg("WSL2 detected, using Windows SSH") + } else { + log.Debug().Str("ssh_path", sshPath).Msg("Native Linux environment, using Linux SSH") + } + // Global flags rootCmd.PersistentFlags().BoolVarP(&tunnelMode, "tunnel", "T", false, "Enable tunnel mode") rootCmd.Flags().BoolVarP(&tunnelOpen, "open", "O", false, "Open a tunnel") @@ -169,6 +182,22 @@ func init() { } } +// detectSSHPath determines the correct SSH binary path based on the environment +func detectSSHPath() string { + if isWSL2() { + // In WSL2, use Windows SSH + return wslSSHPath + } + // Default to Linux SSH + return defaultSSHPath +} + +// isWSL2 checks if we're running in WSL2 by looking for Windows System32 +func isWSL2() bool { + _, err := os.Stat(wslDetectPath) + return err == nil +} + func main() { // Check if this is a tunnel command first args := os.Args[1:] @@ -563,7 +592,7 @@ func openTunnel(name string) error { log.Debug().Strs("command", cmdArgs).Msg("Starting SSH tunnel") // Start SSH process - cmd := exec.Command(realSSHPath, cmdArgs[1:]...) + cmd := exec.Command(sshPath, cmdArgs[1:]...) // Capture stderr to see any SSH errors var stderr bytes.Buffer @@ -708,7 +737,9 @@ func createAdhocTunnel() (TunnelDefinition, error) { } func buildSSHCommand(tunnel TunnelDefinition, sshHost string) []string { - args := []string{"ssh", "-f", "-N"} + // Use the detected SSH path basename for the command + sshBinary := filepath.Base(sshPath) + args := []string{sshBinary, "-f", "-N"} switch tunnel.Type { case "local": @@ -1056,18 +1087,37 @@ func findSSHProcessByPort(port int) int { // executeRealSSH executes the real SSH binary with given arguments func executeRealSSH(args []string) { - // Check if real SSH exists - if _, err := os.Stat(realSSHPath); os.IsNotExist(err) { - log.Error().Str("path", realSSHPath).Msg("Real SSH binary not found") - fmt.Fprintf(os.Stderr, "Error: Real SSH binary not found at %s\n", realSSHPath) + log.Debug().Str("ssh_path", sshPath).Strs("args", args).Msg("Executing real SSH") + + // In WSL2, we need to use exec.Command instead of syscall.Exec for Windows binaries + if isWSL2() { + cmd := exec.Command(sshPath, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + os.Exit(exitErr.ExitCode()) + } + log.Error().Err(err).Msg("Failed to execute SSH") + fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err) + os.Exit(1) + } + os.Exit(0) + } + + // For native Linux, check if SSH exists + if _, err := os.Stat(sshPath); os.IsNotExist(err) { + log.Error().Str("path", sshPath).Msg("Real SSH binary not found") + fmt.Fprintf(os.Stderr, "Error: Real SSH binary not found at %s\n", sshPath) os.Exit(1) } - log.Debug().Str("ssh_path", realSSHPath).Strs("args", args).Msg("Executing real SSH") - - // Execute the real SSH binary - // Using syscall.Exec to replace current process (like exec in shell) - err := syscall.Exec(realSSHPath, append([]string{"ssh"}, args...), os.Environ()) + // Execute the real SSH binary using syscall.Exec (Linux only) + // This replaces the current process (like exec in shell) + err := syscall.Exec(sshPath, append([]string{"ssh"}, args...), os.Environ()) if err != nil { log.Error().Err(err).Msg("Failed to execute SSH") fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err) diff --git a/ansible/tasks/servers/server.yml b/ansible/tasks/servers/server.yml index c1e1a5c..b21dc58 100644 --- a/ansible/tasks/servers/server.yml +++ b/ansible/tasks/servers/server.yml @@ -1,161 +1,163 @@ --- - name: Server setup block: - - name: Ensure openssh-server is installed on Arch-based systems - ansible.builtin.package: - name: openssh - state: present - when: ansible_pkg_mgr == 'pacman' + - name: Ensure openssh-server is installed on Arch-based systems + ansible.builtin.package: + name: openssh + state: present + when: ansible_pkg_mgr == 'pacman' and 'microsoft-standard-WSL2' not in ansible_kernel + become: true - - name: Ensure openssh-server is installed on non-Arch systems - ansible.builtin.package: - name: openssh-server - state: present - when: ansible_pkg_mgr != 'pacman' + - name: Ensure openssh-server is installed on non-Arch systems + ansible.builtin.package: + name: openssh-server + state: present + when: ansible_pkg_mgr != 'pacman' and 'microsoft-standard-WSL2' not in ansible_kernel + become: true - - name: Ensure Borg is installed on Arch-based systems - ansible.builtin.package: - name: borg - state: present - become: true - when: ansible_pkg_mgr == 'pacman' + - name: Ensure Borg is installed on Arch-based systems + ansible.builtin.package: + name: borg + state: present + become: true + when: ansible_pkg_mgr == 'pacman' - - name: Ensure Borg is installed on Debian/Ubuntu systems - ansible.builtin.package: - name: borgbackup - state: present - become: true - when: ansible_pkg_mgr != 'pacman' + - name: Ensure Borg is installed on Debian/Ubuntu systems + ansible.builtin.package: + name: borgbackup + state: present + become: true + when: ansible_pkg_mgr != 'pacman' - - name: Include JuiceFS tasks - ansible.builtin.include_tasks: juicefs.yml - tags: - - juicefs + - name: Include JuiceFS tasks + ansible.builtin.include_tasks: juicefs.yml + tags: + - juicefs - - name: Include Dynamic DNS tasks - ansible.builtin.include_tasks: dynamic-dns.yml - tags: - - dynamic-dns + - name: Include Dynamic DNS tasks + ansible.builtin.include_tasks: dynamic-dns.yml + tags: + - dynamic-dns - - name: Include Borg Backup tasks - ansible.builtin.include_tasks: borg-backup.yml - tags: - - borg-backup + - name: Include Borg Backup tasks + ansible.builtin.include_tasks: borg-backup.yml + tags: + - borg-backup - - name: Include Borg Local Sync tasks - ansible.builtin.include_tasks: borg-local-sync.yml - tags: - - borg-local-sync + - name: Include Borg Local Sync tasks + ansible.builtin.include_tasks: borg-local-sync.yml + tags: + - borg-local-sync - - name: System performance optimizations - ansible.posix.sysctl: - name: "{{ item.name }}" - value: "{{ item.value }}" - state: present - reload: true - become: true - loop: - - { name: "fs.file-max", value: "2097152" } # Max open files for the entire system - - { name: "vm.max_map_count", value: "16777216" } # Max memory map areas a process can have - - { name: "vm.swappiness", value: "10" } # Controls how aggressively the kernel swaps out memory - - { name: "vm.vfs_cache_pressure", value: "50" } # Controls kernel's tendency to reclaim memory for directory/inode caches - - { name: "net.core.somaxconn", value: "65535" } # Max pending connections for a listening socket - - { name: "net.core.netdev_max_backlog", value: "65535" } # Max packets queued on network interface input - - { name: "net.ipv4.tcp_fin_timeout", value: "30" } # How long sockets stay in FIN-WAIT-2 state - - { name: "net.ipv4.tcp_tw_reuse", value: "1" } # Allows reusing TIME_WAIT sockets for new outgoing connections + - name: System performance optimizations + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + state: present + reload: true + become: true + loop: + - { name: "fs.file-max", value: "2097152" } # Max open files for the entire system + - { name: "vm.max_map_count", value: "16777216" } # Max memory map areas a process can have + - { name: "vm.swappiness", value: "10" } # Controls how aggressively the kernel swaps out memory + - { name: "vm.vfs_cache_pressure", value: "50" } # Controls kernel's tendency to reclaim memory for directory/inode caches + - { name: "net.core.somaxconn", value: "65535" } # Max pending connections for a listening socket + - { name: "net.core.netdev_max_backlog", value: "65535" } # Max packets queued on network interface input + - { name: "net.ipv4.tcp_fin_timeout", value: "30" } # How long sockets stay in FIN-WAIT-2 state + - { name: "net.ipv4.tcp_tw_reuse", value: "1" } # Allows reusing TIME_WAIT sockets for new outgoing connections - - name: Include service tasks - ansible.builtin.include_tasks: "services/{{ item.name }}/{{ item.name }}.yml" - loop: "{{ services | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list if specific_service is not defined else services | selectattr('name', 'equalto', specific_service) | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list }}" - loop_control: - label: "{{ item.name }}" - tags: - - services - - always + - name: Include service tasks + ansible.builtin.include_tasks: "services/{{ item.name }}/{{ item.name }}.yml" + loop: "{{ services | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list if specific_service is not defined else services | selectattr('name', 'equalto', specific_service) | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list }}" + loop_control: + label: "{{ item.name }}" + tags: + - services + - always vars: - services: - - name: dashy - enabled: true - hosts: - - mennos-server - - name: gitea - enabled: true - hosts: - - mennos-server - - name: factorio - enabled: true - hosts: - - mennos-server - - name: dozzle - enabled: true - hosts: - - mennos-server - - name: beszel - enabled: true - hosts: - - mennos-server - - name: caddy - enabled: true - hosts: - - mennos-server - - name: golink - enabled: true - hosts: - - mennos-server - - name: immich - enabled: true - hosts: - - mennos-server - - name: plex - enabled: true - hosts: - - mennos-server - - name: tautulli - enabled: true - hosts: - - mennos-server - - name: downloaders - enabled: true - hosts: - - mennos-server - - name: wireguard - enabled: true - hosts: - - mennos-server - - name: nextcloud - enabled: true - hosts: - - mennos-server - - name: cloudreve - enabled: true - hosts: - - mennos-server - - name: echoip - enabled: true - hosts: - - mennos-server - - name: arr-stack - enabled: true - hosts: - - mennos-server - - name: home-assistant - enabled: true - hosts: - - mennos-server - - name: privatebin - enabled: true - hosts: - - mennos-server - - name: unifi-network-application - enabled: true - hosts: - - mennos-server - - name: avorion - enabled: false - hosts: - - mennos-server - - name: sathub - enabled: true - hosts: - - mennos-server + services: + - name: dashy + enabled: true + hosts: + - mennos-server + - name: gitea + enabled: true + hosts: + - mennos-server + - name: factorio + enabled: true + hosts: + - mennos-server + - name: dozzle + enabled: true + hosts: + - mennos-server + - name: beszel + enabled: true + hosts: + - mennos-server + - name: caddy + enabled: true + hosts: + - mennos-server + - name: golink + enabled: true + hosts: + - mennos-server + - name: immich + enabled: true + hosts: + - mennos-server + - name: plex + enabled: true + hosts: + - mennos-server + - name: tautulli + enabled: true + hosts: + - mennos-server + - name: downloaders + enabled: true + hosts: + - mennos-server + - name: wireguard + enabled: true + hosts: + - mennos-server + - name: nextcloud + enabled: true + hosts: + - mennos-server + - name: cloudreve + enabled: true + hosts: + - mennos-server + - name: echoip + enabled: true + hosts: + - mennos-server + - name: arr-stack + enabled: true + hosts: + - mennos-server + - name: home-assistant + enabled: true + hosts: + - mennos-server + - name: privatebin + enabled: true + hosts: + - mennos-server + - name: unifi-network-application + enabled: true + hosts: + - mennos-server + - name: avorion + enabled: false + hosts: + - mennos-server + - name: sathub + enabled: true + hosts: + - mennos-server diff --git a/bin/actions/help.py b/bin/actions/help.py index c5315f5..1455f25 100755 --- a/bin/actions/help.py +++ b/bin/actions/help.py @@ -26,6 +26,7 @@ def main(): printfe("red", f"Error reading help file: {e}") return 1 + print(help_text) println(" ", "cyan") return 0 diff --git a/bin/dotf b/bin/dotf index 2cfae88..84b13e1 100755 --- a/bin/dotf +++ b/bin/dotf @@ -5,10 +5,12 @@ import signal import subprocess import sys + def signal_handler(sig, frame): - print('Exiting.') + print("Exiting.") sys.exit(0) + signal.signal(signal.SIGINT, signal_handler) # Script constants @@ -22,43 +24,54 @@ from helpers.functions import printfe, ensure_dependencies ensure_dependencies() + def run_script(script_path, args): """Run an action script with the given arguments""" if not os.path.isfile(script_path) or not os.access(script_path, os.X_OK): printfe("red", f"Error: Script not found or not executable: {script_path}") return 1 - result = subprocess.run([script_path] + args, env={**os.environ, "DOTFILES_PATH": DOTFILES_PATH}) + result = subprocess.run( + [script_path] + args, env={**os.environ, "DOTFILES_PATH": DOTFILES_PATH} + ) return result.returncode + def update(args): """Run the update action""" return run_script(f"{DOTFILES_BIN}/actions/update.py", args) + def hello(args): """Run the hello action""" return run_script(f"{DOTFILES_BIN}/actions/hello.py", args) + def help(args): """Run the help action""" return run_script(f"{DOTFILES_BIN}/actions/help.py", args) + def service(args): """Run the service/docker action""" return run_script(f"{DOTFILES_BIN}/actions/service.py", args) + def lint(args): """Run the lint action""" return run_script(f"{DOTFILES_BIN}/actions/lint.py", args) + def timers(args): """Run the timers action""" return run_script(f"{DOTFILES_BIN}/actions/timers.py", args) + def source(args): """Run the source action""" return run_script(f"{DOTFILES_BIN}/actions/source.py", args) + def ensure_git_hooks(): """Ensure git hooks are correctly set up""" hooks_dir = os.path.join(DOTFILES_ROOT, ".git/hooks") @@ -66,14 +79,19 @@ def ensure_git_hooks(): # Validate target directory exists if not os.path.isdir(target_link): - printfe("red", f"Error: Git hooks source directory does not exist: {target_link}") + printfe( + "red", f"Error: Git hooks source directory does not exist: {target_link}" + ) return 1 # Handle existing symlink if os.path.islink(hooks_dir): current_link = os.readlink(hooks_dir) if current_link != target_link: - printfe("yellow", "Incorrect git hooks symlink found. Removing and recreating...") + printfe( + "yellow", + "Incorrect git hooks symlink found. Removing and recreating...", + ) os.remove(hooks_dir) else: return 0 @@ -82,6 +100,7 @@ def ensure_git_hooks(): if os.path.isdir(hooks_dir) and not os.path.islink(hooks_dir): printfe("yellow", "Removing existing hooks directory...") import shutil + shutil.rmtree(hooks_dir) # Create new symlink @@ -93,6 +112,7 @@ def ensure_git_hooks(): printfe("red", f"Failed to create git hooks symlink: {e}") return 1 + def main(): # Ensure we're in the correct directory if not os.path.isdir(DOTFILES_ROOT): @@ -114,13 +134,42 @@ def main(): "service": service, "lint": lint, "timers": timers, - "source": source + "source": source, } if command in commands: return commands[command](args) else: + # For invalid commands, show error after logo + if command != "help": + from helpers.functions import logo + + logo(continue_after=True) + print() + printfe("red", f"✗ Error: Unknown command '{command}'") + + # Provide helpful hints for common mistakes + if command == "ls": + printfe("yellow", " Hint: Did you mean 'dotf service ls'?") + elif command == "list": + printfe("yellow", " Hint: Did you mean 'dotf service list'?") + + print() + # Now print help text without logo + dotfiles_path = os.environ.get( + "DOTFILES_PATH", os.path.expanduser("~/.dotfiles") + ) + try: + with open( + f"{dotfiles_path}/bin/resources/help.txt", "r", encoding="utf-8" + ) as f: + print(f.read()) + except OSError as e: + printfe("red", f"Error reading help file: {e}") + return 1 + return 1 return help([]) + if __name__ == "__main__": sys.exit(main()) diff --git a/config/bash.nix b/config/bash.nix index 063eb1c..eda3ccb 100644 --- a/config/bash.nix +++ b/config/bash.nix @@ -137,6 +137,11 @@ bind -x '"\C-r": fzf_history_search' fi + # In case this is WSL, let's add various Windows executables as aliases + if [ -f "/mnt/c/Windows/System32/cmd.exe" ]; then + alias ssh-add="ssh-add.exe" + fi + # Display welcome message for interactive shells if [ -t 1 ]; then command -v helloworld &> /dev/null && helloworld @@ -190,10 +195,6 @@ # Kubernetes aliases "kubectl" = "minikube kubectl --"; - # Editor aliases - "zeditor" = "${config.home.homeDirectory}/.local/bin/zed"; - "zed" = "${config.home.homeDirectory}/.local/bin/zed"; - # SSH alias "ssh" = "${config.home.homeDirectory}/.local/bin/smart-ssh"; diff --git a/flake.nix b/flake.nix index f8b0671..040f361 100644 --- a/flake.nix +++ b/flake.nix @@ -45,6 +45,7 @@ "mennos-server" = mkHomeConfig "x86_64-linux" "mennos-server" true; "mennos-rtlsdr-pc" = mkHomeConfig "x86_64-linux" "mennos-rtlsdr-pc" true; "mennos-laptop" = mkHomeConfig "x86_64-linux" "mennos-laptop" false; + "mennos-desktopw" = mkHomeConfig "x86_64-linux" "mennos-desktopw" true; }; }; } diff --git a/setup.sh b/setup.sh index b70abd9..44d9cf2 100755 --- a/setup.sh +++ b/setup.sh @@ -166,6 +166,13 @@ validate_hostname() { return 0 } +is_wsl() { + if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null; then + return 0 + fi + return 1 +} + update_home_manager_flake() { local hostname="$1" local isServer="$2" @@ -290,7 +297,15 @@ prepare_hostname() { fi log_info "Setting hostname to $hostname..." - sudo hostnamectl set-hostname "$hostname" || die "Failed to set hostname" + + # WSL doesn't support hostnamectl reliably, use /etc/hostname instead + if is_wsl; then + log_info "Detected WSL environment, using alternative hostname method..." + echo "$hostname" | sudo tee /etc/hostname > /dev/null || die "Failed to set hostname" + sudo hostname "$hostname" || log_warning "Failed to set hostname for current session (will take effect on restart)" + else + sudo hostnamectl set-hostname "$hostname" || die "Failed to set hostname" + fi echo "$hostname" > "$hostname_file" || die "Failed to save hostname" log_success "Hostname set successfully." @@ -301,7 +316,14 @@ warning_prompt() { log_error "Please ensure you have a backup of your data before proceeding." log_error "This script will modify system files and may require sudo permissions." echo "" - log_info "This script has been tested on Ubuntu 22.04, 24.04, 24.10, Pop!_OS 24.04 Alpha 7, Debian 12, Fedora 41 and CachyOS." + + if is_wsl; then + log_info "WSL environment detected." + log_info "This script has been tested on Ubuntu under WSL2." + else + log_info "This script has been tested on Ubuntu 22.04, 24.04, 24.10, Pop!_OS 24.04 Alpha 7, Debian 12, Fedora 41 and CachyOS." + fi + log_info "Setup starts in 10 seconds, to abort use Ctrl+C to exit NOW." echo "" sleep 10 @@ -397,6 +419,11 @@ check_compatibility() { local distro distro=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"') + # Special handling for WSL + if is_wsl; then + log_info "Running in WSL environment." + fi + case "$distro" in Fedora*) log_success "Detected Fedora. Proceeding with setup..." @@ -413,9 +440,11 @@ check_compatibility() { ;; Debian*) log_success "Detected Debian. Proceeding with setup..." - log_warning "Debian has known issues with ZFS kernel modules, you might need to manually install it to make ZFS work." - log_warning "Continueing in 5 seconds..." - sleep 5 + if ! is_wsl; then + log_warning "Debian has known issues with ZFS kernel modules, you might need to manually install it to make ZFS work." + log_warning "Continueing in 5 seconds..." + sleep 5 + fi check_command_availibility "apt" ;; Pop!_OS*)