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>
62 lines
2.7 KiB
Markdown
62 lines
2.7 KiB
Markdown
# `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.
|