mirror of
https://github.com/fscotto/infra.git
synced 2026-05-30 15:39:58 +00:00
Screenshots - Swap xfce4-screenshooter for flameshot + grim/slurp/jq; xfce4 used the X11 selection so clipboard didn't round-trip to Wayland clients. Print invokes flameshot's editor; Shift/Alt+Print are quick grim-based captures via a small wrapper script. Flameshot is now autostarted so the first capture isn't slow. Polkit - Add for_window rules so the xfce-polkit dialog (and any other polkit agent) always opens floating. Flatpak dark theme - Write /etc/flatpak/overrides/global with GTK_THEME=Yaru-Blue-dark for legacy GTK apps, plus a sway-portals.conf so the Settings portal is served by xdg-desktop-portal-gtk (wlr doesn't implement it). That lets libadwaita apps see color-scheme=prefer-dark. cleanup-sway: track the new package set. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
565 lines
17 KiB
YAML
565 lines
17 KiB
YAML
---
|
|
- 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
|
|
when: "'void' in group_names"
|
|
|
|
- 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 }}/.bashrc.d"
|
|
- "{{ user_home }}/.tmux"
|
|
- "{{ user_home }}/.tmux/bin"
|
|
- "{{ user_home }}/.tmux/plugins"
|
|
- "{{ user_home }}/.ssh"
|
|
|
|
- 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
|
|
when: "'void' in group_names"
|
|
|
|
- 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
|
|
when: "'void' in group_names"
|
|
|
|
- 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
|
|
when: "'void' in group_names"
|
|
|
|
- name: Check whether SSH host ed25519 key exists
|
|
tags: [services]
|
|
ansible.builtin.stat:
|
|
path: /etc/ssh/ssh_host_ed25519_key
|
|
register: desktop_ssh_host_ed25519_key
|
|
when:
|
|
- (host_sshd_settings | default({})) | length > 0
|
|
or (host_sshd_allow_users | default([])) | length > 0
|
|
|
|
- name: Generate missing SSH host keys on desktop host
|
|
tags: [services]
|
|
ansible.builtin.command: ssh-keygen -A
|
|
changed_when: true
|
|
when:
|
|
- (host_sshd_settings | default({})) | length > 0
|
|
or (host_sshd_allow_users | default([])) | length > 0
|
|
- not desktop_ssh_host_ed25519_key.stat.exists
|
|
|
|
- name: Require authorized SSH keys before disabling password authentication on desktop host
|
|
tags: [services]
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (host_authorized_ssh_keys | default([])) | length > 0
|
|
fail_msg: >-
|
|
SSH password authentication is disabled for this host, but no authorized SSH
|
|
keys are defined. Set vault_ikaros_authorized_ssh_keys in secrets/vault.yml
|
|
or secrets/vault.local.yml before applying this configuration.
|
|
when:
|
|
- "'sshd' in (host_enabled_services | default([]))"
|
|
- (host_sshd_settings | default({})).PasswordAuthentication | default('yes') == 'no'
|
|
|
|
- name: Ensure desktop user SSH directory exists
|
|
tags: [services, dotfiles]
|
|
ansible.builtin.file:
|
|
path: "{{ user_home }}/.ssh"
|
|
state: directory
|
|
owner: "{{ username }}"
|
|
group: "{{ user_group }}"
|
|
mode: "0700"
|
|
when: (host_authorized_ssh_keys | default([])) | length > 0
|
|
|
|
- name: Ensure desktop user authorized_keys file exists
|
|
tags: [services, dotfiles]
|
|
ansible.builtin.file:
|
|
path: "{{ user_home }}/.ssh/authorized_keys"
|
|
state: touch
|
|
owner: "{{ username }}"
|
|
group: "{{ user_group }}"
|
|
mode: "0600"
|
|
when: (host_authorized_ssh_keys | default([])) | length > 0
|
|
|
|
- name: Manage desktop user authorized SSH keys exclusively
|
|
tags: [services, dotfiles]
|
|
ansible.posix.authorized_key:
|
|
user: "{{ username }}"
|
|
key: "{{ host_authorized_ssh_keys | join('\n') }}"
|
|
state: present
|
|
exclusive: true
|
|
manage_dir: false
|
|
when: (host_authorized_ssh_keys | default([])) | length > 0
|
|
|
|
- name: Apply SSH daemon settings on desktop host
|
|
tags: [services]
|
|
ansible.builtin.lineinfile:
|
|
path: /etc/ssh/sshd_config
|
|
regexp: '^\s*{{ item.key }}\s+'
|
|
line: "{{ item.key }} {{ item.value }}"
|
|
state: present
|
|
validate: "sshd -t -f %s"
|
|
notify: Reload SSH service
|
|
loop: "{{ host_sshd_settings | default({}) | dict2items }}"
|
|
loop_control:
|
|
label: "{{ item.key }}"
|
|
when: (host_sshd_settings | default({})) | length > 0
|
|
|
|
- name: Restrict SSH login to allowed desktop users
|
|
tags: [services]
|
|
ansible.builtin.lineinfile:
|
|
path: /etc/ssh/sshd_config
|
|
regexp: '^\s*AllowUsers\s+'
|
|
line: "AllowUsers {{ host_sshd_allow_users | join(' ') }}"
|
|
state: present
|
|
validate: "sshd -t -f %s"
|
|
notify: Reload SSH service
|
|
when: (host_sshd_allow_users | default([])) | length > 0
|
|
|
|
- name: Define effective desktop UFW rules
|
|
tags: [services, packages]
|
|
ansible.builtin.set_fact:
|
|
desktop_ufw_rules_effective: "{{ host_ufw_rules | default([]) }}"
|
|
|
|
- name: Apply host UFW rules on desktop
|
|
tags: [services, packages]
|
|
community.general.ufw:
|
|
rule: "{{ item.rule }}"
|
|
name: "{{ item.name | default(omit) }}"
|
|
port: "{{ item.port | default(omit) }}"
|
|
proto: "{{ item.proto | default(omit) }}"
|
|
from_ip: "{{ item.src | default(omit) }}"
|
|
to_ip: "{{ item.dest | default(omit) }}"
|
|
from_port: "{{ item.from_port | default(omit) }}"
|
|
direction: "{{ item.direction | default(omit) }}"
|
|
interface: "{{ item.interface | default(omit) }}"
|
|
interface_in: "{{ item.interface_in | default(omit) }}"
|
|
interface_out: "{{ item.interface_out | default(omit) }}"
|
|
route: "{{ item.route | default(omit) }}"
|
|
comment: "{{ item.comment | default(omit) }}"
|
|
loop: "{{ desktop_ufw_rules_effective }}"
|
|
loop_control:
|
|
label: "{{ item.name | default(item.port) }}"
|
|
|
|
- name: Enable UFW firewall on desktop when host rules are defined
|
|
tags: [services, packages]
|
|
community.general.ufw:
|
|
state: enabled
|
|
when: (desktop_ufw_rules_effective | default([])) | length > 0
|
|
|
|
- name: Check whether libvirt service directory exists
|
|
tags: [packages, services]
|
|
ansible.builtin.stat:
|
|
path: /etc/sv/libvirtd
|
|
register: libvirtd_service_dir
|
|
when: "'void' in group_names"
|
|
|
|
- name: Enable libvirt daemon service
|
|
tags: [packages, services]
|
|
ansible.builtin.file:
|
|
src: /etc/sv/libvirtd
|
|
dest: /var/service/libvirtd
|
|
state: link
|
|
when:
|
|
- "'void' in group_names"
|
|
- libvirtd_service_dir.stat.exists
|
|
|
|
- name: Check virtualization group availability
|
|
tags: [packages]
|
|
ansible.builtin.getent:
|
|
database: group
|
|
key: "{{ item }}"
|
|
loop:
|
|
- kvm
|
|
- libvirt
|
|
loop_control:
|
|
label: "{{ item }}"
|
|
register: desktop_virtualization_group_state
|
|
failed_when: false
|
|
|
|
- name: Add desktop user to virtualization groups
|
|
tags: [packages]
|
|
ansible.builtin.user:
|
|
name: "{{ username }}"
|
|
groups: "{{ item }}"
|
|
append: true
|
|
loop: >-
|
|
{{
|
|
desktop_virtualization_group_state.results
|
|
| default([])
|
|
| selectattr('failed', 'equalto', false)
|
|
| selectattr('ansible_facts.getent_group', 'defined')
|
|
| map(attribute='item')
|
|
| list
|
|
}}
|
|
loop_control:
|
|
label: "{{ item }}"
|
|
|
|
- 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"
|
|
when: "'void' in group_names"
|
|
|
|
- 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: "'void' in group_names"
|
|
|
|
- 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
|
|
when: "'void' in group_names"
|
|
|
|
- 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([]))
|
|
+ ((desktop_void_dotfiles | default([])) if 'void' in group_names else [])
|
|
}}
|
|
loop_control:
|
|
label: "{{ item.dest }}"
|
|
|
|
- name: Copy Emacs desktop dotfiles
|
|
tags: [dotfiles, dotfiles:desktop, emacs]
|
|
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_emacs_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: Define managed tmux plugin directories
|
|
tags: [dotfiles, dotfiles:desktop, tmux]
|
|
ansible.builtin.set_fact:
|
|
tmux_managed_plugin_names:
|
|
- tpm
|
|
- tmux-sensible
|
|
- tmux-autoreload
|
|
- tmux-resurrect
|
|
- tmux-continuum
|
|
|
|
- name: Check whether tmux plugin directories are git checkouts
|
|
tags: [dotfiles, dotfiles:desktop, tmux]
|
|
ansible.builtin.stat:
|
|
path: "{{ user_home }}/.tmux/plugins/{{ item }}/.git"
|
|
loop: "{{ tmux_managed_plugin_names }}"
|
|
loop_control:
|
|
label: "{{ item }}"
|
|
register: tmux_plugin_git_state
|
|
|
|
- name: Remove stale vendored tmux plugin directories
|
|
tags: [dotfiles, dotfiles:desktop, tmux]
|
|
ansible.builtin.file:
|
|
path: "{{ user_home }}/.tmux/plugins/{{ item.item }}"
|
|
state: absent
|
|
loop: "{{ tmux_plugin_git_state.results }}"
|
|
loop_control:
|
|
label: "{{ item.item }}"
|
|
when:
|
|
- item.stat.exists == false
|
|
|
|
- name: Bootstrap tmux plugin manager checkout
|
|
tags: [dotfiles, dotfiles:desktop, tmux]
|
|
ansible.builtin.git:
|
|
repo: https://github.com/tmux-plugins/tpm
|
|
dest: "{{ user_home }}/.tmux/plugins/tpm"
|
|
version: master
|
|
update: true
|
|
become_user: "{{ username }}"
|
|
environment:
|
|
HOME: "{{ user_home }}"
|
|
|
|
- name: Install tmux plugins through TPM
|
|
tags: [dotfiles, dotfiles:desktop, tmux]
|
|
ansible.builtin.command:
|
|
cmd: "{{ user_home }}/.tmux/plugins/tpm/bin/install_plugins"
|
|
become_user: "{{ username }}"
|
|
environment:
|
|
HOME: "{{ user_home }}"
|
|
register: tmux_plugin_install
|
|
changed_when: >-
|
|
(tmux_plugin_install.stdout | default('')) is search('download success') or
|
|
(tmux_plugin_install.stderr | default('')) is search('download success')
|
|
|
|
- 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/state"
|
|
mode: "0755"
|
|
- path: "{{ user_home }}/.cache"
|
|
mode: "0755"
|
|
- path: "{{ user_home }}/.cache/rclone"
|
|
mode: "0755"
|
|
- path: "{{ user_home }}/.local/state/ssh-agent"
|
|
mode: "0700"
|
|
- path: "{{ user_home }}/.local/share"
|
|
mode: "0755"
|
|
- path: "{{ user_home }}/.local/share/keyrings"
|
|
mode: "0700"
|
|
- path: "{{ user_home }}/Org"
|
|
mode: "0755"
|
|
- path: "{{ user_home }}/Remotes"
|
|
mode: "0755"
|
|
- path: /usr/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: 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: Ensure /etc/flatpak/overrides exists
|
|
tags: [packages, flatpak, theme]
|
|
ansible.builtin.file:
|
|
path: /etc/flatpak/overrides
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
when: (desktop_flatpak_packages | default([])) | length > 0
|
|
|
|
- name: Force dark GTK theme for Flatpak applications
|
|
tags: [packages, flatpak, theme]
|
|
ansible.builtin.copy:
|
|
dest: /etc/flatpak/overrides/global
|
|
content: |
|
|
[Environment]
|
|
GTK_THEME={{ desktop_flatpak_gtk_theme | default('Yaru-Blue-dark') }}
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
when: (desktop_flatpak_packages | default([])) | length > 0
|
|
|
|
- name: Bootstrap rustup toolchain installer
|
|
tags: [packages]
|
|
block:
|
|
- name: Check whether rustup is initialized
|
|
ansible.builtin.stat:
|
|
path: "{{ user_home }}/.cargo/bin/rustc"
|
|
register: rustup_initialized
|
|
|
|
- name: Run rustup-init
|
|
ansible.builtin.command:
|
|
cmd: rustup-init -y --no-modify-path
|
|
creates: "{{ user_home }}/.cargo/bin/rustc"
|
|
become_user: "{{ username }}"
|
|
environment:
|
|
HOME: "{{ user_home }}"
|
|
when: not rustup_initialized.stat.exists
|
|
|
|
- name: Ensure cargo env is sourced in shell profile
|
|
ansible.builtin.lineinfile:
|
|
path: "{{ user_home }}/.bashrc"
|
|
regexp: '\. cargo/env'
|
|
line: . "$HOME/.cargo/env"
|
|
state: present
|
|
create: true
|
|
mode: "0644"
|
|
become_user: "{{ username }}"
|
|
when: not rustup_initialized.stat.exists
|
|
when: desktop_source_tools | length > 0
|
|
|
|
- name: Build and install desktop source tools
|
|
tags: [packages]
|
|
ansible.builtin.include_tasks:
|
|
file: source_tool.yml
|
|
apply:
|
|
tags: [packages]
|
|
loop: "{{ desktop_source_tools + (desktop_void_source_tools | default([])) }}"
|
|
when: (desktop_source_tools + (desktop_void_source_tools | default([]))) | length > 0
|
|
loop_control:
|
|
loop_var: source_tool
|
|
label: "{{ source_tool.name }}"
|
|
|
|
- name: Set desktop tools tmp directory
|
|
tags: [packages]
|
|
ansible.builtin.set_fact:
|
|
desktop_tools_tmp_dir: /tmp/desktop-tools
|
|
|
|
- name: Ensure temporary directory exists for desktop binary tools
|
|
tags: [packages]
|
|
ansible.builtin.file:
|
|
path: "{{ desktop_tools_tmp_dir }}"
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
- name: Install desktop binary tools
|
|
tags: [packages]
|
|
ansible.builtin.include_tasks:
|
|
file: binary_tool.yml
|
|
apply:
|
|
tags: [packages]
|
|
loop: "{{ desktop_binary_tools }}"
|
|
when: desktop_binary_tools | length > 0
|
|
loop_control:
|
|
loop_var: binary_tool
|
|
label: "{{ binary_tool.name }}"
|
|
|
|
- name: Install desktop npm packages
|
|
tags: [packages, npm]
|
|
community.general.npm:
|
|
name: "{{ item.name }}"
|
|
global: true
|
|
state: "{{ item.state | default('present') }}"
|
|
become: true
|
|
loop: "{{ desktop_npm_packages | default([]) }}"
|
|
when: desktop_npm_packages | length > 0
|
|
loop_control:
|
|
label: "{{ item.name }}"
|