--- - name: Create AdGuard Home LXC on mini-pc hosts: mini-pc gather_facts: false vars: # После переезда на OpenTofu (tofu/svc-adguard.tf, VMID 158) этот play — # legacy: он таргетит СТАРЫЙ VMID 144 и его handler сделал бы pct start 144, # подняв остановленный откат на боевом адресе. Пропускаем, пока реестр # говорит provisioner: tofu. Для намеренного legacy rollback: # -e pve_adguard_legacy_provisioning_enabled=true pve_adguard_legacy_provisioning_enabled: >- {{ homelab_services['adguard'].provisioner != 'tofu' }} adguard_vmid: 144 adguard_hostname: adguard adguard_ip: 192.168.1.28/24 adguard_gateway: 192.168.1.1 adguard_rootfs: local-lvm:8 adguard_ostemplate: local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst adguard_pubkey_file: ~/.ssh/id_ed25519_homelab.pub adguard_image: adguard/adguardhome:v0.107.78@sha256:2c127294fa5f96151d9d3a433fb9d66c17e4d18cf698c2b04372a80e26fdd26f handlers: - name: restart adguard lxc ansible.builtin.shell: "pct stop {{ adguard_vmid }} || true; pct start {{ adguard_vmid }}" changed_when: true pre_tasks: - name: Skip legacy AdGuard provisioning after cutover ansible.builtin.meta: end_play when: not (pve_adguard_legacy_provisioning_enabled | bool) tasks: - name: Check if AdGuard LXC exists ansible.builtin.command: "pct config {{ adguard_vmid }}" register: adguard_pct_config changed_when: false failed_when: false - name: Refuse to modify a foreign VMID {{ adguard_vmid }} ansible.builtin.assert: that: - adguard_pct_config.rc != 0 or adguard_existing_hostname == adguard_hostname fail_msg: VMID {{ adguard_vmid }} already exists and is not the AdGuard container. vars: adguard_existing_hostname: >- {{ adguard_pct_config.stdout_lines | select('match', '^hostname: ') | map('regex_replace', '^hostname: ', '') | first | default('') }} - name: Install AdGuard LXC SSH public key on PVE host ansible.builtin.copy: dest: /tmp/adguard-lxc.pub owner: root group: root mode: "0600" content: "{{ lookup('file', adguard_pubkey_file) }}\n" when: adguard_pct_config.rc != 0 - name: Create AdGuard LXC ansible.builtin.command: >- pct create {{ adguard_vmid }} {{ adguard_ostemplate }} --hostname {{ adguard_hostname }} --rootfs {{ adguard_rootfs }} --cores 1 --memory 512 --swap 512 --net0 name=eth0,bridge=vmbr0,gw={{ adguard_gateway }},ip={{ adguard_ip }},firewall=1 --nameserver 1.1.1.1 --unprivileged 1 --features nesting=1,keyctl=1 --onboot 1 --startup order=40 --cmode shell --ssh-public-keys /tmp/adguard-lxc.pub when: adguard_pct_config.rc != 0 - name: Start AdGuard LXC ansible.builtin.command: "pct start {{ adguard_vmid }}" register: adguard_pct_start changed_when: adguard_pct_start.rc == 0 failed_when: adguard_pct_start.rc not in [0, 255] - name: Allow FUSE device in AdGuard LXC config ansible.builtin.lineinfile: path: "/etc/pve/lxc/{{ adguard_vmid }}.conf" line: "lxc.cgroup2.devices.allow: c 10:229 rwm" state: present notify: restart adguard lxc - name: Bind mount FUSE device in AdGuard LXC config ansible.builtin.lineinfile: path: "/etc/pve/lxc/{{ adguard_vmid }}.conf" line: "lxc.mount.entry: /dev/fuse dev/fuse none bind,create=file" state: present notify: restart adguard lxc - name: Apply pending LXC config changes ansible.builtin.meta: flush_handlers - name: Wait for AdGuard SSH through ru-vps ansible.builtin.wait_for_connection: timeout: 120 delegate_to: adguard vars: ansible_become: false - name: Configure AdGuard Home inside LXC # Таргет переопределяем ради blue-green переезда на OpenTofu: во время # миграции этот play нужно прогнать против нового контейнера на временном # адресе. Просто `--limit adguard-new` для этого НЕ годится — лимит # пересекается с паттерном play и даёт ноль хостов, а не перенацеливание # (проверено 2026-09-02 через --list-hosts на всех плейбуках). # Использовать вместе с --limit, чтобы play создания контейнера отсеялся: # ansible-playbook playbooks/pve-adguard.yml \ # -e pve_config_target=adguard-new --limit adguard-new # По умолчанию поведение не меняется. hosts: "{{ pve_config_target | default('adguard') }}" gather_facts: true vars: ansible_become: false adguard_image: adguard/adguardhome:v0.107.78@sha256:2c127294fa5f96151d9d3a433fb9d66c17e4d18cf698c2b04372a80e26fdd26f tasks: - name: Install Docker packages ansible.builtin.apt: name: - docker.io - fuse-overlayfs - ca-certificates - curl - dnsutils state: present update_cache: true - name: Ensure Docker config directory exists ansible.builtin.file: path: /etc/docker state: directory owner: root group: root mode: "0755" - name: Configure Docker storage driver for unprivileged LXC ansible.builtin.copy: dest: /etc/docker/daemon.json owner: root group: root mode: "0644" content: | { "storage-driver": "fuse-overlayfs" } register: adguard_docker_daemon_config - name: Enable Docker service ansible.builtin.systemd: name: docker state: "{{ 'restarted' if adguard_docker_daemon_config.changed else 'started' }}" enabled: true - name: Ensure AdGuard data directories exist ansible.builtin.file: path: "{{ item }}" state: directory owner: root group: root mode: "0750" loop: - /opt/adguard/work - /opt/adguard/conf - name: Check if the configured AdGuard image is present ansible.builtin.command: "docker image inspect {{ adguard_image }}" register: adguard_image_inspect changed_when: false failed_when: false - name: Pull the configured AdGuard image ansible.builtin.command: "docker pull {{ adguard_image }}" when: adguard_image_inspect.rc != 0 register: adguard_image_pull changed_when: true - name: Install AdGuard Home systemd unit ansible.builtin.copy: dest: /etc/systemd/system/adguard.service owner: root group: root mode: "0644" content: | [Unit] Description=AdGuard Home container After=docker.service Requires=docker.service [Service] Restart=always RestartSec=10 ExecStartPre=-/usr/bin/docker rm -f adguard ExecStart=/usr/bin/docker run --rm --name adguard --pull never \ -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 3000:3000/tcp \ -v /opt/adguard/work:/opt/adguardhome/work \ -v /opt/adguard/conf:/opt/adguardhome/conf \ {{ adguard_image }} ExecStop=/usr/bin/docker stop adguard [Install] WantedBy=multi-user.target register: adguard_unit - name: Remove obsolete AdGuard post-start validation unit ansible.builtin.file: path: /etc/systemd/system/adguard-post-start.service state: absent register: adguard_post_start_unit_removed - name: Reload systemd when AdGuard units change ansible.builtin.systemd: daemon_reload: true when: adguard_unit.changed or adguard_post_start_unit_removed.changed - name: Enable and start AdGuard Home ansible.builtin.systemd: name: adguard state: "{{ 'restarted' if adguard_unit.changed or adguard_image_pull.changed else 'started' }}" enabled: true - name: Wait for AdGuard HTTP root ansible.builtin.uri: url: http://127.0.0.1/ status_code: [200, 302] return_content: false register: adguard_http_root retries: 24 delay: 5 until: adguard_http_root.status in [200, 302] - name: Verify AdGuard DNS over UDP ansible.builtin.command: argv: - dig - "@127.0.0.1" - localhost - A - +time=2 - +tries=1 - +short register: adguard_dns_udp changed_when: false retries: 12 delay: 5 until: adguard_dns_udp.rc == 0 and '127.0.0.1' in adguard_dns_udp.stdout - name: Verify AdGuard DNS over TCP ansible.builtin.command: argv: - dig - "@127.0.0.1" - localhost - A - +tcp - +time=2 - +tries=1 - +short register: adguard_dns_tcp changed_when: false retries: 12 delay: 5 until: adguard_dns_tcp.rc == 0 and '127.0.0.1' in adguard_dns_tcp.stdout