Split workstation profiles for Linux and Windows WSL

This commit is contained in:
Fabio Scotto di Santolo
2026-04-01 13:54:07 +02:00
parent 75dd8caf66
commit 3c3ca4a737
18 changed files with 606 additions and 241 deletions

View File

@@ -18,8 +18,10 @@ Project type: Ansible-driven infrastructure, workstation/server provisioning, an
- Void desktops: `ikaros`, `nymph` - Void desktops: `ikaros`, `nymph`
- Ubuntu workstation: `deadalus` - Ubuntu workstation: `deadalus`
- Ubuntu server: `prometheus` - 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` - 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` - Present but currently unwired roles: `base`, `dotfiles`
## Local Instruction Files ## Local Instruction Files
@@ -45,7 +47,7 @@ There is no compile/build pipeline. Confidence comes from syntax checks, dry run
Install tooling if needed: Install tooling if needed:
```bash ```bash
python3 -m pip install ansible ansible-lint yamllint shellcheck-py 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: 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 ikaros --check --diff
ansible-playbook ansible/site.yml --limit nymph --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 --check --diff
ansible-playbook ansible/site.yml --limit deadalus-wsl --check --diff
ansible-playbook ansible/site.yml --limit prometheus --check --diff ansible-playbook ansible/site.yml --limit prometheus --check --diff
ansible-lint ansible/site.yml ansible-lint ansible/site.yml
ansible-lint ansible/roles 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 ikaros
ansible-playbook ansible/site.yml --limit nymph ansible-playbook ansible/site.yml --limit nymph
ansible-playbook ansible/site.yml --limit deadalus ansible-playbook ansible/site.yml --limit deadalus
ansible-playbook ansible/site.yml --limit deadalus-wsl
ansible-playbook ansible/site.yml --limit prometheus ansible-playbook ansible/site.yml --limit prometheus
scripts/bootstrap_mail.sh scripts/bootstrap_mail.sh
pwsh -File scripts/bootstrap_windows_workstation.ps1
``` ```
## Single-Test Equivalents ## 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` - 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` - 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` - 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` - 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 - 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_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_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_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 - 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/.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 - `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 sh -n scripts/bootstrap_mail.sh
shellcheck 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 ## Agent Workflow Expectations
- Do not revert unrelated worktree changes made by the user - Do not revert unrelated worktree changes made by the user

View File

@@ -51,12 +51,13 @@ Il repository è diviso in due componenti principali:
# Macchine gestite # 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: 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 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 - `ansible/site.yml` applica anche il profilo `ubuntu_server` con baseline apt, systemd, dotfiles server e firewall UFW
## Desktop ## Desktop
@@ -90,32 +91,50 @@ Lo stato attuale del profilo desktop include, tra le altre cose:
--- ---
## Workstation ## Workstation
Sistema operativo: Sistemi operativi supportati:
- Ubuntu LTS
Desktop environment:
- GNOME
Macchina:
- `deadalus`
Questo profilo è pensato per sviluppo e lavoro.
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: Lo stato attuale del profilo workstation include:
- installazione pacchetti base Ubuntu via apt - installazione pacchetti base Ubuntu via apt
- installazione e configurazione di Docker dal repository ufficiale - installazione e configurazione di Docker dal repository ufficiale
- gestione dei dotfiles workstation e rendering dei template dedicati - gestione dei dotfiles workstation e rendering dei template dev condivisi
- installazione di Google Chrome e pacchetti Snap workstation - installazione di Google Chrome, pacchetti Snap workstation e estensioni GNOME sul solo host Linux nativo
- gestione delle estensioni GNOME da website e dello stato desiderato delle estensioni abilitate - preparazione del ramo Windows host con app installate dal playbook via `winget`
- attivazione del firewall UFW - 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_sway | sessione desktop Sway |
| profile_desktop_hyprland | sessione desktop Hyprland | | profile_desktop_hyprland | sessione desktop Hyprland |
| profile_desktop_host | override desktop specifici per host | | 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 | | profile_server | configurazione server |
| dotfiles_common | distribuzione dotfiles comuni | | dotfiles_common | distribuzione dotfiles comuni |
| dotfiles | distribuzione configurazioni utente | | dotfiles | distribuzione configurazioni utente |
@@ -217,19 +239,23 @@ I principali ruoli attualmente presenti sono:
# Stato attuale del playbook principale # 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 ```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 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 ubuntu_server -> packages_ubuntu + services_systemd + profile_server
``` ```
Questo significa che, allo stato attuale: Questo significa che, allo stato attuale:
- i desktop Void (`ikaros`, `nymph`) restano il target operativo piu completo - 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 - il server Ubuntu (`prometheus`) e gestito con pacchetti, servizi, dotfiles server e firewall
# Dotfiles # Dotfiles

View File

@@ -0,0 +1,5 @@
---
collections:
- name: ansible.windows
- name: community.general
- name: community.windows

View File

@@ -1,97 +1,2 @@
--- ---
profile_packages: workstation_manage_opencode: true
- 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,4 @@
---
hostname: deadalus-win
ansible_host: windows-host.example.invalid
ansible_user: your-windows-user

View File

@@ -0,0 +1,5 @@
---
hostname: deadalus-wsl
host_packages: []
host_enabled_services: []

View File

@@ -18,11 +18,23 @@ all:
ubuntu: ubuntu:
children: children:
ubuntu_workstation: ubuntu_workstation:
workstation_dev_wsl:
ubuntu_server: ubuntu_server:
workstation: workstation:
children: 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: server:
children: children:
@@ -33,6 +45,25 @@ all:
deadalus: deadalus:
ansible_connection: local 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: ubuntu_server:
hosts: hosts:
prometheus: prometheus:

View File

@@ -91,6 +91,17 @@
+ (ubuntu_packages_base | default([])) + (ubuntu_packages_base | default([]))
+ (ubuntu_docker_packages | default([])) + (ubuntu_docker_packages | default([]))
+ (profile_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_common_packages | default([]))
+ ( + (
(desktop_i3_packages | default([])) (desktop_i3_packages | default([]))
@@ -103,7 +114,13 @@
else [] else []
) )
+ (host_packages | default([])) + (host_packages | default([]))
) | unique )
| difference(
(workstation_dev_wsl_excluded_packages | default([]))
if 'workstation_dev_wsl' in group_names
else []
)
| unique
}} }}
state: present state: present

View File

@@ -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)

View File

@@ -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

View File

@@ -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 - name: Ensure GNOME extension directories exist
tags: [packages, gnome] tags: [packages, gnome]
ansible.builtin.file: ansible.builtin.file:

View File

@@ -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 }}"

View File

@@ -1,5 +1,5 @@
--- ---
- hosts: all - hosts: all:!workstation_host_windows
become: true become: true
pre_tasks: pre_tasks:
- name: Load local vault variables when available - name: Load local vault variables when available
@@ -23,14 +23,35 @@
- profile_desktop_hyprland - profile_desktop_hyprland
- profile_desktop_host - profile_desktop_host
- hosts: ubuntu_workstation - hosts: workstation_dev_ubuntu
become: true become: true
roles: roles:
- packages_ubuntu - packages_ubuntu
- services_systemd - services_systemd
- profile_workstation_dev_common
- hosts: workstation_host_linux
become: true
roles:
- profile_workstation_gnome - 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 - hosts: ubuntu_server
become: true become: true

View File

@@ -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' }))