diff --git a/.gitignore b/.gitignore index 60ca337..9e63007 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ keys/ wireguard/ +certs/ *.vault *.secret ansible/vault_pass diff --git a/ansible/inventory/group_vars/all/vault.yml b/ansible/inventory/group_vars/all/vault.yml index 61f4230..4199043 100644 --- a/ansible/inventory/group_vars/all/vault.yml +++ b/ansible/inventory/group_vars/all/vault.yml @@ -1,35 +1,38 @@ $ANSIBLE_VAULT;1.1;AES256 -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 +31323435376435353462306465366666306330303932626563666533616264343239336236653865 +6362303830313335363234663639323132313064383234650a613661353637613934666538653036 +65343031666139326139626634653861633161633565333539663339633435306664396564656564 +3361396638313836340a616539323161333465336162363063643832363231653037346234363533 +66313661343562643663623132333138663536643062323064353632666365653435623233643634 +61363034363562643733323164633130666133623034373639383663613661323065383230396265 +65366331636235633534636237383332643436336665663366653164313266353236363362383864 +32646131613136393863383031646163356463396662346231366365363436393961333462303434 +64313738393132636537653438393030623363373037343930636137373436646630303137393035 +33666238363039623266666538623634323165363262636565393134393530343137383965303662 +30336536646431633235623636316132613437306338646435383037633534306336393538666237 +39656330376339323464313134326165316163393965633964343136633035653934386633646133 +32386235373031383037613835373966356334383539383162393136616566376335366239323961 +38616334343238373361306561656635653334633561366139386465333462336161333637643234 +63393231353166376362323434396265303630663530373538373665326662386264383762623835 +37366234313930376366386336303963376232393066373133616332613439343430303664656137 +37646630373332636136653634346363323766376635343838336531613934626362393961656266 +65363333346664633261666132633166656665316537343464653436393662656565393136613839 +30633932333062653836616637366365333937366134613965623139323163316332333636646439 +64363238346430346437666363373463376630363539613365313231373237366666353737383264 +39633261623231323962383564346265386633646664386461646332646637613363613134333163 +65396362313139633436623839333032346436626633336165343665346133353330623833333530 +30623362373261633630356563346231616462666336316165376230623166306362623734376538 +36373039333538376634376534376431363462383764366462643436323964313361643732393964 +37633635346433653636333432653365636666633963363230646636393035613335336231303138 +32623735353465653230333264393137396265373866363666303264656135393464343633653063 +33396539393566363735663537626634326432643836393161373837643561653432393237386337 +32353962363964383363393833666165363334303462653736346538663762353133666165303034 +39313833353465316531643238353238316231303638373964363861623766326565316438313839 +38393735643635633431643634633436626335353462623766363663323132636263383235616237 +65326334346462316630376532656338306438303639353537373766333566376238646265663237 +65623366373766376636303033616566313131386564316135323436653563326464643662383534 +38393061346339346461323438656239336236646166343738663232373565393763313365363439 +61633639623661323132616138346433376134616334356534346332356635316333373931353265 +35346432653231353864363935383966333336303962646563336133613336353239646361376532 +31303830386335306237376366316663646136643462636162383134306431633065393338646139 +3063 diff --git a/ansible/inventory/group_vars/proxmox_hosts/vars.yml b/ansible/inventory/group_vars/proxmox_hosts/vars.yml index 1fe0565..eb36729 100644 --- a/ansible/inventory/group_vars/proxmox_hosts/vars.yml +++ b/ansible/inventory/group_vars/proxmox_hosts/vars.yml @@ -15,3 +15,6 @@ wireguard_peers: - name: fedora public_key: "ivtchk9pxEwYwusMzmn7Uq89LFV3uB1I8iYto0EJuy4=" allowed_ips: 10.10.10.2/32 + - name: phone + public_key: "72fMMJTC/Ew38E61as+oEjFKsgKk4DAK8FqDuQOAcEU=" + allowed_ips: 10.10.10.3/32 diff --git a/ansible/playbooks/wireguard-README.md b/ansible/playbooks/wireguard-README.md index d3c4c2f..6ea49a3 100644 --- a/ansible/playbooks/wireguard-README.md +++ b/ansible/playbooks/wireguard-README.md @@ -6,6 +6,7 @@ |--------|--------------| | server | 10.10.10.1 | | fedora | 10.10.10.2 | +| phone | 10.10.10.3 | Pick the next free IP (10.10.10.3, 10.10.10.4, …) for each new machine. diff --git a/ansible/roles/jellyfin/defaults/main.yml b/ansible/roles/jellyfin/defaults/main.yml index 8080c4e..01e5db1 100644 --- a/ansible/roles/jellyfin/defaults/main.yml +++ b/ansible/roles/jellyfin/defaults/main.yml @@ -13,6 +13,8 @@ jellyfin_pgid: "1000" nvidia_driver_version: "580.159.03" +qbittorrent_webui_password: "{{ vault_qbittorrent_password }}" + jellyfin_config_dirs: - /containers/jellyfin/config - /containers/qbittorrent/config diff --git a/ansible/roles/jellyfin/tasks/configure_services.yml b/ansible/roles/jellyfin/tasks/configure_services.yml new file mode 100644 index 0000000..3dd5c8d --- /dev/null +++ b/ansible/roles/jellyfin/tasks/configure_services.yml @@ -0,0 +1,566 @@ +--- +# --- Media library directories --- + +- name: Create media library directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ jellyfin_puid }}" + group: "{{ jellyfin_pgid }}" + mode: "0755" + loop: + - /media/movies + - /media/tv + +# --- qBittorrent: pre-seed credentials on fresh install --- + +- name: Check if qBittorrent config exists + ansible.builtin.stat: + path: /containers/qbittorrent/config/qBittorrent/qBittorrent.conf + register: _qbt_conf + +- name: Deploy qBittorrent credential seed script + ansible.builtin.template: + src: qbittorrent_seed.py.j2 + dest: /tmp/qbt_seed.py + mode: "0700" + when: not _qbt_conf.stat.exists + +- name: Pre-seed qBittorrent credentials + ansible.builtin.command: + cmd: python3 /tmp/qbt_seed.py + when: not _qbt_conf.stat.exists + +# --- Wait for services to be ready --- + +- name: Read Radarr config + ansible.builtin.slurp: + src: /containers/radarr/config/config.xml + register: _radarr_config + +- name: Read Sonarr config + ansible.builtin.slurp: + src: /containers/sonarr/config/config.xml + register: _sonarr_config + +- name: Read Prowlarr config + ansible.builtin.slurp: + src: /containers/prowlarr/config/config.xml + register: _prowlarr_config + +- name: Set API key facts + ansible.builtin.set_fact: + _radarr_key: "{{ (_radarr_config.content | b64decode | regex_search('(.*?)', '\\1'))[0] }}" + _sonarr_key: "{{ (_sonarr_config.content | b64decode | regex_search('(.*?)', '\\1'))[0] }}" + _prowlarr_key: "{{ (_prowlarr_config.content | b64decode | regex_search('(.*?)', '\\1'))[0] }}" + +- name: Wait for Radarr + ansible.builtin.uri: + url: "http://localhost:7878/api/v3/system/status" + headers: + X-Api-Key: "{{ _radarr_key }}" + register: _r + until: _r.status == 200 + retries: 12 + delay: 5 + +- name: Wait for Sonarr + ansible.builtin.uri: + url: "http://localhost:8989/api/v3/system/status" + headers: + X-Api-Key: "{{ _sonarr_key }}" + register: _r + until: _r.status == 200 + retries: 12 + delay: 5 + +- name: Wait for Prowlarr + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/system/status" + headers: + X-Api-Key: "{{ _prowlarr_key }}" + register: _r + until: _r.status == 200 + retries: 12 + delay: 5 + +# --- Prowlarr: fix TPB season search --- +# apibay.org fulltext indexes "s05e01" as a single token, not "s05". When Prowlarr +# appends the season number to the query (the.boys.s05), apibay returns 0 results. +# Removing season/ep from the tv-search capability makes Prowlarr search with just +# the show title; Sonarr's parser then filters the results by season/episode. +# The file is locked read-only so Prowlarr cannot revert it on definition refresh. + +- name: Wait for Prowlarr to write TPB definition file + ansible.builtin.stat: + path: /containers/prowlarr/config/Definitions/thepiratebay.yml + register: _tpb_def + until: _tpb_def.stat.exists + retries: 12 + delay: 5 + +- name: Fix TPB tv-search to not append season to query + ansible.builtin.lineinfile: + path: /containers/prowlarr/config/Definitions/thepiratebay.yml + regexp: '^\s+tv-search: \[q, season, ep\]' + line: ' tv-search: [q]' + register: _tpb_fix + +- name: Lock TPB definition read-only to prevent Prowlarr reverting the fix + ansible.builtin.file: + path: /containers/prowlarr/config/Definitions/thepiratebay.yml + mode: "0444" + +- name: Restart Prowlarr to apply TPB definition fix + ansible.builtin.command: + cmd: docker restart prowlarr + changed_when: true + when: _tpb_fix.changed + +- name: Wait for Prowlarr after restart + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/system/status" + headers: + X-Api-Key: "{{ _prowlarr_key }}" + register: _r + until: _r.status == 200 + retries: 12 + delay: 5 + when: _tpb_fix.changed + +# --- Radarr: download client --- + +- name: Get Radarr download clients + ansible.builtin.uri: + url: "http://localhost:7878/api/v3/downloadclient" + headers: + X-Api-Key: "{{ _radarr_key }}" + register: _radarr_dc + +- name: Add qBittorrent to Radarr + ansible.builtin.uri: + url: "http://localhost:7878/api/v3/downloadclient" + method: POST + headers: + X-Api-Key: "{{ _radarr_key }}" + body_format: json + body: + name: qBittorrent + enable: true + protocol: torrent + priority: 1 + implementation: QBittorrent + configContract: QBittorrentSettings + fields: + - name: host + value: qbittorrent + - name: port + value: 5080 + - name: useSsl + value: false + - name: username + value: admin + - name: password + value: "{{ qbittorrent_webui_password }}" + - name: movieCategory + value: radarr + tags: [] + status_code: 201 + when: _radarr_dc.json | selectattr('name', 'equalto', 'qBittorrent') | list | length == 0 + +# --- Radarr: root folder --- + +- name: Get Radarr root folders + ansible.builtin.uri: + url: "http://localhost:7878/api/v3/rootfolder" + headers: + X-Api-Key: "{{ _radarr_key }}" + register: _radarr_rf + +- name: Add movies root folder to Radarr + ansible.builtin.uri: + url: "http://localhost:7878/api/v3/rootfolder" + method: POST + headers: + X-Api-Key: "{{ _radarr_key }}" + body_format: json + body: + path: /downloads/movies + status_code: 201 + when: _radarr_rf.json | selectattr('path', 'equalto', '/downloads/movies') | list | length == 0 + +# --- Sonarr: download client --- + +- name: Get Sonarr download clients + ansible.builtin.uri: + url: "http://localhost:8989/api/v3/downloadclient" + headers: + X-Api-Key: "{{ _sonarr_key }}" + register: _sonarr_dc + +- name: Add qBittorrent to Sonarr + ansible.builtin.uri: + url: "http://localhost:8989/api/v3/downloadclient" + method: POST + headers: + X-Api-Key: "{{ _sonarr_key }}" + body_format: json + body: + name: qBittorrent + enable: true + protocol: torrent + priority: 1 + implementation: QBittorrent + configContract: QBittorrentSettings + fields: + - name: host + value: qbittorrent + - name: port + value: 5080 + - name: useSsl + value: false + - name: username + value: admin + - name: password + value: "{{ qbittorrent_webui_password }}" + - name: tvCategory + value: sonarr + tags: [] + status_code: 201 + when: _sonarr_dc.json | selectattr('name', 'equalto', 'qBittorrent') | list | length == 0 + +# --- Sonarr: root folder --- + +- name: Get Sonarr root folders + ansible.builtin.uri: + url: "http://localhost:8989/api/v3/rootfolder" + headers: + X-Api-Key: "{{ _sonarr_key }}" + register: _sonarr_rf + +- name: Add TV root folder to Sonarr + ansible.builtin.uri: + url: "http://localhost:8989/api/v3/rootfolder" + method: POST + headers: + X-Api-Key: "{{ _sonarr_key }}" + body_format: json + body: + path: /downloads/tv + status_code: 201 + when: _sonarr_rf.json | selectattr('path', 'equalto', '/downloads/tv') | list | length == 0 + +# --- Prowlarr: apps --- + +- name: Get Prowlarr applications + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/applications" + headers: + X-Api-Key: "{{ _prowlarr_key }}" + register: _prowlarr_apps + +- name: Add Radarr to Prowlarr + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/applications" + method: POST + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: Radarr + syncLevel: fullSync + implementation: Radarr + configContract: RadarrSettings + fields: + - name: prowlarrUrl + value: "http://prowlarr:9696" + - name: baseUrl + value: "http://radarr:7878" + - name: apiKey + value: "{{ _radarr_key }}" + - name: syncCategories + value: [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060] + tags: [] + status_code: 201 + when: _prowlarr_apps.json | selectattr('name', 'equalto', 'Radarr') | list | length == 0 + +- name: Add Sonarr to Prowlarr + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/applications" + method: POST + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: Sonarr + syncLevel: fullSync + implementation: Sonarr + configContract: SonarrSettings + fields: + - name: prowlarrUrl + value: "http://prowlarr:9696" + - name: baseUrl + value: "http://sonarr:8989" + - name: apiKey + value: "{{ _sonarr_key }}" + - name: syncCategories + value: [5000, 5010, 5020, 5030, 5040, 5045, 5050, 5060, 5070, 5080] + tags: [] + status_code: 201 + when: _prowlarr_apps.json | selectattr('name', 'equalto', 'Sonarr') | list | length == 0 + +# --- Prowlarr: FlareSolverr proxy --- + +- name: Get Prowlarr indexer proxies + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexerproxy" + headers: + X-Api-Key: "{{ _prowlarr_key }}" + register: _prowlarr_proxies + +- name: Add FlareSolverr proxy to Prowlarr + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexerproxy" + method: POST + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: FlareSolverr + implementation: FlareSolverr + configContract: FlareSolverrSettings + tags: [1] + fields: + - name: host + value: "http://flaresolverr:8191" + - name: requestTimeout + value: 60 + status_code: 201 + when: _prowlarr_proxies.json | selectattr('name', 'equalto', 'FlareSolverr') | list | length == 0 + +# --- Prowlarr: indexers --- + +- name: Get Prowlarr indexers + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexer" + headers: + X-Api-Key: "{{ _prowlarr_key }}" + register: _prowlarr_indexers + +- name: Add YTS indexer + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexer" + method: POST + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: YTS + enable: true + appProfileId: 1 + priority: 25 + implementation: Cardigann + configContract: CardigannSettings + tags: [] + fields: + - name: definitionFile + value: yts + - name: baseSettings.limitsUnit + value: 0 + - name: torrentBaseSettings.preferMagnetUrl + value: false + - name: apiurl + value: movies-api.accel.li + status_code: 201 + when: _prowlarr_indexers.json | selectattr('name', 'equalto', 'YTS') | list | length == 0 + +- name: Add The Pirate Bay indexer + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexer" + method: POST + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: The Pirate Bay + enable: true + appProfileId: 1 + priority: 25 + implementation: Cardigann + configContract: CardigannSettings + tags: [] + fields: + - name: definitionFile + value: thepiratebay + - name: baseSettings.limitsUnit + value: 0 + - name: torrentBaseSettings.preferMagnetUrl + value: false + - name: apiurl + value: apibay.org + status_code: 201 + when: _prowlarr_indexers.json | selectattr('name', 'equalto', 'The Pirate Bay') | list | length == 0 + +- name: Add EZTV indexer + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexer" + method: POST + timeout: 90 + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: EZTV + enable: true + appProfileId: 1 + priority: 25 + implementation: Cardigann + configContract: CardigannSettings + tags: [1] + fields: + - name: definitionFile + value: eztv + - name: baseSettings.limitsUnit + value: 0 + - name: torrentBaseSettings.preferMagnetUrl + value: false + status_code: 201 + when: _prowlarr_indexers.json | selectattr('name', 'equalto', 'EZTV') | list | length == 0 + ignore_errors: true + +- name: Add 1337x indexer + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/indexer" + method: POST + timeout: 90 + headers: + X-Api-Key: "{{ _prowlarr_key }}" + body_format: json + body: + name: 1337x + enable: true + appProfileId: 1 + priority: 25 + implementation: Cardigann + configContract: CardigannSettings + tags: [1] + fields: + - name: definitionFile + value: 1337x + - name: baseSettings.limitsUnit + value: 0 + - name: torrentBaseSettings.preferMagnetUrl + value: false + - name: downloadlink + value: 0 + - name: downloadlink2 + value: 1 + - name: sort + value: 2 + - name: type + value: 1 + status_code: 201 + when: _prowlarr_indexers.json | selectattr('name', 'equalto', '1337x') | list | length == 0 + ignore_errors: true + +- name: Sync Prowlarr indexers to apps + ansible.builtin.uri: + url: "http://localhost:9696/api/v1/applications/syncindexers" + method: GET + headers: + X-Api-Key: "{{ _prowlarr_key }}" + status_code: [200, 202] + ignore_errors: true + +# --- Jellyseerr: connect Sonarr and Radarr --- + +- name: Check if Jellyseerr is initialized + ansible.builtin.stat: + path: /containers/jellyseerr/config/settings.json + register: _js_settings + +- name: Read Jellyseerr settings + ansible.builtin.slurp: + src: /containers/jellyseerr/config/settings.json + register: _js_raw + when: _js_settings.stat.exists + +- name: Set Jellyseerr API key fact + ansible.builtin.set_fact: + _js_key: "{{ (_js_raw.content | b64decode | from_json).main.apiKey }}" + when: _js_settings.stat.exists + +- name: Get Jellyseerr Sonarr servers + ansible.builtin.uri: + url: "http://localhost:5055/api/v1/settings/sonarr" + headers: + X-Api-Key: "{{ _js_key }}" + register: _js_sonarr + when: _js_settings.stat.exists + +- name: Configure Sonarr in Jellyseerr + ansible.builtin.uri: + url: "http://localhost:5055/api/v1/settings/sonarr/{{ '0' if (_js_sonarr.json | length == 0) else (_js_sonarr.json[0].id | string) }}" + method: "{{ 'POST' if (_js_sonarr.json | length == 0) else 'PUT' }}" + headers: + X-Api-Key: "{{ _js_key }}" + body_format: json + body: + name: sonarr + hostname: sonarr + port: 8989 + apiKey: "{{ _sonarr_key }}" + useSsl: false + baseUrl: "" + activeProfileId: 4 + activeProfileName: HD-1080p + activeDirectory: /downloads/tv + tags: [] + animeTags: [] + is4k: false + isDefault: true + enableSeasonFolders: true + syncEnabled: false + preventSearch: false + tagRequests: false + status_code: [200, 201] + when: > + _js_settings.stat.exists and + (_js_sonarr.json | length == 0 or not _js_sonarr.json[0].isDefault) + +- name: Get Jellyseerr Radarr servers + ansible.builtin.uri: + url: "http://localhost:5055/api/v1/settings/radarr" + headers: + X-Api-Key: "{{ _js_key }}" + register: _js_radarr + when: _js_settings.stat.exists + +- name: Configure Radarr in Jellyseerr + ansible.builtin.uri: + url: "http://localhost:5055/api/v1/settings/radarr/{{ '0' if (_js_radarr.json | length == 0) else (_js_radarr.json[0].id | string) }}" + method: "{{ 'POST' if (_js_radarr.json | length == 0) else 'PUT' }}" + headers: + X-Api-Key: "{{ _js_key }}" + body_format: json + body: + name: radarr + hostname: radarr + port: 7878 + apiKey: "{{ _radarr_key }}" + useSsl: false + baseUrl: "" + activeProfileId: 4 + activeProfileName: HD-1080p + activeDirectory: /downloads/movies + is4k: false + minimumAvailability: released + tags: [] + isDefault: true + syncEnabled: false + preventSearch: false + tagRequests: false + status_code: [200, 201] + when: > + _js_settings.stat.exists and + (_js_radarr.json | length == 0 or not _js_radarr.json[0].isDefault) diff --git a/ansible/roles/jellyfin/tasks/main.yml b/ansible/roles/jellyfin/tasks/main.yml index 96081c5..7e624ab 100644 --- a/ansible/roles/jellyfin/tasks/main.yml +++ b/ansible/roles/jellyfin/tasks/main.yml @@ -152,3 +152,6 @@ enabled: true state: started daemon_reload: true + +- name: Configure services + ansible.builtin.include_tasks: configure_services.yml diff --git a/ansible/roles/jellyfin/templates/qbittorrent_seed.py.j2 b/ansible/roles/jellyfin/templates/qbittorrent_seed.py.j2 new file mode 100644 index 0000000..e17af2a --- /dev/null +++ b/ansible/roles/jellyfin/templates/qbittorrent_seed.py.j2 @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import hashlib, os, base64 + +password = "{{ qbittorrent_webui_password }}" +salt = os.urandom(16) +dk = hashlib.pbkdf2_hmac("sha512", password.encode(), salt, 100000) +pw_hash = "@ByteArray(" + base64.b64encode(salt).decode() + ":" + base64.b64encode(dk).decode() + ")" + +os.makedirs("/containers/qbittorrent/config/qBittorrent", exist_ok=True) +with open("/containers/qbittorrent/config/qBittorrent/qBittorrent.conf", "w") as f: + f.write("[Preferences]\n") + f.write('WebUI\\Password_PBKDF2="{}"\n'.format(pw_hash)) + f.write("WebUI\\Port=5080\n") + f.write("WebUI\\Username=admin\n") + f.write("WebUI\\LocalHostAuth=false\n") + f.write("WebUI\\ServerDomains=*\n") + f.write("WebUI\\Address=*\n") diff --git a/ansible/roles/pihole/defaults/main.yml b/ansible/roles/pihole/defaults/main.yml index 0fabb99..48e942c 100644 --- a/ansible/roles/pihole/defaults/main.yml +++ b/ansible/roles/pihole/defaults/main.yml @@ -11,7 +11,7 @@ pihole_dns_hosts: - { ip: "10.10.1.3", host: "grafana.homelab" } - { ip: "10.10.1.3", host: "proxmox.homelab" } - { ip: "10.10.1.3", host: "adyrem.duckdns.org" } - - { ip: "10.10.1.3", host: "mtg.homelab" } + - { ip: "10.10.1.3", host: "devbox.homelab" } - { ip: "10.10.1.3", host: "jellyfin.homelab" } - { ip: "10.10.1.3", host: "jellyseerr.homelab" } - { ip: "10.10.1.3", host: "radarr.homelab" } diff --git a/ansible/roles/pihole/templates/update_hosts.py.j2 b/ansible/roles/pihole/templates/update_hosts.py.j2 index fa3d8db..502038b 100644 --- a/ansible/roles/pihole/templates/update_hosts.py.j2 +++ b/ansible/roles/pihole/templates/update_hosts.py.j2 @@ -7,15 +7,18 @@ hosts = [ {% endfor %} ] -hosts_str = "[" + ", ".join('"' + h + '"' for h in hosts) + "]" +hosts_str = "[\n" + "".join(' "' + h + '",\n' for h in hosts) + " ]" with open("/etc/pihole/pihole.toml", "r") as f: content = f.read() +# Only replace the first hosts= (DNS custom records section, not DHCP leases) content = re.sub( - r"(?m)^( *hosts\s*=\s*)\[.*\]", + r"( *hosts\s*=\s*)\[.*?\]", r"\g<1>" + hosts_str, content, + count=1, + flags=re.DOTALL, ) with open("/etc/pihole/pihole.toml", "w") as f: diff --git a/ansible/roles/proxmox_host/files/pve_firewall.py b/ansible/roles/proxmox_host/files/pve_firewall.py index c51227a..5da4726 100644 --- a/ansible/roles/proxmox_host/files/pve_firewall.py +++ b/ansible/roles/proxmox_host/files/pve_firewall.py @@ -10,6 +10,7 @@ DESIRED = [ {'proto': 'tcp', 'source': '10.10.10.0/24', 'dport': '2222'}, {'proto': 'tcp', 'source': '192.168.1.0/24', 'dport': '8006'}, {'proto': 'tcp', 'source': '10.10.10.0/24', 'dport': '8006'}, + {'proto': 'tcp', 'source': '10.10.1.3', 'dport': '8006'}, {'proto': 'tcp', 'source': '192.168.1.0/24', 'dport': '3128'}, {'proto': 'udp', 'source': '10.10.0.0/16', 'dport': '53'}, {'proto': 'tcp', 'source': '10.10.0.0/16', 'dport': '53'}, diff --git a/ansible/roles/traefik/defaults/main.yml b/ansible/roles/traefik/defaults/main.yml index bc8ee3a..28e135b 100644 --- a/ansible/roles/traefik/defaults/main.yml +++ b/ansible/roles/traefik/defaults/main.yml @@ -13,6 +13,10 @@ traefik_services: public_host: "adyrem.duckdns.org" public_path: "/git" strip_prefix: "/git" + - name: devbox + host: "devbox.homelab" + backend: "http://10.10.2.10:4001" + public_host: "adyrem.duckdns.org" - name: mtg host: "mtg.homelab" backend: "http://10.10.2.10:3000" diff --git a/ansible/roles/traefik/tasks/main.yml b/ansible/roles/traefik/tasks/main.yml index 02e3e92..4e2d3f8 100644 --- a/ansible/roles/traefik/tasks/main.yml +++ b/ansible/roles/traefik/tasks/main.yml @@ -60,6 +60,41 @@ when: traefik_version not in (traefik_installed_version.stdout | default('')) notify: Restart traefik +- name: Create traefik certs directory + ansible.builtin.file: + path: /etc/traefik/certs + state: directory + owner: traefik + group: traefik + mode: "0750" + +- name: Copy homelab TLS certificate + ansible.builtin.copy: + src: "{{ playbook_dir }}/../../certs/wildcard.homelab.pem" + dest: /etc/traefik/certs/homelab.pem + owner: traefik + group: traefik + mode: "0640" + notify: Restart traefik + +- name: Copy homelab TLS key + ansible.builtin.copy: + src: "{{ playbook_dir }}/../../certs/wildcard.homelab-key.pem" + dest: /etc/traefik/certs/homelab-key.pem + owner: traefik + group: traefik + mode: "0640" + notify: Restart traefik + +- name: Write Traefik TLS config + ansible.builtin.template: + src: tls.yml.j2 + dest: /etc/traefik/conf.d/tls.yml + owner: traefik + group: traefik + mode: "0640" + notify: Restart traefik + - name: Write Traefik static config ansible.builtin.template: src: traefik.yml.j2 diff --git a/ansible/roles/traefik/templates/tls.yml.j2 b/ansible/roles/traefik/templates/tls.yml.j2 new file mode 100644 index 0000000..66f5814 --- /dev/null +++ b/ansible/roles/traefik/templates/tls.yml.j2 @@ -0,0 +1,6 @@ +tls: + stores: + default: + defaultCertificate: + certFile: /etc/traefik/certs/homelab.pem + keyFile: /etc/traefik/certs/homelab-key.pem