diff --git a/01_infra_setup.md b/01_infra_setup.md index a8ba9c0..6ef0978 100644 --- a/01_infra_setup.md +++ b/01_infra_setup.md @@ -162,12 +162,6 @@ Note that, by applying these playbooks, both the root user and the `counterweigh ```bash cp ansible/infra_secrets.yml.example ansible/infra_secrets.yml ``` - > **DEPRECATED (2026-09-11).** Uptime Kuma has been decommissioned. The server - > deployment was removed from this repo; what it monitored and how it was set up is - > preserved in [`archive/uptime_kuma/`](archive/uptime_kuma/). The monitoring blocks in - > the playbooks are kept but inert (`uptime_kuma_enabled: false`) so the check logic - > survives for whatever replaces it. The credentials below no longer exist in the vault. - * Edit `ansible/infra_secrets.yml` and add your Uptime Kuma credentials: ```yaml uptime_kuma_username: "admin" diff --git a/02_vps_core_services_setup.md b/02_vps_core_services_setup.md index 5d75de9..19cd122 100644 --- a/02_vps_core_services_setup.md +++ b/02_vps_core_services_setup.md @@ -49,12 +49,6 @@ Checklist: ## Uptime Kuma -> **DEPRECATED (2026-09-11).** Uptime Kuma has been decommissioned. The server -> deployment was removed from this repo; what it monitored and how it was set up is -> preserved in [`archive/uptime_kuma/`](archive/uptime_kuma/). The monitoring blocks in -> the playbooks are kept but inert (`uptime_kuma_enabled: false`) so the check logic -> survives for whatever replaces it. The credentials below no longer exist in the vault. - Uptime Kuma gets used to monitor the availability of services, keep track of their uptime and notify issues. ### Deploy diff --git a/README.md b/README.md index 89ef092..80dbaf6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Assumes that you've set `ansible/.vault_pass` with `chmod 600`. + Plan install + File based config + Crossbackup to Desky via rsync -* ~~Uptime Kuma~~ — decommissioned 2026-09-11, see `archive/uptime_kuma/` +* Uptime Kuma + Deployed on Vipy + Crossbackup to Desky via rsync * Vaultwarden diff --git a/ansible/group_vars/all/vault.yml b/ansible/group_vars/all/vault.yml index d1f18ec..798e2d8 100644 --- a/ansible/group_vars/all/vault.yml +++ b/ansible/group_vars/all/vault.yml @@ -1,60 +1,45 @@ $ANSIBLE_VAULT;1.1;AES256 -61356165613635386631393135656434646436303665313031346566323336313138353433316463 -3363323534613064643132663335623238366431393062340a346538396662306537663163623366 -38626166383933616331623231373137306562623637313263333237633661663436666266616433 -3862346438643638650a306634333535653633613534646630386131333236366538333765323333 -38656163303837303732663561373232393132343331663164656262393730326434373731333636 -36613538646431396536363936336562616431656665653965373864633366663836353434626434 -34383932373461333564303439623565383661646365386665393831383463663662356536356236 -36373964666236626465366161636135393734356536633466383262326537343833636630343738 -62353066316131613737373162643363653662656261363465386364323962656537373061373032 -33343763353464383438363438343965653532393831343930393562633630383932653862623637 -38386239353237356631646436356166373961333464396639383538383662326534343339313330 -65373339356364636634616532363832386631323062363530313861336238353261353334306235 -62616338396431316537346638656365356564346666366366343638356261623664393263323937 -30343261363562383332323462336435376664386134646562643836363834313237373631353731 -35653535663864643266313332356635363262363533663232656531373130336539633066376139 -32316665393831663035623962656364363831333563366135636164346335383738363336663566 -63356532643563393939383635386462663561386434323939303431653438653131363538383034 -33623933333464363032656636643033326162626163353633343062633966343332383138363963 -36383831663562616533316436366566323061386535343538393861383462333166343562316633 -30623665623035393537393965626363323132656433313339396233356666346634316332616336 -37363635363330323230373332326565343530653335383437373230366563366237633665626331 -66623336626230663361636439316337393865383035326136653264666438666566646132353036 -38616264313833316536623238633339373466613866626366383835656638623863323838653030 -63303938376164653966356435386333363731656666313234663535666165646233313137343563 -36386437393139656438333262383437656666343831313239323961373637653163643664356565 -62346431393133656530316262303763646165643336396661666431383436323562336137653031 -62626538373839613734396366653065306534636630346338316237616161613037616364356431 -38643965376133616161336633383664326230383435363334353137303162663738313331346238 -39356161616533616134356231323530306338333162343363353531303263636632613036386638 -63633136386232306234323936303563646466313935326631396565383432386130656638616266 -32383334363237336539396665336366643764633131643663643137376438323666326435626461 -64346633636431393137633537306431646564386565303933636434386462346630626537346438 -32623333666133303061646564366366326665363163396262633164323631636337346130303239 -38373936663337356134666132303165393365663763396362623434633737373538653566646134 -62316164396438303532616266313062326666633130656338653139376634306664633031333037 -31636166306565353334633435656233336233363664306264626237623366336161303134353433 -63383739666462623336386537346662633666626466393039653439346436653937633537396436 -35393339383066326630353066623132333034656539363561346462626265363263303535343961 -39316461616630326539613731303039613736393633373338646266323938326162373831346336 -30656130343463366534323030646238313465306266383034623065636665623366333063383736 -38373063393837306462303564643962373334343139626338623935336435643730646532633630 -66363730386636633639346463363365343239373265303738353732653633653437636130636664 -39386365353334653765343335303263363461313965383664326563333734626533376436626530 -63353163323637303730353564383733653365613635353764333266393532653663326533646132 -39313763373735323835626437306435373238653432393936643165663663656665316132653330 -35383038353532656434366336346235363563636264303734633138323963396562306232646236 -38366536306561653937336333373434336164663336613839353439356435333833396363636437 -38323934613735643363656233333037336465336564313966623063376566663030303230323262 -65643564666534326234306164343365383632333061316238623565353538313538396364313337 -35336230313339643736653238386231343661623337306236383665356632366236356335323530 -66373831616239376231636361333430343433303233393066323865663434643433303832373262 -36366534646161323130393931626362626139663139643263366639656531613436313533363130 -65643736363833333939613566663339623964333262643863333030623138633464386238613934 -31323639333234336264313663376465323737353766643839303665313737336534386665363034 -30326439666433373232306136306365343764643434306561343339353132346430646436343362 -32333765616262363930353435616563333736313533653339656231316230346166363335356638 -37333561366339613136306438306130343230663732333862663838396463623661303961336433 -65646332373139303462303633346432366530643130366133363937653739653036366136373434 -343864373734666431326239373866313734 +65343264303166346334396163363362326334626531336363363766326135393462373564313539 +6633346163343664393232363965383334303563396236360a316265623239333431636133306663 +65623237393564323936303936633036343137646637633963396166313462626165616463616665 +3235373562626238640a636163623535376565373835653831666361636563616230353639626131 +38393530363461346334356437613934306330666563326661343465653362313038346535386337 +62623964633764343964653061373137366539326433396231396339313231633632353465323139 +33333866383834376165303964353639386234646535626333363932333764646137663036303530 +32326635343631623535323830333934376466323338353234323464666435383436633139343037 +62666333383231356163383530353339336230373431616665333638633234656231393166303639 +32386134386264613839313564316164336461373738646630636630363731643066633132336636 +64663532663661653539653431353763303436323234383864353064333631643435633733366435 +32656332626633313134623361343965643962663134613363626265353865643738363738316232 +62356136623166303038653830393138646637636335366362323239316333616331383765663861 +33643331323766646463663862396232306233343134326436326235336165616662383636616539 +39373962336264636363303734343666646534343733666632626338393936393032626130643233 +30633963656166343834396431323233363036353165623934336532333662323932336630636235 +63326631366234383732316464383364316337393034303433646365343763633135336336376161 +33326436303863326666616234396438623864333538313363623261353962336430316132343830 +36353761326133666166393930343233643861366138363930623362336566386230316236353365 +65663632383436333536636161393133316334376262653330303238636431353966646666663665 +30386137623066343135616534323336313162643066636330323934646162303362373838313532 +61386163356537613334616537656134373835326265393765303436396233383239393365336136 +64623130383538306331323939386634316663663263306362363830383930346232313965613035 +33353235663230663635316136666335343233646539613832643431393134303865616232636435 +66366437353863336235646438353431616465396365373562636461623938303663326461656134 +66353735343237363164326236663363376333316163353462626234393861663535323463623233 +66623934336662323830616362653035353563343564636431383837643961303637366131633132 +33376133376465623030643935643737636339333236316566636663323934626265633863343963 +38626563323166343264643465626264393838643330393739386330656461656561306539653232 +63393838626233386531353063366131366237633162393537363963633361386238643232336230 +36363062386262303364303736373061316265373730366437326166336163386335623665323234 +35383865646466363534383833653764303833323230613863313735393264336132303934633465 +65616138636134613361316539343739353735666538653438333264623034306633663634306438 +33643165353534623039626536636433373963383866303535333931633633393138663464336562 +36643432366233323235613139626236346135633237613265343933646434373939396265626366 +34313835346463303132326537323166363032306133623662343736393435383330356433306138 +65653931613832313832323936353638303937613863646430633266306330656135386533306462 +30663366396566393134653663356531316639303635343236666333363637636433336533356663 +35346261363134326165316463626439306130653165636134616434663736616666643963336464 +61643834613164336234386237393333636234306162306563363430646262363963366666653666 +64323036323731626236343361613566643565666138386631633462613836613766626562353934 +32363936626466346464356131653232326330386239666638626333346238623964383666393961 +38393264663636353161663636393733396332333838333962393439346533353332653362356230 +62613039376539636363 diff --git a/ansible/infra_secrets.yml b/ansible/infra_secrets.yml index 4803d86..798e2d8 100644 --- a/ansible/infra_secrets.yml +++ b/ansible/infra_secrets.yml @@ -1,60 +1,45 @@ $ANSIBLE_VAULT;1.1;AES256 -34616531626537376463326332306434383163393761363536633133363161373631376437653234 -3831656536313531343433376264643261396634633965370a303931336236323766313065636535 -31343338616364386132623232373266663665353563666535383232666262663062323864303932 -3439373734316639650a633732333064633335383833663261326464336538636666323063646331 -66653464636534663935616461636132613162303530663237376661343338396133323431623561 -30303563303465633231306339326561313533333536376433393130376634303932643339656336 -30616130343733626162366561393939393766623138616537393032376665396139333561623830 -33623130336235633034666561383165616439316334323664623661343438663733373833306335 -30396165313731636435636263353133326135346236323030653734353831626238666663656364 -62386164363361373464383730333733626333333736306535336635613634646535383237326332 -37636532663933663739303634333235343137333363316430643263326231613635643633636437 -30336462633233396430633939616661643261666136656363363461343230653738613166346434 -63376363666462346539343063343562623962303130616535306439653134633630353963373337 -64313239353162363862643432666438383330636638333464323731643163643236313535313030 -65656231383738356537326661663163376634613031396436646633376139313237343534653065 -64323532346133313265346334353530393633396339316330366536646565353836396662303866 -65373030393463306664623235626437613965303837313365373632643935656630303936373837 -64663539623764656636613263346638376665626262663430333231633735653563643864343835 -62303637383332653366383066326536316336306539623230353066343739316430356437313365 -33323031613130643832636133636263303565623330306135333762363036633737343933623437 -33633436363062643436376632336235666330656265316333323361316430656631383734343238 -65616366666536363866343361666563643336306532666332643230656264303565353032393932 -36613738326239376161383064646634623639356439663965323237303361343838373737636235 -39656465333063633136643463356564636537326339653033633063366265636138656466646264 -32643961343863656464353439346531633036613138353333353631626665396239326465303632 -39336664333038636435366333623336353963323535316436646136313739373537376537353735 -66333265633232346536313933333836643439656666636236626266323333383935383565616139 -38626133356364613562393664643563346539366364636332613162663337356232333735393431 -30343635393462356431646336636663333735343164616261343836363366616162613563613066 -66333034613234626431356536323832393466363135653231336238356334353638613838626430 -63636630313533363563376161663637353362383431663130646666646433353864373736613662 -62343666633862643839666532656132653939663437373164393536653739376265303130353636 -62616538326330333136626238306133363634326365366637363961333635356133613564373131 -64626139656633663065353862343135393233333231386166346532656534623361623061336434 -37616565626134333863376532393861376132323434656433613731613834633963386635353336 -39653263346637303836663763653264396139383363616461393333333362616332663863363834 -61613832326466646133626337663364333566326633313664613236346135653332373035313034 -62356530313065393737393634313166313039616635363365633031336434396463326462316236 -62353838636533303664663132353838636632353733316639643964666139383539333761663531 -37366530373165653063303032383461326535613336343635626538373635356266396666346133 -61383434323565306438356634646338666232363238393932353365363461376130323363363236 -34643862653937353461383265663933646264373365623465313666643662633334366437646434 -34343332613832366235663563353435323738356339383338333361383561336366376238393237 -38656265663164376666616366623062393530366261623361383365376235643035353838313235 -32396430376139313430316439333539343266393965353030636638656661316137313437663832 -64366661323865383262323036613230316233666566386133363633303931323464663238323861 -62333132346165353830653062353763306232366563323430383933653163373562326536393362 -66353931376262383931613931303034356537363137366235643430323465623162363031373034 -64653563666533363062333831336463376530306639616631303266386136666233653638653732 -31313537393838333730303235363661326333646531653935633935373362303732653565656438 -36626330623063373832333739643666396135653838643164376264393863646433353036643731 -34623636623366353466333237373265323936643134613230326565303663306431373634356334 -37613732393262626135306632623336613364323632316636623037656538313236666562633438 -37306361333130376538366232393637646263656239303431623963626265386634313735373162 -64393733303132383464386330626135336237303563326238633437346164333738646431333730 -62616533363638643261306565373835303832313539656564386132393863373039653938616261 -36343031356361336563316230343433633033353130373933313361326164633765316433616162 -62356338393634653330623933313433343630386264396337373532376335316362333164363963 -363663666365336564663232363462613162 +65343264303166346334396163363362326334626531336363363766326135393462373564313539 +6633346163343664393232363965383334303563396236360a316265623239333431636133306663 +65623237393564323936303936633036343137646637633963396166313462626165616463616665 +3235373562626238640a636163623535376565373835653831666361636563616230353639626131 +38393530363461346334356437613934306330666563326661343465653362313038346535386337 +62623964633764343964653061373137366539326433396231396339313231633632353465323139 +33333866383834376165303964353639386234646535626333363932333764646137663036303530 +32326635343631623535323830333934376466323338353234323464666435383436633139343037 +62666333383231356163383530353339336230373431616665333638633234656231393166303639 +32386134386264613839313564316164336461373738646630636630363731643066633132336636 +64663532663661653539653431353763303436323234383864353064333631643435633733366435 +32656332626633313134623361343965643962663134613363626265353865643738363738316232 +62356136623166303038653830393138646637636335366362323239316333616331383765663861 +33643331323766646463663862396232306233343134326436326235336165616662383636616539 +39373962336264636363303734343666646534343733666632626338393936393032626130643233 +30633963656166343834396431323233363036353165623934336532333662323932336630636235 +63326631366234383732316464383364316337393034303433646365343763633135336336376161 +33326436303863326666616234396438623864333538313363623261353962336430316132343830 +36353761326133666166393930343233643861366138363930623362336566386230316236353365 +65663632383436333536636161393133316334376262653330303238636431353966646666663665 +30386137623066343135616534323336313162643066636330323934646162303362373838313532 +61386163356537613334616537656134373835326265393765303436396233383239393365336136 +64623130383538306331323939386634316663663263306362363830383930346232313965613035 +33353235663230663635316136666335343233646539613832643431393134303865616232636435 +66366437353863336235646438353431616465396365373562636461623938303663326461656134 +66353735343237363164326236663363376333316163353462626234393861663535323463623233 +66623934336662323830616362653035353563343564636431383837643961303637366131633132 +33376133376465623030643935643737636339333236316566636663323934626265633863343963 +38626563323166343264643465626264393838643330393739386330656461656561306539653232 +63393838626233386531353063366131366237633162393537363963633361386238643232336230 +36363062386262303364303736373061316265373730366437326166336163386335623665323234 +35383865646466363534383833653764303833323230613863313735393264336132303934633465 +65616138636134613361316539343739353735666538653438333264623034306633663634306438 +33643165353534623039626536636433373963383866303535333931633633393138663464336562 +36643432366233323235613139626236346135633237613265343933646434373939396265626366 +34313835346463303132326537323166363032306133623662343736393435383330356433306138 +65653931613832313832323936353638303937613863646430633266306330656135386533306462 +30663366396566393134653663356531316639303635343236666333363637636433336533356663 +35346261363134326165316463626439306130653165636134616434663736616666643963336464 +61643834613164336234386237393333636234306162306563363430646262363963366666653666 +64323036323731626236343361613566643565666138386631633462613836613766626562353934 +32363936626466346464356131653232326330386239666638626333346238623964383666393961 +38393264663636353161663636393733396332333838333962393439346533353332653362356230 +62613039376539636363 diff --git a/ansible/inventory.ini b/ansible/inventory.ini index b9b2906..a061637 100644 --- a/ansible/inventory.ini +++ b/ansible/inventory.ini @@ -2,20 +2,19 @@ vipy ansible_host=167.172.107.33 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua watchtower ansible_host=164.92.239.72 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua spacey ansible_host=64.227.112.128 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua - [nodito_host] nodito ansible_host=192.168.1.139 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -# Requires the tailnet to be up on the control node. + [nodito_vms] -knots_box_local ansible_host=knots-box lan_ip=192.168.1.135 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -fulcrum_box_local ansible_host=fulcrum-box lan_ip=192.168.1.140 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -mempool_box_local ansible_host=mempool-box lan_ip=192.168.1.142 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -memos_box_local ansible_host=memos-box lan_ip=192.168.1.145 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -forgejo_runner_local ansible_host=forgejo-runner-box lan_ip=192.168.1.132 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -arbret_staging_local ansible_host=arbret-staging-box lan_ip=192.168.1.147 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -small_backups_local ansible_host=small-backups-box lan_ip=192.168.1.131 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua -nonkeiwaisi_local ansible_host=nonkeiwaisi-box lan_ip=192.168.1.151 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +knots_box_local ansible_host=192.168.1.135 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +fulcrum_box_local ansible_host=192.168.1.142 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +mempool_box_local ansible_host=192.168.1.140 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +memos_box_local ansible_host=192.168.1.130 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +forgejo_runner_local ansible_host=192.168.1.147 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +arbret_staging_local ansible_host=192.168.1.142 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +small_backups_local ansible_host=192.168.1.148 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua +nonkeiwaisi_local ansible_host=192.168.1.151 ansible_user=counterweight ansible_port=22 ansible_ssh_private_key_file=~/.ssh/counterganzua # Local connection to laptop: this assumes you're running ansible commands from your personal laptop [lapy] @@ -59,13 +58,4 @@ localhost [managed:children] vps nodito_host -nodito_vms - -# Hosts that run Caddy and therefore have /etc/caddy/sites-enabled. -[caddy:children] -edge -monitoring -vpn_control - -[backup_store] -small_backups_local +nodito_vms \ No newline at end of file diff --git a/ansible/roles/caddy_site/README.md b/ansible/roles/caddy_site/README.md deleted file mode 100644 index c8e32af..0000000 --- a/ansible/roles/caddy_site/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# `caddy_site` - -Writes one Caddy site file into `{{ caddy_sites_dir }}`, makes sure the main -Caddyfile imports that directory, validates the result, and reloads Caddy once. - -Replaces the four-task block that was copy-pasted into 10 playbooks. - -Runs on any host in the `[caddy]` group — `edge` (vipy), `monitoring` -(watchtower) and `vpn_control` (spacey). - -## Usage - -```yaml -- ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: forgejo # -> forgejo.conf - caddy_site_domain: "{{ forgejo_domain }}" - caddy_site_upstream: "localhost:{{ forgejo_port }}" -``` - -Use `include_role`, not a `roles:` block, so the call stays in task order next -to the tasks it depends on. Variables passed this way are scoped to the include -and do not leak into later calls — so **every call must pass everything it -needs**; nothing carries over. - -## Shapes - -Pick exactly one of `caddy_site_upstream`, `caddy_site_root`, `caddy_site_body`. - -| Want | Set | -|---|---| -| `reverse_proxy host:port` | `caddy_site_upstream` | -| static `root *` + `file_server` | `caddy_site_root` | -| anything else | `caddy_site_body` (raw, indented 4 for you) | - -`caddy_site_upstream` accepts two modifiers, which add a block to the -`reverse_proxy`: - -- `caddy_site_headers_up: {"X-Forwarded-Host": "..."}` -- `caddy_site_resolvers: "100.100.100.100"` — Tailscale MagicDNS - -and `caddy_site_basic_auth` wraps the site in a `basic_auth` block. - -## `caddy_site_basic_auth` is a LIST, not a dict - -```yaml -caddy_site_basic_auth: - - user: "{{ datum_dashboard_username }}" - hash: "{{ datum_dashboard_password_hash }}" -``` - -**Ansible does not template dictionary keys.** With `{ "{{ user }}": "hash" }` -the value is rendered and the key is not, so the literal string -`{{ datum_dashboard_username }}` lands in the config file. Found while building -this role; the `assert` refuses a mapping so it cannot happen again. - -## Secrets and `--diff` - -Rendered site files can carry credentials — `datum-gateway.conf` holds a bcrypt -hash — and `--diff` prints rendered content. The template task therefore sets -`diff: "{{ caddy_site_reveal | bool }}"`, default `false`, so `--diff` runs are -safe everywhere. Pass `-e caddy_site_reveal=true` to see what moved on a site -you know is not secret. - -## Validation - -`validate: "caddy validate --adapter caddyfile --config %s"` runs against the -rendered temp file before it is moved into place. Verified on vipy that a single -site fragment validates cleanly (rc=0, `Valid configuration`) and that a -malformed one is rejected (rc=1, with the syntax error and line number). A -failed validate leaves the live file untouched, so a broken config can no longer -reach a running Caddy. - -What it cannot catch is a conflict with the global `/etc/caddy/Caddyfile`. - -## The reload is a handler - -`Reload caddy` fires **once, at the end of the play**, however many sites -notified it. The code this replaced ran `command: systemctl reload caddy` -immediately, mid-play. If a later task in the same play needs the new config to -be live, flush first: - -```yaml -- ansible.builtin.meta: flush_handlers -``` - -## Known intentional difference - -The `resolvers` block is commented `# Use Tailscale MagicDNS to resolve the -upstream hostname` in every case. `datum-gateway` previously said `# Resolve via -Tailscale MagicDNS`. Migrating it therefore rewrites one comment line, which -Caddy ignores. Every other site renders byte-identical to what its playbook -produced. - -## Sites on the hosts that this role does NOT manage - -Four vhosts exist in `/etc/caddy/sites-enabled/` that no playbook writes. They -were made by hand. The role only ever writes the one file it is told to, so it -leaves them alone — but nothing in the repo records them, and that is why they -are listed here. Checked 2026-09-11: - -| File | Host | Serves | State | -|---|---|---|---| -| `uptime-kuma.conf` | watchtower | `localhost:3001` | **HTTP 302 — still live**, see below | -| `arbretstaging.conf` | vipy | `arbret-staging-box:80` via MagicDNS | HTTP 200 | -| `bitcoininfra.conf` | vipy | static `file_server` from `/var/www/bitcoin-services-home` | HTTP 200 | -| `scriberr.conf` | vipy | `scriberr-box:8080` via MagicDNS | HTTP 502 — upstream down | - -**`uptime-kuma.conf` must not be deleted as dead config.** Uptime Kuma was -"decommissioned" in the repo — its playbooks archived and its credentials pulled -from the vault — but the container is **still running** on watchtower -(`louislam/uptime-kuma:latest`, created 2026-02-07, `restart=unless-stopped`) -and is still reachable at its public subdomain. Only the Ansible code was -retired; the service was not. See `archive/uptime_kuma/`. - -`scriberr` returning 502 is the one that looks like genuine rot: it proxies to a -`scriberr-box` that is not answering, and `scriberr-box` is not in the inventory. diff --git a/ansible/roles/caddy_site/defaults/main.yml b/ansible/roles/caddy_site/defaults/main.yml deleted file mode 100644 index cafa3dc..0000000 --- a/ansible/roles/caddy_site/defaults/main.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -# Required -caddy_site_name: "" # file basename -> .conf -caddy_site_domain: "" # site address line; may hold several, comma separated - -# Pick exactly one shape -caddy_site_upstream: "" # "localhost:3000" -> reverse_proxy -caddy_site_root: "" # filesystem path -> root * + file_server -caddy_site_body: "" # raw escape hatch for one-off sites; wins over both - -# reverse_proxy modifiers -caddy_site_resolvers: "" # "100.100.100.100" for Tailscale MagicDNS -caddy_site_headers_up: {} # {"X-Forwarded-Host": "wallet.example.com"} -# A LIST, not a dict: Ansible does not template dict *keys*, so a Jinja -# expression for the username silently passes through as literal text. -caddy_site_basic_auth: [] # [{user: "{{ x_user }}", hash: "{{ x_hash }}"}] - -# Placement. caddy_sites_dir comes from services_config.yml; this is the fallback. -caddy_sites_dir: /etc/caddy/sites-enabled - -# Rendered site files can carry credentials (basic_auth hashes), so --diff is -# suppressed by default. Pass -e caddy_site_reveal=true to see what moved. -caddy_site_reveal: false diff --git a/ansible/roles/caddy_site/handlers/main.yml b/ansible/roles/caddy_site/handlers/main.yml deleted file mode 100644 index 4bfa7ff..0000000 --- a/ansible/roles/caddy_site/handlers/main.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -# Fires once at the end of the play, however many sites notified it. -# Anything later in the same play that needs the new config live must be -# preceded by `- ansible.builtin.meta: flush_handlers`. -# become is explicit because handlers do not inherit it from the task that -# notified them. headscale's play runs become: no and elevates per task, so -# without this the reload would run unprivileged and fail. -- name: Reload caddy - become: true - ansible.builtin.systemd: - name: caddy - state: reloaded diff --git a/ansible/roles/caddy_site/tasks/main.yml b/ansible/roles/caddy_site/tasks/main.yml deleted file mode 100644 index 5ad3976..0000000 --- a/ansible/roles/caddy_site/tasks/main.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -- name: Assert caddy_site parameters are sane - ansible.builtin.assert: - that: - - caddy_site_name | length > 0 - - caddy_site_domain | length > 0 - - (caddy_site_upstream | length > 0) or (caddy_site_root | length > 0) or (caddy_site_body | length > 0) - - caddy_site_basic_auth is not mapping - fail_msg: >- - caddy_site: '{{ caddy_site_name | default("") }}' needs a name, a domain and - one of caddy_site_upstream / caddy_site_root / caddy_site_body. - caddy_site_basic_auth must be a LIST of {user, hash} — Ansible does not template dict keys. - quiet: true - -- name: Ensure Caddy sites-enabled directory exists - ansible.builtin.file: - path: "{{ caddy_sites_dir }}" - state: directory - owner: root - group: root - mode: '0755' - -- name: Ensure Caddyfile imports sites-enabled - ansible.builtin.lineinfile: - path: /etc/caddy/Caddyfile - line: 'import sites-enabled/*' - insertafter: EOF - state: present - create: yes - mode: '0644' - backup: yes - -# `validate` runs `caddy validate` against the rendered temp file before it is -# moved into place: verified on vipy that a single site fragment validates -# cleanly (rc=0, "Valid configuration") and that a malformed one is rejected -# (rc=1). A failed validate leaves the live file untouched. -- name: "Write Caddy site '{{ caddy_site_name }}'" - ansible.builtin.template: - src: site.conf.j2 - dest: "{{ caddy_sites_dir }}/{{ caddy_site_name }}.conf" - owner: root - group: root - mode: '0644' - validate: "caddy validate --adapter caddyfile --config %s" - diff: "{{ caddy_site_reveal | bool }}" - notify: Reload caddy diff --git a/ansible/roles/caddy_site/templates/site.conf.j2 b/ansible/roles/caddy_site/templates/site.conf.j2 deleted file mode 100644 index 5d34c01..0000000 --- a/ansible/roles/caddy_site/templates/site.conf.j2 +++ /dev/null @@ -1,34 +0,0 @@ -{{ caddy_site_domain }} { -{% if caddy_site_body %} -{{ caddy_site_body | trim | indent(4, first=True) }} -{% else %} -{% if caddy_site_basic_auth %} - basic_auth { -{% for cred in caddy_site_basic_auth %} - {{ cred.user }} {{ cred.hash }} -{% endfor %} - } -{% endif %} -{% if caddy_site_root %} - root * {{ caddy_site_root }} - file_server -{% endif %} -{% if caddy_site_upstream %} -{% if caddy_site_headers_up or caddy_site_resolvers %} - reverse_proxy {{ caddy_site_upstream }} { -{% for key, value in caddy_site_headers_up.items() %} - header_up {{ key }} {{ value }} -{% endfor %} -{% if caddy_site_resolvers %} - # Use Tailscale MagicDNS to resolve the upstream hostname - transport http { - resolvers {{ caddy_site_resolvers }} - } -{% endif %} - } -{% else %} - reverse_proxy {{ caddy_site_upstream }} -{% endif %} -{% endif %} -{% endif %} -} diff --git a/ansible/roles/socket_proxy/README.md b/ansible/roles/socket_proxy/README.md deleted file mode 100644 index cebb95f..0000000 --- a/ansible/roles/socket_proxy/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# `socket_proxy` - -Exposes a service running on a private Tailscale host through a public TCP port -on an edge machine, using `systemd-socket-proxyd`. Writes a `.socket` and a -`.service` unit, enables the socket, opens the UFW port, and checks the upstream -is reachable. - -## Usage - -```yaml -- ansible.builtin.include_role: - name: socket_proxy - vars: - socket_proxy_name: fulcrum-ssl # -> fulcrum-ssl-proxy.{socket,service} - socket_proxy_description: "Fulcrum SSL" # -> "Fulcrum SSL Proxy Socket" - socket_proxy_listen_port: "{{ fulcrum_ssl_port }}" - socket_proxy_upstream_host: "{{ fulcrum_tailscale_hostname }}" -``` - -`socket_proxy_upstream_port` defaults to `socket_proxy_listen_port`, which is -what all three current callers want. - -## Optional unit settings - -These exist because the **live** `bitcoin-p2p-proxy` units on vipy carried -settings the playbook never wrote. Somebody added them by hand, so running -`deploy_bitcoin_knots_playbook.yml` would have silently removed them: - -| Variable | Emits | Why it matters | -|---|---|---| -| `socket_proxy_free_bind` | `FreeBind=true` in `[Socket]` | Lets the socket bind to an address that is not up yet. Without it the socket can fail to start on boot. | -| `socket_proxy_documentation` | `Documentation=` in both units | Cosmetic. | -| `socket_proxy_timeout_stop_sec` | `TimeoutStopSec=` | Bounds how long a stop can hang. | -| `socket_proxy_log_to_journal` | `StandardOutput=journal` + `StandardError=journal` | Cosmetic on modern systemd, which defaults to the journal anyway. | - -Only `bitcoin-p2p` passes any of them. - -## `socket_proxy_ufw_comment` - -Defaults to `" public access"`, which reproduces the live rule -comment for bitcoin-p2p and fulcrum-ssl. **datum-stratum must pass it -explicitly** — its live comment is `DATUM Gateway Stratum public access` while -the derived default would be `DATUM Stratum public access`, and UFW treats the -comment as part of the rule, so the mismatch rewrites the rule on every run. - -## The upstream check never fails the play - -`wait_for` on the upstream carries `failed_when: false`. The proxy is correctly -configured whether or not the backend happens to be up, and this is the one task -that depends on another machine. The original plays used `ignore_errors: yes`, -which prints a red "ignoring" line; `failed_when: false` is the quieter -equivalent. - -## Restarts - -The handler restarts the `.socket`, not the `.service` — that is what picks up a -changed unit; the service is started by the socket on the next connection. - -**Restarting a socket drops connections that are currently open through it.** -For bitcoin-p2p that means peers reconnect; for datum-stratum it means a mining -client has to reconnect and may lose in-flight shares. The handler only fires -when a unit file actually changes. diff --git a/ansible/roles/socket_proxy/defaults/main.yml b/ansible/roles/socket_proxy/defaults/main.yml deleted file mode 100644 index 6a512ea..0000000 --- a/ansible/roles/socket_proxy/defaults/main.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -# Required -socket_proxy_name: "" # "bitcoin-p2p" -> bitcoin-p2p-proxy.{socket,service} -socket_proxy_description: "" # "Bitcoin P2P" -> "Bitcoin P2P Proxy Socket" -socket_proxy_listen_port: 0 # public port on the edge host -socket_proxy_upstream_host: "" # Tailscale hostname, e.g. "knots-box" - -# Optional -socket_proxy_upstream_port: "" # defaults to socket_proxy_listen_port -socket_proxy_documentation: "" # Documentation= in both units -socket_proxy_free_bind: false # FreeBind=true: bind before the address is up -socket_proxy_timeout_stop_sec: "" # TimeoutStopSec= -socket_proxy_log_to_journal: false # StandardOutput/StandardError=journal - -# Firewall -socket_proxy_ufw_proto: tcp -socket_proxy_ufw_comment: "" # defaults to " public access" diff --git a/ansible/roles/socket_proxy/handlers/main.yml b/ansible/roles/socket_proxy/handlers/main.yml deleted file mode 100644 index 7fdf421..0000000 --- a/ansible/roles/socket_proxy/handlers/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# Restarting the .socket is what picks up a changed unit; the .service is -# started by the socket on the next connection. -- name: Restart socket proxy - ansible.builtin.systemd: - name: "{{ socket_proxy_name }}-proxy.socket" - state: restarted - daemon_reload: yes diff --git a/ansible/roles/socket_proxy/tasks/main.yml b/ansible/roles/socket_proxy/tasks/main.yml deleted file mode 100644 index 4315555..0000000 --- a/ansible/roles/socket_proxy/tasks/main.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -- name: Assert socket_proxy parameters are sane - ansible.builtin.assert: - that: - - socket_proxy_name | length > 0 - - socket_proxy_description | length > 0 - - socket_proxy_listen_port | int > 0 - - socket_proxy_upstream_host | length > 0 - fail_msg: >- - socket_proxy: '{{ socket_proxy_name | default("") }}' needs a name, - a description, a listen port and an upstream host. - quiet: true - -- name: "Create the {{ socket_proxy_name }}-proxy socket unit" - ansible.builtin.template: - src: proxy.socket.j2 - dest: "/etc/systemd/system/{{ socket_proxy_name }}-proxy.socket" - owner: root - group: root - mode: '0644' - notify: Restart socket proxy - -- name: "Create the {{ socket_proxy_name }}-proxy service unit" - ansible.builtin.template: - src: proxy.service.j2 - dest: "/etc/systemd/system/{{ socket_proxy_name }}-proxy.service" - owner: root - group: root - mode: '0644' - notify: Restart socket proxy - -- name: "Enable and start the {{ socket_proxy_name }}-proxy socket" - ansible.builtin.systemd: - name: "{{ socket_proxy_name }}-proxy.socket" - enabled: yes - state: started - daemon_reload: yes - -- name: "Allow the {{ socket_proxy_name }} port through UFW" - community.general.ufw: - rule: allow - port: "{{ socket_proxy_listen_port | string }}" - proto: "{{ socket_proxy_ufw_proto }}" - comment: "{{ socket_proxy_ufw_comment | default(socket_proxy_description ~ ' public access', true) }}" - -# Reachability of the upstream over Tailscale. Deliberately non-fatal: the proxy -# is still correctly configured if the backend happens to be down, and this is -# the one check that depends on another machine being up. -- name: "Verify {{ socket_proxy_upstream_host }} is reachable over Tailscale" - ansible.builtin.wait_for: - host: "{{ socket_proxy_upstream_host }}" - port: "{{ socket_proxy_upstream_port | default(socket_proxy_listen_port, true) }}" - timeout: 10 - failed_when: false diff --git a/ansible/roles/socket_proxy/templates/proxy.service.j2 b/ansible/roles/socket_proxy/templates/proxy.service.j2 deleted file mode 100644 index e59d251..0000000 --- a/ansible/roles/socket_proxy/templates/proxy.service.j2 +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description={{ socket_proxy_description }} Proxy to {{ socket_proxy_upstream_host }} -{% if socket_proxy_documentation %} -Documentation={{ socket_proxy_documentation }} -{% endif %} -Requires={{ socket_proxy_name }}-proxy.socket -After=network.target - -[Service] -Type=notify -ExecStart=/lib/systemd/systemd-socket-proxyd {{ socket_proxy_upstream_host }}:{{ socket_proxy_upstream_port | default(socket_proxy_listen_port, true) }} -{% if socket_proxy_timeout_stop_sec %} -TimeoutStopSec={{ socket_proxy_timeout_stop_sec }} -{% endif %} -{% if socket_proxy_log_to_journal %} -StandardOutput=journal -StandardError=journal -{% endif %} diff --git a/ansible/roles/socket_proxy/templates/proxy.socket.j2 b/ansible/roles/socket_proxy/templates/proxy.socket.j2 deleted file mode 100644 index 0dc1721..0000000 --- a/ansible/roles/socket_proxy/templates/proxy.socket.j2 +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description={{ socket_proxy_description }} Proxy Socket -{% if socket_proxy_documentation %} -Documentation={{ socket_proxy_documentation }} -{% endif %} - -[Socket] -ListenStream={{ socket_proxy_listen_port }} -{% if socket_proxy_free_bind %} -FreeBind=true -{% endif %} - -[Install] -WantedBy=sockets.target diff --git a/ansible/services/bitcoin-knots/deploy_bitcoin_knots_playbook.yml b/ansible/services/bitcoin-knots/deploy_bitcoin_knots_playbook.yml index 1ae5823..fcc02b4 100644 --- a/ansible/services/bitcoin-knots/deploy_bitcoin_knots_playbook.yml +++ b/ansible/services/bitcoin-knots/deploy_bitcoin_knots_playbook.yml @@ -740,7 +740,7 @@ state: restarted -- name: Setup public Bitcoin P2P forwarding on the edge host +- name: Setup public Bitcoin P2P forwarding on vipy via systemd-socket-proxyd hosts: edge become: yes vars_files: @@ -753,21 +753,62 @@ uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" tasks: - - name: Expose Bitcoin P2P through a socket proxy - ansible.builtin.include_role: - name: socket_proxy - vars: - socket_proxy_name: bitcoin-p2p - socket_proxy_description: "Bitcoin P2P" - socket_proxy_listen_port: "{{ bitcoin_p2p_port }}" - socket_proxy_upstream_host: "{{ bitcoin_tailscale_hostname }}" - # These four were added by hand on vipy and were NOT in this playbook; - # writing the unit without them would have dropped FreeBind, which lets - # the socket bind before the address is up. - socket_proxy_documentation: "https://github.com/bitcoin/bitcoin" - socket_proxy_free_bind: true - socket_proxy_timeout_stop_sec: 5 - socket_proxy_log_to_journal: true + - name: Create Bitcoin P2P proxy socket unit + copy: + dest: /etc/systemd/system/bitcoin-p2p-proxy.socket + content: | + [Unit] + Description=Bitcoin P2P Proxy Socket + + [Socket] + ListenStream={{ bitcoin_p2p_port }} + + [Install] + WantedBy=sockets.target + owner: root + group: root + mode: '0644' + notify: Restart bitcoin-p2p-proxy socket + + - name: Create Bitcoin P2P proxy service unit + copy: + dest: /etc/systemd/system/bitcoin-p2p-proxy.service + content: | + [Unit] + Description=Bitcoin P2P Proxy to {{ bitcoin_tailscale_hostname }} + Requires=bitcoin-p2p-proxy.socket + After=network.target + + [Service] + Type=notify + ExecStart=/lib/systemd/systemd-socket-proxyd {{ bitcoin_tailscale_hostname }}:{{ bitcoin_p2p_port }} + owner: root + group: root + mode: '0644' + + - name: Reload systemd daemon + systemd: + daemon_reload: yes + + - name: Enable and start Bitcoin P2P proxy socket + systemd: + name: bitcoin-p2p-proxy.socket + enabled: yes + state: started + + - name: Allow Bitcoin P2P port through UFW + ufw: + rule: allow + port: "{{ bitcoin_p2p_port | string }}" + proto: tcp + comment: "Bitcoin P2P public access" + + - name: Verify connectivity to knots-box via Tailscale + wait_for: + host: "{{ bitcoin_tailscale_hostname }}" + port: "{{ bitcoin_p2p_port }}" + timeout: 10 + ignore_errors: yes - name: Display public endpoint when: uptime_kuma_enabled | default(false) @@ -890,3 +931,8 @@ - /tmp/setup_bitcoin_p2p_tcp_monitor.py - /tmp/ansible_bitcoin_p2p_config.yml + handlers: + - name: Restart bitcoin-p2p-proxy socket + systemd: + name: bitcoin-p2p-proxy.socket + state: restarted diff --git a/ansible/services/caddy_playbook.yml b/ansible/services/caddy_playbook.yml index 29e74b2..de98c8f 100644 --- a/ansible/services/caddy_playbook.yml +++ b/ansible/services/caddy_playbook.yml @@ -1,5 +1,5 @@ - name: Install and configure Caddy on Debian 12 - hosts: caddy + hosts: vps become: yes tasks: diff --git a/ansible/services/datum-gateway/deploy_datum_gateway_playbook.yml b/ansible/services/datum-gateway/deploy_datum_gateway_playbook.yml index 0c53e9d..3fa36d4 100644 --- a/ansible/services/datum-gateway/deploy_datum_gateway_playbook.yml +++ b/ansible/services/datum-gateway/deploy_datum_gateway_playbook.yml @@ -487,7 +487,7 @@ # =========================================== # Caddy Reverse Proxy for DATUM Dashboard (on vipy) # =========================================== -- name: Configure Caddy reverse proxy for DATUM Gateway dashboard on the edge host +- name: Configure Caddy reverse proxy for DATUM Gateway dashboard on vipy hosts: edge become: yes vars_files: @@ -498,29 +498,64 @@ vars: datum_gateway_subdomain: "{{ subdomains.datum_gateway }}" datum_gateway_domain: "{{ datum_gateway_subdomain }}.{{ root_domain }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" tasks: - - name: Publish the DATUM Gateway dashboard through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: datum-gateway - caddy_site_domain: "{{ datum_gateway_domain }}" - caddy_site_upstream: "knots-box:{{ datum_gateway_api_port }}" - caddy_site_resolvers: "100.100.100.100" - caddy_site_basic_auth: - - user: "{{ datum_dashboard_username }}" - hash: "{{ datum_dashboard_password_hash }}" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: "0755" - # The role validates the site fragment on its own. This re-validates the - # whole assembled Caddyfile, which is the only thing that catches a - # conflict between this site and another. Kept from the hand-rolled - # version; the other nine services never had it. - - name: Validate the assembled Caddyfile - ansible.builtin.command: caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: "import sites-enabled/*" + insertafter: EOF + state: present + backup: yes + create: yes + mode: "0644" + + - name: Create Caddy reverse proxy config for DATUM Gateway dashboard + copy: + dest: "{{ caddy_sites_dir }}/datum-gateway.conf" + content: | + {{ datum_gateway_domain }} { + basic_auth { + {{ datum_dashboard_username }} {{ datum_dashboard_password_hash }} + } + reverse_proxy knots-box:{{ datum_gateway_api_port }} { + # Resolve via Tailscale MagicDNS + transport http { + resolvers 100.100.100.100 + } + } + } + owner: root + group: root + mode: "0644" + + - name: Validate Caddy config + command: caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile + register: caddy_validate changed_when: false + - name: Display Caddy validation output + debug: + msg: "{{ caddy_validate.stdout_lines + caddy_validate.stderr_lines }}" + + - name: Reload Caddy + command: systemctl reload caddy + register: caddy_reload + + - name: Display Caddy reload output + debug: + msg: "{{ caddy_reload.stdout_lines + caddy_reload.stderr_lines }}" + - name: Display DATUM Gateway dashboard URL when: uptime_kuma_enabled | default(false) debug: @@ -650,7 +685,7 @@ # Miners connect to vipy:23334; traffic is forwarded to knots-box:23334 # over the Tailscale network, matching the Bitcoin P2P proxy pattern. # =========================================== -- name: Setup public Stratum port forwarding on the edge host +- name: Setup public Stratum port forwarding on vipy via systemd-socket-proxyd hosts: edge become: yes vars_files: @@ -663,17 +698,62 @@ uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" tasks: - - name: Expose the DATUM Stratum port through a socket proxy - ansible.builtin.include_role: - name: socket_proxy - vars: - socket_proxy_name: datum-stratum - socket_proxy_description: "DATUM Stratum" - socket_proxy_listen_port: "{{ datum_gateway_stratum_port }}" - socket_proxy_upstream_host: "{{ datum_tailscale_hostname }}" - # Matches the UFW comment already on vipy; the derived default would - # have said "DATUM Stratum" and rewritten the rule. - socket_proxy_ufw_comment: "DATUM Gateway Stratum public access" + - name: Create Stratum proxy socket unit + copy: + dest: /etc/systemd/system/datum-stratum-proxy.socket + content: | + [Unit] + Description=DATUM Stratum Proxy Socket + + [Socket] + ListenStream={{ datum_gateway_stratum_port }} + + [Install] + WantedBy=sockets.target + owner: root + group: root + mode: "0644" + notify: Restart datum-stratum-proxy socket + + - name: Create Stratum proxy service unit + copy: + dest: /etc/systemd/system/datum-stratum-proxy.service + content: | + [Unit] + Description=DATUM Stratum Proxy to {{ datum_tailscale_hostname }} + Requires=datum-stratum-proxy.socket + After=network.target + + [Service] + Type=notify + ExecStart=/lib/systemd/systemd-socket-proxyd {{ datum_tailscale_hostname }}:{{ datum_gateway_stratum_port }} + owner: root + group: root + mode: "0644" + + - name: Reload systemd daemon + systemd: + daemon_reload: yes + + - name: Enable and start Stratum proxy socket + systemd: + name: datum-stratum-proxy.socket + enabled: yes + state: started + + - name: Allow Stratum port through UFW + ufw: + rule: allow + port: "{{ datum_gateway_stratum_port | string }}" + proto: tcp + comment: "DATUM Gateway Stratum public access" + + - name: Verify connectivity to knots-box Stratum via Tailscale + wait_for: + host: "{{ datum_tailscale_hostname }}" + port: "{{ datum_gateway_stratum_port }}" + timeout: 10 + ignore_errors: yes - name: Display public Stratum endpoint when: uptime_kuma_enabled | default(false) @@ -800,3 +880,8 @@ - /tmp/setup_datum_stratum_tcp_monitor.py - /tmp/ansible_datum_stratum_config.yml + handlers: + - name: Restart datum-stratum-proxy socket + systemd: + name: datum-stratum-proxy.socket + state: restarted diff --git a/ansible/services/forgejo-runner/SETUP.md b/ansible/services/forgejo-runner/SETUP.md index 5cb2a3f..a66d295 100644 --- a/ansible/services/forgejo-runner/SETUP.md +++ b/ansible/services/forgejo-runner/SETUP.md @@ -25,6 +25,4 @@ ansible-playbook ansible/services/forgejo-runner/deploy_forgejo_runner_playbook. 1. On the VM: `systemctl status forgejo-runner` should show active 2. In Forgejo: **Site Administration** > **Actions** > **Runners** should show the runner as online -3. ~~In Uptime Kuma: the `forgejo-runner-healthcheck` push monitor should be receiving pings~~ - *(Uptime Kuma was decommissioned 2026-09-11 — this check no longer applies. The - healthcheck timer still runs on the host but pushes nowhere. See `archive/uptime_kuma/`.)* +3. In Uptime Kuma: the `forgejo-runner-healthcheck` push monitor should be receiving pings diff --git a/ansible/services/forgejo/deploy_forgejo_playbook.yml b/ansible/services/forgejo/deploy_forgejo_playbook.yml index db78e95..acb1473 100644 --- a/ansible/services/forgejo/deploy_forgejo_playbook.yml +++ b/ansible/services/forgejo/deploy_forgejo_playbook.yml @@ -8,6 +8,7 @@ - ./forgejo_vars.yml vars: forgejo_subdomain: "{{ subdomains.forgejo }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" forgejo_domain: "{{ forgejo_subdomain }}.{{ root_domain }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" @@ -87,13 +88,35 @@ enabled: yes state: started - - name: Publish Forgejo through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: forgejo - caddy_site_domain: "{{ forgejo_domain }}" - caddy_site_upstream: "localhost:{{ forgejo_port }}" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy reverse proxy configuration for forgejo + copy: + dest: "{{ caddy_sites_dir }}/forgejo.conf" + content: | + {{ forgejo_domain }} { + reverse_proxy localhost:{{ forgejo_port }} + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy # ═════════════════════════════════════════════════════════════════════════ # DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11. diff --git a/ansible/services/fulcrum/deploy_fulcrum_playbook.yml b/ansible/services/fulcrum/deploy_fulcrum_playbook.yml index 01e9f17..a1548a7 100644 --- a/ansible/services/fulcrum/deploy_fulcrum_playbook.yml +++ b/ansible/services/fulcrum/deploy_fulcrum_playbook.yml @@ -540,7 +540,7 @@ state: restarted -- name: Setup public Fulcrum SSL forwarding on the edge host +- name: Setup public Fulcrum SSL forwarding on vipy via systemd-socket-proxyd hosts: edge become: yes vars_files: @@ -552,14 +552,62 @@ uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" tasks: - - name: Expose Fulcrum SSL through a socket proxy - ansible.builtin.include_role: - name: socket_proxy - vars: - socket_proxy_name: fulcrum-ssl - socket_proxy_description: "Fulcrum SSL" - socket_proxy_listen_port: "{{ fulcrum_ssl_port }}" - socket_proxy_upstream_host: "{{ fulcrum_tailscale_hostname }}" + - name: Create Fulcrum SSL proxy socket unit + copy: + dest: /etc/systemd/system/fulcrum-ssl-proxy.socket + content: | + [Unit] + Description=Fulcrum SSL Proxy Socket + + [Socket] + ListenStream={{ fulcrum_ssl_port }} + + [Install] + WantedBy=sockets.target + owner: root + group: root + mode: '0644' + notify: Restart fulcrum-ssl-proxy socket + + - name: Create Fulcrum SSL proxy service unit + copy: + dest: /etc/systemd/system/fulcrum-ssl-proxy.service + content: | + [Unit] + Description=Fulcrum SSL Proxy to {{ fulcrum_tailscale_hostname }} + Requires=fulcrum-ssl-proxy.socket + After=network.target + + [Service] + Type=notify + ExecStart=/lib/systemd/systemd-socket-proxyd {{ fulcrum_tailscale_hostname }}:{{ fulcrum_ssl_port }} + owner: root + group: root + mode: '0644' + + - name: Reload systemd daemon + systemd: + daemon_reload: yes + + - name: Enable and start Fulcrum SSL proxy socket + systemd: + name: fulcrum-ssl-proxy.socket + enabled: yes + state: started + + - name: Allow Fulcrum SSL port through UFW + ufw: + rule: allow + port: "{{ fulcrum_ssl_port | string }}" + proto: tcp + comment: "Fulcrum SSL public access" + + - name: Verify connectivity to fulcrum-box via Tailscale + wait_for: + host: "{{ fulcrum_tailscale_hostname }}" + port: "{{ fulcrum_ssl_port }}" + timeout: 10 + ignore_errors: yes - name: Display public endpoint when: uptime_kuma_enabled | default(false) @@ -682,4 +730,9 @@ - /tmp/setup_fulcrum_ssl_tcp_monitor.py - /tmp/ansible_fulcrum_ssl_config.yml + handlers: + - name: Restart fulcrum-ssl-proxy socket + systemd: + name: fulcrum-ssl-proxy.socket + state: restarted diff --git a/ansible/services/headscale/deploy_headscale_playbook.yml b/ansible/services/headscale/deploy_headscale_playbook.yml index 527e4c2..61ba22e 100644 --- a/ansible/services/headscale/deploy_headscale_playbook.yml +++ b/ansible/services/headscale/deploy_headscale_playbook.yml @@ -8,6 +8,7 @@ - ./headscale_vars.yml vars: headscale_subdomain: "{{ subdomains.headscale }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" headscale_domain: "{{ headscale_subdomain }}.{{ root_domain }}" headscale_base_domain: "tailnet.{{ root_domain }}" headscale_namespace: "{{ service_settings.headscale.namespace }}" @@ -229,34 +230,39 @@ port: '3478' proto: udp - - name: Publish headscale through Caddy - ansible.builtin.include_role: - name: caddy_site - # This play is become: no and elevates per task. `apply` is how an - # include_role passes become down to the role's tasks - `become:` on - # the include itself is rejected. The role's handler sets its own. - apply: - become: yes - vars: - caddy_site_name: headscale - caddy_site_domain: "{{ headscale_domain }}" - # Raw body, and it must stay raw: the /admin* route in front of - # Headplane is not expressible as a plain reverse_proxy. The previous - # version of this task wrote only `reverse_proxy localhost:8080`, which - # would have deleted the admin route and its auth on the next run. - caddy_site_body: | - @headplane { - path /admin* + - name: Ensure Caddy sites-enabled directory exists + become: yes + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + become: yes + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy reverse proxy configuration for headscale + become: yes + copy: + dest: "{{ caddy_sites_dir }}/headscale.conf" + content: | + {{ headscale_domain }} { + reverse_proxy localhost:{{ headscale_port }} } - handle @headplane { - basicauth { - {{ headscale_ui_username }} {{ headscale_ui_password_hash }} - } - reverse_proxy http://localhost:{{ headplane_port }} - } - # Headscale API is protected by its own API key authentication - # All API operations require a valid Bearer token in the Authorization header - reverse_proxy * http://localhost:{{ headscale_port }} + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + become: yes + command: systemctl reload caddy # ═════════════════════════════════════════════════════════════════════════ # DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11. diff --git a/ansible/services/headscale/headscale_vars.yml b/ansible/services/headscale/headscale_vars.yml index 05fc040..0b0ee50 100644 --- a/ansible/services/headscale/headscale_vars.yml +++ b/ansible/services/headscale/headscale_vars.yml @@ -22,6 +22,3 @@ remote_port: "{{ hostvars.get(remote_host_name, {}).get('ansible_port', 22) }}" # Local backup local_backup_dir: "{{ lookup('env', 'HOME') }}/headscale-backups" backup_script_path: "{{ lookup('env', 'HOME') }}/.local/bin/headscale_backup.sh" - -# Headplane (headscale admin UI), proxied at /admin* behind Caddy basic auth -headplane_port: 3000 diff --git a/ansible/services/lnbits/deploy_lnbits_playbook.yml b/ansible/services/lnbits/deploy_lnbits_playbook.yml index 65bdfbf..d596052 100644 --- a/ansible/services/lnbits/deploy_lnbits_playbook.yml +++ b/ansible/services/lnbits/deploy_lnbits_playbook.yml @@ -8,6 +8,7 @@ - ./lnbits_vars.yml vars: lnbits_subdomain: "{{ subdomains.lnbits }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" lnbits_domain: "{{ lnbits_subdomain }}.{{ root_domain }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" @@ -146,15 +147,39 @@ enabled: yes state: started - - name: Publish LNBits through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: lnbits - caddy_site_domain: "{{ lnbits_domain }}" - caddy_site_upstream: "localhost:{{ lnbits_port }}" - caddy_site_headers_up: - X-Forwarded-Host: "{{ lnbits_domain }}" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + create: yes + mode: '0644' + + - name: Create Caddy reverse proxy configuration for lnbits + copy: + dest: "{{ caddy_sites_dir }}/lnbits.conf" + content: | + {{ lnbits_domain }} { + reverse_proxy localhost:{{ lnbits_port }} { + header_up X-Forwarded-Host {{ lnbits_domain }} + } + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy # ═════════════════════════════════════════════════════════════════════════ # DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11. diff --git a/ansible/services/memos/deploy_memos_playbook.yml b/ansible/services/memos/deploy_memos_playbook.yml index 04e99cf..5ab254e 100644 --- a/ansible/services/memos/deploy_memos_playbook.yml +++ b/ansible/services/memos/deploy_memos_playbook.yml @@ -156,7 +156,7 @@ state: restarted -- name: Configure Caddy reverse proxy for Memos on the edge host (via Tailscale) +- name: Configure Caddy reverse proxy for Memos on vipy (proxying via Tailscale) hosts: edge become: yes vars_files: @@ -166,18 +166,45 @@ - ./memos_vars.yml vars: memos_subdomain: "{{ subdomains.memos }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" memos_domain: "{{ memos_subdomain }}.{{ root_domain }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" tasks: - - name: Publish Memos through Caddy (via Tailscale) - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: memos - caddy_site_domain: "{{ memos_domain }}" - caddy_site_upstream: "{{ memos_tailscale_hostname }}:{{ memos_port }}" - caddy_site_resolvers: "100.100.100.100" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy reverse proxy configuration for memos (via Tailscale) + copy: + dest: "{{ caddy_sites_dir }}/memos.conf" + content: | + {{ memos_domain }} { + reverse_proxy {{ memos_tailscale_hostname }}:{{ memos_port }} { + # Use Tailscale MagicDNS to resolve the upstream hostname + transport http { + resolvers 100.100.100.100 + } + } + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy - name: Create Uptime Kuma monitor setup script for Memos when: uptime_kuma_enabled | default(false) diff --git a/ansible/services/mempool/deploy_mempool_playbook.yml b/ansible/services/mempool/deploy_mempool_playbook.yml index e9cab0e..1dfea1b 100644 --- a/ansible/services/mempool/deploy_mempool_playbook.yml +++ b/ansible/services/mempool/deploy_mempool_playbook.yml @@ -603,7 +603,7 @@ - /tmp/ansible_mempool_config.yml - /tmp/mempool_push_urls.yml -- name: Configure Caddy reverse proxy for Mempool on the edge host +- name: Configure Caddy reverse proxy for Mempool on vipy hosts: edge become: yes vars_files: @@ -614,16 +614,47 @@ vars: mempool_subdomain: "{{ subdomains.mempool }}" mempool_domain: "{{ mempool_subdomain }}.{{ root_domain }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" tasks: - - name: Publish Mempool through Caddy (via Tailscale) - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: mempool - caddy_site_domain: "{{ mempool_domain }}" - caddy_site_upstream: "mempool-box:{{ mempool_frontend_port }}" - caddy_site_resolvers: "100.100.100.100" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + create: yes + mode: '0644' + + - name: Create Caddy reverse proxy configuration for Mempool + copy: + dest: "{{ caddy_sites_dir }}/mempool.conf" + content: | + {{ mempool_domain }} { + reverse_proxy mempool-box:{{ mempool_frontend_port }} { + # Use Tailscale MagicDNS to resolve the upstream hostname + transport http { + resolvers 100.100.100.100 + } + } + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + systemd: + name: caddy + state: reloaded - name: Display Mempool URL when: uptime_kuma_enabled | default(false) diff --git a/ansible/services/ntfy-emergency-app/deploy_ntfy_emergency_app_playbook.yml b/ansible/services/ntfy-emergency-app/deploy_ntfy_emergency_app_playbook.yml index 7379d5f..a8d97a6 100644 --- a/ansible/services/ntfy-emergency-app/deploy_ntfy_emergency_app_playbook.yml +++ b/ansible/services/ntfy-emergency-app/deploy_ntfy_emergency_app_playbook.yml @@ -8,6 +8,7 @@ - ./ntfy_emergency_app_vars.yml vars: ntfy_emergency_app_subdomain: "{{ subdomains.ntfy_emergency_app }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" ntfy_emergency_app_domain: "{{ ntfy_emergency_app_subdomain }}.{{ root_domain }}" ntfy_service_domain: "{{ subdomains.ntfy }}.{{ root_domain }}" ntfy_emergency_app_ntfy_url: "https://{{ ntfy_service_domain }}" @@ -48,13 +49,35 @@ args: chdir: "{{ ntfy_emergency_app_dir }}" - - name: Publish ntfy-emergency-app through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: ntfy-emergency-app - caddy_site_domain: "{{ ntfy_emergency_app_domain }}" - caddy_site_upstream: "localhost:{{ ntfy_emergency_app_port }}" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy reverse proxy configuration for ntfy-emergency-app + copy: + dest: "{{ caddy_sites_dir }}/ntfy-emergency-app.conf" + content: | + {{ ntfy_emergency_app_domain }} { + reverse_proxy localhost:{{ ntfy_emergency_app_port }} + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy # ═════════════════════════════════════════════════════════════════════════ # DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11. diff --git a/ansible/services/ntfy/deploy_ntfy_playbook.yml b/ansible/services/ntfy/deploy_ntfy_playbook.yml index 61fafe1..d030123 100644 --- a/ansible/services/ntfy/deploy_ntfy_playbook.yml +++ b/ansible/services/ntfy/deploy_ntfy_playbook.yml @@ -8,6 +8,7 @@ - ./ntfy_vars.yml vars: ntfy_subdomain: "{{ subdomains.ntfy }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" ntfy_domain: "{{ ntfy_subdomain }}.{{ root_domain }}" tasks: @@ -75,23 +76,42 @@ shell: | (echo "{{ ntfy_password }}"; echo "{{ ntfy_password }}") | ntfy user add --role=admin "{{ ntfy_username }}" - - name: Publish ntfy through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: ntfy - caddy_site_domain: "{{ ntfy_domain }}, http://{{ ntfy_domain }}" - # Raw body: ntfy needs a plain-HTTP listener for its CLI/app clients, - # with only GETs to the docs and topic paths redirected to HTTPS. - caddy_site_body: | - reverse_proxy 127.0.0.1:{{ ntfy_port }} + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' - @httpget { - protocol http - method GET - path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/) + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy reverse proxy configuration for ntfy + copy: + dest: "{{ caddy_sites_dir }}/ntfy.conf" + content: | + {{ ntfy_domain }}, http://{{ ntfy_domain }} { + reverse_proxy 127.0.0.1:{{ ntfy_port }} + + @httpget { + protocol http + method GET + path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/) + } + redir @httpget https://{host}{uri} } - redir @httpget https://{host}{uri} + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy handlers: - name: Restart ntfy diff --git a/ansible/services/personal-blog/deploy_personal_blog_playbook.yml b/ansible/services/personal-blog/deploy_personal_blog_playbook.yml index 96d030f..af7a1f3 100644 --- a/ansible/services/personal-blog/deploy_personal_blog_playbook.yml +++ b/ansible/services/personal-blog/deploy_personal_blog_playbook.yml @@ -8,6 +8,7 @@ - ./personal_blog_vars.yml vars: personal_blog_subdomain: "{{ subdomains.personal_blog }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" personal_blog_domain: "{{ personal_blog_subdomain }}.{{ root_domain }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" @@ -50,13 +51,36 @@ group: www-data mode: '0664' - - name: Publish the blog through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: personal-blog - caddy_site_domain: "{{ personal_blog_domain }}" - caddy_site_root: "{{ personal_blog_web_root }}" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy file server configuration for personal blog + copy: + dest: "{{ caddy_sites_dir }}/personal-blog.conf" + content: | + {{ personal_blog_domain }} { + root * {{ personal_blog_web_root }} + file_server + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy # ═════════════════════════════════════════════════════════════════════════ # DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11. diff --git a/ansible/services/phoenixd/deploy_phoenixd_playbook.yml b/ansible/services/phoenixd/deploy_phoenixd_playbook.yml index 8b30a1f..f20ecdc 100644 --- a/ansible/services/phoenixd/deploy_phoenixd_playbook.yml +++ b/ansible/services/phoenixd/deploy_phoenixd_playbook.yml @@ -19,7 +19,7 @@ # ⚠️ After the first run, back up {{ phoenixd_data_dir }}/seed.dat. Losing it # means losing the funds. See setup_backup_phoenixd_to_lapy.yml. -- name: Deploy phoenixd on the edge host +- name: Deploy phoenixd on vipy hosts: edge become: yes vars_files: diff --git a/ansible/services/vaultwarden/deploy_vaultwarden_playbook.yml b/ansible/services/vaultwarden/deploy_vaultwarden_playbook.yml index 74e87d8..9868d13 100644 --- a/ansible/services/vaultwarden/deploy_vaultwarden_playbook.yml +++ b/ansible/services/vaultwarden/deploy_vaultwarden_playbook.yml @@ -8,6 +8,7 @@ - ./vaultwarden_vars.yml vars: vaultwarden_subdomain: "{{ subdomains.vaultwarden }}" + caddy_sites_dir: "{{ caddy_sites_dir }}" vaultwarden_domain: "{{ vaultwarden_subdomain }}.{{ root_domain }}" uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}" @@ -80,13 +81,35 @@ name: fail2ban state: restarted - - name: Publish Vaultwarden through Caddy - ansible.builtin.include_role: - name: caddy_site - vars: - caddy_site_name: vaultwarden - caddy_site_domain: "{{ vaultwarden_domain }}" - caddy_site_upstream: "localhost:{{ vaultwarden_port }}" + - name: Ensure Caddy sites-enabled directory exists + file: + path: "{{ caddy_sites_dir }}" + state: directory + owner: root + group: root + mode: '0755' + + - name: Ensure Caddyfile includes import directive for sites-enabled + lineinfile: + path: /etc/caddy/Caddyfile + line: 'import sites-enabled/*' + insertafter: EOF + state: present + backup: yes + + - name: Create Caddy reverse proxy configuration for vaultwarden + copy: + dest: "{{ caddy_sites_dir }}/vaultwarden.conf" + content: | + {{ vaultwarden_domain }} { + reverse_proxy localhost:{{ vaultwarden_port }} + } + owner: root + group: root + mode: '0644' + + - name: Reload Caddy to apply new config + command: systemctl reload caddy # ═════════════════════════════════════════════════════════════════════════ # DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.