From 436deb267e6118d6e0d6531bd763b366e46e67b0 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Wed, 8 Oct 2025 13:01:37 +0200 Subject: [PATCH 1/8] Add smart alias configuration for rtlsdr --- ansible/tasks/global/utils/smart-ssh/config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ansible/tasks/global/utils/smart-ssh/config.yaml b/ansible/tasks/global/utils/smart-ssh/config.yaml index 5cf26e9..7305818 100644 --- a/ansible/tasks/global/utils/smart-ssh/config.yaml +++ b/ansible/tasks/global/utils/smart-ssh/config.yaml @@ -22,6 +22,12 @@ smart_aliases: check_host: "192.168.1.253" timeout: "2s" + rtlsdr: + primary: "rtlsdr-local" + fallback: "rtlsdr" + check_host: "192.168.1.252" + timeout: "2s" + # Background SSH Tunnel Definitions tunnels: # Example: Desktop database tunnel From 1856b2fb9e559f27e03e33034b2dab62ba16affd Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Mon, 20 Oct 2025 11:27:00 +0200 Subject: [PATCH 2/8] adds fastmail app as flatpak --- ansible/tasks/workstations/flatpaks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible/tasks/workstations/flatpaks.yml b/ansible/tasks/workstations/flatpaks.yml index 13956be..239e16c 100644 --- a/ansible/tasks/workstations/flatpaks.yml +++ b/ansible/tasks/workstations/flatpaks.yml @@ -53,6 +53,7 @@ - io.mango3d.LycheeSlicer # Utilities + - com.fastmail.Fastmail - com.ranfdev.DistroShelf - io.missioncenter.MissionCenter - io.gitlab.elescoute.spacelaunch From 77424506d64ae4b7e068b489eba61b5fbb5f78ce Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Mon, 20 Oct 2025 11:27:21 +0200 Subject: [PATCH 3/8] Update Nextcloud config and flake.lock dependencies --- config/autostart/Nextcloud.desktop | 0 config/nextcloud.cfg | 72 ++++++++++++++++++++++++++++++ flake.lock | 12 ++--- 3 files changed, 78 insertions(+), 6 deletions(-) mode change 100644 => 100755 config/autostart/Nextcloud.desktop diff --git a/config/autostart/Nextcloud.desktop b/config/autostart/Nextcloud.desktop old mode 100644 new mode 100755 diff --git a/config/nextcloud.cfg b/config/nextcloud.cfg index 53f0f0e..410df3f 100644 --- a/config/nextcloud.cfg +++ b/config/nextcloud.cfg @@ -1,8 +1,80 @@ [General] clientVersion=3.16.0-1 (Debian built) +desktopEnterpriseChannel=daily isVfsEnabled=false launchOnSystemStartup=true +optionalServerNotifications=true +overrideLocalDir= +overrideServerUrl= promptDeleteAllFiles=false +showCallNotifications=true +showChatNotifications=true [Accounts] +0\Folders\1\ignoreHiddenFiles=false +0\Folders\1\journalPath=.sync_42a4129584d0.db +0\Folders\1\localPath=/home/menno/Nextcloud/ +0\Folders\1\paused=false +0\Folders\1\targetPath=/ +0\Folders\1\version=2 +0\Folders\1\virtualFilesMode=off +0\Folders\2\ignoreHiddenFiles=false +0\Folders\2\journalPath=.sync_65a742b0aa83.db +0\Folders\2\localPath=/home/menno/Desktop/ +0\Folders\2\paused=false +0\Folders\2\targetPath=/Desktop +0\Folders\2\version=2 +0\Folders\2\virtualFilesMode=off +0\Folders\3\ignoreHiddenFiles=false +0\Folders\3\journalPath=.sync_65289e64a490.db +0\Folders\3\localPath=/home/menno/Documents/ +0\Folders\3\paused=false +0\Folders\3\targetPath=/Documents +0\Folders\3\version=2 +0\Folders\3\virtualFilesMode=off +0\Folders\4\ignoreHiddenFiles=false +0\Folders\4\journalPath=.sync_283a65eecb9c.db +0\Folders\4\localPath=/home/menno/Music/ +0\Folders\4\paused=false +0\Folders\4\targetPath=/Music +0\Folders\4\version=2 +0\Folders\4\virtualFilesMode=off +0\Folders\5\ignoreHiddenFiles=false +0\Folders\5\journalPath=.sync_884042991bd6.db +0\Folders\5\localPath=/home/menno/3D Objects/ +0\Folders\5\paused=false +0\Folders\5\targetPath=/3D Objects +0\Folders\5\version=2 +0\Folders\5\virtualFilesMode=off +0\Folders\6\ignoreHiddenFiles=false +0\Folders\6\journalPath=.sync_90ea5e3c7a33.db +0\Folders\6\localPath=/home/menno/Videos/ +0\Folders\6\paused=false +0\Folders\6\targetPath=/Videos +0\Folders\6\version=2 +0\Folders\6\virtualFilesMode=off +0\authType=webflow +0\dav_user=menno +0\displayName=Menno van Leeuwen +0\encryptionCertificateSha256Fingerprint=@ByteArray() +0\networkDownloadLimit=0 +0\networkDownloadLimitSetting=-2 +0\networkProxyHostName= +0\networkProxyNeedsAuth=false +0\networkProxyPort=0 +0\networkProxySetting=0 +0\networkProxyType=2 +0\networkProxyUser= +0\networkUploadLimit=0 +0\networkUploadLimitSetting=-2 +0\serverColor=@Variant(\0\0\0\x43\x1\xff\xff\x1c\x1c$$<<\0\0) +0\serverHasValidSubscription=false +0\serverTextColor=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) +0\serverVersion=32.0.0.13 +0\url=https://drive.mvl.sh +0\version=13 +0\webflow_user=menno version=13 + +[Settings] +geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\x4\xe\0\0\x2\x37\0\0\x6W\0\0\0\0\0\0\x4\xe\0\0\x2\x37\0\0\x6W\0\0\0\x1\0\0\0\0\x14\0\0\0\0\0\0\0\x4\xe\0\0\x2\x37\0\0\x6W) diff --git a/flake.lock b/flake.lock index 651abac..169e1fd 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1759994382, - "narHash": "sha256-wSK+3UkalDZRVHGCRikZ//CyZUJWDJkBDTQX1+G77Ow=", + "lastModified": 1760862643, + "narHash": "sha256-PXwG0TM7Ek87DNx4LbGWuD93PbFeKAJs4FfALtp7Wo0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5da4a26309e796daa7ffca72df93dbe53b8164c7", + "rev": "33c6dca0c0cb31d6addcd34e90a63ad61826b28c", "type": "github" }, "original": { @@ -77,11 +77,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1751283143, - "narHash": "sha256-I3DMLT0qg5xxjS7BrmOBIK6pG+vZqOhKivEGnkDIli8=", + "lastModified": 1760894497, + "narHash": "sha256-u2unItzVvUe3Y2opdJrISGtHSmQLVnDOIfhWvSBrw74=", "owner": "brizzbuzz", "repo": "opnix", - "rev": "1a807befe8f418da0df24c54b9633c395d840d0e", + "rev": "92974503378ca6ec6206b74cd3a78377a5796cbb", "type": "github" }, "original": { From e1b07a6edfb430541b0a850225a8386452ac034f Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Wed, 22 Oct 2025 16:18:08 +0200 Subject: [PATCH 4/8] 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*) From fb1661386bc837e47c29289f4b1068f7ebe16787 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Wed, 22 Oct 2025 17:57:12 +0200 Subject: [PATCH 5/8] chore: add Bun install path and prepend to PATH --- config/bash.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/bash.nix b/config/bash.nix index 9829313..063eb1c 100644 --- a/config/bash.nix +++ b/config/bash.nix @@ -39,6 +39,7 @@ export STARSHIP_ENABLE_RIGHT_PROMPT="true" export STARSHIP_ENABLE_BASH_COMPLETION="true" export XDG_DATA_DIRS="/usr/share:/var/lib/flatpak/exports/share:${config.home.homeDirectory}/.local/share/flatpak/exports/share" + export BUN_INSTALL="$HOME/.bun" # Source .profile (If exists) if [ -f "${config.home.homeDirectory}/.profile" ]; then @@ -212,6 +213,7 @@ export PATH="$PATH:${config.home.homeDirectory}/.cargo/bin" export PATH="$PATH:${config.home.homeDirectory}/.dotfiles/bin" export PATH="/usr/bin:$PATH" + export PATH="$BUN_INSTALL/bin:$PATH" # PKG_CONFIG_PATH if [ -d /usr/lib/pkgconfig ]; then From 310fb92ec960942fa9afb0e381440325efedb867 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Thu, 23 Oct 2025 04:20:15 +0200 Subject: [PATCH 6/8] Add WSL aliases for Windows SSH and Zed --- .../tasks/global/utils/smart-ssh/smart-ssh.go | 130 ++++++++++++------ config/bash.nix | 9 +- 2 files changed, 95 insertions(+), 44 deletions(-) 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/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"; From 11af7f16e57e066c159b61e7862d4a2225cb89b9 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Thu, 23 Oct 2025 13:38:16 +0200 Subject: [PATCH 7/8] Set formatter to prettier and update format_on_save option --- ansible/templates/zed.jsonc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible/templates/zed.jsonc b/ansible/templates/zed.jsonc index 6420e0b..3bb2fed 100644 --- a/ansible/templates/zed.jsonc +++ b/ansible/templates/zed.jsonc @@ -10,6 +10,7 @@ // ############################################# // ## Theming ## // ############################################# + "formatter": "prettier", "context_servers": { "mcp-server-context7": { "source": "extension", @@ -96,7 +97,7 @@ "hide_mouse": "on_typing", "on_last_window_closed": "quit_app", "ensure_final_newline_on_save": true, - "format_on_save": "prettier", + "format_on_save": "on", "tab_size": 2, "inlay_hints": { "enabled": true, From e2701dcdf4f9a47549fbd3478a049fbfda9fde92 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Thu, 23 Oct 2025 13:43:26 +0200 Subject: [PATCH 8/8] Set executable permission for equibop.desktop and update bash.nix Add BUN_INSTALL env var and include Bun bin in PATH --- config/autostart/equibop.desktop | 0 config/bash.nix | 2 ++ 2 files changed, 2 insertions(+) mode change 100644 => 100755 config/autostart/equibop.desktop diff --git a/config/autostart/equibop.desktop b/config/autostart/equibop.desktop old mode 100644 new mode 100755 diff --git a/config/bash.nix b/config/bash.nix index 9829313..063eb1c 100644 --- a/config/bash.nix +++ b/config/bash.nix @@ -39,6 +39,7 @@ export STARSHIP_ENABLE_RIGHT_PROMPT="true" export STARSHIP_ENABLE_BASH_COMPLETION="true" export XDG_DATA_DIRS="/usr/share:/var/lib/flatpak/exports/share:${config.home.homeDirectory}/.local/share/flatpak/exports/share" + export BUN_INSTALL="$HOME/.bun" # Source .profile (If exists) if [ -f "${config.home.homeDirectory}/.profile" ]; then @@ -212,6 +213,7 @@ export PATH="$PATH:${config.home.homeDirectory}/.cargo/bin" export PATH="$PATH:${config.home.homeDirectory}/.dotfiles/bin" export PATH="/usr/bin:$PATH" + export PATH="$BUN_INSTALL/bin:$PATH" # PKG_CONFIG_PATH if [ -d /usr/lib/pkgconfig ]; then