Repository navigation
feat(sponsord): add the sponsord role, standalone or beside a node - #82
Merged
Merged
Conversation
Deploy decdn/sponsord, the onboarding sponsor (treasury signer and
PaymentPool keeper), with a new `sponsord` role and
`playbooks/sponsord.yml` (`make deploy-sponsord`), which `site.yml` also
imports. The role needs no node: hosts go in a new `sponsord_hosts` group,
on their own or also in `decdn_nodes`. It ships in the `decdn.node`
collection.
Role:
- Install: a locally built binary by default (no `sponsord-v*` release
yet), or a GPG-verified release. The version stamp is written only after
an exact `--version` match.
- Unit: `DynamicUser`. The API token (generated on the host, never
replaced), treasury keystore and password arrive as `LoadCredential=`
credentials. systemd 254+ writes credentials 0440 and sponsord rejects
a group-readable keystore, so `ExecStartPre` copies it 0600 into the
unit's RuntimeDirectory. Upstream's reference unit fails on Ubuntu
24.04 for this reason.
- RPC URL: dual-homed like `decdn_rpc_url`, with the same provenance
guards. A host-provisioned `secret.env` may hold `SPONSORD_RPC_URL`
only, because an EnvironmentFile would override every validated setting.
- Loopback listener only. The deploy fails unless `/healthz` answers
`{"ok":true}` from a listener owned by the running unit.
- A hash record of every restart input (credentials, env files, unit,
binary), written once the daemon is healthy, restarts it after a
rotation, an out-of-band edit, or a run that failed before restarting.
- `sponsord_network` takes chain_id and the PaymentPool address from
`vars/main/networks.yml`, a subset that sync-network-profiles.py now
generates (and `--check`s) alongside decdn_node's.
grafana_alloy:
- `grafana_alloy_sponsord_enabled` scrapes sponsord's `/metrics` with its
own job and service_name. It also re-levels its journald stream from the
plain-text level, and labels its exit errors and panics `error`.
- `grafana_alloy_node_enabled` turns the node scrape off on a host without
a node. `playbooks/group_vars/all.yml` derives both from group membership.
- Existing configs render unchanged except the systemd collector's unit
list, which now includes sponsord: expect one Alloy restart.
Tests:
- New molecule scenario `sponsord` (Debian 12 + Ubuntu 24.04, stub daemon
enforcing upstream's startup checks): the standalone playbook,
idempotence, credential rotation, the fatal readiness gate and the
secret.env provenance hand-offs.
- grafana-cloud now co-locates sponsord with a node.
- validation gains 22 sponsord cases.
- lint-alloy renders sponsord-only and co-located configs and runs both
daemons' log levels through the real binary.
The public sponsord-onramp is not deployed yet. cloud-init stays
node-only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Secret-file overwrite protection, API-token validation, listener validation, and release-path coverage need correction.
Review effort: Balanced
Findings: 4
Open (4)
What changed in this PR
Adds a standalone/co-located Ansible deployment path for sponsord, including hardened service management, network profiles, observability, packaging, and tests.
Changes:
- Adds the
sponsordrole, playbook, inventory configuration, and Galaxy packaging. - Adds credential handling, release/manual installation, readiness checks, and restart tracking.
- Extends Grafana Alloy monitoring and Molecule coverage for sponsord.
| File | Description |
|---|---|
scripts/sync-network-profiles.py |
Generates sponsord network profiles. |
README.md |
Lists the new role and playbook. |
docs/requirements.md |
Documents tested sponsord platforms. |
ansible/tests/alloy-config/validate.sh |
Tests sponsord Alloy pipelines. |
ansible/tests/alloy-config/render.yml |
Renders sponsord Alloy variants. |
ansible/roles/sponsord/vars/main/networks.yml |
Adds generated chain configuration. |
ansible/roles/sponsord/vars/main/arch.yml |
Defines release architecture mappings. |
ansible/roles/sponsord/templates/sponsord.service.j2 |
Adds the hardened systemd unit. |
ansible/roles/sponsord/templates/sponsord.env.j2 |
Renders non-secret daemon settings. |
ansible/roles/sponsord/tasks/main.yml |
Implements validation, provisioning, and readiness. |
ansible/roles/sponsord/tasks/install.yml |
Implements manual and release installation. |
ansible/roles/sponsord/README.md |
Documents setup and operations. |
ansible/roles/sponsord/meta/main.yml |
Adds role metadata. |
ansible/roles/sponsord/handlers/main.yml |
Adds restart handling. |
ansible/roles/sponsord/files/sponsord-release-KEYS.asc |
Adds trusted release keys. |
ansible/roles/sponsord/files/secret.env.example |
Provides an RPC secret example. |
ansible/roles/sponsord/defaults/main.yml |
Defines role configuration defaults. |
ansible/roles/grafana_alloy/templates/config.alloy.j2 |
Adds sponsord metrics and log processing. |
ansible/roles/grafana_alloy/tasks/preflight.yml |
Validates new Alloy settings. |
ansible/roles/grafana_alloy/README.md |
Documents sponsord observability. |
ansible/roles/grafana_alloy/defaults/main.yml |
Adds per-daemon monitoring toggles. |
ansible/README.md |
Documents sponsord deployment. |
ansible/playbooks/sponsord.yml |
Adds the standalone sponsord playbook. |
ansible/playbooks/site.yml |
Imports sponsord deployment. |
ansible/playbooks/group_vars/all.yml |
Derives monitoring toggles from groups. |
ansible/molecule/validation/includes/sponsord-secret-env-case.yml |
Adds secret-file validation cases. |
ansible/molecule/validation/converge.yml |
Adds sponsord negative tests. |
ansible/molecule/sponsord/verify.yml |
Verifies deployed sponsord state. |
ansible/molecule/sponsord/side_effect.yml |
Tests rotations and readiness failures. |
ansible/molecule/sponsord/prepare.yml |
Prepares sponsord test hosts. |
ansible/molecule/sponsord/molecule.yml |
Defines the sponsord scenario. |
ansible/molecule/sponsord/files/sponsord-stub |
Adds the test daemon stub. |
ansible/molecule/sponsord/converge.yml |
Exercises the standalone playbook. |
ansible/molecule/grafana-cloud/verify.yml |
Verifies co-located monitoring. |
ansible/molecule/grafana-cloud/prepare.yml |
Stages sponsord test credentials. |
ansible/molecule/grafana-cloud/converge.yml |
Adds sponsord to the co-located scenario. |
ansible/Makefile |
Adds sponsord deployment targets. |
ansible/inventory/hosts.yml.example |
Adds the optional sponsord group. |
ansible/inventory/host_vars/decdn-node-1/secret.yml.example |
Documents inventory RPC configuration. |
ansible/inventory/group_vars/sponsord_hosts.yml |
Adds sponsord inventory examples. |
ansible/galaxy/README.md |
Lists sponsord in the collection. |
ansible/galaxy/galaxy.yml |
Updates collection metadata. |
ansible/galaxy/CHANGELOG.md |
Records the new role. |
ansible/galaxy/build.sh |
Packages the sponsord role. |
AGENTS.md |
Adds sponsord repository guidance. |
.github/workflows/upstream-drift.yml |
Documents the new generated mirror. |
.github/workflows/molecule.yml |
Notes the expanded scenario count. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…e mode Address the PR #82 review. - secret.env: an existing file with no provenance record now needs sponsord_secret_env_overwrite_host_file before an inventory URL replaces it. decdn_node's no-record exemption covers hosts that predate its record; this role writes its record straight after the file, so an unrecorded file is always the operator's. - API token: mirror upstream's Secret::resolve. Strip one trailing "\n" / "\r\n", then reject any other line break. A token with a line break inside can't be sent in an Authorization header, yet upstream would start on it. - sponsord_bind_address: every octet must be 0-255. Before, 127.999.0.1 passed and only failed at daemon start. - Release mode now runs in molecule/sponsord against a locally signed loopback mirror. It covers: - upstream's archive naming; - the stamp write, and a re-run with the mirror down; - rejection of a bad checksum, a bad signature and a binary that is not the pinned version, with no stamp left behind; - retained staging dirs; - manual mode clearing the stamp. - validation: cases for the octet, the unrecorded overwrite and the multi-line token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dn/sponsord#36 The 0440 credential mode was observed on Ubuntu 24.04's systemd 255 (Debian 12's 252 writes 0400); the exact release that changed it is not pinned, so stop claiming "254+". Also drop an untested Debian 13 claim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
This adds a
sponsordrole andplaybooks/sponsord.yml(make deploy-sponsord;site.ymlimports it too). They deploy decdn/sponsord, the onboarding sponsor: it signs capabilities with the treasury key and keeps the PaymentPool topped up. The role works in three setups:sponsord_hostsgroup);decdn_nodes;decdn.nodeGalaxy collection.The role covers the daemon only. The public
sponsord-onrampis a follow-up. cloud-init stays node-only, because its lint allows onlydecdn_nodes.Role behaviour
sponsord-v*release yet. A GPG-verified release is the other option; its version stamp is written only after an exact--versionmatch.DynamicUser. The API token, treasury keystore and password arrive asLoadCredential=credentials, never as environment variables.ExecStartPrecopies the keystore 0600 into the unit's private RuntimeDirectory.sponsordbuild on Ubuntu 24.04 found this. Upstream's owndeploy/systemd/sponsord.servicefails there for the same reason, so it's worth an issue upstream.decdn_rpc_url. Asecret.envwritten on the host may holdSPONSORD_RPC_URLonly. As an EnvironmentFile it would override the validated bind address, pool and contract./healthzanswers{"ok":true}from a listener owned by the running unit. The daemon binds only after the keystore decrypts and the on-chain pool-owner check passes.sponsord_networksupplies chain_id and the PaymentPool address. It readsroles/sponsord/vars/main/networks.yml, a subset thatscripts/sync-network-profiles.pynow generates alongside decdn_node's.--checkcovers both files, so the upstream-drift job needs no change.grafana_alloy
grafana_alloy_sponsord_enabled: scrapes sponsord's/metrics(job="sponsord",service_name="sponsord"). It also re-levels sponsord's journald stream from the plain-text level, because journald files all stdout as info. Its exit errors and panics getlevel="error".grafana_alloy_node_enabled: turns the decdn-node scrape off on a host without a node.playbooks/group_vars/all.ymlderives both from group membership. They are host-scoped, so a co-located host's two plays render one config.sponsord. Expect one Alloy restart on the next deploy.Test plan
make molecule: all 12 scenarios pass.sponsordscenario, on Debian 12 and Ubuntu 24.04, using a stub daemon that enforces upstream's startup checks. It covers:grafana-cloudco-locates sponsord with a node.validationhas 25 new sponsord cases.make lint-alloy: the real Alloy 1.19.2 binary loads the sponsord-only and co-located configs, and both daemons' log levels are checked through it.make lint;make lint-ansible(production profile).make security(0 HIGH);make test-scripts;make galaxy-check(four roles).scripts/sync-network-profiles.py <decdn> --check: both mirrors are current, and a stale sponsord mirror exits 1.sponsordbuild on Ubuntu 24.04 (not committed). With the role's unit, the daemon gets past credential loading, the keystore permission check and decryption. It then stops only at the deliberately unreachable RPC endpoint.Known follow-ups:
molecule checkstep;🤖 Generated with Claude Code