Compare commits

...

10 commits

Author SHA1 Message Date
a8a2815c5f
inventory 2026-09-12 15:14:47 +02:00
cea2523e15
caddy: close out Plan 4
All five close-out greps return nothing: no sites-enabled handling outside
roles/, no `systemctl reload caddy`, no caddy_sites_dir self-reference, no
inline proxy unit writes. 37 playbooks syntax clean. The 14 Caddy site files
and 6 proxy units on the hosts are byte-identical to the Stage 0 baseline.

Seven play names still said "on vipy" while the play targeted a group. Renamed
to "on the edge host" - the last place a play claimed a hostname after Plan 2.

Documented the four vhosts in /etc/caddy/sites-enabled that no playbook writes
(uptime-kuma, arbretstaging, bitcoininfra, scriberr) in the caddy_site README.
None deleted.

uptime-kuma.conf was going to be deleted as dead config. It is not dead: the
louislam/uptime-kuma container is STILL RUNNING on watchtower - created
2026-02-07, restart=unless-stopped, healthy - and uptime.contrapeso.xyz returns
302, not the 502 a dead backend would give. The "decommissioning" retired the
Ansible code and the vault credentials, not the service. PLAN_3 claimed "the
tokens died with the server"; that is corrected there.

The Caddyfile.* backups are kept: one per host, Nov-Dec 2025, not churning, and
the only record of each Caddyfile before the import line was added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:53:09 +02:00
c4094b692f
bitcoin-knots, fulcrum, datum-gateway: add and use the socket_proxy role
Three near-identical hosts: edge plays become one role plus three short
calls. 183 lines removed, 34 added, plus a 111-line role.

Verified before touching any playbook: all six live units on vipy reproduced
byte-identically. Then --limit edge --check per playbook - bitcoin-knots and
fulcrum changed=0; datum-gateway changed=2, both attributable to the already
known caddy_site comment line and the Reload caddy handler it triggers.
The 6 units and 14 Caddy files on the hosts are byte-identical afterwards.

PLAN_4 claimed these three plays had "no behavioural drift at all". That was
wrong - it came from a diff truncated by head -60. The live bitcoin-p2p-proxy
units carry four settings this playbook never wrote:

  .socket   Documentation=, FreeBind=true
  .service  Documentation=, TimeoutStopSec=5,
            StandardOutput=journal, StandardError=journal

FreeBind is the one that matters: it lets the socket bind to an address that
is not up yet, so without it the socket can fail to start on boot. Running
the bitcoin-knots playbook would have stripped it. Same class of hazard as
headscale. The role expresses all four; bitcoin-p2p is the only caller that
passes any.

Also: UFW treats the rule comment as part of the rule. datum-stratum's live
comment is "DATUM Gateway Stratum public access" but the role's derived
default produced "DATUM Stratum public access", which rewrote the rule.
Caught in the dry-run; datum now passes the comment explicitly.

Two deliberate differences from the original, both documented in the README:
ignore_errors: yes on the upstream check became failed_when: false, and the
handler restarts the .socket, which drops connections open through it - it
fires only when a unit file actually changes.

The inert Uptime Kuma TCP monitor blocks stay in the playbooks rather than
being pulled into a new role (12/12/18 guarded tasks).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:50:15 +02:00
16cbd189b8
ntfy, datum-gateway, headscale: use the caddy_site role
Completes Stage 3. No hand-rolled Caddy plumbing remains anywhere:
`grep sites-enabled` outside roles/ returns nothing, and so does
`grep "systemctl reload caddy"`.

ntfy uses caddy_site_body for its plain-HTTP listener and @httpget redirect.
Verified ok/unchanged against watchtower; the one other changed task is a
pre-existing "Update APT cache".

datum-gateway keeps a whole-Caddyfile validate after the role call. The role
validates its own fragment, but only a whole-file validate catches a conflict
between two sites, and this playbook was the only one that ever had it. Its
two debug tasks that echoed command output are gone with the commands.

headscale is the one that mattered. Its playbook wrote
`reverse_proxy localhost:8080`, but spacey is actually running a /admin*
route in front of Headplane behind Caddy basic auth. Running that playbook
would have deleted the admin route and its auth - a hazard that predates this
work. It now renders the config that is really there, verified ok/unchanged
via --start-at-task (the play cannot reach Caddy in check mode: "Install
headscale package" fails because the .deb is not really downloaded, before
and after this edit alike).

Supporting changes for headscale:
  - headscale_ui_password_hash added to infra_secrets.yml and the identical
    group_vars/all/vault.yml, read from the live config on spacey. The vault
    already had headscale_ui_username (= counterweight, confirmed) and
    headscale_ui_password; I did not verify the password is the plaintext of
    this hash.
  - headplane_port added to headscale_vars.yml.
  - The role's handler now sets become: true. Handlers do not inherit become
    from the task that notified them, and this play runs become: no.
  - The include uses `apply: become: yes`; `become:` on an include_role is
    rejected outright.

All 14 site files on all 3 hosts still byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:37:38 +02:00
82fca48b08
lnbits, memos, mempool: use the caddy_site role
lnbits is the header_up shape; memos and mempool are the Tailscale MagicDNS
shape. 108 lines removed, 25 added.

mempool was the one playbook already reloading Caddy correctly
(systemd: state: reloaded rather than command: systemctl reload caddy), so
its end marker differed - the role's handler does the same thing.

Verified:
  - lnbits: full --check, site task ok, byte-identical to the live file
  - memos, mempool: --check --diff via --limit edge shows exactly one added
    line each, the standardised MagicDNS comment. Both playbooks fail earlier
    in check mode on their VM play ("Extract memos binary", the same
    download-does-not-happen-in-check-mode artifact as forgejo), but the edits
    are confined to the hosts: edge play - memos at line 169+, play 2 starts
    at 159; mempool at 617+, play 2 starts at 606.

The added comment means the next real run of memos/mempool rewrites one
comment line. Those two host files were already stale against their
playbooks before this change.

All 14 site files on all 3 hosts still byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:29:06 +02:00
4bee182978
ntfy-emergency-app, vaultwarden, forgejo: use the caddy_site role
The plain reverse_proxy shape. All three removed a byte-identical 23-line
block (verified by md5 of the diff with the service name normalised) and
gained the same 7-line include_role call. The caddy_sites_dir self-reference
goes with it.

Verified in check mode, nothing applied to the hosts yet:
  - ntfy-emergency-app: site task ok, changed=0
  - vaultwarden: site task ok; the one changed task is a pre-existing
    always-restarts fail2ban step, identical before the edit
  - forgejo: check mode cannot run this playbook at all - get_url does not
    download in check mode so the next task fails on "Source /tmp/forgejo not
    found". Confirmed identical before the edit. Covered instead by the
    Stage 2 dry-run, which ran the role against vipy with forgejo's real
    parameters and reported ok/unchanged.

All 14 site files on all 3 hosts still byte-identical. Real runs for these
three are still outstanding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:24:57 +02:00
39a1b43577
personal-blog: use the caddy_site role
First service on the role. 31 lines of copy-pasted Caddy plumbing become 7.

Verified: --check before and after the edit reports the same three unrelated
tasks as changed, so the edit introduces nothing. Real run leaves all 14 site
files on all 3 hosts byte-identical, and the blog still answers HTTP 200. A
second consecutive run reports the site task ok with the handler not firing.

Side effect worth noting: the playbook no longer has a perpetually-changed
task. `command: systemctl reload caddy` always reported changed; the role's
handler only fires when the file actually moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:19:01 +02:00
cc9340b7cc
caddy: add the caddy_site role
Replaces the four-task Caddy vhost block currently copy-pasted into 10
playbooks. Nothing calls it yet; this commit only adds the role.

Verified by rendering all 10 sites through the template and diffing against
what the current playbooks produce: 9 of 10 byte-identical. The tenth is
datum-gateway, where the resolvers comment is standardised, rewriting one
comment line Caddy ignores.

Then dry-run against the live hosts (--check, nothing written):
  - vipy: forgejo, vaultwarden, lnbits, personal-blog, ntfy-emergency-app
    all report ok/unchanged against the real files
  - watchtower: ntfy renders identical via caddy_site_body, blank line and
    {host}{uri} placeholders intact
  - spacey: headscale renders identical when given the config that is
    actually running
  - memos, mempool, datum-gateway report changed - the comment, as expected
All 14 site files on all 3 hosts confirmed unchanged afterwards.

Two things the build turned up:

- Ansible does not template dict *keys*, so caddy_site_basic_auth is a list
  of {user, hash}. As a dict, a Jinja username passes through literally.
  The assert refuses a mapping.
- `caddy validate` does accept a single site fragment - rc=0 on a good one,
  rc=1 with a line number on a broken one. This was the plan's one untested
  claim. A failed validate leaves the live file untouched.

The reload is now a handler, so it fires once at end of play rather than
immediately; anything needing the new config live mid-play must
flush_handlers first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:10:43 +02:00
b07ed72a92
caddy: add a [caddy] role group and target it
services/caddy_playbook.yml was the one play still targeting a location
group (vps) rather than a role group. The two coincide today — vps is
exactly vipy, watchtower and spacey, the three hosts with
/etc/caddy/sites-enabled — but adding a fourth VPS that does not run
Caddy would have silently pulled it into the play.

[caddy:children] is edge + monitoring + vpn_control. Verified the play
selects the same three machines before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:03:25 +02:00
8a3fddbe49
docs: mark Uptime Kuma as decommissioned
README, both setup guides and the forgejo-runner notes now point at
archive/uptime_kuma/ instead of describing a live service.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 22:43:56 +02:00
33 changed files with 717 additions and 640 deletions

View file

@ -162,6 +162,12 @@ 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"

View file

@ -49,6 +49,12 @@ 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

View file

@ -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
* ~~Uptime Kuma~~ — decommissioned 2026-09-11, see `archive/uptime_kuma/`
+ Deployed on Vipy
+ Crossbackup to Desky via rsync
* Vaultwarden

View file

@ -1,45 +1,60 @@
$ANSIBLE_VAULT;1.1;AES256
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
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

View file

@ -1,45 +1,60 @@
$ANSIBLE_VAULT;1.1;AES256
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
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

View file

@ -2,19 +2,20 @@
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=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
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
# Local connection to laptop: this assumes you're running ansible commands from your personal laptop
[lapy]
@ -58,4 +59,13 @@ localhost
[managed:children]
vps
nodito_host
nodito_vms
nodito_vms
# Hosts that run Caddy and therefore have /etc/caddy/sites-enabled.
[caddy:children]
edge
monitoring
vpn_control
[backup_store]
small_backups_local

View file

@ -0,0 +1,118 @@
# `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.

View file

@ -0,0 +1,23 @@
---
# Required
caddy_site_name: "" # file basename -> <name>.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

View file

@ -0,0 +1,12 @@
---
# 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

View file

@ -0,0 +1,46 @@
---
- 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("<unnamed>") }}' 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

View file

@ -0,0 +1,34 @@
{{ 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 %}
}

View file

@ -0,0 +1,62 @@
# `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 `"<description> 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.

View file

@ -0,0 +1,17 @@
---
# 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 "<description> public access"

View file

@ -0,0 +1,8 @@
---
# 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

View file

@ -0,0 +1,54 @@
---
- 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("<unnamed>") }}' 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

View file

@ -0,0 +1,18 @@
[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 %}

View file

@ -0,0 +1,14 @@
[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

View file

@ -740,7 +740,7 @@
state: restarted
- name: Setup public Bitcoin P2P forwarding on vipy via systemd-socket-proxyd
- name: Setup public Bitcoin P2P forwarding on the edge host
hosts: edge
become: yes
vars_files:
@ -753,62 +753,21 @@
uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks:
- 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: 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: Display public endpoint
when: uptime_kuma_enabled | default(false)
@ -931,8 +890,3 @@
- /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

View file

@ -1,5 +1,5 @@
- name: Install and configure Caddy on Debian 12
hosts: vps
hosts: caddy
become: yes
tasks:

View file

@ -487,7 +487,7 @@
# ===========================================
# Caddy Reverse Proxy for DATUM Dashboard (on vipy)
# ===========================================
- name: Configure Caddy reverse proxy for DATUM Gateway dashboard on vipy
- name: Configure Caddy reverse proxy for DATUM Gateway dashboard on the edge host
hosts: edge
become: yes
vars_files:
@ -498,64 +498,29 @@
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: Ensure Caddy sites-enabled directory exists
file:
path: "{{ caddy_sites_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- 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 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
# 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
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:
@ -685,7 +650,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 vipy via systemd-socket-proxyd
- name: Setup public Stratum port forwarding on the edge host
hosts: edge
become: yes
vars_files:
@ -698,62 +663,17 @@
uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks:
- 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: 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: Display public Stratum endpoint
when: uptime_kuma_enabled | default(false)
@ -880,8 +800,3 @@
- /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

View file

@ -25,4 +25,6 @@ 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
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/`.)*

View file

@ -8,7 +8,6 @@
- ./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 }}"
@ -88,35 +87,13 @@
enabled: yes
state: started
- 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
- 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 }}"
# ═════════════════════════════════════════════════════════════════════════
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.

View file

@ -540,7 +540,7 @@
state: restarted
- name: Setup public Fulcrum SSL forwarding on vipy via systemd-socket-proxyd
- name: Setup public Fulcrum SSL forwarding on the edge host
hosts: edge
become: yes
vars_files:
@ -552,62 +552,14 @@
uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks:
- 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: 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: Display public endpoint
when: uptime_kuma_enabled | default(false)
@ -730,9 +682,4 @@
- /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

View file

@ -8,7 +8,6 @@
- ./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 }}"
@ -230,39 +229,34 @@
port: '3478'
proto: udp
- 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 }}
- 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*
}
owner: root
group: root
mode: '0644'
- name: Reload Caddy to apply new config
become: yes
command: systemctl reload caddy
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 }}
# ═════════════════════════════════════════════════════════════════════════
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.

View file

@ -22,3 +22,6 @@ 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

View file

@ -8,7 +8,6 @@
- ./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 }}"
@ -147,39 +146,15 @@
enabled: yes
state: started
- 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
- 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 }}"
# ═════════════════════════════════════════════════════════════════════════
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.

View file

@ -156,7 +156,7 @@
state: restarted
- name: Configure Caddy reverse proxy for Memos on vipy (proxying via Tailscale)
- name: Configure Caddy reverse proxy for Memos on the edge host (via Tailscale)
hosts: edge
become: yes
vars_files:
@ -166,45 +166,18 @@
- ./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: 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: 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: Create Uptime Kuma monitor setup script for Memos
when: uptime_kuma_enabled | default(false)

View file

@ -603,7 +603,7 @@
- /tmp/ansible_mempool_config.yml
- /tmp/mempool_push_urls.yml
- name: Configure Caddy reverse proxy for Mempool on vipy
- name: Configure Caddy reverse proxy for Mempool on the edge host
hosts: edge
become: yes
vars_files:
@ -614,47 +614,16 @@
vars:
mempool_subdomain: "{{ subdomains.mempool }}"
mempool_domain: "{{ mempool_subdomain }}.{{ root_domain }}"
caddy_sites_dir: "{{ caddy_sites_dir }}"
tasks:
- 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: 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: Display Mempool URL
when: uptime_kuma_enabled | default(false)

View file

@ -8,7 +8,6 @@
- ./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 }}"
@ -49,35 +48,13 @@
args:
chdir: "{{ ntfy_emergency_app_dir }}"
- 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
- 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 }}"
# ═════════════════════════════════════════════════════════════════════════
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.

View file

@ -8,7 +8,6 @@
- ./ntfy_vars.yml
vars:
ntfy_subdomain: "{{ subdomains.ntfy }}"
caddy_sites_dir: "{{ caddy_sites_dir }}"
ntfy_domain: "{{ ntfy_subdomain }}.{{ root_domain }}"
tasks:
@ -76,42 +75,23 @@
shell: |
(echo "{{ ntfy_password }}"; echo "{{ ntfy_password }}") | ntfy user add --role=admin "{{ ntfy_username }}"
- name: Ensure Caddy sites-enabled directory exists
file:
path: "{{ caddy_sites_dir }}"
state: directory
owner: root
group: root
mode: '0755'
- 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 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}
@httpget {
protocol http
method GET
path_regexp ^/([-_a-z0-9]{0,64}$|docs/|static/)
}
owner: root
group: root
mode: '0644'
- name: Reload Caddy to apply new config
command: systemctl reload caddy
redir @httpget https://{host}{uri}
handlers:
- name: Restart ntfy

View file

@ -8,7 +8,6 @@
- ./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 }}"
@ -51,36 +50,13 @@
group: www-data
mode: '0664'
- 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
- 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 }}"
# ═════════════════════════════════════════════════════════════════════════
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.

View file

@ -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 vipy
- name: Deploy phoenixd on the edge host
hosts: edge
become: yes
vars_files:

View file

@ -8,7 +8,6 @@
- ./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 }}"
@ -81,35 +80,13 @@
name: fail2ban
state: restarted
- 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
- 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 }}"
# ═════════════════════════════════════════════════════════════════════════
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.