From e1b07a6edfb430541b0a850225a8386452ac034f Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Wed, 22 Oct 2025 16:18:08 +0200 Subject: [PATCH] Add WSL support and fix config formatting --- ansible/inventory.ini | 5 +- ansible/playbook.yml | 20 +-- ansible/tasks/servers/server.yml | 296 ++++++++++++++++--------------- bin/actions/help.py | 1 + bin/dotf | 59 +++++- flake.nix | 1 + setup.sh | 39 +++- 7 files changed, 253 insertions(+), 168 deletions(-) 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/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/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*)