From afd240de8685399d2974acc51c0e1d5e29d6ffc5 Mon Sep 17 00:00:00 2001 From: Adyrem Date: Fri, 15 May 2026 16:52:20 +0200 Subject: [PATCH] Add Pi-hole and Traefik LXC containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LXC 103 (pihole): Debian 12, 10.10.1.2, Pi-hole v6 with local DNS for gitea/grafana/traefik/pihole.homelab, Proxmox dnsmasq updated to use Pi-hole upstream - LXC 104 (traefik): Debian 12, 10.10.1.3, Traefik v3 routing gitea.homelab → 10.10.1.125:3000, grafana.homelab → 10.10.1.137:3000 - DNAT rules on vmbr0 forwarding :80/:443 to Traefik, persisted in /etc/network/interfaces - Ansible roles: pihole, traefik; playbooks: pihole.yml, traefik.yml - site.yml excludes Debian containers from Arch-specific common role Co-Authored-By: Claude Sonnet 4.6 --- .claude/scheduled_tasks.lock | 1 + ansible/inventory/group_vars/all/vault.yml | 66 +++++++------- .../group_vars/pihole_servers/vars.yml | 2 + .../group_vars/traefik_servers/vars.yml | 2 + ansible/inventory/hosts.yml | 12 +++ ansible/playbooks/pihole.yml | 5 ++ ansible/playbooks/site.yml | 14 ++- ansible/playbooks/traefik.yml | 5 ++ ansible/roles/pihole/defaults/main.yml | 11 +++ ansible/roles/pihole/handlers/main.yml | 10 +++ ansible/roles/pihole/tasks/main.yml | 58 +++++++++++++ .../roles/pihole/templates/setupVars.conf.j2 | 15 ++++ .../roles/pihole/templates/update_hosts.py.j2 | 22 +++++ ansible/roles/traefik/defaults/main.yml | 17 ++++ ansible/roles/traefik/handlers/main.yml | 9 ++ ansible/roles/traefik/tasks/main.yml | 86 +++++++++++++++++++ ansible/roles/traefik/templates/routes.yml.j2 | 17 ++++ .../traefik/templates/traefik.service.j2 | 18 ++++ .../roles/traefik/templates/traefik.yml.j2 | 15 ++++ 19 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 .claude/scheduled_tasks.lock create mode 100644 ansible/inventory/group_vars/pihole_servers/vars.yml create mode 100644 ansible/inventory/group_vars/traefik_servers/vars.yml create mode 100644 ansible/playbooks/pihole.yml create mode 100644 ansible/playbooks/traefik.yml create mode 100644 ansible/roles/pihole/defaults/main.yml create mode 100644 ansible/roles/pihole/handlers/main.yml create mode 100644 ansible/roles/pihole/tasks/main.yml create mode 100644 ansible/roles/pihole/templates/setupVars.conf.j2 create mode 100644 ansible/roles/pihole/templates/update_hosts.py.j2 create mode 100644 ansible/roles/traefik/defaults/main.yml create mode 100644 ansible/roles/traefik/handlers/main.yml create mode 100644 ansible/roles/traefik/tasks/main.yml create mode 100644 ansible/roles/traefik/templates/routes.yml.j2 create mode 100644 ansible/roles/traefik/templates/traefik.service.j2 create mode 100644 ansible/roles/traefik/templates/traefik.yml.j2 diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..327459a --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"e2025a38-3d43-46f4-8c97-bbac5bc5681b","pid":40455,"procStart":"489090","acquiredAt":1778850952882} \ No newline at end of file diff --git a/ansible/inventory/group_vars/all/vault.yml b/ansible/inventory/group_vars/all/vault.yml index e2ea42f..61f4230 100644 --- a/ansible/inventory/group_vars/all/vault.yml +++ b/ansible/inventory/group_vars/all/vault.yml @@ -1,33 +1,35 @@ $ANSIBLE_VAULT;1.1;AES256 -34333966316434323536326366663637633834393865623238636338306361393837393530363665 -3566366534376133643163376261343461653336643136330a356162653062663538636466346438 -31626464363033623331353037333131356434386631616566323337616166306431383330333261 -6464656132613962610a306163353135653131623064386337643563646266623839383138326133 -31353466653633323030326432633364376236313239623439663265393365356262393138666263 -62323962306439643166613930383933323132316132363236363639333336663237643966386337 -63633764663263303933336332343266333161333835363235303965373134386434373532646137 -38663965396332356561363932633736613836393861626435666162396666633033393963353264 -35633438316238623933623966306330363432386632653936323436656337306430383439356462 -64396137316464363835373137366434323263343330313163653638306434616635656537656264 -66396366666562643734313235613364623734613662663161636332353035346666373732613165 -64356630323239363665383933363139613738323866326134613661663731396566323065383438 -39613335383434613432666266343935646537613266386335393131333635343633653736373634 -64653164396439363631643565663839623966343165666236303434666431636431356136613335 -36353166336664333031663435323161363137386237343034383863356430323433343766646134 -36366665313130373363303136346239633538393230313832663430393233663062343834383534 -64373363376139663165363663333962353538326461353838383865653963343036366336343563 -65613336386438373262666263633761386332303737613038333735336639613230336636333265 -30393735643432623935363832313738376630373036623630663735343935386165616133333563 -63366637313961653264346539383233346465383263353566326134396333663632636630343133 -64636261346437333166316430643437393032396237353231346136363634353838376238386335 -39613865346266396639353838316331356430343331633330636534316335633563623464613661 -34646132333135303361336135303263636362353937346635356464656438623539353836383132 -65626566366539306536663462373236616430343135633439643836363365323761373463356131 -65363337313334393265383362666639636132623032303764326335306336616136646165376264 -30383561303866646138643734666462653765653362383163346432303435663738633936383866 -39613831323436393061666532343765333331313961636164346338656561353731323064316630 -32626238633863353530356436383665363239663965356564356239313334393732363265656665 -30386266313538336339303731386634643839383261383361333931333739383563316134666331 -66323064353733303838393635653037373538616138323763396536343064323633656562643337 -39636138633066356134303739363061653065643464393331626531623030653766313764623630 -64636434636233653065 +30353336323330616638366131616265353935386336623962313139666630386233333435376132 +6632383465666533323435613938636564636166353265300a636531383030323130356439663538 +39373933303438346138306430393831663236626132353031623837643563353536383432393866 +3963613139663531620a613631636261653764663539323363326538373438326666646666643432 +66663430333263653439613937326134653830663631363236663238663933613363343039393734 +62313035653337363836633333636231643635656134396639653431646236353737353133343631 +30343237373232356532363732366236343565353739353065363562643932386164366163343235 +33363837626434306339623733623464646633656431643732303330353232656439313639363362 +33376364343965323464663634386530303462613833333136623635366537363265646163653038 +38376563353865636437313838313236333662376430303930356432663261383834626563343139 +66643730373239623737383138663935313031373062646363663865366233383663336330333134 +30333032366131363630303735653265636337616531656133343935306136303830333439656230 +36303565643939616138323932393637373935396264666162356538313165656438306232613665 +64666334306134323730383665666664303930646630343636653335653735323561393061323031 +65643831353464353235626535346337663263396432356232323434653562656365386663636131 +39663136313664353331373164616331626135356234623961663430653661316465393439643033 +64653562383533383839386237323439376139333962396336386633653566656563636533366336 +35313366336266633636383763643463376134316366393235313964313239373836336665353339 +66303531336333646333326265646331353363653036313261663462626462653964306465383665 +66313331656538336534653166363535336232316363303335663835656566623132383131373436 +36366235643935373061366565623761333335666163326464326636626462663661346163323438 +64653234333236383039393963336235643163653834303237366363633330613639353332616535 +61323265346161313561396435643630383435633466636639316665353366343266306232663439 +32653662666631636662323833646562353861333365303430326130633832313162316337643063 +34393038646164363837306461303634653464343232663139363237353939323137333238613537 +34616139376632373931343437316337383332343162363939633634303831353336333433633935 +39343439313865343036376634653863653137653131363866363761653065313738316663383239 +38383232626262303463393164326366633138313932366530663136616233313831303337316537 +38343661663264303337346161323135366164373039663934343261663237396563383239663262 +39383765643765396331333938666335663862303962353562663361323764643031373431306664 +61646561353732323765363034326362663239666634316639356232633934663130663431613236 +64336335653664646430653061313836386361316564353334336364626332303839363335343363 +65653931363331383834373364616538336135633936363533383461373931666666373863386438 +3231333561313139393966383964613731616139666536613536 diff --git a/ansible/inventory/group_vars/pihole_servers/vars.yml b/ansible/inventory/group_vars/pihole_servers/vars.yml new file mode 100644 index 0000000..83f9955 --- /dev/null +++ b/ansible/inventory/group_vars/pihole_servers/vars.yml @@ -0,0 +1,2 @@ +ansible_user: root +ansible_become: false diff --git a/ansible/inventory/group_vars/traefik_servers/vars.yml b/ansible/inventory/group_vars/traefik_servers/vars.yml new file mode 100644 index 0000000..83f9955 --- /dev/null +++ b/ansible/inventory/group_vars/traefik_servers/vars.yml @@ -0,0 +1,2 @@ +ansible_user: root +ansible_become: false diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index e085c8b..23c7057 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -9,6 +9,10 @@ all: ansible_host: 10.10.1.125 monitoring: ansible_host: 10.10.1.137 + pihole: + ansible_host: 10.10.1.2 + traefik: + ansible_host: 10.10.1.3 monitoring_servers: hosts: @@ -17,3 +21,11 @@ all: gitea_servers: hosts: gitea: + + pihole_servers: + hosts: + pihole: + + traefik_servers: + hosts: + traefik: diff --git a/ansible/playbooks/pihole.yml b/ansible/playbooks/pihole.yml new file mode 100644 index 0000000..3da3264 --- /dev/null +++ b/ansible/playbooks/pihole.yml @@ -0,0 +1,5 @@ +--- +- name: Deploy Pi-hole + hosts: pihole_servers + roles: + - pihole diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml index bb72a7a..fd9ee93 100644 --- a/ansible/playbooks/site.yml +++ b/ansible/playbooks/site.yml @@ -1,6 +1,6 @@ --- -- name: Common setup for all VMs - hosts: all +- name: Common setup for all Arch VMs + hosts: all:!pihole_servers:!traefik_servers roles: - common - node_exporter @@ -15,3 +15,13 @@ hosts: gitea_servers roles: - gitea + +- name: Pi-hole + hosts: pihole_servers + roles: + - pihole + +- name: Traefik + hosts: traefik_servers + roles: + - traefik diff --git a/ansible/playbooks/traefik.yml b/ansible/playbooks/traefik.yml new file mode 100644 index 0000000..8e272ef --- /dev/null +++ b/ansible/playbooks/traefik.yml @@ -0,0 +1,5 @@ +--- +- name: Deploy Traefik + hosts: traefik_servers + roles: + - traefik diff --git a/ansible/roles/pihole/defaults/main.yml b/ansible/roles/pihole/defaults/main.yml new file mode 100644 index 0000000..3531002 --- /dev/null +++ b/ansible/roles/pihole/defaults/main.yml @@ -0,0 +1,11 @@ +--- +pihole_password: "{{ vault_pihole_password }}" +pihole_dns_upstream1: "8.8.8.8" +pihole_dns_upstream2: "8.8.4.4" +pihole_local_domain: "homelab" +pihole_interface: "eth0" +pihole_dns_hosts: + - { ip: "10.10.1.2", host: "pihole.homelab" } + - { ip: "10.10.1.3", host: "traefik.homelab" } + - { ip: "10.10.1.125", host: "gitea.homelab" } + - { ip: "10.10.1.137", host: "grafana.homelab" } diff --git a/ansible/roles/pihole/handlers/main.yml b/ansible/roles/pihole/handlers/main.yml new file mode 100644 index 0000000..70dfc94 --- /dev/null +++ b/ansible/roles/pihole/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart pihole-FTL + ansible.builtin.service: + name: pihole-FTL + state: restarted + +- name: Update pihole hosts + ansible.builtin.command: + cmd: python3 /tmp/pihole_update_hosts.py + notify: Restart pihole-FTL diff --git a/ansible/roles/pihole/tasks/main.yml b/ansible/roles/pihole/tasks/main.yml new file mode 100644 index 0000000..b7ef641 --- /dev/null +++ b/ansible/roles/pihole/tasks/main.yml @@ -0,0 +1,58 @@ +--- +- name: Install Pi-hole dependencies + ansible.builtin.apt: + name: + - curl + - git + - libcap2-bin + - dns-root-data + state: present + update_cache: true + +- name: Create Pi-hole config directory + ansible.builtin.file: + path: /etc/pihole + state: directory + mode: "0755" + +- name: Write Pi-hole setup vars + ansible.builtin.template: + src: setupVars.conf.j2 + dest: /etc/pihole/setupVars.conf + mode: "0644" + +- name: Check if Pi-hole is already installed + ansible.builtin.stat: + path: /usr/local/bin/pihole + register: pihole_binary + +- name: Download Pi-hole installer + ansible.builtin.get_url: + url: https://install.pi-hole.net + dest: /tmp/pihole-install.sh + mode: "0755" + when: not pihole_binary.stat.exists + +- name: Run Pi-hole unattended installer + ansible.builtin.command: + cmd: /tmp/pihole-install.sh --unattended + creates: /usr/local/bin/pihole + when: not pihole_binary.stat.exists + +- name: Set Pi-hole web password + ansible.builtin.command: + cmd: /usr/local/bin/pihole setpassword "{{ pihole_password }}" + changed_when: true + +- name: Set local DNS hosts in pihole.toml + ansible.builtin.template: + src: update_hosts.py.j2 + dest: /tmp/pihole_update_hosts.py + mode: "0755" + notify: Update pihole hosts + +- name: Ensure pihole-FTL is running + ansible.builtin.service: + name: pihole-FTL + state: started + enabled: true diff --git a/ansible/roles/pihole/templates/setupVars.conf.j2 b/ansible/roles/pihole/templates/setupVars.conf.j2 new file mode 100644 index 0000000..70a1d6f --- /dev/null +++ b/ansible/roles/pihole/templates/setupVars.conf.j2 @@ -0,0 +1,15 @@ +PIHOLE_INTERFACE={{ pihole_interface }} +PIHOLE_DNS_1={{ pihole_dns_upstream1 }} +PIHOLE_DNS_2={{ pihole_dns_upstream2 }} +QUERY_LOGGING=true +INSTALL_WEB_SERVER=true +INSTALL_WEB_INTERFACE=true +LIGHTTPD_ENABLED=true +CACHE_SIZE=10000 +DNS_FQDN_REQUIRED=false +DNS_BOGUS_PRIV=true +DNSMASQ_LISTENING=local +WEBPASSWORD= +BLOCKING_ENABLED=true +PIHOLE_DOMAIN={{ pihole_local_domain }} +IPV4_ADDRESS=10.10.1.2/24 diff --git a/ansible/roles/pihole/templates/update_hosts.py.j2 b/ansible/roles/pihole/templates/update_hosts.py.j2 new file mode 100644 index 0000000..fa3d8db --- /dev/null +++ b/ansible/roles/pihole/templates/update_hosts.py.j2 @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import re + +hosts = [ +{% for h in pihole_dns_hosts %} + "{{ h.ip }} {{ h.host }}", +{% endfor %} +] + +hosts_str = "[" + ", ".join('"' + h + '"' for h in hosts) + "]" + +with open("/etc/pihole/pihole.toml", "r") as f: + content = f.read() + +content = re.sub( + r"(?m)^( *hosts\s*=\s*)\[.*\]", + r"\g<1>" + hosts_str, + content, +) + +with open("/etc/pihole/pihole.toml", "w") as f: + f.write(content) diff --git a/ansible/roles/traefik/defaults/main.yml b/ansible/roles/traefik/defaults/main.yml new file mode 100644 index 0000000..c8cff7e --- /dev/null +++ b/ansible/roles/traefik/defaults/main.yml @@ -0,0 +1,17 @@ +--- +traefik_version: "3.3.4" +traefik_http_port: 80 +traefik_https_port: 443 +traefik_dashboard_port: 8080 +traefik_log_level: "INFO" + +traefik_services: + - name: gitea + host: "gitea.homelab" + backend: "http://10.10.1.125:3000" + - name: grafana + host: "grafana.homelab" + backend: "http://10.10.1.137:3000" + - name: pihole + host: "pihole.homelab" + backend: "http://10.10.1.2:80" diff --git a/ansible/roles/traefik/handlers/main.yml b/ansible/roles/traefik/handlers/main.yml new file mode 100644 index 0000000..559310a --- /dev/null +++ b/ansible/roles/traefik/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: Reload systemd + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart traefik + ansible.builtin.systemd: + name: traefik + state: restarted diff --git a/ansible/roles/traefik/tasks/main.yml b/ansible/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..eada09e --- /dev/null +++ b/ansible/roles/traefik/tasks/main.yml @@ -0,0 +1,86 @@ +--- +- name: Install dependencies + ansible.builtin.apt: + name: + - curl + - ca-certificates + state: present + update_cache: true + +- name: Create traefik user + ansible.builtin.user: + name: traefik + system: true + shell: /usr/sbin/nologin + home: /etc/traefik + create_home: false + +- name: Create traefik directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: traefik + group: traefik + mode: "0750" + loop: + - /etc/traefik + - /etc/traefik/conf.d + +- name: Check installed Traefik version + ansible.builtin.command: + cmd: /usr/local/bin/traefik version + register: traefik_installed_version + changed_when: false + failed_when: false + +- name: Download Traefik binary + ansible.builtin.get_url: + url: "https://github.com/traefik/traefik/releases/download/v{{ traefik_version }}/traefik_v{{ traefik_version }}_linux_amd64.tar.gz" + dest: /tmp/traefik.tar.gz + mode: "0644" + when: traefik_version not in (traefik_installed_version.stdout | default('')) + +- name: Extract Traefik binary + ansible.builtin.unarchive: + src: /tmp/traefik.tar.gz + dest: /usr/local/bin + include: + - traefik + remote_src: true + mode: "0755" + when: traefik_version not in (traefik_installed_version.stdout | default('')) + notify: Restart traefik + +- name: Write Traefik static config + ansible.builtin.template: + src: traefik.yml.j2 + dest: /etc/traefik/traefik.yml + owner: traefik + group: traefik + mode: "0640" + notify: Restart traefik + +- name: Write Traefik dynamic routes config + ansible.builtin.template: + src: routes.yml.j2 + dest: /etc/traefik/conf.d/routes.yml + owner: traefik + group: traefik + mode: "0640" + notify: Restart traefik + +- name: Write Traefik systemd service + ansible.builtin.template: + src: traefik.service.j2 + dest: /etc/systemd/system/traefik.service + mode: "0644" + notify: + - Reload systemd + - Restart traefik + +- name: Enable and start Traefik + ansible.builtin.systemd: + name: traefik + state: started + enabled: true + daemon_reload: true diff --git a/ansible/roles/traefik/templates/routes.yml.j2 b/ansible/roles/traefik/templates/routes.yml.j2 new file mode 100644 index 0000000..3c6c5b0 --- /dev/null +++ b/ansible/roles/traefik/templates/routes.yml.j2 @@ -0,0 +1,17 @@ +http: + routers: +{% for svc in traefik_services %} + {{ svc.name }}: + rule: "Host(`{{ svc.host }}`)" + service: {{ svc.name }} + entryPoints: + - web +{% endfor %} + + services: +{% for svc in traefik_services %} + {{ svc.name }}: + loadBalancer: + servers: + - url: "{{ svc.backend }}" +{% endfor %} diff --git a/ansible/roles/traefik/templates/traefik.service.j2 b/ansible/roles/traefik/templates/traefik.service.j2 new file mode 100644 index 0000000..22b479a --- /dev/null +++ b/ansible/roles/traefik/templates/traefik.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description=Traefik reverse proxy +After=network-online.target +Wants=network-online.target + +[Service] +Type=notify +User=traefik +Group=traefik +ExecStart=/usr/local/bin/traefik --configFile=/etc/traefik/traefik.yml +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/traefik/templates/traefik.yml.j2 b/ansible/roles/traefik/templates/traefik.yml.j2 new file mode 100644 index 0000000..a539223 --- /dev/null +++ b/ansible/roles/traefik/templates/traefik.yml.j2 @@ -0,0 +1,15 @@ +api: + dashboard: true + insecure: true + +entryPoints: + web: + address: ":{{ traefik_http_port }}" + +providers: + file: + directory: /etc/traefik/conf.d + watch: true + +log: + level: {{ traefik_log_level }}