Files
infra/ansible/roles/profile_desktop_common/tasks/main.yml
2026-04-17 11:32:55 +02:00

583 lines
18 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
- 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: 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
- name: Enable libvirt daemon service
tags: [packages, services]
ansible.builtin.file:
src: /etc/sv/libvirtd
dest: /var/service/libvirtd
state: link
when: 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"
- 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/state"
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 }}/.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: 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
st_repo: https://codeberg.org/fscotto/st
st_src_dir: "{{ user_home }}/.local/src/st"
ollama_asset: >-
{{
'ollama-linux-amd64.tar.zst' if ansible_facts['architecture'] == 'x86_64'
else 'ollama-linux-arm64.tar.zst' if ansible_facts['architecture'] in ['aarch64', 'arm64']
else ''
}}
gitmux_version: v0.11.5
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: Set derived desktop external tool asset metadata
tags: [packages]
ansible.builtin.set_fact:
gitmux_asset: "gitmux_{{ gitmux_version }}_linux_{{ gitmux_arch }}.tar.gz"
- name: Ensure architecture is supported for Ollama binary
tags: [packages]
ansible.builtin.fail:
msg: "Unsupported architecture {{ ansible_facts['architecture'] }} for Ollama release binary"
when: ollama_asset == ''
- 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 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 effective OpenCode release metadata
tags: [packages]
ansible.builtin.set_fact:
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 the latest GitHub release"
when: opencode_asset == {}
- name: Define desktop online archive binaries
tags: [packages]
ansible.builtin.set_fact:
desktop_online_archive_binaries:
- name: opencode
asset_name: "{{ opencode_asset.name }}"
url: "{{ opencode_asset.browser_download_url }}"
checksum: "{{ opencode_asset.digest | default(omit) }}"
extracted_binary: opencode
install_dest: /usr/local/bin/opencode
- name: gitmux
asset_name: "{{ gitmux_asset }}"
url: "https://github.com/arl/gitmux/releases/download/{{ gitmux_version }}/{{ gitmux_asset }}"
checksum: "sha256:https://github.com/arl/gitmux/releases/download/{{ gitmux_version }}/checksums.txt"
extracted_binary: gitmux
install_dest: /usr/local/bin/gitmux
- name: Download desktop online archive binary releases
tags: [packages]
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "{{ desktop_tools_tmp_dir }}/{{ item.asset_name }}"
checksum: "{{ item.checksum | default(omit) }}"
mode: "0644"
loop: "{{ desktop_online_archive_binaries }}"
loop_control:
label: "{{ item.name }}"
- name: Extract desktop online archive binary releases
tags: [packages]
ansible.builtin.unarchive:
src: "{{ desktop_tools_tmp_dir }}/{{ item.asset_name }}"
dest: "{{ desktop_tools_tmp_dir }}"
remote_src: true
loop: "{{ desktop_online_archive_binaries }}"
loop_control:
label: "{{ item.name }}"
- name: Install desktop online archive binaries
tags: [packages]
ansible.builtin.copy:
src: "{{ desktop_tools_tmp_dir }}/{{ item.extracted_binary }}"
dest: "{{ item.install_dest }}"
remote_src: true
owner: root
group: root
mode: "0755"
loop: "{{ desktop_online_archive_binaries }}"
loop_control:
label: "{{ item.name }}"
- name: Clone st repository
tags: [packages]
ansible.builtin.git:
repo: "{{ st_repo }}"
dest: "{{ st_src_dir }}"
update: true
become_user: "{{ username }}"
environment:
HOME: "{{ user_home }}"
register: st_repo_state
- 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: "{{ st_src_dir }}"
when: st_repo_state.changed or not st_binary.stat.exists
- name: Clean st build artifacts
tags: [packages]
ansible.builtin.command:
cmd: make clean
chdir: "{{ st_src_dir }}"
when: st_repo_state.changed or not st_binary.stat.exists
- name: Download Ollama release archive
tags: [packages]
ansible.builtin.get_url:
url: "https://ollama.com/download/{{ ollama_asset }}"
dest: "{{ desktop_tools_tmp_dir }}/{{ ollama_asset }}"
mode: "0644"
register: ollama_archive_download
- name: Check whether Ollama is already installed
tags: [packages]
ansible.builtin.stat:
path: /usr/local/bin/ollama
register: ollama_install_state
- name: Remove previous Ollama install paths when required
tags: [packages]
ansible.builtin.file:
path: /usr/local/lib/ollama
state: absent
when:
- ollama_archive_download.changed or not ollama_install_state.stat.exists
- name: Extract Ollama release archive into /usr/local
tags: [packages]
ansible.builtin.shell: >-
zstd -d -c "{{ desktop_tools_tmp_dir }}/{{ ollama_asset }}" |
tar -xf - -C /usr/local
args:
executable: /bin/sh
changed_when: true
when:
- ollama_archive_download.changed or not ollama_install_state.stat.exists
- name: Install Codex CLI globally
tags: [packages]
community.general.npm:
name: "@openai/codex"
global: true
state: present