From 9065261bffff53160060dc8c1fcfd6fb13d90605 Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Wed, 1 Apr 2026 13:54:07 +0200 Subject: [PATCH] Split workstation profiles for Linux and Windows WSL --- AGENTS.md | 19 ++- README.md | 78 +++++++---- ansible/collections/requirements.yml | 5 + ansible/inventory/group_vars/workstation.yml | 97 +------------- .../inventory/group_vars/workstation_dev.yml | 33 +++++ .../group_vars/workstation_dev_wsl.yml | 17 +++ .../group_vars/workstation_host_linux.yml | 66 ++++++++++ .../group_vars/workstation_host_windows.yml | 20 +++ ansible/inventory/host_vars/deadalus-win.yml | 4 + ansible/inventory/host_vars/deadalus-wsl.yml | 5 + ansible/inventory/hosts.yml | 33 ++++- ansible/roles/packages_ubuntu/tasks/main.yml | 19 ++- .../tasks/main.yml | 124 ++++++++++++++++++ .../tasks/main.yml | 30 +++++ .../profile_workstation_gnome/tasks/main.yml | 113 ---------------- .../tasks/main.yml | 108 +++++++++++++++ ansible/site.yml | 25 +++- scripts/bootstrap_windows_workstation.ps1 | 51 +++++++ 18 files changed, 606 insertions(+), 241 deletions(-) create mode 100644 ansible/collections/requirements.yml create mode 100644 ansible/inventory/group_vars/workstation_dev.yml create mode 100644 ansible/inventory/group_vars/workstation_dev_wsl.yml create mode 100644 ansible/inventory/group_vars/workstation_host_linux.yml create mode 100644 ansible/inventory/group_vars/workstation_host_windows.yml create mode 100644 ansible/inventory/host_vars/deadalus-win.yml create mode 100644 ansible/inventory/host_vars/deadalus-wsl.yml create mode 100644 ansible/roles/profile_workstation_dev_common/tasks/main.yml create mode 100644 ansible/roles/profile_workstation_dev_wsl/tasks/main.yml create mode 100644 ansible/roles/profile_workstation_host_windows/tasks/main.yml create mode 100644 scripts/bootstrap_windows_workstation.ps1 diff --git a/AGENTS.md b/AGENTS.md index e7739dd..247f5c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,8 +18,10 @@ Project type: Ansible-driven infrastructure, workstation/server provisioning, an - Void desktops: `ikaros`, `nymph` - Ubuntu workstation: `deadalus` - Ubuntu server: `prometheus` +- Workstation topology now supports Linux host + Ubuntu dev and Windows host + Ubuntu WSL dev as separate layers +- The WSL dev environment is intended to be managed by running Ansible locally from inside the distro, while the Windows host is managed remotely via PSRP - Most hosts use `ansible_connection: local` -- Current playbook layering: `all -> dotfiles_common`, `void -> packages_void + services_runit + profile_desktop_common + profile_desktop_i3 + profile_desktop_sway + profile_desktop_hyprland + profile_desktop_host`, `ubuntu_workstation -> packages_ubuntu + services_systemd + profile_workstation_gnome`, `ubuntu_server -> packages_ubuntu + services_systemd + profile_server` +- Current playbook layering: `all:!workstation_host_windows -> dotfiles_common`, `void -> packages_void + services_runit + profile_desktop_common + profile_desktop_i3 + profile_desktop_sway + profile_desktop_hyprland + profile_desktop_host`, `workstation_dev_ubuntu -> packages_ubuntu + services_systemd + profile_workstation_dev_common`, `workstation_host_linux -> profile_workstation_gnome`, `workstation_dev_wsl -> packages_ubuntu + services_systemd + profile_workstation_dev_common + profile_workstation_dev_wsl`, `workstation_host_windows -> profile_workstation_host_windows`, `ubuntu_server -> packages_ubuntu + services_systemd + profile_server` - Present but currently unwired roles: `base`, `dotfiles` ## Local Instruction Files @@ -45,7 +47,7 @@ There is no compile/build pipeline. Confidence comes from syntax checks, dry run Install tooling if needed: ```bash python3 -m pip install ansible ansible-lint yamllint shellcheck-py -ansible-galaxy collection install community.general +ansible-galaxy collection install -r ansible/collections/requirements.yml ``` Core validation from the repo root: @@ -54,6 +56,7 @@ ansible-playbook ansible/site.yml --syntax-check ansible-playbook ansible/site.yml --limit ikaros --check --diff ansible-playbook ansible/site.yml --limit nymph --check --diff ansible-playbook ansible/site.yml --limit deadalus --check --diff +ansible-playbook ansible/site.yml --limit deadalus-wsl --check --diff ansible-playbook ansible/site.yml --limit prometheus --check --diff ansible-lint ansible/site.yml ansible-lint ansible/roles @@ -66,8 +69,10 @@ ansible-playbook ansible/site.yml ansible-playbook ansible/site.yml --limit ikaros ansible-playbook ansible/site.yml --limit nymph ansible-playbook ansible/site.yml --limit deadalus +ansible-playbook ansible/site.yml --limit deadalus-wsl ansible-playbook ansible/site.yml --limit prometheus scripts/bootstrap_mail.sh +pwsh -File scripts/bootstrap_windows_workstation.ps1 ``` ## Single-Test Equivalents @@ -80,6 +85,7 @@ There is no pytest, Molecule, or unit-test suite. Use the narrowest command matc - Single YAML file lint: `yamllint ansible/path/to/file.yml` - Waybar config validation: `python3 -m json.tool dotfiles/desktop/.config/waybar/config-sway.jsonc >/dev/null` and `python3 -m json.tool dotfiles/desktop/.config/waybar/config-hyprland.jsonc >/dev/null` - Script syntax/lint: `sh -n scripts/bootstrap_mail.sh` and `shellcheck scripts/bootstrap_mail.sh` +- Windows bootstrap script parse check: `pwsh -NoProfile -Command "[void][System.Management.Automation.Language.Parser]::ParseFile('scripts/bootstrap_windows_workstation.ps1', [ref]$null, [ref]$null)"` - For shell changes outside vendored tmux plugins, prefer validating the touched file with `sh -n` and `shellcheck` - Prefer one limited-host dry run for vars, templates, dotfiles, packages, services, PAM, display manager, and firewall changes @@ -146,6 +152,11 @@ There is no pytest, Molecule, or unit-test suite. Use the narrowest command matc - `profile_desktop_sway` contains the wlroots/Sway session pieces and deploys shared Sway + Waybar dotfiles - `profile_desktop_hyprland` contains the optional Hyprland/Wayland session pieces - `profile_desktop_host` carries host-specific desktop overrides such as NVIDIA, PRIME wrappers, and host-only WirePlumber config +- `profile_workstation_dev_common` carries the Ubuntu dev layer shared by native workstation and WSL Ubuntu +- `profile_workstation_gnome` now carries Linux host-only GNOME setup, extensions, and UFW +- `profile_workstation_dev_wsl` carries WSL-specific Ubuntu tweaks such as `systemd` +- `profile_workstation_host_windows` manages the Windows host via PSRP and installs host applications via `winget` called from `win_powershell` +- `deadalus-wsl` is modeled as a local inventory target intended to be run from inside the Ubuntu WSL distro - Do not auto-restart `emptty` during playbook runs on active desktop hosts; prefer a manual restart from SSH or another TTY after the run - `dotfiles/desktop/.xinitrc` is part of the X11 session bootstrap path; changes there affect login behavior - `dotfiles/desktop/.local/bin/start-sway-session` is the Sway session bootstrap path; keep it aligned with DBus, keyring, and host-specific environment overrides @@ -170,6 +181,10 @@ If you touched `scripts/bootstrap_mail.sh`, also run: sh -n scripts/bootstrap_mail.sh shellcheck scripts/bootstrap_mail.sh ``` +If you touched `scripts/bootstrap_windows_workstation.ps1`, also run: +```bash +pwsh -NoProfile -Command "[void][System.Management.Automation.Language.Parser]::ParseFile('scripts/bootstrap_windows_workstation.ps1', [ref]$null, [ref]$null)" +``` ## Agent Workflow Expectations - Do not revert unrelated worktree changes made by the user diff --git a/README.md b/README.md index a7f3635..53d4464 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,13 @@ Il repository è diviso in due componenti principali: # Macchine gestite -Il repository modella attualmente tre tipologie di profilo. +Il repository modella attualmente tre tipologie di profilo e prepara due filoni workstation: Linux nativa e Windows + WSL. Nota sullo stato attuale del playbook principale: - `ansible/site.yml` applica oggi in automatico il profilo desktop su host Void Linux -- `ansible/site.yml` applica anche il profilo `ubuntu_workstation` con setup apt, systemd, dotfiles workstation, firewall UFW e integrazione GNOME +- `ansible/site.yml` applica la workstation Linux nativa separando il layer dev comune dal layer host GNOME +- `ansible/site.yml` prepara anche il ramo `windows_workstation_host` + `workstation_dev_wsl` per il modello Windows + WSL - `ansible/site.yml` applica anche il profilo `ubuntu_server` con baseline apt, systemd, dotfiles server e firewall UFW ## Desktop @@ -90,32 +91,50 @@ Lo stato attuale del profilo desktop include, tra le altre cose: --- -## Workstation +## Workstation -Sistema operativo: - -- Ubuntu LTS - -Desktop environment: - -- GNOME - -Macchina: - -- `deadalus` - -Questo profilo è pensato per sviluppo e lavoro. +Sistemi operativi supportati: -Il profilo workstation Ubuntu e agganciato al playbook principale e include gia una base operativa per uso desktop e sviluppo. +- Ubuntu LTS nativa +- Windows host + Ubuntu WSL + +Desktop environment host Linux: + +- GNOME + +Macchine attuali: + +- `deadalus` come workstation Ubuntu nativa +- supporto strutturale preparato per futuri host Windows + WSL + +Questo profilo è pensato per sviluppo e lavoro, con separazione tra layer host e layer dev. + +Il profilo workstation e agganciato al playbook principale e ora distingue: + +- layer dev Ubuntu condiviso tra workstation Linux nativa e Ubuntu in WSL +- layer host Linux GNOME +- layer host Windows con bootstrap WSL, gestione app via `winget` e VS Code lato Windows +- layer WSL dedicato per sviluppo con `systemd` Lo stato attuale del profilo workstation include: - installazione pacchetti base Ubuntu via apt - installazione e configurazione di Docker dal repository ufficiale -- gestione dei dotfiles workstation e rendering dei template dedicati -- installazione di Google Chrome e pacchetti Snap workstation -- gestione delle estensioni GNOME da website e dello stato desiderato delle estensioni abilitate -- attivazione del firewall UFW +- gestione dei dotfiles workstation e rendering dei template dev condivisi +- installazione di Google Chrome, pacchetti Snap workstation e estensioni GNOME sul solo host Linux nativo +- preparazione del ramo Windows host con app installate dal playbook via `winget` +- preparazione del ramo WSL Ubuntu con `systemd` per il toolchain di sviluppo +- attivazione del firewall UFW sul solo host Linux nativo + +Workflow Windows + WSL previsto: + +1. eseguire `scripts/bootstrap_windows_workstation.ps1` su Windows come amministratore +2. riavviare Windows se richiesto dalle feature WSL +3. avviare Ubuntu WSL almeno una volta e completare la creazione dell'utente Linux +4. installare Ansible dentro WSL Ubuntu +5. lanciare il playbook da WSL su `deadalus-wsl` per configurare l'ambiente dev locale +6. lanciare da WSL anche il playbook su `deadalus-win` via `psrp` per configurare l'host Windows +7. usare VS Code con le estensioni Remote (`WSL`, `SSH`, `Dev Containers`) dal lato Windows --- @@ -208,7 +227,10 @@ I principali ruoli attualmente presenti sono: | profile_desktop_sway | sessione desktop Sway | | profile_desktop_hyprland | sessione desktop Hyprland | | profile_desktop_host | override desktop specifici per host | -| profile_workstation_gnome | configurazione workstation GNOME | +| profile_workstation_dev_common | configurazione dev Ubuntu condivisa | +| profile_workstation_gnome | configurazione host workstation GNOME | +| profile_workstation_dev_wsl | configurazione WSL Ubuntu per sviluppo | +| profile_workstation_host_windows | configurazione host Windows workstation | | profile_server | configurazione server | | dotfiles_common | distribuzione dotfiles comuni | | dotfiles | distribuzione configurazioni utente | @@ -217,19 +239,23 @@ I principali ruoli attualmente presenti sono: # Stato attuale del playbook principale -Il playbook `ansible/site.yml` e attualmente composto da quattro blocchi: +Il playbook `ansible/site.yml` e attualmente composto da sei blocchi: ```text -all -> dotfiles_common +all:!workstation_host_windows -> dotfiles_common void -> packages_void + services_runit + profile_desktop_common + profile_desktop_i3 + profile_desktop_sway + profile_desktop_hyprland + profile_desktop_host -ubuntu_workstation -> packages_ubuntu + services_systemd + profile_workstation_gnome +workstation_dev_ubuntu -> packages_ubuntu + services_systemd + profile_workstation_dev_common +workstation_host_linux -> profile_workstation_gnome +workstation_dev_wsl -> packages_ubuntu + services_systemd + profile_workstation_dev_common + profile_workstation_dev_wsl +workstation_host_windows -> profile_workstation_host_windows ubuntu_server -> packages_ubuntu + services_systemd + profile_server ``` Questo significa che, allo stato attuale: - i desktop Void (`ikaros`, `nymph`) restano il target operativo piu completo -- la workstation Ubuntu (`deadalus`) e gestita con pacchetti, servizi, dotfiles, estensioni GNOME e firewall +- la workstation Ubuntu (`deadalus`) e gestita separando ambiente dev e layer host GNOME +- il ramo Windows + WSL e predisposto con bootstrap PowerShell e play Windows/WSL dedicati - il server Ubuntu (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewall # Dotfiles diff --git a/ansible/collections/requirements.yml b/ansible/collections/requirements.yml new file mode 100644 index 0000000..89cb9a7 --- /dev/null +++ b/ansible/collections/requirements.yml @@ -0,0 +1,5 @@ +--- +collections: + - name: ansible.windows + - name: community.general + - name: community.windows diff --git a/ansible/inventory/group_vars/workstation.yml b/ansible/inventory/group_vars/workstation.yml index 6029712..2745a6d 100644 --- a/ansible/inventory/group_vars/workstation.yml +++ b/ansible/inventory/group_vars/workstation.yml @@ -1,97 +1,2 @@ --- -profile_packages: - - gnupg - - gpg-agent - - pcscd - - yubikey-manager - - pinentry-gnome3 - - openssh-client - - libfido2-1 - - meld - - gufw - - libreoffice - - network-manager-openconnect-gnome - - gnome-shell-extension-manager - - gnome-shell-extensions - - gnome-tweaks - - podman - - podman-compose - - distrobox - -workstation_manage_google_chrome: true - -workstation_removed_snap_packages: - - firefox - -workstation_snap_packages: - - name: intellij-idea-ultimate - classic: true - channel: latest/stable - - name: postman - classic: false - channel: latest/stable - - name: thunderbird - classic: false - channel: latest/stable - - name: code - classic: true - channel: latest/stable - - name: xournalpp - classic: false - channel: latest/stable - - name: pdfarranger - classic: false - channel: latest/stable - - name: spotify - classic: false - channel: latest/stable - - name: telegram-desktop - classic: false - channel: latest/stable - - name: dbeaver-ce - classic: true - channel: latest/stable - -workstation_user_directories: - - path: "{{ user_home }}/.config" - mode: "0755" - - path: "{{ user_home }}/.local" - mode: "0755" - - path: "{{ user_home }}/.local/bin" - mode: "0755" - - path: "{{ user_home }}/.gnupg" - mode: "0700" - -workstation_dotfiles: - - src: .gnupg/gpg-agent.conf - dest: .gnupg/gpg-agent.conf - mode: "0600" - - src: .gitignore_global - dest: .gitignore_global - mode: "0644" - - src: .themes.gitignore - dest: .themes.gitignore - mode: "0644" - -workstation_templates: - - src: workstation/.gitconfig.j2 - dest: .gitconfig - mode: "0644" - -workstation_gnome_extensions: - - id: 9308 - uuid: bluetooth-battery-monitor@v8v88v8v88.com - version_tag: 68559 - enabled: true - - id: 1401 - uuid: bluetooth-quick-connect@bjarosze.gmail.com - version_tag: 65323 - enabled: true - - id: 6099 - uuid: paperwm@paperwm.github.com - version_tag: 68525 - enabled: true - -workstation_disabled_gnome_extensions: - - tiling-assistant@ubuntu.com - - ubuntu-dock@ubuntu.com +workstation_manage_opencode: true diff --git a/ansible/inventory/group_vars/workstation_dev.yml b/ansible/inventory/group_vars/workstation_dev.yml new file mode 100644 index 0000000..d7c8552 --- /dev/null +++ b/ansible/inventory/group_vars/workstation_dev.yml @@ -0,0 +1,33 @@ +--- +workstation_dev_packages: + - distrobox + - gnupg + - gpg-agent + - libfido2-1 + - openssh-client + +workstation_user_directories: + - path: "{{ user_home }}/.config" + mode: "0755" + - path: "{{ user_home }}/.local" + mode: "0755" + - path: "{{ user_home }}/.local/bin" + mode: "0755" + - path: "{{ user_home }}/.gnupg" + mode: "0700" + +workstation_dotfiles: + - src: .gnupg/gpg-agent.conf + dest: .gnupg/gpg-agent.conf + mode: "0600" + - src: .gitignore_global + dest: .gitignore_global + mode: "0644" + - src: .themes.gitignore + dest: .themes.gitignore + mode: "0644" + +workstation_templates: + - src: workstation/.gitconfig.j2 + dest: .gitconfig + mode: "0644" diff --git a/ansible/inventory/group_vars/workstation_dev_wsl.yml b/ansible/inventory/group_vars/workstation_dev_wsl.yml new file mode 100644 index 0000000..1d10c6a --- /dev/null +++ b/ansible/inventory/group_vars/workstation_dev_wsl.yml @@ -0,0 +1,17 @@ +--- +enabled_services: + - docker + +workstation_dev_wsl_packages: [] +workstation_dev_wsl_excluded_packages: + - pcscd + - pinentry-gnome3 + - podman + - podman-compose + - ufw + - yubikey-manager +workstation_is_wsl: true +workstation_manage_google_chrome: false +workstation_removed_snap_packages: [] +workstation_snap_packages: [] +workstation_wsl_systemd_enabled: true diff --git a/ansible/inventory/group_vars/workstation_host_linux.yml b/ansible/inventory/group_vars/workstation_host_linux.yml new file mode 100644 index 0000000..a5f4852 --- /dev/null +++ b/ansible/inventory/group_vars/workstation_host_linux.yml @@ -0,0 +1,66 @@ +--- +workstation_host_linux_packages: + - gnome-shell-extension-manager + - gnome-shell-extensions + - gnome-tweaks + - gufw + - libreoffice + - meld + - network-manager-openconnect-gnome + - pcscd + - pinentry-gnome3 + - podman + - podman-compose + - yubikey-manager + +workstation_manage_google_chrome: true + +workstation_removed_snap_packages: + - firefox + +workstation_snap_packages: + - name: intellij-idea-ultimate + classic: true + channel: latest/stable + - name: postman + classic: false + channel: latest/stable + - name: thunderbird + classic: false + channel: latest/stable + - name: code + classic: true + channel: latest/stable + - name: xournalpp + classic: false + channel: latest/stable + - name: pdfarranger + classic: false + channel: latest/stable + - name: spotify + classic: false + channel: latest/stable + - name: telegram-desktop + classic: false + channel: latest/stable + - name: dbeaver-ce + classic: true + channel: latest/stable + +workstation_gnome_extensions: + - id: 9308 + uuid: bluetooth-battery-monitor@v8v88v8v88.com + version_tag: 68559 + enabled: true + - id: 1401 + uuid: bluetooth-quick-connect@bjarosze.gmail.com + version_tag: 65323 + enabled: true + - id: 6099 + uuid: paperwm@paperwm.github.com + version_tag: 68525 + enabled: true + +workstation_disabled_gnome_extensions: + - tiling-assistant@ubuntu.com + - ubuntu-dock@ubuntu.com diff --git a/ansible/inventory/group_vars/workstation_host_windows.yml b/ansible/inventory/group_vars/workstation_host_windows.yml new file mode 100644 index 0000000..de5d831 --- /dev/null +++ b/ansible/inventory/group_vars/workstation_host_windows.yml @@ -0,0 +1,20 @@ +--- +ansible_connection: psrp +ansible_psrp_auth: negotiate +ansible_psrp_cert_validation: ignore +ansible_psrp_protocol: http +ansible_port: 5985 +ansible_shell_type: powershell + +windows_winget_packages: + - id: Microsoft.VisualStudioCode + name: Visual Studio Code + - id: Microsoft.WindowsTerminal + name: Windows Terminal + +windows_vscode_extensions: + - ms-vscode-remote.remote-containers + - ms-vscode-remote.remote-ssh + - ms-vscode-remote.remote-wsl + +windows_wsl_distribution_name: Ubuntu diff --git a/ansible/inventory/host_vars/deadalus-win.yml b/ansible/inventory/host_vars/deadalus-win.yml new file mode 100644 index 0000000..47296c1 --- /dev/null +++ b/ansible/inventory/host_vars/deadalus-win.yml @@ -0,0 +1,4 @@ +--- +hostname: deadalus-win +ansible_host: windows-host.example.invalid +ansible_user: your-windows-user diff --git a/ansible/inventory/host_vars/deadalus-wsl.yml b/ansible/inventory/host_vars/deadalus-wsl.yml new file mode 100644 index 0000000..ebb7b9d --- /dev/null +++ b/ansible/inventory/host_vars/deadalus-wsl.yml @@ -0,0 +1,5 @@ +--- +hostname: deadalus-wsl + +host_packages: [] +host_enabled_services: [] diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index 8c3ff1d..83024f5 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -18,11 +18,23 @@ all: ubuntu: children: ubuntu_workstation: + workstation_dev_wsl: ubuntu_server: workstation: children: - ubuntu_workstation: + workstation_host: + workstation_dev: + + workstation_host: + children: + workstation_host_linux: + workstation_host_windows: + + workstation_dev: + children: + workstation_dev_ubuntu: + workstation_dev_wsl: server: children: @@ -33,6 +45,25 @@ all: deadalus: ansible_connection: local + workstation_host_linux: + hosts: + deadalus: + ansible_connection: local + + workstation_dev_ubuntu: + hosts: + deadalus: + ansible_connection: local + + workstation_host_windows: + hosts: + deadalus-win: + + workstation_dev_wsl: + hosts: + deadalus-wsl: + ansible_connection: local + ubuntu_server: hosts: prometheus: diff --git a/ansible/roles/packages_ubuntu/tasks/main.yml b/ansible/roles/packages_ubuntu/tasks/main.yml index 391b640..5d78fb4 100644 --- a/ansible/roles/packages_ubuntu/tasks/main.yml +++ b/ansible/roles/packages_ubuntu/tasks/main.yml @@ -91,6 +91,17 @@ + (ubuntu_packages_base | default([])) + (ubuntu_docker_packages | default([])) + (profile_packages | default([])) + + (workstation_dev_packages | default([])) + + ( + (workstation_host_linux_packages | default([])) + if 'workstation_host_linux' in group_names + else [] + ) + + ( + (workstation_dev_wsl_packages | default([])) + if 'workstation_dev_wsl' in group_names + else [] + ) + (desktop_common_packages | default([])) + ( (desktop_i3_packages | default([])) @@ -103,7 +114,13 @@ else [] ) + (host_packages | default([])) - ) | unique + ) + | difference( + (workstation_dev_wsl_excluded_packages | default([])) + if 'workstation_dev_wsl' in group_names + else [] + ) + | unique }} state: present diff --git a/ansible/roles/profile_workstation_dev_common/tasks/main.yml b/ansible/roles/profile_workstation_dev_common/tasks/main.yml new file mode 100644 index 0000000..cc582ec --- /dev/null +++ b/ansible/roles/profile_workstation_dev_common/tasks/main.yml @@ -0,0 +1,124 @@ +--- +- name: Ensure workstation user directories exist + tags: [dotfiles, dotfiles:workstation] + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ workstation_user_directories | default([]) }}" + loop_control: + label: "{{ item.path }}" + +- name: Copy workstation dotfiles + tags: [dotfiles, dotfiles:workstation] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/workstation/{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ workstation_dotfiles | default([]) }}" + loop_control: + label: "{{ item.dest }}" + +- name: Render workstation templates + tags: [dotfiles, dotfiles:workstation] + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ workstation_templates | default([]) }}" + loop_control: + label: "{{ item.dest }}" + +- name: Set workstation external tool release metadata + tags: [packages] + ansible.builtin.set_fact: + workstation_tools_tmp_dir: /tmp/workstation-tools + opencode_asset_name: >- + {{ + 'opencode-linux-x64-baseline.tar.gz' if ansible_facts['architecture'] == 'x86_64' + else 'opencode-linux-arm64.tar.gz' if ansible_facts['architecture'] in ['aarch64', 'arm64'] + else '' + }} + when: workstation_manage_opencode | default(false) + +- name: Ensure architecture is supported for OpenCode binary + tags: [packages] + ansible.builtin.fail: + msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for OpenCode release binary" + when: + - workstation_manage_opencode | default(false) + - opencode_asset_name == '' + +- name: Ensure temporary directory exists for workstation external tools + tags: [packages] + ansible.builtin.file: + path: "{{ workstation_tools_tmp_dir }}" + state: directory + mode: "0755" + when: workstation_manage_opencode | default(false) + +- name: Fetch latest OpenCode release metadata + tags: [packages] + ansible.builtin.uri: + url: https://api.github.com/repos/anomalyco/opencode/releases/latest + headers: + Accept: application/vnd.github+json + return_content: true + register: opencode_latest_release + changed_when: false + when: workstation_manage_opencode | default(false) + +- name: Set OpenCode release asset metadata + tags: [packages] + ansible.builtin.set_fact: + opencode_version: "{{ opencode_latest_release.json.tag_name }}" + opencode_asset: >- + {{ + opencode_latest_release.json.assets + | selectattr('name', 'equalto', opencode_asset_name) + | first + | default({}) + }} + when: workstation_manage_opencode | default(false) + +- name: Ensure latest OpenCode asset metadata is available + tags: [packages] + ansible.builtin.fail: + msg: "Could not find OpenCode asset {{ opencode_asset_name }} in release {{ opencode_version }}" + when: + - workstation_manage_opencode | default(false) + - opencode_asset == {} + +- name: Download OpenCode release archive + tags: [packages] + ansible.builtin.get_url: + url: "{{ opencode_asset.browser_download_url }}" + dest: "{{ workstation_tools_tmp_dir }}/{{ opencode_asset.name }}" + checksum: "{{ opencode_asset.digest | default(omit) }}" + mode: "0644" + when: workstation_manage_opencode | default(false) + +- name: Extract OpenCode release archive + tags: [packages] + ansible.builtin.unarchive: + src: "{{ workstation_tools_tmp_dir }}/{{ opencode_asset.name }}" + dest: "{{ workstation_tools_tmp_dir }}" + remote_src: true + when: workstation_manage_opencode | default(false) + +- name: Install OpenCode binary + tags: [packages] + ansible.builtin.copy: + src: "{{ workstation_tools_tmp_dir }}/opencode" + dest: /usr/local/bin/opencode + remote_src: true + owner: root + group: root + mode: "0755" + when: workstation_manage_opencode | default(false) diff --git a/ansible/roles/profile_workstation_dev_wsl/tasks/main.yml b/ansible/roles/profile_workstation_dev_wsl/tasks/main.yml new file mode 100644 index 0000000..247568c --- /dev/null +++ b/ansible/roles/profile_workstation_dev_wsl/tasks/main.yml @@ -0,0 +1,30 @@ +--- +- name: Ensure WSL boot configuration file exists + tags: [packages, services] + ansible.builtin.file: + path: /etc/wsl.conf + state: touch + owner: root + group: root + mode: "0644" + when: workstation_wsl_systemd_enabled | default(false) + +- name: Enable systemd in WSL + tags: [packages, services] + community.general.ini_file: + path: /etc/wsl.conf + section: boot + option: systemd + value: 'true' + mode: "0644" + register: workstation_wsl_systemd_config + when: workstation_wsl_systemd_enabled | default(false) + +- name: Note when WSL must be restarted + tags: [packages, services] + ansible.builtin.debug: + msg: "Restart the WSL distro with 'wsl --shutdown' from Windows to apply /etc/wsl.conf changes." + changed_when: false + when: + - workstation_wsl_systemd_enabled | default(false) + - workstation_wsl_systemd_config is changed diff --git a/ansible/roles/profile_workstation_gnome/tasks/main.yml b/ansible/roles/profile_workstation_gnome/tasks/main.yml index ca57415..cd39a31 100644 --- a/ansible/roles/profile_workstation_gnome/tasks/main.yml +++ b/ansible/roles/profile_workstation_gnome/tasks/main.yml @@ -1,117 +1,4 @@ --- -- name: Ensure workstation user directories exist - tags: [dotfiles, dotfiles:workstation] - ansible.builtin.file: - path: "{{ item.path }}" - state: directory - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "{{ item.mode }}" - loop: "{{ workstation_user_directories | default([]) }}" - loop_control: - label: "{{ item.path }}" - -- name: Copy workstation dotfiles - tags: [dotfiles, dotfiles:workstation] - ansible.builtin.copy: - src: "{{ playbook_dir }}/../dotfiles/workstation/{{ item.src }}" - dest: "{{ user_home }}/{{ item.dest }}" - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "{{ item.mode }}" - loop: "{{ workstation_dotfiles | default([]) }}" - loop_control: - label: "{{ item.dest }}" - -- name: Render workstation templates - tags: [dotfiles, dotfiles:workstation] - ansible.builtin.template: - src: "{{ item.src }}" - dest: "{{ user_home }}/{{ item.dest }}" - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "{{ item.mode }}" - loop: "{{ workstation_templates | default([]) }}" - loop_control: - label: "{{ item.dest }}" - -- name: Set workstation external tool release metadata - tags: [packages] - ansible.builtin.set_fact: - workstation_tools_tmp_dir: /tmp/workstation-tools - opencode_asset_name: >- - {{ - 'opencode-linux-x64-baseline.tar.gz' if ansible_facts['architecture'] == 'x86_64' - else 'opencode-linux-arm64.tar.gz' if ansible_facts['architecture'] in ['aarch64', 'arm64'] - else '' - }} - -- name: Ensure architecture is supported for OpenCode binary - tags: [packages] - ansible.builtin.fail: - msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for OpenCode release binary" - when: opencode_asset_name == '' - -- name: Ensure temporary directory exists for workstation external tools - tags: [packages] - ansible.builtin.file: - path: "{{ workstation_tools_tmp_dir }}" - state: directory - mode: "0755" - -- name: Fetch latest OpenCode release metadata - tags: [packages] - ansible.builtin.uri: - url: https://api.github.com/repos/anomalyco/opencode/releases/latest - headers: - Accept: application/vnd.github+json - return_content: true - register: opencode_latest_release - changed_when: false - -- name: Set OpenCode release asset metadata - tags: [packages] - ansible.builtin.set_fact: - opencode_version: "{{ opencode_latest_release.json.tag_name }}" - opencode_asset: >- - {{ - opencode_latest_release.json.assets - | selectattr('name', 'equalto', opencode_asset_name) - | first - | default({}) - }} - -- name: Ensure latest OpenCode asset metadata is available - tags: [packages] - ansible.builtin.fail: - msg: "Could not find OpenCode asset {{ opencode_asset_name }} in release {{ opencode_version }}" - when: opencode_asset == {} - -- name: Download OpenCode release archive - tags: [packages] - ansible.builtin.get_url: - url: "{{ opencode_asset.browser_download_url }}" - dest: "{{ workstation_tools_tmp_dir }}/{{ opencode_asset.name }}" - checksum: "{{ opencode_asset.digest | default(omit) }}" - mode: "0644" - -- name: Extract OpenCode release archive - tags: [packages] - ansible.builtin.unarchive: - src: "{{ workstation_tools_tmp_dir }}/{{ opencode_asset.name }}" - dest: "{{ workstation_tools_tmp_dir }}" - remote_src: true - -- name: Install OpenCode binary - tags: [packages] - ansible.builtin.copy: - src: "{{ workstation_tools_tmp_dir }}/opencode" - dest: /usr/local/bin/opencode - remote_src: true - owner: root - group: root - mode: "0755" - - name: Ensure GNOME extension directories exist tags: [packages, gnome] ansible.builtin.file: diff --git a/ansible/roles/profile_workstation_host_windows/tasks/main.yml b/ansible/roles/profile_workstation_host_windows/tasks/main.yml new file mode 100644 index 0000000..3d8dce1 --- /dev/null +++ b/ansible/roles/profile_workstation_host_windows/tasks/main.yml @@ -0,0 +1,108 @@ +--- +- name: Enable required Windows features for WSL + tags: [packages, services, wsl] + ansible.windows.win_optional_feature: + name: + - Microsoft-Windows-Subsystem-Linux + - VirtualMachinePlatform + state: present + include_parent: true + +- name: Ensure winget is available on Windows host + tags: [packages] + ansible.windows.win_powershell: + script: | + $winget = Get-Command winget.exe -ErrorAction SilentlyContinue + if ($null -eq $winget) { + throw 'winget.exe is not available on the Windows host. Install App Installer or rerun the bootstrap script.' + } + $Ansible.Changed = $false + +- name: Ensure WSL 2 is the default backend + tags: [packages, services, wsl] + ansible.windows.win_powershell: + script: | + $status = & wsl --status 2>$null + if ($LASTEXITCODE -eq 0 -and $status -match 'Default Version:\s*2') { + $Ansible.Changed = $false + return + } + + & wsl --set-default-version 2 + if ($LASTEXITCODE -ne 0) { + throw 'Failed to set WSL default version to 2.' + } + + $Ansible.Changed = $true + +- name: Check whether target WSL distribution exists + tags: [packages, services, wsl] + ansible.windows.win_powershell: + script: | + $distributions = @(wsl --list --quiet) | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + $ubuntuDistribution = $distributions | Where-Object { $_ -like '{{ windows_wsl_distribution_name }}*' } | Select-Object -First 1 + if ($null -ne $ubuntuDistribution) { + $Ansible.Result = @{ exists = $true; distribution = $ubuntuDistribution } + $Ansible.Changed = $false + return + } + + $Ansible.Result = @{ exists = $false; distribution = $null } + $Ansible.Changed = $false + register: windows_wsl_distribution_state + +- name: Fail when target WSL distribution is missing + tags: [packages, services, wsl] + ansible.builtin.fail: + msg: >- + No WSL Ubuntu distribution matching {{ windows_wsl_distribution_name }}* is installed on the Windows host. + Run scripts/bootstrap_windows_workstation.ps1 first and launch the distro once before applying the Windows play. + when: not (windows_wsl_distribution_state.result.exists | default(false)) + +- name: Install Windows workstation applications with winget + tags: [packages] + ansible.windows.win_powershell: + script: | + $packageId = '{{ item.id }}' + $packageName = '{{ item.name | default(item.id) }}' + $installed = & winget list --id $packageId --exact --accept-source-agreements --disable-interactivity 2>$null + if ($LASTEXITCODE -eq 0 -and $installed -match [regex]::Escape($packageId)) { + $Ansible.Changed = $false + return + } + + & winget install --id $packageId --exact --silent --accept-package-agreements --accept-source-agreements --disable-interactivity + if ($LASTEXITCODE -ne 0) { + throw "Failed to install $packageName with winget" + } + + $Ansible.Changed = $true + loop: "{{ windows_winget_packages | default([]) }}" + loop_control: + label: "{{ item.id }}" + +- name: Install VS Code WSL extensions on Windows host + tags: [packages, vscode] + ansible.windows.win_powershell: + script: | + $extensionId = '{{ item }}' + $code = Get-Command code.cmd -ErrorAction SilentlyContinue + if ($null -eq $code) { + throw 'code.cmd is not available. Ensure Visual Studio Code is installed before managing extensions.' + } + + $installedExtensions = & $code.Source --list-extensions + if ($installedExtensions -contains $extensionId) { + $Ansible.Changed = $false + return + } + + & $code.Source --install-extension $extensionId --force + if ($LASTEXITCODE -ne 0) { + throw "Failed to install VS Code extension $extensionId" + } + + $Ansible.Changed = $true + loop: "{{ windows_vscode_extensions | default([]) }}" + loop_control: + label: "{{ item }}" diff --git a/ansible/site.yml b/ansible/site.yml index 29a7905..421e498 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -1,5 +1,5 @@ --- -- hosts: all +- hosts: all:!workstation_host_windows become: true pre_tasks: - name: Load local vault variables when available @@ -23,14 +23,35 @@ - profile_desktop_hyprland - profile_desktop_host -- hosts: ubuntu_workstation +- hosts: workstation_dev_ubuntu become: true roles: - packages_ubuntu - services_systemd + - profile_workstation_dev_common + +- hosts: workstation_host_linux + become: true + + roles: - profile_workstation_gnome +- hosts: workstation_dev_wsl + become: true + + roles: + - packages_ubuntu + - services_systemd + - profile_workstation_dev_common + - profile_workstation_dev_wsl + +- hosts: workstation_host_windows + gather_facts: false + + roles: + - profile_workstation_host_windows + - hosts: ubuntu_server become: true diff --git a/scripts/bootstrap_windows_workstation.ps1 b/scripts/bootstrap_windows_workstation.ps1 new file mode 100644 index 0000000..ec40bb4 --- /dev/null +++ b/scripts/bootstrap_windows_workstation.ps1 @@ -0,0 +1,51 @@ +#requires -RunAsAdministrator +param( + [string]$Distribution = 'Ubuntu', + [switch]$SkipUbuntuInstall +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Enable-FeatureIfNeeded { + param( + [Parameter(Mandatory = $true)] + [string]$FeatureName + ) + + $feature = Get-WindowsOptionalFeature -Online -FeatureName $FeatureName + if ($feature.State -ne 'Enabled') { + Enable-WindowsOptionalFeature -Online -FeatureName $FeatureName -All -NoRestart | Out-Null + return $true + } + + return $false +} + +$rebootRequired = $false +$rebootRequired = (Enable-FeatureIfNeeded -FeatureName 'Microsoft-Windows-Subsystem-Linux') -or $rebootRequired +$rebootRequired = (Enable-FeatureIfNeeded -FeatureName 'VirtualMachinePlatform') -or $rebootRequired + +wsl --set-default-version 2 + +$installedDistributions = @(wsl --list --quiet) | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } +$installedUbuntuDistribution = $installedDistributions | Where-Object { $_ -like 'Ubuntu*' } | Select-Object -First 1 + +if (-not $SkipUbuntuInstall -and $null -eq $installedUbuntuDistribution) { + wsl --install --distribution $Distribution --no-launch + $rebootRequired = $true +} + +Enable-PSRemoting -SkipNetworkProfileCheck -Force +Set-Service -Name WinRM -StartupType Automatic + +Write-Host '' +Write-Host 'Bootstrap completato.' +Write-Host 'Passi successivi:' +Write-Host '1. Riavvia Windows se richiesto dalle feature WSL.' +Write-Host '2. Avvia la distro Ubuntu almeno una volta e completa la creazione dell''utente Linux.' +Write-Host '3. Installa Ansible dentro WSL Ubuntu e lancia il playbook da li.' +Write-Host '4. Le applicazioni Windows saranno installate dal playbook Ansible via winget, non da questo bootstrap.' +Write-Host '' +Write-Host ('WSL distro Ubuntu rilevata: {0}' -f $(if ($null -ne $installedUbuntuDistribution) { $installedUbuntuDistribution } else { 'nessuna, verra installata ' + $Distribution })) +Write-Host ('Riavvio consigliato: {0}' -f $(if ($rebootRequired) { 'yes' } else { 'no' }))