From 0a80706ea2d42fa1d9e797edd43a1652d866cb08 Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Mon, 30 Mar 2026 12:43:41 +0200 Subject: [PATCH] Reorganize desktop roles: extract common bootstrap and host-specific layers - Add profile_desktop_common with shared desktop bootstrap (emptty, PAM, dotfiles, templates, GPG, Maildir, Flatpak, st, external tools) - Add profile_desktop_host with host-specific tasks (NVIDIA/PRIME on nymph) - Reduce profile_desktop_i3 to i3/X11-only tasks - Create profile_desktop_hyprland for Hyprland Wayland session - Add dual-session support (i3 + Hyprland) on nymph with session choice - Create shared Hyprland/Waybar dotfiles under dotfiles/desktop/ - Fix Waybar: bottom position, no persistent workspaces, sort by number - Rename host_dotfiles to host_i3_dotfiles for clarity - Make emptty restart manual by default to avoid session drops --- AGENTS.md | 8 +- README.md | 21 +- ansible/inventory/group_vars/desktop.yml | 97 ++- ansible/inventory/host_vars/ikaros.yml | 8 +- ansible/inventory/host_vars/nymph.yml | 28 +- ansible/roles/packages_ubuntu/tasks/main.yml | 16 +- ansible/roles/packages_void/tasks/main.yml | 16 +- .../profile_desktop_common/handlers/main.yml | 19 + .../profile_desktop_common/tasks/main.yml | 509 ++++++++++++++++ .../templates/emptty-conf.j2 | 18 + .../roles/profile_desktop_host/tasks/main.yml | 35 ++ .../profile_desktop_host/tasks/nymph.yml | 41 ++ .../profile_desktop_hyprland/tasks/main.yml | 36 ++ .../templates/Hyprland.desktop.j2} | 2 +- .../profile_desktop_i3/handlers/main.yml | 5 - .../roles/profile_desktop_i3/tasks/main.yml | 574 +----------------- .../templates/emptty-conf.j2 | 12 - ansible/site.yml | 3 + dotfiles/desktop/.config/hypr/host.conf | 0 dotfiles/desktop/.config/hypr/hypridle.conf | 16 + dotfiles/desktop/.config/hypr/hyprland.conf | 171 ++++++ dotfiles/desktop/.config/hypr/hyprlock.conf | 46 ++ dotfiles/desktop/.config/hypr/hyprpaper.conf | 4 + dotfiles/desktop/.config/waybar/config.jsonc | 111 ++++ .../.config/waybar/style.css | 53 +- dotfiles/desktop/.local/bin/lock-session | 23 + dotfiles/desktop/.local/bin/powermenu | 42 ++ .../desktop/.local/bin/screenshot-wayland | 33 + dotfiles/desktop/.local/bin/setup-gtk-theme | 38 ++ .../desktop/.local/bin/start-hyprland-session | 22 + dotfiles/nymph/.config/hypr/host.conf | 5 + dotfiles/nymph/.config/hypr/hyprland.conf | 193 ------ dotfiles/nymph/.config/hypr/hyprpaper.conf | 4 - dotfiles/nymph/.config/waybar/config.jsonc | 146 ----- 34 files changed, 1342 insertions(+), 1013 deletions(-) create mode 100644 ansible/roles/profile_desktop_common/handlers/main.yml create mode 100644 ansible/roles/profile_desktop_common/tasks/main.yml create mode 100644 ansible/roles/profile_desktop_common/templates/emptty-conf.j2 create mode 100644 ansible/roles/profile_desktop_host/tasks/main.yml create mode 100644 ansible/roles/profile_desktop_host/tasks/nymph.yml create mode 100644 ansible/roles/profile_desktop_hyprland/tasks/main.yml rename ansible/roles/{profile_desktop_i3/files/Hyprland.desktop => profile_desktop_hyprland/templates/Hyprland.desktop.j2} (77%) delete mode 100644 ansible/roles/profile_desktop_i3/handlers/main.yml delete mode 100644 ansible/roles/profile_desktop_i3/templates/emptty-conf.j2 create mode 100644 dotfiles/desktop/.config/hypr/host.conf create mode 100644 dotfiles/desktop/.config/hypr/hypridle.conf create mode 100644 dotfiles/desktop/.config/hypr/hyprland.conf create mode 100644 dotfiles/desktop/.config/hypr/hyprlock.conf create mode 100644 dotfiles/desktop/.config/hypr/hyprpaper.conf create mode 100644 dotfiles/desktop/.config/waybar/config.jsonc rename dotfiles/{nymph => desktop}/.config/waybar/style.css (53%) create mode 100644 dotfiles/desktop/.local/bin/lock-session create mode 100644 dotfiles/desktop/.local/bin/powermenu create mode 100644 dotfiles/desktop/.local/bin/screenshot-wayland create mode 100644 dotfiles/desktop/.local/bin/setup-gtk-theme create mode 100644 dotfiles/desktop/.local/bin/start-hyprland-session create mode 100644 dotfiles/nymph/.config/hypr/host.conf delete mode 100644 dotfiles/nymph/.config/hypr/hyprland.conf delete mode 100644 dotfiles/nymph/.config/hypr/hyprpaper.conf delete mode 100644 dotfiles/nymph/.config/waybar/config.jsonc diff --git a/AGENTS.md b/AGENTS.md index 2da6459..72e778f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Project type: Ansible-driven infrastructure, workstation/server provisioning, an - Ubuntu server: `prometheus` - Most hosts use `ansible_connection: local` - `all -> dotfiles_common` -- `void -> packages_void, services_runit, profile_desktop_i3` +- `void -> packages_void, services_runit, profile_desktop_common, profile_desktop_i3, profile_desktop_hyprland, profile_desktop_host` - `ubuntu_workstation -> packages_ubuntu, services_systemd, profile_workstation_gnome` - `ubuntu_server -> packages_ubuntu, services_systemd, profile_server` - Present but unwired roles: `base`, `dotfiles` @@ -107,7 +107,7 @@ There is no pytest, Molecule, or unit-test suite. Use the narrowest command matc ### Variables, Types, And Naming - Use `snake_case` for vars, facts, and registered values -- Follow existing families such as `common_packages`, `profile_packages`, `host_packages`, `desktop_dotfiles`, `host_dotfiles`, `enabled_services`, and `host_enabled_services` +- Follow existing families such as `common_packages`, `profile_packages`, `host_packages`, `desktop_common_packages`, `desktop_i3_packages`, `desktop_hyprland_packages`, `desktop_common_dotfiles`, `desktop_i3_dotfiles`, `desktop_hyprland_dotfiles`, `host_i3_dotfiles`, `host_hyprland_dotfiles`, `enabled_services`, and `host_enabled_services` - Keep booleans as booleans, not quoted strings - Keep structured values as YAML lists/maps, not comma-separated strings - Guard optional lists with `default([])`, mappings with `default({})`, and strings with `default('')` @@ -134,8 +134,10 @@ There is no pytest, Molecule, or unit-test suite. Use the narrowest command matc - Be careful with display-manager/session changes on desktop hosts; validate on one host first ## Area-Specific Notes -- `profile_desktop_i3` now manages `emptty` on Void desktops; keep session changes coordinated with `.xinitrc` +- `profile_desktop_common` manages `emptty` and the shared Void desktop bootstrap; `profile_desktop_i3` adds the X11/i3 session; `profile_desktop_hyprland` adds the optional Wayland session on hosts that enable it; `profile_desktop_host` carries host-specific desktop overrides such as NVIDIA or host session dotfiles +- 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-hyprland-session` is the Wayland session bootstrap path; keep it aligned with DBus and keyring expectations - `nymph` has special NVIDIA/PRIME handling; keep host-specific logic guarded by hostname or host vars - `ikaros` is treated as the more stable personal desktop; prefer validating risky desktop changes elsewhere first diff --git a/README.md b/README.md index a7b843c..7a427bb 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,10 @@ Sistema operativo: - Void Linux -Window manager: - -- i3 +Sessioni desktop: + +- `ikaros`: i3 +- `nymph`: i3 + Hyprland con scelta sessione a login Macchine: @@ -79,7 +80,8 @@ Queste macchine condividono la stessa configurazione base desktop e vengono mant Lo stato attuale del profilo desktop include, tra le altre cose: - dotfiles comuni e desktop -- sessione i3 e servizi desktop correlati +- sessione i3 su tutti i desktop Void e sessione Hyprland opzionale su `nymph` +- `emptty` con scelta sessione a login su `nymph` e default host-specific sugli altri desktop - pacchetti Void Linux e servizi runit - Flatpak con remoto Flathub - GNOME Keyring e bootstrap della posta via script dedicato @@ -183,7 +185,10 @@ I principali ruoli attualmente presenti sono: | packages_ubuntu | installazione pacchetti su Ubuntu | | services_runit | gestione servizi runit | | services_systemd | gestione servizi systemd | -| profile_desktop_i3 | configurazione desktop i3 | +| profile_desktop_common | bootstrap desktop Void condiviso | +| profile_desktop_i3 | sessione desktop i3 | +| profile_desktop_hyprland | sessione desktop Hyprland | +| profile_desktop_host | override desktop specifici per host | | profile_workstation_gnome | configurazione workstation GNOME | | profile_server | configurazione server | | dotfiles_common | distribuzione dotfiles comuni | @@ -197,7 +202,7 @@ Il playbook `ansible/site.yml` e attualmente composto da quattro blocchi: ```text all -> dotfiles_common -void -> packages_void + services_runit + profile_desktop_i3 +void -> packages_void + services_runit + profile_desktop_common + profile_desktop_i3 + profile_desktop_hyprland + profile_desktop_host ubuntu_workstation -> packages_ubuntu + services_systemd + profile_workstation_gnome ubuntu_server -> packages_ubuntu + services_systemd + profile_server ``` @@ -267,9 +272,10 @@ ansible-playbook ansible/site.yml Allo stato attuale questo comando: - distribuisce i dotfiles comuni a tutti gli host -- per gli host Void applica pacchetti, servizi runit e profilo desktop i3 +- per gli host Void applica bootstrap desktop condiviso, sessioni i3/Hyprland e override specifici per host - per gli host `ubuntu_workstation` applica pacchetti Ubuntu, servizi systemd, profilo workstation GNOME, UFW, dotfiles, Snap e template dedicati - per gli host `ubuntu_server` applica pacchetti Ubuntu, servizi systemd, profilo server, UFW, dotfiles e template dedicati +- non riavvia automaticamente `emptty`; le modifiche al display manager vanno applicate manualmente da SSH o da una TTY separata - carica `secrets/vault.yml` solo se presente Per validare prima di applicare: @@ -277,6 +283,7 @@ Per validare prima di applicare: ```bash 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 prometheus --check --diff ``` diff --git a/ansible/inventory/group_vars/desktop.yml b/ansible/inventory/group_vars/desktop.yml index 6bca33d..36d6610 100644 --- a/ansible/inventory/group_vars/desktop.yml +++ b/ansible/inventory/group_vars/desktop.yml @@ -2,36 +2,57 @@ desktop_manage_icloud_keyring: false desktop_protonmail_bridge_cert_path: ~/.var/app/ch.protonmail.protonmail-bridge/config/protonmail/bridge-v3/cert.pem -desktop_x11_packages: - - arandr - - autorandr +desktop_sessions_enabled: + - i3 + +desktop_default_session: i3 +desktop_default_session_env: xorg +desktop_restart_emptty_automatically: false +desktop_emptty_session_error_logging: disabled + +desktop_common_packages: - brightnessctl - dex - dunst - emptty + - network-manager-applet + - rofi + - udiskie + - xfce-polkit + - xdg-desktop-portal + - xdg-desktop-portal-gtk + +desktop_i3_packages: + - arandr + - autorandr - feh - i3 - i3blocks - i3blocks-blocklets - i3lock-color - i3status - - network-manager-applet - - rofi - scrot - setxkbmap - - udiskie - volumeicon - xclip - - xfce-polkit - xfce4-clipman-plugin - xfce4-screenshooter - xkbutils - - xdg-desktop-portal - - xdg-desktop-portal-gtk - xorg-fonts - xorg-minimal - xss-lock +desktop_hyprland_packages: + - grim + - hypridle + - hyprland + - hyprlock + - hyprpaper + - slurp + - Waybar + - wl-clipboard + - xdg-desktop-portal-hyprland + profile_packages: - alacritty - bluez @@ -68,19 +89,11 @@ profile_packages: - yaru - yaru-plus -desktop_dotfiles: +desktop_common_dotfiles: - name: XDG autostart entries src: .config/autostart/ dest: .config/autostart/ mode: preserve - - name: i3 config - src: .config/i3/ - dest: .config/i3/ - mode: preserve - - name: i3blocks config - src: .config/i3blocks/ - dest: .config/i3blocks/ - mode: preserve - name: dunst config src: .config/dunst/ dest: .config/dunst/ @@ -113,10 +126,6 @@ desktop_dotfiles: src: .config/yt-dlp/ dest: .config/yt-dlp/ mode: preserve - - name: .xinitrc - src: .xinitrc - dest: .xinitrc - mode: "0644" - name: .gitignore_global src: .gitignore_global dest: .gitignore_global @@ -129,6 +138,50 @@ desktop_dotfiles: src: .emacs.d/ dest: .emacs.d/ mode: preserve + - name: GTK theme setup script + src: .local/bin/setup-gtk-theme + dest: .local/bin/setup-gtk-theme + mode: "0755" + - name: Lock session script + src: .local/bin/lock-session + dest: .local/bin/lock-session + mode: "0755" + - name: Powermenu script + src: .local/bin/powermenu + dest: .local/bin/powermenu + mode: "0755" + +desktop_i3_dotfiles: + - name: i3 config + src: .config/i3/ + dest: .config/i3/ + mode: preserve + - name: i3blocks config + src: .config/i3blocks/ + dest: .config/i3blocks/ + mode: preserve + - name: .xinitrc + src: .xinitrc + dest: .xinitrc + mode: "0644" + +desktop_hyprland_dotfiles: + - name: Hyprland config + src: .config/hypr/ + dest: .config/hypr/ + mode: preserve + - name: Waybar config + src: .config/waybar/ + dest: .config/waybar/ + mode: preserve + - name: Hyprland session wrapper + src: .local/bin/start-hyprland-session + dest: .local/bin/start-hyprland-session + mode: "0755" + - name: Wayland screenshot script + src: .local/bin/screenshot-wayland + dest: .local/bin/screenshot-wayland + mode: "0755" desktop_flatpak_packages: - be.alexandervanhee.gradia diff --git a/ansible/inventory/host_vars/ikaros.yml b/ansible/inventory/host_vars/ikaros.yml index 36fe3f2..21baad5 100644 --- a/ansible/inventory/host_vars/ikaros.yml +++ b/ansible/inventory/host_vars/ikaros.yml @@ -1,6 +1,12 @@ --- hostname: ikaros +desktop_sessions_enabled: + - i3 + +desktop_default_session: i3 +desktop_default_session_env: xorg + host_packages: - mesa-dri - vulkan-loader @@ -8,7 +14,7 @@ host_packages: - mesa-vaapi - xf86-video-amdgpu -host_dotfiles: +host_i3_dotfiles: - src: .config/autorandr/ dest: .config/autorandr/ mode: preserve diff --git a/ansible/inventory/host_vars/nymph.yml b/ansible/inventory/host_vars/nymph.yml index ba826fb..fc5044d 100644 --- a/ansible/inventory/host_vars/nymph.yml +++ b/ansible/inventory/host_vars/nymph.yml @@ -1,13 +1,17 @@ --- hostname: nymph +desktop_sessions_enabled: + - i3 + - hyprland + +desktop_prompt_for_session: true +desktop_emptty_session_error_logging: rotate + host_xbps_repositories: - name: hyprland-void url: "https://raw.githubusercontent.com/Makrennel/hyprland-void/repository-x86_64-glibc" -host_emptty_wayland_sessions: - - Hyprland.desktop - host_packages: - nvidia - mesa-dri @@ -16,22 +20,16 @@ host_packages: - intel-video-accel - tlp - tlp-rdw - - hyprland - - hyprpaper - - xdg-desktop-portal-hyprland - - Waybar - - wlogout host_enabled_services: - tlp -host_dotfiles: +host_i3_dotfiles: - src: .config/autorandr/ dest: .config/autorandr/ mode: preserve - - src: .config/hypr/ - dest: .config/hypr/ - mode: preserve - - src: .config/waybar/ - dest: .config/waybar/ - mode: preserve + +host_hyprland_dotfiles: + - src: .config/hypr/host.conf + dest: .config/hypr/host.conf + mode: "0644" diff --git a/ansible/roles/packages_ubuntu/tasks/main.yml b/ansible/roles/packages_ubuntu/tasks/main.yml index ac7ee2a..40d9b37 100644 --- a/ansible/roles/packages_ubuntu/tasks/main.yml +++ b/ansible/roles/packages_ubuntu/tasks/main.yml @@ -86,12 +86,24 @@ ansible.builtin.apt: name: >- {{ - (common_packages | default([])) + ( + (common_packages | default([])) + (ubuntu_packages_base | default([])) + (ubuntu_docker_packages | default([])) + (profile_packages | default([])) - + (desktop_x11_packages | default([])) + + (desktop_common_packages | default([])) + + ( + (desktop_i3_packages | default([])) + if 'i3' in (desktop_sessions_enabled | default([])) + else [] + ) + + ( + (desktop_hyprland_packages | default([])) + if 'hyprland' in (desktop_sessions_enabled | default([])) + else [] + ) + (host_packages | default([])) + ) | unique }} state: present diff --git a/ansible/roles/packages_void/tasks/main.yml b/ansible/roles/packages_void/tasks/main.yml index df68525..e577d88 100644 --- a/ansible/roles/packages_void/tasks/main.yml +++ b/ansible/roles/packages_void/tasks/main.yml @@ -51,11 +51,23 @@ community.general.xbps: name: >- {{ - (common_packages | default([])) + ( + (common_packages | default([])) + (void_packages_base | default([])) + (profile_packages | default([])) - + (desktop_x11_packages | default([])) + + (desktop_common_packages | default([])) + + ( + (desktop_i3_packages | default([])) + if 'i3' in (desktop_sessions_enabled | default([])) + else [] + ) + + ( + (desktop_hyprland_packages | default([])) + if 'hyprland' in (desktop_sessions_enabled | default([])) + else [] + ) + (host_packages | default([])) + ) | unique }} state: present update_cache: false diff --git a/ansible/roles/profile_desktop_common/handlers/main.yml b/ansible/roles/profile_desktop_common/handlers/main.yml new file mode 100644 index 0000000..d04425a --- /dev/null +++ b/ansible/roles/profile_desktop_common/handlers/main.yml @@ -0,0 +1,19 @@ +--- +- name: Restart emptty service + listen: Restart emptty + ansible.builtin.command: sv restart emptty + changed_when: true + when: + - not ansible_check_mode + - desktop_restart_emptty_automatically | default(false) + +- name: Report manual emptty restart requirement + listen: Restart emptty + ansible.builtin.debug: + msg: >- + Emptty configuration changed but automatic restart is disabled. + Restart it manually from SSH or another TTY with `sudo sv restart emptty` + to avoid dropping the active graphical session. + when: + - not ansible_check_mode + - not (desktop_restart_emptty_automatically | default(false)) diff --git a/ansible/roles/profile_desktop_common/tasks/main.yml b/ansible/roles/profile_desktop_common/tasks/main.yml new file mode 100644 index 0000000..0a6c2dd --- /dev/null +++ b/ansible/roles/profile_desktop_common/tasks/main.yml @@ -0,0 +1,509 @@ +--- +- name: Configure elogind to suspend on lid close + tags: [packages] + ansible.builtin.lineinfile: + path: /etc/elogind/logind.conf + regexp: '^#?HandleLidSwitch=' + line: 'HandleLidSwitch=suspend' + state: present + +- name: Ensure common config directories exist + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0755" + loop: + - "{{ user_home }}/.config" + - "{{ user_home }}/.config/autostart" + - "{{ user_home }}/.config/dunst" + - "{{ user_home }}/.config/alacritty" + - "{{ user_home }}/.config/Thunar" + - "{{ user_home }}/.config/rofi" + +- name: Ensure user local bin directory exists + tags: [dotfiles, dotfiles:desktop, dotfiles:host] + ansible.builtin.file: + path: "{{ user_home }}/.local/bin" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0755" + +- name: Enable gnome-keyring PAM auth hook + tags: [packages, gnome] + ansible.builtin.lineinfile: + path: /etc/pam.d/login + insertafter: '^auth\s+include\s+system-local-login$' + line: "auth optional pam_gnome_keyring.so" + state: present + +- name: Enable gnome-keyring PAM session hook + tags: [packages, gnome] + ansible.builtin.lineinfile: + path: /etc/pam.d/login + insertafter: '^session\s+include\s+system-local-login$' + line: "session optional pam_gnome_keyring.so auto_start" + state: present + +- name: Enable gnome-keyring PAM password hook + tags: [packages, gnome] + ansible.builtin.lineinfile: + path: /etc/pam.d/login + insertafter: '^password\s+include\s+system-local-login$' + line: "password optional pam_gnome_keyring.so use_authtok" + state: present + +- name: Ensure emptty log directory exists + tags: [packages, services, emptty] + ansible.builtin.file: + path: /var/log/emptty + state: directory + owner: root + group: root + mode: "0755" + +- name: Ensure emptty session directories exist + tags: [packages, services, emptty] + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - /etc/emptty/xsessions + - /etc/emptty/wayland-sessions + +- name: Configure emptty + tags: [packages, services, emptty] + ansible.builtin.template: + src: emptty-conf.j2 + dest: /etc/emptty/conf + owner: root + group: root + mode: "0644" + notify: Restart emptty + +- name: Copy common desktop dotfiles + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/desktop/{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ desktop_common_dotfiles | default([]) }}" + loop_control: + label: "{{ item.dest }}" + +- name: Render desktop templates with private values + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: + - src: desktop/.gitconfig.j2 + dest: .gitconfig + mode: "0644" + - src: desktop/.mbsyncrc.j2 + dest: .mbsyncrc + mode: "0600" + - src: desktop/.msmtprc.j2 + dest: .msmtprc + mode: "0600" + - src: desktop/email.el.j2 + dest: .emacs.d/lisp/misc/email.el + mode: "0644" + loop_control: + label: "{{ item.dest }}" + +- name: Refresh user font cache + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.command: fc-cache -f + become_user: "{{ username }}" + environment: + HOME: "{{ user_home }}" + changed_when: false + +- name: Ensure .gnupg directory exists + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.file: + path: "{{ user_home }}/.gnupg" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0700" + +- name: Copy gpg-agent.conf + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/desktop/.gnupg/gpg-agent.conf" + dest: "{{ user_home }}/.gnupg/gpg-agent.conf" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0600" + +- name: Ensure local user directories exist + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.file: + path: "{{ item.path }}" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: + - path: "{{ user_home }}/.local" + mode: "0755" + - path: "{{ user_home }}/.local/share" + mode: "0755" + - path: "{{ user_home }}/.local/share/keyrings" + mode: "0700" + - path: "{{ user_home }}/.local/src" + mode: "0755" + +- name: Ensure maildir directories exist + tags: [dotfiles, dotfiles:desktop] + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0700" + loop: + - "{{ user_home }}/Maildir" + - "{{ user_home }}/Maildir/iCloudAccount" + - "{{ user_home }}/Maildir/ProtonMailAccount" + +- name: Bootstrap iCloud keyring secret from Ansible vault + tags: [dotfiles, dotfiles:desktop, gnome] + when: desktop_manage_icloud_keyring | default(false) + block: + - name: Store iCloud mail password in GNOME Keyring + ansible.builtin.getent: + database: passwd + key: "{{ username }}" + + - name: Set desktop user runtime UID + ansible.builtin.set_fact: + desktop_user_uid: "{{ ansible_facts.getent_passwd[username][1] }}" + + - name: Check whether desktop user DBus session address file exists + ansible.builtin.stat: + path: "{{ user_home }}/.dbus-session-bus-address" + register: desktop_user_bus_address_file + + - name: Read desktop user DBus session address + ansible.builtin.slurp: + src: "{{ user_home }}/.dbus-session-bus-address" + register: desktop_user_bus_address_raw + when: + - (vault_icloud_mail_password | default('')) | length > 0 + - desktop_user_bus_address_file.stat.exists + + - name: Set desktop user DBus session address + ansible.builtin.set_fact: + desktop_user_bus_address: >- + {{ desktop_user_bus_address_raw.content | b64decode | trim }} + when: + - (vault_icloud_mail_password | default('')) | length > 0 + - desktop_user_bus_address_file.stat.exists + + - name: Check whether GNOME Keyring default collection is available + ansible.builtin.command: + cmd: >- + gdbus call --session + --dest org.freedesktop.secrets + --object-path /org/freedesktop/secrets + --method org.freedesktop.Secret.Service.ReadAlias default + become: true + become_user: "{{ username }}" + environment: + HOME: "{{ user_home }}" + XDG_RUNTIME_DIR: "/run/user/{{ desktop_user_uid }}" + DBUS_SESSION_BUS_ADDRESS: "{{ desktop_user_bus_address }}" + register: icloud_keyring_default_alias + failed_when: false + changed_when: false + when: + - (vault_icloud_mail_password | default('')) | length > 0 + - desktop_user_bus_address | default('') | length > 0 + + - name: Set GNOME Keyring default collection path + ansible.builtin.set_fact: + icloud_keyring_default_alias_path: >- + {{ + ( + icloud_keyring_default_alias.stdout + | default('') + | regex_findall("objectpath '([^']+)'") + | first + ) + | default('') + }} + when: + - (vault_icloud_mail_password | default('')) | length > 0 + - desktop_user_bus_address | default('') | length > 0 + - icloud_keyring_default_alias.rc | default(1) == 0 + + - name: Store iCloud mail password in GNOME Keyring + ansible.builtin.command: + cmd: secret-tool store --label="iCloud Mail" icloud-mail icloud + stdin: "{{ vault_icloud_mail_password }}" + stdin_add_newline: false + become: true + become_user: "{{ username }}" + environment: + HOME: "{{ user_home }}" + XDG_RUNTIME_DIR: "/run/user/{{ desktop_user_uid }}" + DBUS_SESSION_BUS_ADDRESS: "{{ desktop_user_bus_address }}" + register: icloud_keyring_store + failed_when: false + changed_when: icloud_keyring_store.rc == 0 + no_log: true + when: + - (vault_icloud_mail_password | default('')) | length > 0 + - desktop_user_bus_address | default('') | length > 0 + - icloud_keyring_default_alias.rc | default(1) == 0 + - (icloud_keyring_default_alias_path | default('')) | length > 0 + - (icloud_keyring_default_alias_path | default('')) != '/' + + - name: Warn when iCloud keyring storage is skipped + ansible.builtin.debug: + msg: >- + Unable to store iCloud password in GNOME Keyring automatically. + {% if (desktop_user_bus_address | default('')) | length == 0 %} + No saved DBus session address was found in {{ user_home }}/.dbus-session-bus-address. + {% elif icloud_keyring_default_alias.rc | default(1) != 0 %} + The Secret Service default alias could not be queried for {{ username }}. + {% elif (icloud_keyring_default_alias_path | default('')) == '/' %} + The Secret Service default alias is unset, so the login keyring is not initialized. + {% endif %} + Ensure a graphical user session is active, the login keyring exists and is unlocked, then run: + secret-tool store --label="iCloud Mail" icloud-mail icloud + when: + - (vault_icloud_mail_password | default('')) | length > 0 + - icloud_keyring_store.rc | default(1) != 0 + +- name: Clone st repository + tags: [packages] + ansible.builtin.git: + repo: https://codeberg.org/fscotto/st + dest: "{{ user_home }}/.local/src/st" + update: true + become_user: "{{ username }}" + environment: + HOME: "{{ user_home }}" + register: st_repo + +- name: Check whether st binary is installed + tags: [packages] + ansible.builtin.stat: + path: /usr/local/bin/st + register: st_binary + +- name: Build and install st + tags: [packages] + ansible.builtin.command: + cmd: make clean install + chdir: "{{ user_home }}/.local/src/st" + when: st_repo.changed or not st_binary.stat.exists + +- name: Clean st build artifacts + tags: [packages] + ansible.builtin.command: + cmd: make clean + chdir: "{{ user_home }}/.local/src/st" + when: st_repo.changed or not st_binary.stat.exists + +- name: Ensure flathub remote is configured + tags: [packages] + community.general.flatpak_remote: + name: "{{ desktop_flatpak_remote_name | default('flathub') }}" + state: present + flatpakrepo_url: "{{ desktop_flatpak_remote_url | default('https://dl.flathub.org/repo/flathub.flatpakrepo') }}" + when: (desktop_flatpak_packages | default([])) | length > 0 + +- name: Install desktop flatpak applications + tags: [packages] + community.general.flatpak: + name: "{{ desktop_flatpak_packages }}" + state: present + remote: "{{ desktop_flatpak_remote_name | default('flathub') }}" + method: system + when: (desktop_flatpak_packages | default([])) | length > 0 + +- name: Install Flatpak extensions + tags: [packages] + community.general.flatpak: + name: "{{ item }}" + state: present + remote: "{{ desktop_flatpak_remote_name | default('flathub') }}" + method: system + loop: "{{ desktop_flatpak_extensions | default([]) }}" + when: + - (desktop_flatpak_packages | default([])) | length > 0 + - (desktop_flatpak_extensions | default([])) | length > 0 + - item | length > 0 + +- name: Set desktop external tool release metadata + tags: [packages] + ansible.builtin.set_fact: + desktop_tools_tmp_dir: /tmp/desktop-tools + gitmux_version: v0.11.5 + bw_version: 1.22.1 + 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 '' + }} + gitmux_arch: >- + {{ + 'amd64' if ansible_facts['architecture'] == 'x86_64' + else 'arm64' 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 architecture is supported for gitmux binary + tags: [packages] + ansible.builtin.fail: + msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for gitmux release binary" + when: gitmux_arch == '' + +- name: Ensure architecture is supported for bw binary + tags: [packages] + ansible.builtin.fail: + msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for bw release binary" + when: ansible_facts['architecture'] != 'x86_64' + +- name: Ensure temporary directory exists for external tools + tags: [packages] + ansible.builtin.file: + path: "{{ desktop_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: "{{ desktop_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: "{{ desktop_tools_tmp_dir }}/{{ opencode_asset.name }}" + dest: "{{ desktop_tools_tmp_dir }}" + remote_src: true + +- name: Install OpenCode binary + tags: [packages] + ansible.builtin.copy: + src: "{{ desktop_tools_tmp_dir }}/opencode" + dest: /usr/local/bin/opencode + remote_src: true + owner: root + group: root + mode: "0755" + +- name: Set gitmux asset metadata + tags: [packages] + ansible.builtin.set_fact: + gitmux_asset: "gitmux_{{ gitmux_version }}_linux_{{ gitmux_arch }}.tar.gz" + +- name: Download gitmux release archive + tags: [packages] + ansible.builtin.get_url: + url: "https://github.com/arl/gitmux/releases/download/{{ gitmux_version }}/{{ gitmux_asset }}" + dest: "{{ desktop_tools_tmp_dir }}/{{ gitmux_asset }}" + checksum: "sha256:https://github.com/arl/gitmux/releases/download/{{ gitmux_version }}/checksums.txt" + mode: "0644" + +- name: Extract gitmux release archive + tags: [packages] + ansible.builtin.unarchive: + src: "{{ desktop_tools_tmp_dir }}/{{ gitmux_asset }}" + dest: "{{ desktop_tools_tmp_dir }}" + remote_src: true + +- name: Install gitmux binary + tags: [packages] + ansible.builtin.copy: + src: "{{ desktop_tools_tmp_dir }}/gitmux" + dest: /usr/local/bin/gitmux + remote_src: true + owner: root + group: root + mode: "0755" + +- name: Set bw asset metadata + tags: [packages] + ansible.builtin.set_fact: + bw_asset: "bw-linux-{{ bw_version }}.zip" + +- name: Download bw release archive + tags: [packages] + ansible.builtin.get_url: + url: "https://github.com/bitwarden/cli/releases/download/v{{ bw_version }}/{{ bw_asset }}" + dest: "{{ desktop_tools_tmp_dir }}/{{ bw_asset }}" + checksum: "sha256:https://github.com/bitwarden/cli/releases/download/v{{ bw_version }}/bw-linux-sha256-{{ bw_version }}.txt" + mode: "0644" + +- name: Extract bw release archive + tags: [packages] + ansible.builtin.unarchive: + src: "{{ desktop_tools_tmp_dir }}/{{ bw_asset }}" + dest: "{{ desktop_tools_tmp_dir }}" + remote_src: true + +- name: Install bw binary + tags: [packages] + ansible.builtin.copy: + src: "{{ desktop_tools_tmp_dir }}/bw" + dest: /usr/local/bin/bw + remote_src: true + owner: root + group: root + mode: "0755" diff --git a/ansible/roles/profile_desktop_common/templates/emptty-conf.j2 b/ansible/roles/profile_desktop_common/templates/emptty-conf.j2 new file mode 100644 index 0000000..c54363e --- /dev/null +++ b/ansible/roles/profile_desktop_common/templates/emptty-conf.j2 @@ -0,0 +1,18 @@ +TTY_NUMBER=7 +SWITCH_TTY=true +DEFAULT_ENV={{ desktop_default_session_env | default('xorg') }} +{% if not (desktop_prompt_for_session | default(false)) and (desktop_default_session | default('')) | length > 0 %} +DEFAULT_SESSION={{ desktop_default_session | default('') }} +{% if (desktop_default_session_env | default('')) | length > 0 %} +DEFAULT_SESSION_ENV={{ desktop_default_session_env | default('') }} +{% endif %} +{% endif %} +DBUS_LAUNCH=false +XINITRC_LAUNCH=true +XORG_SESSIONS_PATH=/etc/emptty/xsessions +WAYLAND_SESSIONS_PATH=/etc/emptty/wayland-sessions +VERTICAL_SELECTION=true +IDENTIFY_ENVS=true +SELECT_LAST_USER=global +LOGGING=rotate +SESSION_ERROR_LOGGING={{ desktop_emptty_session_error_logging | default('disabled') }} diff --git a/ansible/roles/profile_desktop_host/tasks/main.yml b/ansible/roles/profile_desktop_host/tasks/main.yml new file mode 100644 index 0000000..f1d1dc2 --- /dev/null +++ b/ansible/roles/profile_desktop_host/tasks/main.yml @@ -0,0 +1,35 @@ +--- +- name: Include nymph desktop host tasks + tags: [packages, nvidia, dotfiles, dotfiles:host] + ansible.builtin.include_tasks: nymph.yml + when: hostname == 'nymph' + +- name: Copy host-specific i3 dotfiles + tags: [dotfiles, dotfiles:desktop, dotfiles:host, i3] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/{{ hostname }}/{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ host_i3_dotfiles | default([]) }}" + loop_control: + label: "{{ item.dest }}" + when: + - "'i3' in (desktop_sessions_enabled | default([]))" + - (host_i3_dotfiles | default([])) | length > 0 + +- name: Copy host-specific Hyprland dotfiles + tags: [dotfiles, dotfiles:desktop, dotfiles:host, hyprland] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/{{ hostname }}/{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ host_hyprland_dotfiles | default([]) }}" + loop_control: + label: "{{ item.dest }}" + when: + - "'hyprland' in (desktop_sessions_enabled | default([]))" + - (host_hyprland_dotfiles | default([])) | length > 0 diff --git a/ansible/roles/profile_desktop_host/tasks/nymph.yml b/ansible/roles/profile_desktop_host/tasks/nymph.yml new file mode 100644 index 0000000..167c82a --- /dev/null +++ b/ansible/roles/profile_desktop_host/tasks/nymph.yml @@ -0,0 +1,41 @@ +--- +- name: Configure GRUB kernel parameters for NVIDIA hybrid graphics + tags: [packages, nvidia] + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX=' + line: 'GRUB_CMDLINE_LINUX="rd.luks.uuid=1e15d159-5d05-4a1f-9639-ac200dff9f9c rootflags=subvol=@ apparmor=1 security=apparmor nouveau.modeset=0 nvidia-drm.modeset=1"' + state: present + +- name: Regenerate GRUB configuration + tags: [packages, nvidia] + ansible.builtin.command: grub-mkconfig -o /boot/grub/grub.cfg + changed_when: true + +- name: Configure NVIDIA power management for hybrid graphics + tags: [packages, nvidia] + ansible.builtin.copy: + dest: /etc/modprobe.d/nvidia-power-management.conf + content: | + options nvidia "NVreg_DynamicPowerManagement=0x02" + owner: root + group: root + mode: "0644" + +- name: Install prime-run wrapper script for NVIDIA PRIME offload + tags: [nvidia, dotfiles, dotfiles:host] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/nymph/.local/bin/prime-run" + dest: "{{ user_home }}/.local/bin/prime-run" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0755" + force: false + +- name: Wrap alacritty with prime-run for NVIDIA PRIME offload + tags: [nvidia, dotfiles, dotfiles:desktop, dotfiles:host] + ansible.builtin.lineinfile: + path: "{{ user_home }}/.config/i3/config" + regexp: '^bindsym \$mod\+Return exec --no-startup-id /usr/bin/alacritty' + line: 'bindsym $mod+Return exec --no-startup-id ~/.local/bin/prime-run /usr/bin/alacritty' + when: "'i3' in (desktop_sessions_enabled | default([]))" diff --git a/ansible/roles/profile_desktop_hyprland/tasks/main.yml b/ansible/roles/profile_desktop_hyprland/tasks/main.yml new file mode 100644 index 0000000..0822329 --- /dev/null +++ b/ansible/roles/profile_desktop_hyprland/tasks/main.yml @@ -0,0 +1,36 @@ +--- +- name: Ensure Hyprland config directories exist + tags: [dotfiles, dotfiles:desktop, hyprland] + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "0755" + loop: + - "{{ user_home }}/.config/hypr" + - "{{ user_home }}/.config/waybar" + when: "'hyprland' in (desktop_sessions_enabled | default([]))" + +- name: Install Hyprland emptty session entry + tags: [packages, services, emptty, hyprland] + ansible.builtin.template: + src: Hyprland.desktop.j2 + dest: /etc/emptty/wayland-sessions/Hyprland.desktop + owner: root + group: root + mode: "0644" + when: "'hyprland' in (desktop_sessions_enabled | default([]))" + +- name: Copy Hyprland desktop dotfiles + tags: [dotfiles, dotfiles:desktop, hyprland] + ansible.builtin.copy: + src: "{{ playbook_dir }}/../dotfiles/desktop/{{ item.src }}" + dest: "{{ user_home }}/{{ item.dest }}" + owner: "{{ username }}" + group: "{{ user_group }}" + mode: "{{ item.mode }}" + loop: "{{ desktop_hyprland_dotfiles | default([]) }}" + loop_control: + label: "{{ item.dest }}" + when: "'hyprland' in (desktop_sessions_enabled | default([]))" diff --git a/ansible/roles/profile_desktop_i3/files/Hyprland.desktop b/ansible/roles/profile_desktop_hyprland/templates/Hyprland.desktop.j2 similarity index 77% rename from ansible/roles/profile_desktop_i3/files/Hyprland.desktop rename to ansible/roles/profile_desktop_hyprland/templates/Hyprland.desktop.j2 index f75dca0..c114779 100644 --- a/ansible/roles/profile_desktop_i3/files/Hyprland.desktop +++ b/ansible/roles/profile_desktop_hyprland/templates/Hyprland.desktop.j2 @@ -1,7 +1,7 @@ [Desktop Entry] Name=Hyprland Comment=Hyprland Wayland compositor -Exec=Hyprland +Exec={{ user_home }}/.local/bin/start-hyprland-session TryExec=Hyprland Type=Application DesktopNames=Hyprland diff --git a/ansible/roles/profile_desktop_i3/handlers/main.yml b/ansible/roles/profile_desktop_i3/handlers/main.yml deleted file mode 100644 index c5e610b..0000000 --- a/ansible/roles/profile_desktop_i3/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: Restart emptty - ansible.builtin.command: sv restart emptty - changed_when: true - when: not ansible_check_mode diff --git a/ansible/roles/profile_desktop_i3/tasks/main.yml b/ansible/roles/profile_desktop_i3/tasks/main.yml index 279abac..16c53f6 100644 --- a/ansible/roles/profile_desktop_i3/tasks/main.yml +++ b/ansible/roles/profile_desktop_i3/tasks/main.yml @@ -1,14 +1,6 @@ --- -- name: Configure elogind to suspend on lid close - tags: [packages] - ansible.builtin.lineinfile: - path: /etc/elogind/logind.conf - regexp: '^#?HandleLidSwitch=' - line: 'HandleLidSwitch=suspend' - state: present - -- name: Ensure config directories exist - tags: [dotfiles, dotfiles:desktop] +- name: Ensure i3 config directories exist + tags: [dotfiles, dotfiles:desktop, i3] ansible.builtin.file: path: "{{ item }}" state: directory @@ -16,577 +8,29 @@ group: "{{ user_group }}" mode: "0755" loop: - - "{{ user_home }}/.config" - - "{{ user_home }}/.config/autostart" - "{{ user_home }}/.config/i3" - "{{ user_home }}/.config/i3blocks" - - "{{ user_home }}/.config/dunst" - - "{{ user_home }}/.config/alacritty" - - "{{ user_home }}/.config/Thunar" - - "{{ user_home }}/.config/rofi" - -- name: Configure GRUB kernel parameters for NVIDIA hybrid graphics - tags: [packages, nvidia] - ansible.builtin.lineinfile: - path: /etc/default/grub - regexp: '^GRUB_CMDLINE_LINUX=' - line: 'GRUB_CMDLINE_LINUX="rd.luks.uuid=1e15d159-5d05-4a1f-9639-ac200dff9f9c rootflags=subvol=@ apparmor=1 security=apparmor nouveau.modeset=0 nvidia-drm.modeset=1"' - state: present - when: ansible_facts['hostname'] == 'nymph' - -- name: Regenerate GRUB configuration - tags: [packages, nvidia] - ansible.builtin.command: grub-mkconfig -o /boot/grub/grub.cfg - changed_when: true - when: ansible_facts['hostname'] == 'nymph' - -- name: Configure NVIDIA power management for hybrid graphics - tags: [packages, nvidia] - ansible.builtin.copy: - dest: /etc/modprobe.d/nvidia-power-management.conf - content: | - options nvidia "NVreg_DynamicPowerManagement=0x02" - owner: root - group: root - mode: "0644" - when: ansible_facts['hostname'] == 'nymph' - -- name: Ensure user local bin directory exists - tags: [nvidia, dotfiles, dotfiles:host] - ansible.builtin.file: - path: "{{ user_home }}/.local/bin" - state: directory - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "0755" - when: ansible_facts['hostname'] == 'nymph' - -- name: Install prime-run wrapper script for NVIDIA PRIME offload - tags: [nvidia, dotfiles, dotfiles:host] - ansible.builtin.copy: - src: "{{ playbook_dir }}/../dotfiles/nymph/.local/bin/prime-run" - dest: "{{ user_home }}/.local/bin/prime-run" - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "0755" - force: false - when: ansible_facts['hostname'] == 'nymph' - -- name: Enable gnome-keyring PAM auth hook - tags: [packages, gnome] - ansible.builtin.lineinfile: - path: /etc/pam.d/login - insertafter: '^auth\s+include\s+system-local-login$' - line: "auth optional pam_gnome_keyring.so" - state: present - -- name: Enable gnome-keyring PAM session hook - tags: [packages, gnome] - ansible.builtin.lineinfile: - path: /etc/pam.d/login - insertafter: '^session\s+include\s+system-local-login$' - line: "session optional pam_gnome_keyring.so auto_start" - state: present - -- name: Enable gnome-keyring PAM password hook - tags: [packages, gnome] - ansible.builtin.lineinfile: - path: /etc/pam.d/login - insertafter: '^password\s+include\s+system-local-login$' - line: "password optional pam_gnome_keyring.so use_authtok" - state: present - -- name: Ensure emptty log directory exists - tags: [packages, services, emptty] - ansible.builtin.file: - path: /var/log/emptty - state: directory - owner: root - group: root - mode: "0755" - -- name: Ensure emptty session directories exist - tags: [packages, services, emptty] - ansible.builtin.file: - path: "{{ item }}" - state: directory - owner: root - group: root - mode: "0755" - loop: - - /etc/emptty/xsessions - - /etc/emptty/wayland-sessions + when: "'i3' in (desktop_sessions_enabled | default([]))" - name: Install allowed emptty X11 sessions - tags: [packages, services, emptty] + tags: [packages, services, emptty, i3] ansible.builtin.copy: src: i3.desktop dest: /etc/emptty/xsessions/i3.desktop owner: root group: root mode: "0644" + when: "'i3' in (desktop_sessions_enabled | default([]))" -- name: Install host-specific Wayland sessions for emptty - tags: [packages, services, emptty, hyprland] - ansible.builtin.copy: - src: "{{ item }}" - dest: "/etc/emptty/wayland-sessions/{{ item }}" - owner: root - group: root - mode: "0644" - loop: "{{ host_emptty_wayland_sessions | default([]) }}" - loop_control: - label: "{{ item }}" - when: (host_emptty_wayland_sessions | default([])) | length > 0 - -- name: Configure emptty - tags: [packages, services, emptty] - ansible.builtin.template: - src: emptty-conf.j2 - dest: /etc/emptty/conf - owner: root - group: root - mode: "0644" - notify: Restart emptty - -- name: Copy desktop dotfiles - tags: [dotfiles, dotfiles:desktop] +- name: Copy i3 desktop dotfiles + tags: [dotfiles, dotfiles:desktop, i3] ansible.builtin.copy: src: "{{ playbook_dir }}/../dotfiles/desktop/{{ item.src }}" dest: "{{ user_home }}/{{ item.dest }}" owner: "{{ username }}" group: "{{ user_group }}" mode: "{{ item.mode }}" - loop: "{{ desktop_dotfiles | default([]) }}" + loop: "{{ desktop_i3_dotfiles | default([]) }}" loop_control: label: "{{ item.dest }}" - -- name: Wrap alacritty with prime-run for NVIDIA PRIME offload - tags: [nvidia, dotfiles, dotfiles:desktop, dotfiles:host] - ansible.builtin.lineinfile: - path: "{{ user_home }}/.config/i3/config" - regexp: '^bindsym \$mod\+Return exec --no-startup-id /usr/bin/alacritty' - line: 'bindsym $mod+Return exec --no-startup-id ~/.local/bin/prime-run /usr/bin/alacritty' - when: ansible_facts['hostname'] == 'nymph' - -- name: Copy host-specific dotfiles - tags: [dotfiles, dotfiles:desktop, dotfiles:host] - ansible.builtin.copy: - src: "{{ playbook_dir }}/../dotfiles/{{ hostname }}/{{ item.src }}" - dest: "{{ user_home }}/{{ item.dest }}" - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "{{ item.mode }}" - loop: "{{ host_dotfiles | default([]) }}" - loop_control: - label: "{{ item.dest }}" - when: host_dotfiles is defined and host_dotfiles | length > 0 - -- name: Render desktop templates with private values - tags: [dotfiles, dotfiles:desktop] - ansible.builtin.template: - src: "{{ item.src }}" - dest: "{{ user_home }}/{{ item.dest }}" - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "{{ item.mode }}" - loop: - - src: desktop/.gitconfig.j2 - dest: .gitconfig - mode: "0644" - - src: desktop/.mbsyncrc.j2 - dest: .mbsyncrc - mode: "0600" - - src: desktop/.msmtprc.j2 - dest: .msmtprc - mode: "0600" - - src: desktop/email.el.j2 - dest: .emacs.d/lisp/misc/email.el - mode: "0644" - loop_control: - label: "{{ item.dest }}" - -- name: Refresh user font cache - tags: [dotfiles, dotfiles:desktop] - ansible.builtin.command: fc-cache -f - become_user: "{{ username }}" - environment: - HOME: "{{ user_home }}" - changed_when: false - -- name: Ensure .gnupg directory exists - tags: [dotfiles, dotfiles:desktop] - ansible.builtin.file: - path: "{{ user_home }}/.gnupg" - state: directory - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "0700" - -- name: Copy gpg-agent.conf - tags: [dotfiles, dotfiles:desktop] - ansible.builtin.copy: - src: "{{ playbook_dir }}/../dotfiles/desktop/.gnupg/gpg-agent.conf" - dest: "{{ user_home }}/.gnupg/gpg-agent.conf" - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "0600" - -- name: Ensure local user directories exist - tags: [dotfiles, dotfiles:desktop] - ansible.builtin.file: - path: "{{ item.path }}" - state: directory - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "{{ item.mode }}" - loop: - - path: "{{ user_home }}/.local" - mode: "0755" - - path: "{{ user_home }}/.local/share" - mode: "0755" - - path: "{{ user_home }}/.local/share/keyrings" - mode: "0700" - - path: "{{ user_home }}/.local/src" - mode: "0755" - -- name: Ensure maildir directories exist - tags: [dotfiles, dotfiles:desktop] - ansible.builtin.file: - path: "{{ item }}" - state: directory - owner: "{{ username }}" - group: "{{ user_group }}" - mode: "0700" - loop: - - "{{ user_home }}/Maildir" - - "{{ user_home }}/Maildir/iCloudAccount" - - "{{ user_home }}/Maildir/ProtonMailAccount" - -- name: Bootstrap iCloud keyring secret from Ansible vault - tags: [dotfiles, dotfiles:desktop, gnome] - when: desktop_manage_icloud_keyring | default(false) - block: - - name: Store iCloud mail password in GNOME Keyring - ansible.builtin.getent: - database: passwd - key: "{{ username }}" - - - name: Set desktop user runtime UID - ansible.builtin.set_fact: - desktop_user_uid: "{{ ansible_facts.getent_passwd[username][1] }}" - - - name: Check whether desktop user DBus session address file exists - ansible.builtin.stat: - path: "{{ user_home }}/.dbus-session-bus-address" - register: desktop_user_bus_address_file - - - name: Read desktop user DBus session address - ansible.builtin.slurp: - src: "{{ user_home }}/.dbus-session-bus-address" - register: desktop_user_bus_address_raw - when: - - (vault_icloud_mail_password | default('')) | length > 0 - - desktop_user_bus_address_file.stat.exists - - - name: Set desktop user DBus session address - ansible.builtin.set_fact: - desktop_user_bus_address: >- - {{ desktop_user_bus_address_raw.content | b64decode | trim }} - when: - - (vault_icloud_mail_password | default('')) | length > 0 - - desktop_user_bus_address_file.stat.exists - - - name: Check whether GNOME Keyring default collection is available - ansible.builtin.command: - cmd: >- - gdbus call --session - --dest org.freedesktop.secrets - --object-path /org/freedesktop/secrets - --method org.freedesktop.Secret.Service.ReadAlias default - become: true - become_user: "{{ username }}" - environment: - HOME: "{{ user_home }}" - XDG_RUNTIME_DIR: "/run/user/{{ desktop_user_uid }}" - DBUS_SESSION_BUS_ADDRESS: "{{ desktop_user_bus_address }}" - register: icloud_keyring_default_alias - failed_when: false - changed_when: false - when: - - (vault_icloud_mail_password | default('')) | length > 0 - - desktop_user_bus_address | default('') | length > 0 - - - name: Set GNOME Keyring default collection path - ansible.builtin.set_fact: - icloud_keyring_default_alias_path: >- - {{ - ( - icloud_keyring_default_alias.stdout - | default('') - | regex_findall("objectpath '([^']+)'") - | first - ) - | default('') - }} - when: - - (vault_icloud_mail_password | default('')) | length > 0 - - desktop_user_bus_address | default('') | length > 0 - - icloud_keyring_default_alias.rc | default(1) == 0 - - - name: Store iCloud mail password in GNOME Keyring - ansible.builtin.command: - cmd: secret-tool store --label="iCloud Mail" icloud-mail icloud - stdin: "{{ vault_icloud_mail_password }}" - stdin_add_newline: false - become: true - become_user: "{{ username }}" - environment: - HOME: "{{ user_home }}" - XDG_RUNTIME_DIR: "/run/user/{{ desktop_user_uid }}" - DBUS_SESSION_BUS_ADDRESS: "{{ desktop_user_bus_address }}" - register: icloud_keyring_store - failed_when: false - changed_when: icloud_keyring_store.rc == 0 - no_log: true - when: - - (vault_icloud_mail_password | default('')) | length > 0 - - desktop_user_bus_address | default('') | length > 0 - - icloud_keyring_default_alias.rc | default(1) == 0 - - (icloud_keyring_default_alias_path | default('')) | length > 0 - - (icloud_keyring_default_alias_path | default('')) != '/' - - - name: Warn when iCloud keyring storage is skipped - ansible.builtin.debug: - msg: >- - Unable to store iCloud password in GNOME Keyring automatically. - {% if (desktop_user_bus_address | default('')) | length == 0 %} - No saved DBus session address was found in {{ user_home }}/.dbus-session-bus-address. - {% elif icloud_keyring_default_alias.rc | default(1) != 0 %} - The Secret Service default alias could not be queried for {{ username }}. - {% elif (icloud_keyring_default_alias_path | default('')) == '/' %} - The Secret Service default alias is unset, so the login keyring is not initialized. - {% endif %} - Ensure a graphical user session is active, the login keyring exists and is unlocked, then run: - secret-tool store --label="iCloud Mail" icloud-mail icloud - when: - - (vault_icloud_mail_password | default('')) | length > 0 - - icloud_keyring_store.rc | default(1) != 0 - -- name: Clone st repository - tags: [packages] - ansible.builtin.git: - repo: https://codeberg.org/fscotto/st - dest: "{{ user_home }}/.local/src/st" - update: true - become_user: "{{ username }}" - environment: - HOME: "{{ user_home }}" - register: st_repo - -- name: Check whether st binary is installed - tags: [packages] - ansible.builtin.stat: - path: /usr/local/bin/st - register: st_binary - -- name: Build and install st - tags: [packages] - ansible.builtin.command: - cmd: make clean install - chdir: "{{ user_home }}/.local/src/st" - when: st_repo.changed or not st_binary.stat.exists - -- name: Clean st build artifacts - tags: [packages] - ansible.builtin.command: - cmd: make clean - chdir: "{{ user_home }}/.local/src/st" - when: st_repo.changed or not st_binary.stat.exists - -- name: Ensure flathub remote is configured - tags: [packages] - community.general.flatpak_remote: - name: "{{ desktop_flatpak_remote_name | default('flathub') }}" - state: present - flatpakrepo_url: "{{ desktop_flatpak_remote_url | default('https://dl.flathub.org/repo/flathub.flatpakrepo') }}" - when: (desktop_flatpak_packages | default([])) | length > 0 - -- name: Install desktop flatpak applications - tags: [packages] - community.general.flatpak: - name: "{{ desktop_flatpak_packages }}" - state: present - remote: "{{ desktop_flatpak_remote_name | default('flathub') }}" - method: system - when: (desktop_flatpak_packages | default([])) | length > 0 - -- name: Install Flatpak extensions - tags: [packages] - community.general.flatpak: - name: "{{ item }}" - state: present - remote: "{{ desktop_flatpak_remote_name | default('flathub') }}" - method: system - loop: "{{ desktop_flatpak_extensions | default([]) }}" - when: - - (desktop_flatpak_packages | default([])) | length > 0 - - (desktop_flatpak_extensions | default([])) | length > 0 - - item | length > 0 - -- name: Set desktop external tool release metadata - tags: [packages] - ansible.builtin.set_fact: - desktop_tools_tmp_dir: /tmp/desktop-tools - gitmux_version: v0.11.5 - bw_version: 1.22.1 - 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 '' - }} - gitmux_arch: >- - {{ - 'amd64' if ansible_facts['architecture'] == 'x86_64' - else 'arm64' 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 architecture is supported for gitmux binary - tags: [packages] - ansible.builtin.fail: - msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for gitmux release binary" - when: gitmux_arch == '' - -- name: Ensure architecture is supported for bw binary - tags: [packages] - ansible.builtin.fail: - msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for bw release binary" - when: ansible_facts['architecture'] != 'x86_64' - -- name: Ensure temporary directory exists for external tools - tags: [packages] - ansible.builtin.file: - path: "{{ desktop_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: "{{ desktop_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: "{{ desktop_tools_tmp_dir }}/{{ opencode_asset.name }}" - dest: "{{ desktop_tools_tmp_dir }}" - remote_src: true - -- name: Install OpenCode binary - tags: [packages] - ansible.builtin.copy: - src: "{{ desktop_tools_tmp_dir }}/opencode" - dest: /usr/local/bin/opencode - remote_src: true - owner: root - group: root - mode: "0755" - -- name: Set gitmux asset metadata - tags: [packages] - ansible.builtin.set_fact: - gitmux_asset: "gitmux_{{ gitmux_version }}_linux_{{ gitmux_arch }}.tar.gz" - -- name: Download gitmux release archive - tags: [packages] - ansible.builtin.get_url: - url: "https://github.com/arl/gitmux/releases/download/{{ gitmux_version }}/{{ gitmux_asset }}" - dest: "{{ desktop_tools_tmp_dir }}/{{ gitmux_asset }}" - checksum: "sha256:https://github.com/arl/gitmux/releases/download/{{ gitmux_version }}/checksums.txt" - mode: "0644" - -- name: Extract gitmux release archive - tags: [packages] - ansible.builtin.unarchive: - src: "{{ desktop_tools_tmp_dir }}/{{ gitmux_asset }}" - dest: "{{ desktop_tools_tmp_dir }}" - remote_src: true - -- name: Install gitmux binary - tags: [packages] - ansible.builtin.copy: - src: "{{ desktop_tools_tmp_dir }}/gitmux" - dest: /usr/local/bin/gitmux - remote_src: true - owner: root - group: root - mode: "0755" - -- name: Set bw asset metadata - tags: [packages] - ansible.builtin.set_fact: - bw_asset: "bw-linux-{{ bw_version }}.zip" - -- name: Download bw release archive - tags: [packages] - ansible.builtin.get_url: - url: "https://github.com/bitwarden/cli/releases/download/v{{ bw_version }}/{{ bw_asset }}" - dest: "{{ desktop_tools_tmp_dir }}/{{ bw_asset }}" - checksum: "sha256:https://github.com/bitwarden/cli/releases/download/v{{ bw_version }}/bw-linux-sha256-{{ bw_version }}.txt" - mode: "0644" - -- name: Extract bw release archive - tags: [packages] - ansible.builtin.unarchive: - src: "{{ desktop_tools_tmp_dir }}/{{ bw_asset }}" - dest: "{{ desktop_tools_tmp_dir }}" - remote_src: true - -- name: Install bw binary - tags: [packages] - ansible.builtin.copy: - src: "{{ desktop_tools_tmp_dir }}/bw" - dest: /usr/local/bin/bw - remote_src: true - owner: root - group: root - mode: "0755" + when: "'i3' in (desktop_sessions_enabled | default([]))" diff --git a/ansible/roles/profile_desktop_i3/templates/emptty-conf.j2 b/ansible/roles/profile_desktop_i3/templates/emptty-conf.j2 deleted file mode 100644 index 51709df..0000000 --- a/ansible/roles/profile_desktop_i3/templates/emptty-conf.j2 +++ /dev/null @@ -1,12 +0,0 @@ -TTY_NUMBER=7 -SWITCH_TTY=true -DEFAULT_ENV=xorg -DBUS_LAUNCH=false -XINITRC_LAUNCH=true -XORG_SESSIONS_PATH=/etc/emptty/xsessions -WAYLAND_SESSIONS_PATH=/etc/emptty/wayland-sessions -VERTICAL_SELECTION=true -IDENTIFY_ENVS=true -SELECT_LAST_USER=global -LOGGING=rotate -SESSION_ERROR_LOGGING=disabled diff --git a/ansible/site.yml b/ansible/site.yml index f3d93b3..1b6b9c7 100644 --- a/ansible/site.yml +++ b/ansible/site.yml @@ -17,7 +17,10 @@ roles: - packages_void - services_runit + - profile_desktop_common - profile_desktop_i3 + - profile_desktop_hyprland + - profile_desktop_host - hosts: ubuntu_workstation become: true diff --git a/dotfiles/desktop/.config/hypr/host.conf b/dotfiles/desktop/.config/hypr/host.conf new file mode 100644 index 0000000..e69de29 diff --git a/dotfiles/desktop/.config/hypr/hypridle.conf b/dotfiles/desktop/.config/hypr/hypridle.conf new file mode 100644 index 0000000..307e1ab --- /dev/null +++ b/dotfiles/desktop/.config/hypr/hypridle.conf @@ -0,0 +1,16 @@ +general { + lock_cmd = pidof hyprlock || hyprlock + before_sleep_cmd = pidof hyprlock || hyprlock + after_sleep_cmd = hyprctl dispatch dpms on +} + +listener { + timeout = 600 + on-timeout = pidof hyprlock || hyprlock +} + +listener { + timeout = 900 + on-timeout = hyprctl dispatch dpms off + on-resume = hyprctl dispatch dpms on +} diff --git a/dotfiles/desktop/.config/hypr/hyprland.conf b/dotfiles/desktop/.config/hypr/hyprland.conf new file mode 100644 index 0000000..b93dece --- /dev/null +++ b/dotfiles/desktop/.config/hypr/hyprland.conf @@ -0,0 +1,171 @@ +$mod = SUPER +$terminal = alacritty +$fallback_terminal = st +$menu = rofi -show drun -theme ~/.config/rofi/config.rasi +$powermenu = ~/.local/bin/powermenu +$locker = ~/.local/bin/lock-session +$screenshot = ~/.local/bin/screenshot-wayland + +exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP +exec-once = dex --autostart --environment Hyprland +exec-once = gnome-keyring-daemon --start --components=secrets +exec-once = ~/.local/bin/setup-gtk-theme +exec-once = hyprpaper +exec-once = waybar +exec-once = hypridle +exec-once = dunst +exec-once = pipewire +exec-once = pipewire-pulse +exec-once = wireplumber +exec-once = /usr/libexec/xfce-polkit +exec-once = nm-applet +exec-once = blueman-applet +exec-once = udiskie --tray --automount + +env = XCURSOR_THEME,Yaru +env = XCURSOR_SIZE,24 +env = GTK_THEME,Yaru-blue-dark + +source = ~/.config/hypr/host.conf + +monitor = ,preferred,auto,1 + +input { + kb_layout = us + kb_variant = intl + follow_mouse = 1 + sensitivity = 0 + + touchpad { + natural_scroll = true + } +} + +general { + layout = dwindle + gaps_in = 0 + gaps_out = 0 + border_size = 2 + col.active_border = rgb(4a90d9) + col.inactive_border = rgb(3a3a46) + allow_tearing = false +} + +dwindle { + preserve_split = yes +} + +decoration { + rounding = 0 + + blur { + enabled = false + } +} + +animations { + enabled = no +} + +misc { + disable_hyprland_logo = true + disable_splash_rendering = true +} + +windowrulev2 = float,class:^(Rofi)$ +windowrulev2 = float,class:^(org.pulseaudio.pavucontrol)$ +windowrulev2 = float,class:^(nm-connection-editor)$ +windowrulev2 = float,class:^(blueman-manager)$ + +bind = $mod, Return, exec, $terminal +bind = $mod SHIFT, Return, exec, $fallback_terminal +bind = $mod, D, exec, $menu +bind = $mod SHIFT, V, exec, pavucontrol +bind = $mod SHIFT, F, exec, thunar +bind = $mod SHIFT, X, exec, $locker +bind = $mod SHIFT, Q, killactive +bind = $mod, F, fullscreen, 1 +bind = $mod, SPACE, togglefloating +bind = $mod SHIFT, SPACE, togglefloating +bind = $mod, V, togglesplit +bind = $mod, B, togglesplit +bind = $mod, minus, togglespecialworkspace, scratchpad +bind = $mod SHIFT, minus, movetoworkspace, special:scratchpad + +bind = $mod, H, movefocus, l +bind = $mod, J, movefocus, d +bind = $mod, K, movefocus, u +bind = $mod, L, movefocus, r +bind = $mod, left, movefocus, l +bind = $mod, down, movefocus, d +bind = $mod, up, movefocus, u +bind = $mod, right, movefocus, r + +bind = $mod SHIFT, H, movewindow, l +bind = $mod SHIFT, J, movewindow, d +bind = $mod SHIFT, K, movewindow, u +bind = $mod SHIFT, L, movewindow, r +bind = $mod SHIFT, left, movewindow, l +bind = $mod SHIFT, down, movewindow, d +bind = $mod SHIFT, up, movewindow, u +bind = $mod SHIFT, right, movewindow, r + +bind = $mod, 1, workspace, 1 +bind = $mod, 2, workspace, 2 +bind = $mod, 3, workspace, 3 +bind = $mod, 4, workspace, 4 +bind = $mod, 5, workspace, 5 +bind = $mod, 6, workspace, 6 +bind = $mod, 7, workspace, 7 +bind = $mod, 8, workspace, 8 +bind = $mod, 9, workspace, 9 +bind = $mod, 0, workspace, 10 + +bind = $mod SHIFT, 1, movetoworkspace, 1 +bind = $mod SHIFT, 2, movetoworkspace, 2 +bind = $mod SHIFT, 3, movetoworkspace, 3 +bind = $mod SHIFT, 4, movetoworkspace, 4 +bind = $mod SHIFT, 5, movetoworkspace, 5 +bind = $mod SHIFT, 6, movetoworkspace, 6 +bind = $mod SHIFT, 7, movetoworkspace, 7 +bind = $mod SHIFT, 8, movetoworkspace, 8 +bind = $mod SHIFT, 9, movetoworkspace, 9 +bind = $mod SHIFT, 0, movetoworkspace, 10 + +bind = $mod SHIFT, C, exec, hyprctl reload +bind = $mod SHIFT, R, exec, hyprctl reload +bind = $mod SHIFT, BackSpace, exit +bind = $mod SHIFT, Escape, exec, $powermenu + +bind = , Print, exec, $screenshot full +bind = SHIFT, Print, exec, $screenshot region +bind = ALT, Print, exec, $screenshot output + +bind = , XF86AudioRaiseVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ +5% +bind = , XF86AudioLowerVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ -5% +bind = , XF86AudioMute, exec, pactl set-sink-mute @DEFAULT_SINK@ toggle +bind = , XF86AudioMicMute, exec, pactl set-source-mute @DEFAULT_SOURCE@ toggle +bind = , XF86MonBrightnessUp, exec, brightnessctl set +10% +bind = , XF86MonBrightnessDown, exec, brightnessctl set 10%- +bind = , XF86AudioPlay, exec, playerctl play-pause +bind = , XF86AudioNext, exec, playerctl next +bind = , XF86AudioPrev, exec, playerctl previous + +bind = $mod, R, submap, resize + +submap = resize +bind = , H, resizeactive, -40 0 +bind = , J, resizeactive, 0 40 +bind = , K, resizeactive, 0 -40 +bind = , L, resizeactive, 40 0 +bind = , left, resizeactive, -40 0 +bind = , down, resizeactive, 0 40 +bind = , up, resizeactive, 0 -40 +bind = , right, resizeactive, 40 0 +bind = , escape, submap, reset +bind = , Return, submap, reset +bind = $mod, R, submap, reset +submap = reset + +bindm = $mod, mouse:272, movewindow +bindm = $mod, mouse:273, resizewindow diff --git a/dotfiles/desktop/.config/hypr/hyprlock.conf b/dotfiles/desktop/.config/hypr/hyprlock.conf new file mode 100644 index 0000000..7b469a0 --- /dev/null +++ b/dotfiles/desktop/.config/hypr/hyprlock.conf @@ -0,0 +1,46 @@ +general { + hide_cursor = true + grace = 2 + no_fade_in = false +} + +background { + monitor = + color = rgba(1f1f28ff) +} + +label { + monitor = + text = cmd[update:1000] echo "$(date +'%H:%M')" + color = rgba(d0d0d0ff) + font_size = 42 + position = 0, 80 + halign = center + valign = center +} + +label { + monitor = + text = cmd[update:60000] echo "$(date +'%Y-%m-%d')" + color = rgba(9a9a9aff) + font_size = 18 + position = 0, 35 + halign = center + valign = center +} + +input-field { + monitor = + size = 280, 56 + outline_thickness = 2 + dots_size = 0.2 + dots_spacing = 0.2 + outer_color = rgba(4a90d9ff) + inner_color = rgba(1f1f28ff) + font_color = rgba(d0d0d0ff) + fade_on_empty = false + placeholder_text = Password... + position = 0, -20 + halign = center + valign = center +} diff --git a/dotfiles/desktop/.config/hypr/hyprpaper.conf b/dotfiles/desktop/.config/hypr/hyprpaper.conf new file mode 100644 index 0000000..e767648 --- /dev/null +++ b/dotfiles/desktop/.config/hypr/hyprpaper.conf @@ -0,0 +1,4 @@ +preload = ~/.config/i3/wallpapers/dont_like_wallpapers.jpg +wallpaper = ,~/.config/i3/wallpapers/dont_like_wallpapers.jpg +splash = false +ipc = on diff --git a/dotfiles/desktop/.config/waybar/config.jsonc b/dotfiles/desktop/.config/waybar/config.jsonc new file mode 100644 index 0000000..f3e681b --- /dev/null +++ b/dotfiles/desktop/.config/waybar/config.jsonc @@ -0,0 +1,111 @@ +{ + "layer": "top", + "position": "bottom", + "height": 28, + "spacing": 4, + + "modules-left": [ + "hyprland/workspaces" + ], + + "modules-right": [ + "pulseaudio", + "backlight", + "network", + "bluetooth", + "battery", + "temperature", + "memory", + "clock", + "custom/powermenu", + "tray" + ], + + "hyprland/workspaces": { + "disable-scroll": true, + "all-outputs": true, + "format": "{name}", + "on-click": "activate", + "sort-by-number": true + }, + + "pulseaudio": { + "scroll-step": 1, + "format": "VOL {volume}%", + "format-muted": "MUTE", + "on-click": "pavucontrol", + "tooltip": false + }, + + "backlight": { + "device": "intel_backlight", + "on-scroll-up": "brightnessctl set +5%", + "on-scroll-down": "brightnessctl set 5%-", + "format": "BL {percent}%" + }, + + "network": { + "interval": 1, + "tooltip": true, + "format-wifi": "WIFI", + "format-ethernet": "ETH", + "format-linked": "LINK", + "format-disconnected": "OFF", + "tooltip-format": "{ifname}", + "tooltip-format-wifi": "{essid} ({signalStrength}%)", + "tooltip-format-ethernet": "{ifname}", + "tooltip-format-disconnected": "Disconnected", + "on-click": "nm-connection-editor" + }, + + "bluetooth": { + "format": "BT", + "format-connected": "BT {device_alias}", + "format-off": "BT OFF", + "on-click": "blueman-manager" + }, + + "battery": { + "interval": 10, + "states": { + "warning": 20, + "critical": 10 + }, + "format": "BAT {capacity}%", + "format-charging": "BAT {capacity}% AC", + "format-plugged": "BAT {capacity}% AC", + "format-alt": "{time}" + }, + + "temperature": { + "critical-threshold": 80, + "format": "TMP {temperatureC}C", + "tooltip": false + }, + + "memory": { + "interval": 5, + "format": "MEM {percentage}%", + "states": { + "warning": 85 + } + }, + + "clock": { + "interval": 60, + "timezone": "Europe/Rome", + "format": "{:%Y-%m-%d %H:%M}", + "format-alt": "{:%A %d/%m/%Y}" + }, + + "custom/powermenu": { + "format": "POWER", + "on-click": "~/.local/bin/powermenu", + "tooltip": false + }, + + "tray": { + "icon-size": 15, + "spacing": 6 + } +} diff --git a/dotfiles/nymph/.config/waybar/style.css b/dotfiles/desktop/.config/waybar/style.css similarity index 53% rename from dotfiles/nymph/.config/waybar/style.css rename to dotfiles/desktop/.config/waybar/style.css index 96baad3..1eeae03 100644 --- a/dotfiles/nymph/.config/waybar/style.css +++ b/dotfiles/desktop/.config/waybar/style.css @@ -4,104 +4,87 @@ min-height: 0; padding: 0; margin: 0; - font-family: "Liberation Mono", "Font Awesome 6 Free", "Font Awesome 6 Brands", monospace; + font-family: "Liberation Mono", monospace; font-size: 12px; } window#waybar { - background: #000000; - color: #dcdcdc; - border-bottom: 1px solid #101010; + background: #1f1f28; + color: #d0d0d0; + border-top: 2px solid #3a3a46; } -/* blocchi principali */ #workspaces, -#custom-music, -#temperature, -#memory, -#cpu, #pulseaudio, #backlight, #network, #bluetooth, #battery, +#temperature, +#memory, #clock, #custom-powermenu, #tray { - font-size: 16px; padding: 0 8px; margin: 3px 0; } -/* workspaces */ #workspaces { padding-left: 10px; } #workspaces button { background: transparent; - color: #6f6f6f; + color: #9a9a9a; padding: 0 7px; margin: 0 2px; - border-bottom: 1px solid transparent; + border-top: 2px solid transparent; } #workspaces button:hover { - color: #dcdcdc; + color: #d0d0d0; background: transparent; } #workspaces button.active { color: #ffffff; - border-bottom: 1px solid #ffffff; + border-top: 2px solid #4a90d9; } #workspaces button.urgent { color: #ffffff; - background: #0f0f0f; + background: #c7162b; } -/* centro */ -#custom-music { - color: #bfbfbf; -} - -/* moduli destra */ -#temperature, -#memory, -#cpu, #pulseaudio, #backlight, #network, #bluetooth, -#battery { +#battery, +#temperature, +#memory { color: #d0d0d0; } -/* clock e power leggermente più evidenti */ #clock, #custom-powermenu { color: #ffffff; } #clock { - letter-spacing: 0.5px; padding: 0 12px; } #custom-powermenu { - padding-left: 12px; - padding-right: 12px; + padding: 0 12px; } #tray { padding-right: 12px; } -/* tooltip */ tooltip { - background: #000000; - border: 1px solid #ffffff; - color: #e6e6e6; - padding: 10px; + background: #1f1f28; + border: 1px solid #4a90d9; + color: #d0d0d0; } diff --git a/dotfiles/desktop/.local/bin/lock-session b/dotfiles/desktop/.local/bin/lock-session new file mode 100644 index 0000000..4d30958 --- /dev/null +++ b/dotfiles/desktop/.local/bin/lock-session @@ -0,0 +1,23 @@ +#!/bin/sh + +set -eu + +case "${XDG_CURRENT_DESKTOP:-}" in + *Hyprland*|*hyprland*) + exec hyprlock + ;; + *i3*|*I3*) + exec "$HOME/.config/i3/scripts/lockscreen" + ;; +esac + +if command -v hyprlock >/dev/null 2>&1; then + exec hyprlock +fi + +if [ -x "$HOME/.config/i3/scripts/lockscreen" ]; then + exec "$HOME/.config/i3/scripts/lockscreen" +fi + +printf '%s\n' 'No supported lock command found.' >&2 +exit 1 diff --git a/dotfiles/desktop/.local/bin/powermenu b/dotfiles/desktop/.local/bin/powermenu new file mode 100644 index 0000000..fa968f7 --- /dev/null +++ b/dotfiles/desktop/.local/bin/powermenu @@ -0,0 +1,42 @@ +#!/bin/sh + +set -eu + +choice="$(printf 'Shutdown\nReboot\nLogout\nLock\nSuspend' \ +| rofi -dmenu \ + -i \ + -p 'Power' \ + -theme ~/.config/rofi/config.rasi \ + -theme-str 'window { width: 20%; location: center; anchor: center; } listview { columns: 1; spacing: 6px; }')" + +[ -n "$choice" ] || exit 0 + +case "$choice" in + Lock) + "$HOME/.local/bin/lock-session" + ;; + Logout) + case "${XDG_CURRENT_DESKTOP:-}" in + *Hyprland*|*hyprland*) + hyprctl dispatch exit + ;; + *i3*|*I3*) + i3-msg exit + ;; + *) + printf '%s\n' 'Unsupported desktop session for logout.' >&2 + exit 1 + ;; + esac + ;; + Suspend) + "$HOME/.local/bin/lock-session" || true + loginctl suspend + ;; + Reboot) + loginctl reboot + ;; + Shutdown) + loginctl poweroff + ;; +esac diff --git a/dotfiles/desktop/.local/bin/screenshot-wayland b/dotfiles/desktop/.local/bin/screenshot-wayland new file mode 100644 index 0000000..b35d332 --- /dev/null +++ b/dotfiles/desktop/.local/bin/screenshot-wayland @@ -0,0 +1,33 @@ +#!/bin/sh + +set -eu + +mode=${1:-full} +target_dir="$HOME/Pictures/Screenshots" +target_file="$target_dir/$(date +%Y-%m-%d-%H%M%S).png" + +mkdir -p "$target_dir" + +case "$mode" in + full) + grim "$target_file" + ;; + region) + grim -g "$(slurp)" "$target_file" + ;; + output) + grim -g "$(slurp -o)" "$target_file" + ;; + *) + printf 'Usage: %s [full|region|output]\n' "$0" >&2 + exit 1 + ;; +esac + +if command -v wl-copy >/dev/null 2>&1; then + wl-copy < "$target_file" +fi + +if command -v notify-send >/dev/null 2>&1; then + notify-send "Screenshot saved" "$target_file" +fi diff --git a/dotfiles/desktop/.local/bin/setup-gtk-theme b/dotfiles/desktop/.local/bin/setup-gtk-theme new file mode 100644 index 0000000..eea3bdb --- /dev/null +++ b/dotfiles/desktop/.local/bin/setup-gtk-theme @@ -0,0 +1,38 @@ +#!/bin/sh + +THEME="Yaru-blue-dark" +ICONS="Yaru-blue-dark" +CURSOR="Yaru" +FONT_UI="Liberation Sans 10" + +mkdir -p "$HOME/.config/gtk-3.0" +mkdir -p "$HOME/.config/gtk-4.0" + +cat > "$HOME/.config/gtk-3.0/settings.ini" < "$HOME/.config/gtk-4.0/settings.ini" </dev/null 2>&1; then + gsettings set org.gnome.desktop.interface gtk-theme "$THEME" >/dev/null 2>&1 || true + gsettings set org.gnome.desktop.interface icon-theme "$ICONS" >/dev/null 2>&1 || true + gsettings set org.gnome.desktop.interface cursor-theme "$CURSOR" >/dev/null 2>&1 || true + gsettings set org.gnome.desktop.interface color-scheme prefer-dark >/dev/null 2>&1 || true +fi diff --git a/dotfiles/desktop/.local/bin/start-hyprland-session b/dotfiles/desktop/.local/bin/start-hyprland-session new file mode 100644 index 0000000..03afa8d --- /dev/null +++ b/dotfiles/desktop/.local/bin/start-hyprland-session @@ -0,0 +1,22 @@ +#!/bin/sh + +[ $# -gt 0 ] || set -- Hyprland + +[ -r /etc/profile ] && . /etc/profile +[ -r "$HOME/.profile" ] && . "$HOME/.profile" + +set -e + +session_name=${1##*/} + +export XDG_CURRENT_DESKTOP="$session_name" +export XDG_SESSION_DESKTOP="$session_name" +export XDG_SESSION_TYPE=wayland + +exec dbus-run-session sh -eu -c ' + umask 077 + printf "%s\n" "$DBUS_SESSION_BUS_ADDRESS" > "$HOME/.dbus-session-bus-address" + eval "$(ssh-agent -s)" >/dev/null + gpgconf --launch gpg-agent + exec "$@" +' sh "$@" diff --git a/dotfiles/nymph/.config/hypr/host.conf b/dotfiles/nymph/.config/hypr/host.conf new file mode 100644 index 0000000..2f15a37 --- /dev/null +++ b/dotfiles/nymph/.config/hypr/host.conf @@ -0,0 +1,5 @@ +env = AQ_DRM_DEVICES,/dev/dri/card0:/dev/dri/card1 +env = __GLX_VENDOR_LIBRARY_NAME,nvidia +env = GBM_BACKEND,nvidia-drm +env = LIBVA_DRIVER_NAME,iHD +env = WLR_NO_HARDWARE_CURSORS,1 diff --git a/dotfiles/nymph/.config/hypr/hyprland.conf b/dotfiles/nymph/.config/hypr/hyprland.conf deleted file mode 100644 index 04924e3..0000000 --- a/dotfiles/nymph/.config/hypr/hyprland.conf +++ /dev/null @@ -1,193 +0,0 @@ -############################################# -# HYPRLAND CONFIG -# ThinkPad T480 (Intel + NVIDIA) -# Intel primary GPU + NVIDIA offload -############################################# - -################ -# Variables -################ - -$mod = SUPER - -#$terminal = WINIT_UNIX_BACKEND=x11 ~/.local/bin/nvidia-run alacritty -$terminal = alacritty -$menu = rofi -show drun -config ~/.config/rofi/config.rasi -$powermenu = ~/.local/bin/powermenu - -################ -# Autostart -################ - -exec-once = hyprpaper -exec-once = waybar -exec-once = dunst - -################ -# Environment -################ - -env = AQ_DRM_DEVICES,/dev/dri/card0:/dev/dri/card1 -env = __GLX_VENDOR_LIBRARY_NAME,nvidia -env = GBM_BACKEND,nvidia-drm -env = LIBVA_DRIVER_NAME,iHD -env = WLR_NO_HARDWARE_CURSORS,1 - -################ -# Monitor -################ - -monitor = ,preferred,auto,1 - -################ -# Input -################ - -input { - - kb_layout = us - kb_variant = alt-intl - - follow_mouse = 1 - sensitivity = 0 - - touchpad { - natural_scroll = true - } - -} - -################ -# General -################ - -general { - - layout = master - - gaps_in = 6 - gaps_out = 10 - - border_size = 1 - - col.active_border = rgb(ffffff) - col.inactive_border = rgb(1a1a1a) - - allow_tearing = false -} - -################ -# Master layout -################ - -master { - - new_status = master - mfact = 0.55 - inherit_fullscreen = true - -} - -################ -# Decoration -################ - -decoration { - - rounding = 0 - - blur { - enabled = false - } - -} - -################ -# Animations -################ - -animations { - - enabled = yes - - bezier = easeOut, 0.16, 1, 0.3, 1 - - animation = windows,1,4,easeOut - animation = fade,1,4,easeOut - animation = workspaces,1,4,easeOut -} - -################ -# Misc -################ - -misc { - - disable_hyprland_logo = true - disable_splash_rendering = true - -} - -################ -# Window rules -################ - -windowrulev2 = opacity 0.96 0.96,class:^(Alacritty)$ -windowrulev2 = float,class:^(Rofi)$ - -################ -# Keybinds -################ - -# Terminal -bind = $mod, Return, exec, $terminal - -# Launcher -bind = $mod, D, exec, $menu - -# Powermenu -bind = $mod, Escape, exec, $powermenu - -# Exit Hyprland (default) -bind = $mod SHIFT, E, exit - -# Close window -bind = $mod, Q, killactive - -# Fullscreen -bind = $mod, F, fullscreen, 1 - -# Floating -bind = $mod, Space, togglefloating - -################ -# Focus movement -################ - -bind = $mod, H, movefocus, l -bind = $mod, L, movefocus, r -bind = $mod, K, movefocus, u -bind = $mod, J, movefocus, d - -################ -# Master navigation -################ - -bind = $mod, J, layoutmsg, cyclenext -bind = $mod, K, layoutmsg, cycleprev - -################ -# Workspaces -################ - -bind = $mod, 1, workspace, 1 -bind = $mod, 2, workspace, 2 -bind = $mod, 3, workspace, 3 -bind = $mod, 4, workspace, 4 -bind = $mod, 5, workspace, 5 - -bind = $mod SHIFT, 1, movetoworkspace, 1 -bind = $mod SHIFT, 2, movetoworkspace, 2 -bind = $mod SHIFT, 3, movetoworkspace, 3 -bind = $mod SHIFT, 4, movetoworkspace, 4 -bind = $mod SHIFT, 5, movetoworkspace, 5 diff --git a/dotfiles/nymph/.config/hypr/hyprpaper.conf b/dotfiles/nymph/.config/hypr/hyprpaper.conf deleted file mode 100644 index e213d3a..0000000 --- a/dotfiles/nymph/.config/hypr/hyprpaper.conf +++ /dev/null @@ -1,4 +0,0 @@ -preload = ~/Pictures/wallpapers/gargantua-black-hyprland.png -wallpaper = ,~/Pictures/wallpapers/gargantua-black-hyprland.png -splash = false -ipc = on diff --git a/dotfiles/nymph/.config/waybar/config.jsonc b/dotfiles/nymph/.config/waybar/config.jsonc deleted file mode 100644 index 6da7e1d..0000000 --- a/dotfiles/nymph/.config/waybar/config.jsonc +++ /dev/null @@ -1,146 +0,0 @@ -{ - "layer": "top", - "position": "top", - "height": 28, - "spacing": 4, - - "modules-left": [ - "hyprland/workspaces" - ], - - "modules-center": [ - "custom/music" - ], - - "modules-right": [ - "temperature", - "memory", - "cpu", - //"pulseaudio", - "backlight", - "network", - "bluetooth", - "battery", - "clock", - "custom/powermenu", - "tray" - ], - - "hyprland/workspaces": { - "disable-scroll": true, - "all-outputs": true, - "format": "{name}", - "on-click": "activate", - "persistent-workspaces": { - "*": [1, 2, 3, 4, 5] - } - }, - - "backlight": { - "device": "intel_backlight", - "on-scroll-up": "light -A 5", - "on-scroll-down": "light -U 5", - "format": "{percent}% {icon}", - "format-icons": ["", "", "", "", "", "", "", "", ""] - }, - - "pulseaudio": { - "scroll-step": 1, - "format": "{volume}% {icon}", - "format-muted": "mute ", - "format-icons": { - "headphone": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""] - }, - "on-click": "pavucontrol", - "tooltip": false - }, - - "battery": { - "interval": 10, - "states": { - "warning": 20, - "critical": 10 - }, - "format": "{capacity}% {icon}", - "format-charging": "{capacity}% 󰂄", - "format-plugged": "{capacity}% ", - "format-alt": "{time} {icon}", - "format-icons": ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂"] - }, - - "clock": { - "interval": 1, - "timezone": "Europe/Rome", - "format": "{:%H:%M}", - "tooltip-format": "{:%Y %B}\n{calendar}", - "format-alt": "{:%A %d/%m/%Y}" - }, - - "memory": { - "interval": 1, - "format": "{percentage}% ", - "states": { - "warning": 85 - } - }, - - "cpu": { - "interval": 1, - "format": "{usage}% ", - "tooltip": false - }, - - "network": { - "interval": 1, - "tooltip": true, - "format-wifi": "{icon}", - "format-ethernet": "{icon}", - "format-linked": "󰈁", - "format-icons": ["󰤟", "󰤢", "󰤥", "󰤨"], - "format-disconnected": "󰈂", - "tooltip-format": "{ifname}", - "tooltip-format-wifi": "{essid} ({signalStrength}%) ", - "tooltip-format-ethernet": "{ifname} ", - "tooltip-format-disconnected": "Disconnected", - "on-click": "nm-connection-editor" - }, - - "temperature": { - "critical-threshold": 80, - "format": "{temperatureC}°C ", - "tooltip": false - }, - - "custom/powermenu": { - "format": "", - "on-click": "~/.local/bin/powermenu", - "tooltip": false - }, - - "tray": { - "icon-size": 15, - "spacing": 6 - }, - - "bluetooth": { - "format": "", - "format-connected": "{device_alias} ", - "format-connected-battery": "{device_alias} {device_battery_percentage}% ", - "format-off": "󰂲", - "tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected", - "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}", - "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}", - "tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%", - "on-click": "blueman-manager" - }, - - "custom/music": { - "format": "{}", - "return-type": "json", - "exec": "waybar-module-music" - } -}