personal_infra/ansible/services/headscale/templates/acl.json.j2
counterweight f8fe3c6baf
rebuild: fold monitoring into spacey and prd-arbret into vipy
All four droplets were deleted by mistake on 2026-10-17 and rebuilt as two:
Gatus and signal-api now share spacey with headscale, and arbret moves onto
vipy. The old IPs were recycled to strangers within a day.

- inventory: [observability] is spacey. Left as it was, the bare name
  `monitoring` resolved through ~/.ssh/config to an IP that now belongs to
  someone else. [arbret]/prd-arbret is gone.
- inventory: lan_ip fallbacks corrected from the Proxmox guest agent -
  memos-box is .130 (was .145) and arbret-staging-box .145 (was .147).
- spacey: gatus_port 8081. headscale already holds 0.0.0.0:8080, and the two
  never shared a host before.
- 402: status.<domain> is expected to resolve to spacey.
- headscale ACL: prd-arbret, prd-monitoring and stalwart-box removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 22:10:22 +02:00

128 lines
3.2 KiB
Django/Jinja

{#
Headscale ACL policy.
CAPTURED FROM THE LIVE FILE on spacey, 2026-09-19. Until then this playbook
wrote `{}` here - an empty policy - while the real rules were maintained by
hand on the box. Ansible had already flattened them once:
/etc/headscale/acl.json.937464.2026-09-12@14:03:08~ is its own backup of a
1596-byte policy it replaced with {}. The next full site.yml run would have
done it again, taking the whole tailnet's access rules with it.
So this is the source of truth now. It is a template and not inline `content:`
(the style used elsewhere in this playbook) precisely because it needs to be
diffable in review - an empty policy is the kind of change that must never
again slip through as three invisible characters.
ADDING A HOST: it needs a tagOwners entry, and an acls rule naming the exact
ports its peers may reach. A node with no rule is INVISIBLE to the others -
it will not appear in `tailscale status` and MagicDNS will not resolve it,
which surfaces as a Caddy 502 with "no such host" rather than as a refused
connection.
#}
{
"acls": [
{
"action": "accept",
"dst": [
"*:22",
"tag:arbret-staging-box:4117",
"tag:prd-arbret:4117",
"tag:nodito:8006"
],
"src": [
"tag:framie"
]
},
{
"action": "accept",
"dst": [
"tag:knots-box:8333",
"tag:knots-box:7152",
"tag:knots-box:23334",
"tag:fulcrum-box:50002",
"tag:mempool-box:8080",
"tag:memos-box:5230",
"tag:music-box:4533",
"tag:jar-lid-archive-staging-box:8000",
"tag:monerod-box:18089",
"tag:monerod-box:8081",
"tag:arbret-staging-box:80"
],
"src": [
"tag:prd-vipy"
]
},
{
"action": "accept",
"dst": [
"tag:knots-box:8332",
"tag:knots-box:28332-28335"
],
"src": [
"tag:fulcrum-box"
]
},
{
"action": "accept",
"dst": [
"tag:knots-box:8332",
"tag:fulcrum-box:50001"
],
"src": [
"tag:mempool-box"
]
},
{
"action": "accept",
"dst": [
"tag:prd-vipy:22",
"tag:memos-box:22",
"tag:jar-lid-archive-staging-box:22"
],
"src": [
"tag:small-backups-box"
]
}
],
"tagOwners": {
"tag:arbret-staging-box": [
"{{ headscale_namespace }}@"
],
"tag:forgejo-runner-box": [
"{{ headscale_namespace }}@"
],
"tag:framie": [
"{{ headscale_namespace }}@"
],
"tag:fulcrum-box": [
"{{ headscale_namespace }}@"
],
"tag:jar-lid-archive-staging-box": [
"{{ headscale_namespace }}@"
],
"tag:knots-box": [
"{{ headscale_namespace }}@"
],
"tag:memos-box": [
"{{ headscale_namespace }}@"
],
"tag:mempool-box": [
"{{ headscale_namespace }}@"
],
"tag:monerod-box": [
"{{ headscale_namespace }}@"
],
"tag:music-box": [
"{{ headscale_namespace }}@"
],
"tag:nodito": [
"{{ headscale_namespace }}@"
],
"tag:prd-vipy": [
"{{ headscale_namespace }}@"
],
"tag:small-backups-box": [
"{{ headscale_namespace }}@"
]
}
}