From cda06537e6cfb4ac98d61c49c3be36cd17f5b721 Mon Sep 17 00:00:00 2001 From: Thomas Willetal Date: Sat, 23 May 2026 22:29:43 +0200 Subject: [PATCH 1/2] Add systemd role with configuration and handlers for systemd-resolved --- ansible/playbooks/workstation.yml | 2 + .../systemd/files/NetworkManager-dns.conf | 2 + ansible/roles/systemd/files/resolved.conf | 44 +++++++++++++++ ansible/roles/systemd/handlers/main.yml | 12 +++++ ansible/roles/systemd/tasks/main.yml | 53 +++++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 ansible/roles/systemd/files/NetworkManager-dns.conf create mode 100644 ansible/roles/systemd/files/resolved.conf create mode 100644 ansible/roles/systemd/handlers/main.yml create mode 100644 ansible/roles/systemd/tasks/main.yml diff --git a/ansible/playbooks/workstation.yml b/ansible/playbooks/workstation.yml index 4c57bcd..84a5d11 100644 --- a/ansible/playbooks/workstation.yml +++ b/ansible/playbooks/workstation.yml @@ -6,6 +6,8 @@ roles: - role: common tags: [common] + - role: systemd + tags: [systemd] - role: podman tags: [podman] - role: shell_config diff --git a/ansible/roles/systemd/files/NetworkManager-dns.conf b/ansible/roles/systemd/files/NetworkManager-dns.conf new file mode 100644 index 0000000..c81a643 --- /dev/null +++ b/ansible/roles/systemd/files/NetworkManager-dns.conf @@ -0,0 +1,2 @@ +[main] +dns=systemd-resolved diff --git a/ansible/roles/systemd/files/resolved.conf b/ansible/roles/systemd/files/resolved.conf new file mode 100644 index 0000000..d6a5a1d --- /dev/null +++ b/ansible/roles/systemd/files/resolved.conf @@ -0,0 +1,44 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Entries in this file show the compile time defaults. Local configuration +# should be created by either modifying this file (or a copy of it placed in +# /etc/ if the original file is shipped in /usr/), or by creating "drop-ins" in +# the /etc/systemd/resolved.conf.d/ directory. The latter is generally +# recommended. Defaults can be restored by simply deleting the main +# configuration file and all drop-ins located in /etc/. +# +# Use 'systemd-analyze cat-config systemd/resolved.conf' to display the full config. +# +# See resolved.conf(5) for details. + +[Resolve] +# Use DNS from DHCP – do NOT hardcode here unless you want to override +# DNS=192.168.178.1 + +# Optional public DNS fallback if DHCP DNS is unavailable +FallbackDNS=1.1.1.1 8.8.8.8 + +# Allow resolving local *.fritz.box hostnames +Domains=fritz.box + +# Enable Multicast DNS (for `.local` discovery) +MulticastDNS=yes + +# Disable LLMNR (legacy, usually unnecessary) +LLMNR=no + +# Enable local DNS cache +Cache=yes + +# Enable stub listener on 127.0.0.53 (used by NetworkManager) +DNSStubListener=yes + +# DO NOT expose DNS to LAN unless you're intentionally running a server +#DNSStubListenerExtra=192.168.100.1 + +ReadEtcHosts=yes diff --git a/ansible/roles/systemd/handlers/main.yml b/ansible/roles/systemd/handlers/main.yml new file mode 100644 index 0000000..83626a3 --- /dev/null +++ b/ansible/roles/systemd/handlers/main.yml @@ -0,0 +1,12 @@ +--- +- name: Restart systemd-resolved + ansible.builtin.systemd: + name: systemd-resolved + state: restarted + become: true + +- name: Reload NetworkManager + ansible.builtin.systemd: + name: NetworkManager + state: reloaded + become: true diff --git a/ansible/roles/systemd/tasks/main.yml b/ansible/roles/systemd/tasks/main.yml new file mode 100644 index 0000000..d903545 --- /dev/null +++ b/ansible/roles/systemd/tasks/main.yml @@ -0,0 +1,53 @@ +--- +- name: Deploy systemd-resolved configuration + ansible.builtin.copy: + src: resolved.conf + dest: /etc/systemd/resolved.conf + owner: root + group: root + mode: "0644" + become: true + notify: Restart systemd-resolved + +- name: Enable and start systemd-resolved + ansible.builtin.systemd: + name: systemd-resolved + enabled: true + state: started + become: true + +- name: Ensure /etc/resolv.conf points to systemd-resolved stub + ansible.builtin.file: + src: /run/systemd/resolve/stub-resolv.conf + dest: /etc/resolv.conf + state: link + force: true + become: true + +- name: Configure NetworkManager to use systemd-resolved + when: ansible_facts.services['NetworkManager.service'] is defined + block: + - name: Ensure NetworkManager conf.d directory exists + ansible.builtin.file: + path: /etc/NetworkManager/conf.d + state: directory + owner: root + group: root + mode: "0755" + become: true + + - name: Deploy NetworkManager DNS config + ansible.builtin.copy: + src: NetworkManager-dns.conf + dest: /etc/NetworkManager/conf.d/dns.conf + owner: root + group: root + mode: "0644" + become: true + notify: Reload NetworkManager + +- name: Enable systemd linger for user + ansible.builtin.command: + cmd: loginctl enable-linger {{ ansible_user }} + creates: /var/lib/systemd/linger/{{ ansible_user }} + become: true From a46abed503bd686b2d5eeedb6a0403e7c0e4643d Mon Sep 17 00:00:00 2001 From: Thomas Willetal Date: Sun, 24 May 2026 10:24:13 +0200 Subject: [PATCH 2/2] Add CI workflow for linting Ansible playbooks with yamllint and ansible-lint --- .editorconfig | 36 +++++++++++++++++++ .github/workflows/ci.yml | 39 +++++++++++++++++++++ .vscode/tasks.json | 36 +++++++++++++++++++ ansible/ansible.cfg | 19 +++++----- ansible/roles/common/tasks/main.yml | 7 ++-- ansible/roles/git_config/tasks/main.yml | 1 + ansible/roles/podman/tasks/main.yml | 15 ++++---- ansible/roles/shell_config/tasks/main.yml | 24 ++++++++----- install-ansible | 10 +++++- requirements.txt | 2 +- run-lint | 42 +++++++++++++++++++++++ 11 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100755 run-lint diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1963b65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 + +[*.json] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.sh] +indent_size = 4 + +[install-ansible] +indent_size = 4 + +[install-requirements] +indent_size = 4 + +[run-ansible] +indent_size = 4 + +[run-lint] +indent_size = 4 + +[nvidia/*] +indent_size = 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e76d789 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + security-events: write + +jobs: + lint: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: | + ./install-requirements + ./install-ansible + + - name: Run linters + run: ./run-lint --github-format + + - name: Publish ansible-lint results + uses: github/codeql-action/upload-sarif@v4 + if: >- + always() && + !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) + with: + sarif_file: ansible-lint.sarif + category: ansible-lint diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ae77a6a..fbd7924 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,6 +1,42 @@ { "version": "2.0.0", "tasks": [ + { + "label": "ansible: lint", + "type": "shell", + "command": "${workspaceFolder}/run-lint", + "problemMatcher": [ + { + "owner": "yamllint", + "fileLocation": "absolute", + "severity": "error", + "pattern": { + "regexp": "^(.+):(\\d+):(\\d+): \\[(error|warning)\\] (.+)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + }, + { + "owner": "ansible-lint", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "^([\\w-]+(?:\\[[\\w-]+\\])?): (.+)$", + "message": 2 + }, + { + "regexp": "^(.+):(\\d+)(?::(\\d+))? Task/Handler:", + "file": 1, + "line": 2, + "column": 3 + } + ] + } + ] + }, { "label": "ansible: run all roles", "type": "shell", diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index 3f6fe80..bff64ee 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -1,12 +1,15 @@ [defaults] -inventory = inventories/production/hosts.yml -roles_path = roles -collections_path = collections -host_key_checking = False -retry_files_enabled = False -stdout_callback = default -INJECT_FACTS_AS_VARS = False +inventory = inventories/production/hosts.yml +roles_path = roles + +host_key_checking = False +retry_files_enabled = False + +stdout_callback = ansible.builtin.default +result_format = yaml + +inject_facts_as_vars = False [privilege_escalation] -become = True +become = True become_method = sudo diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml index 95110dc..9cab90b 100644 --- a/ansible/roles/common/tasks/main.yml +++ b/ansible/roles/common/tasks/main.yml @@ -14,8 +14,11 @@ become: true - name: Dearmor NodeSource GPG key - ansible.builtin.command: - cmd: gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg /tmp/nodesource-repo.gpg.key + ansible.builtin.command: > + gpg --dearmor + -o /etc/apt/keyrings/nodesource.gpg + /tmp/nodesource-repo.gpg.key + args: creates: /etc/apt/keyrings/nodesource.gpg become: true diff --git a/ansible/roles/git_config/tasks/main.yml b/ansible/roles/git_config/tasks/main.yml index 0c263f7..7129fb1 100644 --- a/ansible/roles/git_config/tasks/main.yml +++ b/ansible/roles/git_config/tasks/main.yml @@ -27,3 +27,4 @@ block: | [include] path = {{ home_dir }}/.config/git/ansible_gitconfig + mode: "0644" diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml index 342abb8..3603724 100644 --- a/ansible/roles/podman/tasks/main.yml +++ b/ansible/roles/podman/tasks/main.yml @@ -13,7 +13,7 @@ create: true mode: "0644" become: true - register: subuid_result + register: podman_subuid_result - name: Ensure subgid entry for rootless podman ansible.builtin.lineinfile: @@ -23,7 +23,7 @@ create: true mode: "0644" become: true - register: subgid_result + register: podman_subgid_result - name: Install podman-compose for user via pip ansible.builtin.pip: @@ -49,9 +49,11 @@ cmd: > podman system connection add --default podman-user unix:///run/user/{{ ansible_facts['user_uid'] }}/podman/podman.sock - register: podman_conn_result - changed_when: podman_conn_result.rc == 0 - failed_when: podman_conn_result.rc != 0 and 'already exists' not in podman_conn_result.stderr + register: podman_connection_result + changed_when: podman_connection_result.rc == 0 + failed_when: + - podman_connection_result.rc != 0 + - "'already exists' not in podman_connection_result.stderr" - name: Ensure containers config directory exists ansible.builtin.file: @@ -70,4 +72,5 @@ - name: Migrate podman storage when subuid/subgid changed ansible.builtin.command: cmd: podman system migrate - when: subuid_result.changed or subgid_result.changed + changed_when: podman_subuid_result.changed or podman_subgid_result.changed + when: podman_subuid_result.changed or podman_subgid_result.changed diff --git a/ansible/roles/shell_config/tasks/main.yml b/ansible/roles/shell_config/tasks/main.yml index e0c1cab..c3bd384 100644 --- a/ansible/roles/shell_config/tasks/main.yml +++ b/ansible/roles/shell_config/tasks/main.yml @@ -105,9 +105,9 @@ - name: Set fish as default shell for user (LDAP/SSSD compatible) ansible.builtin.command: cmd: chsh -s /usr/bin/fish - register: chsh_result - changed_when: chsh_result.rc == 0 - failed_when: chsh_result.rc != 0 + register: shell_config_chsh_result + changed_when: shell_config_chsh_result.rc == 0 + failed_when: shell_config_chsh_result.rc != 0 become: true - name: Ensure user fonts directory exists @@ -116,18 +116,24 @@ state: directory mode: "0755" -- name: Download Hack Nerd Font +- name: Download Nerd Font + vars: + nerd_font_url: > + https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Hack.zip ansible.builtin.get_url: - url: https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Hack.zip + url: "{{ nerd_font_url }}" dest: /tmp/Hack.zip mode: "0644" - name: Extract Hack Nerd Font ansible.builtin.unarchive: src: /tmp/Hack.zip - dest: "{{ home_dir }}/.local/share/fonts/NerdFonts" + dest: > + {{ home_dir }}/.local/share/fonts/NerdFonts remote_src: true - creates: "{{ home_dir }}/.local/share/fonts/NerdFonts/HackNerdFont-Regular.ttf" + creates: > + {{ home_dir }}/.local/share/fonts/NerdFonts/ + HackNerdFont-Regular.ttf - name: Refresh font cache ansible.builtin.command: @@ -137,10 +143,10 @@ - name: Check if KDE Konsole is installed ansible.builtin.stat: path: /usr/bin/konsole - register: konsole_binary + register: shell_config_konsole_binary - name: Configure Konsole - when: konsole_binary.stat.exists + when: shell_config_konsole_binary.stat.exists block: - name: Ensure Konsole profiles directory exists ansible.builtin.file: diff --git a/install-ansible b/install-ansible index 860614b..fa94f12 100755 --- a/install-ansible +++ b/install-ansible @@ -12,5 +12,13 @@ if ! grep -qF "${USER_BIN}" "${HOME}/.bashrc"; then echo "Added ${USER_BIN} to ~/.bashrc" fi -pipx upgrade ansible-core 2>/dev/null || pipx install ansible-core +PIPX_PACKAGES=( + ansible-core + ansible-lint + yamllint +) + +for package in "${PIPX_PACKAGES[@]}"; do + pipx upgrade "${package}" 2>/dev/null || pipx install "${package}" +done ansible-galaxy collection install -r "${SCRIPT_DIR}/requirements.yml" --upgrade \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bbe5761..aa9d17a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pipx==1.11.1 +pipx==1.12.0 podman-compose==1.5.0 diff --git a/run-lint b/run-lint new file mode 100755 index 0000000..13d4d73 --- /dev/null +++ b/run-lint @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}/ansible" + +export PATH="${PATH}:$(python3 -m site --user-base)/bin" +export PYTHONUNBUFFERED=1 + +GITHUB_FORMAT=false +SARIF_FILE="${SCRIPT_DIR}/ansible-lint.sarif" + +for arg in "$@"; do + case "$arg" in + --github-format) + GITHUB_FORMAT=true + ;; + esac +done + +EXIT_CODE=0 + +run_yamllint() { + if [[ "${GITHUB_FORMAT}" == "true" ]]; then + yamllint --format github . || return $? + else + yamllint . || return $? + fi +} + +run_ansible_lint() { + if [[ "${GITHUB_FORMAT}" == "true" ]]; then + ansible-lint -f sarif > "${SARIF_FILE}" || return $? + else + ansible-lint || return $? + fi +} + +run_yamllint || EXIT_CODE=$? +run_ansible_lint || EXIT_CODE=$? + +exit "${EXIT_CODE}"