Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
!**/.env.example
# An operator's local Compose override (compose/README.md); decdn-compose includes it
compose/compose.override.yaml
# A hosts.yml filled in inside this checkout: real host IPs never belong in this public
# repo. The fleet example's own .gitignore must not ignore it (a private copy commits
# it), and this file does not travel with `cp -R examples/collection-fleet`.
/examples/collection-fleet/inventory/hosts.y*ml

# Private keys & certs (defense-in-depth; real keystores live under /etc on the host)
**/cert.pem
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ monitoring/ # Grafana dashboards + Prometheus alert rules (maintaine
sponsord/ # sponsord's (+ promtool unit tests), Ansible/Compose only
iroh-relay/ # the relay's (+ promtool unit tests, exported-metrics.txt), Ansible and Compose
docs/ # cross-path operator docs: node-operators.md + publishers.md (the front doors), requirements.md, lifecycle.md
examples/collection-fleet/ # a consumer project for the Galaxy collections: requirements.yml, ansible.cfg, .gitignore (the inventory comes from ansible/inventory/'s examples)
examples/collection-fleet/ # a consumer project for the Galaxy collections: requirements.yml, ansible.cfg, .gitignore, inventory/ (ansible/inventory/'s templates: every tracked file identical but the hosts-*.yml.example headers, which end at the first blank line and hold only comments; make test-scripts checks, and that both parse to the same inventory, so change both. The root .gitignore keeps a hosts.yml filled in there out of this public repo)
scripts/ # upstream-mirror generators, the compose Alloy config generator (render-compose-alloy.sh), the decdn bump (open-decdn-bump.sh, bump_decdn.py), the release script (release.sh, git-cliff: ../cliff.toml) and its gate (+ its Artifact Hub changes generator), the molecule driver (molecule.sh), ansible/Makefile's LIMIT preflight (limit-guard.sh)
```

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ targets, so a local pass means a CI pass. Deploy targets live in
| `make lint-alloy` | renders `roles/grafana_alloy`'s templates and validates them with the **real** digest-pinned Alloy binary. The molecule stub exits 0 for everything, so this is the only gate that proves the config loads. It also checks that `compose/alloy/config.alloy` (the Compose `alloy` profile's render of the same template) is current, and runs it in Alloy: every component healthy, each daemon scraped only under the profiles that start it. `ALLOY_BIN=<path>` skips the download. |
| `make lint-compose` | reads `compose/compose.yaml` as written (`yq`) against `compose/tests/bind-sources.jq` (every bind mount's `create_host_path`), then renders it with every profile on (with its example env, without env files, with an empty `.env`) and asserts `compose/tests/invariants.jq`, `inline-env.jq` and `fail-closed.jq` |
| `make lint-cloud-init` | `cloud-init schema` on `cloud-init/user-data-node.yaml` and `cloud-init/user-data-publisher.yaml`, then `cloud-init/tests/lint.py` on each: only the templates' top-level modules and no YAML anchors, no secrets (only the bootstrap's own files, once each, plain `content` only, no secret-looking keys or assignments), no hardening skip, `release` installs verified against the vendored keys with a host-generated node wallet (trust knobs only in their own group's `vars`, no moved keyring or secret path), only localhost, in `decdn_nodes` and/or `sponsord_hosts` (an origin only beside `decdn_nodes` and with a backend, every backend key only in `decdn_origin_nodes.vars`, the onramp only beside sponsord), a keyed admin account, `runcmd` exactly stage 1, shellcheck-clean scripts, and a collection lock that covers `ansible/requirements.yml` (needs `cloud-init`, `shellcheck`, `yq`). `CLOUD_INIT_FILE=<path>` checks your own filled-in copy. |
| `make test-scripts` | `tests/scripts-test.sh`: the `ansible/Makefile` scoping guards (dry runs, and `limit-guard.sh` on a fixture inventory), the molecule driver's selection guards and locks (no containers), the split scenarios' shared inventories, the release gate, `scripts/release.sh` on a fixture repo and `pr-title.yml`'s check (the former skipped without `git-cliff` or `ssh-keygen` on PATH, failed in CI), the negative cases of `lint-compose` and `lint-cloud-init` (the latter skipped without `cloud-init` on PATH), `compose/decdn-compose`'s unit tests (`compose/tests/test_decdn_compose.py`), `scripts/bump_decdn.py`'s (`scripts/tests/test_bump_decdn.py`), that `compose/alloy/config.alloy` is a fresh render of the `grafana_alloy` template (`scripts/render-compose-alloy.sh --check`; needs ansible-core, failed in CI without it) and that Compose's `grafana/alloy` image is `grafana_alloy_version`, and the cloud-init bootstrap's contracts: `baseline-plays.sh` on fixtures and on the real playbooks (every node.yml, origin.yml and sponsord.yml play runs `baseline` tagged `baseline`), the two templates' shared stage 1, login hint and `final_message`, `bootstrap.sh`'s groups against lint.py's, and lint.py's `FORBIDDEN_VARS` against the roles' keyrings and secret paths; and the Galaxy collections' shipped playbooks: every playbook in a `playbooks.txt`, `galaxy/stage-playbooks.py` on both collections and its refusals, that it changes only role names, `decdn.node`'s `backup.yml`/`decommission.yml` against the fleet-wide node plays in both directions (needs yq, failed in CI without it), and the two roles' shared `*_backup_local_dir` default. `UPSTREAM=<decdn checkout>` adds the sync generators' exit codes. |
| `make test-scripts` | `tests/scripts-test.sh`: the `ansible/Makefile` scoping guards (dry runs, and `limit-guard.sh` on a fixture inventory), the molecule driver's selection guards and locks (no containers), the split scenarios' shared inventories, the release gate, `scripts/release.sh` on a fixture repo and `pr-title.yml`'s check (the former skipped without `git-cliff` or `ssh-keygen` on PATH, failed in CI), the negative cases of `lint-compose` and `lint-cloud-init` (the latter skipped without `cloud-init` on PATH), `compose/decdn-compose`'s unit tests (`compose/tests/test_decdn_compose.py`), `scripts/bump_decdn.py`'s (`scripts/tests/test_bump_decdn.py`), that `compose/alloy/config.alloy` is a fresh render of the `grafana_alloy` template (`scripts/render-compose-alloy.sh --check`; needs ansible-core, failed in CI without it) and that Compose's `grafana/alloy` image is `grafana_alloy_version`, and the cloud-init bootstrap's contracts: `baseline-plays.sh` on fixtures and on the real playbooks (every node.yml, origin.yml and sponsord.yml play runs `baseline` tagged `baseline`), the two templates' shared stage 1, login hint and `final_message`, `bootstrap.sh`'s groups against lint.py's, and lint.py's `FORBIDDEN_VARS` against the roles' keyrings and secret paths; and the Galaxy collections' shipped playbooks: every playbook in a `playbooks.txt`, `galaxy/stage-playbooks.py` on both collections and its refusals, that it changes only role names, `decdn.node`'s `backup.yml`/`decommission.yml` against the fleet-wide node plays in both directions (needs yq, failed in CI without it), and the two roles' shared `*_backup_local_dir` default; and `examples/collection-fleet/inventory/` against `ansible/inventory/` (the same tracked files, byte for byte but for the hosts examples' comment headers; both hosts examples parse to the same inventory, needs ansible-core, failed in CI without it), and the ignore rules that keep a `secret.*` or a filled-in `hosts.yml` there out of git, with negative cases on a fixture. `UPSTREAM=<decdn checkout>` adds the sync generators' exit codes. |
| `make security` | KICS IaC scan of `ansible/`, the rendered chart and `compose/` (digest-pinned engine, fail on HIGH) |
| `make galaxy-check` | build the `decdn.node` and `decdn.publisher` collections, run galaxy-importer's checks on each, then install each and check its playbooks by FQCN, firewall holes included (`ansible/galaxy/check-playbooks.sh`; `make -C ansible galaxy-check-node` / `galaxy-check-publisher` for one) |

Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,14 @@ one VM); on Kubernetes, the [Helm chart](charts/decdn-node/README.md).

From your workstation (ansible-core ≥ 2.15, e.g. `pipx install ansible-core`) with an
SSH key in `~/.ssh/`, and a fresh Debian or Ubuntu host. The collections bring the roles
and playbooks; you clone this repository once, only to copy the example project and
inventory templates into a private repository of your own:
and playbooks; you clone this repository once, only to copy the example project (its
inventory templates included) into a private repository of your own:

```bash
git clone --depth 1 https://github.com/decdn/devops.git
cp -R devops/examples/collection-fleet my-fleet && cd my-fleet # make this your private repo
ansible-galaxy collection install -r requirements.yml -p collections
mkdir inventory && cp ../devops/ansible/inventory/hosts-node.yml.example inventory/hosts.yml
cp -R ../devops/ansible/inventory/{group_vars,host_vars} inventory/
cp inventory/hosts-node.yml.example inventory/hosts.yml
cp inventory/host_vars/decdn-node-1/secret.yml.example inventory/host_vars/decdn-node-1/secret.yml
$EDITOR inventory/hosts.yml # ansible_host: the node's IP
$EDITOR inventory/host_vars/decdn-node-1/main.yml # decdn_region; add decdn_node_generate_keystore: true (the node makes its wallet)
Expand Down
4 changes: 3 additions & 1 deletion ansible/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ Ansible loads `group_vars/` and `host_vars/` from beside whichever `hosts.yml` y
the overlay carries its own: create them next to its `hosts.yml`, starting from the public
`inventory/group_vars/` and `inventory/host_vars/` templates. Git-ignore
`host_vars/*/secret.*` in the private repo, so it never tracks a per-node secret. The public
`inventory/group_vars/` and `inventory/host_vars/` are **not** loaded for it. Add child groups
`inventory/group_vars/` and `inventory/host_vars/` are **not** loaded for it. (Deploying from
the Galaxy collections instead? [`examples/collection-fleet/`](../examples/collection-fleet/README.md)
is a ready-made private repository for that path, inventory templates and `.gitignore` included.) Add child groups
of your own when hosts differ by group (disk size, an egress cap on metered bandwidth), each
with a `group_vars/<group>.yml`. Origins go in `decdn_origin_nodes`, as a child of
`decdn_nodes` (see `inventory/hosts-publisher.yml.example`). Settings every host needs regardless of
Expand Down
6 changes: 4 additions & 2 deletions ansible/galaxy/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ ansible-playbook -i inventory/hosts.yml decdn.node.decommission -l <host>
```

Start the inventory from
[`hosts-node.yml.example`](https://github.com/decdn/devops/blob/main/ansible/inventory/hosts-node.yml.example)
and the `group_vars/` and `host_vars/` beside it. The playbooks open the node's QUIC
[`hosts-node.yml.example`](https://github.com/decdn/devops/blob/main/examples/collection-fleet/inventory/hosts-node.yml.example)
and the `group_vars/` and `host_vars/` beside it, in
[`examples/collection-fleet/`](https://github.com/decdn/devops/tree/main/examples/collection-fleet),
a ready-made project for these collections. The playbooks open the node's QUIC
port in the baseline firewall themselves (`baseline_extra_inbound`, from their own
`group_vars/`, which ship beside them), and `decommission` refuses more hosts than
`decdn_decommission_max_hosts` (default 1).
Expand Down
6 changes: 4 additions & 2 deletions ansible/galaxy/publisher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ ansible-playbook -i inventory/hosts.yml decdn.publisher.decommission -l <host>
```

Start the inventory from
[`hosts-publisher.yml.example`](https://github.com/decdn/devops/blob/main/ansible/inventory/hosts-publisher.yml.example)
and the `group_vars/` and `host_vars/` beside it. Beyond the roles, the playbooks
[`hosts-publisher.yml.example`](https://github.com/decdn/devops/blob/main/examples/collection-fleet/inventory/hosts-publisher.yml.example)
and the `group_vars/` and `host_vars/` beside it, in
[`examples/collection-fleet/`](https://github.com/decdn/devops/tree/main/examples/collection-fleet),
a ready-made project for these collections. Beyond the roles, the playbooks
bring:

- **the firewall holes**, from the groups a host is in: the node's udp/4433, Caddy's
Expand Down
13 changes: 7 additions & 6 deletions ansible/inventory/group_vars/decdn_nodes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
# The sensitive rpc_url is provisioned as /etc/decdn/decdn.env on the host, or (if
# you would rather the role authored that file) in a git-ignored secret.yml.

# The node's public QUIC firewall hole (udp/4433) is NOT set here. It lives in
# playbooks/group_vars/ (all.yml builds it) so it applies to every inventory,
# including a private fleet overlay passed with INVENTORY=.
# The node's public QUIC firewall hole (udp/4433) is NOT set here. The playbooks set
# it from their own group_vars/ (all.yml builds it), which ship beside them in
# decdn/devops and in its Galaxy collections, so it applies to any inventory.
#
# Settings every node shares (decdn_network, decdn_node_install_method, …) can move
# here from host_vars/<node>/main.yml once you run more than one node. host_vars
Expand All @@ -25,9 +25,10 @@

# --- Cache sizing -----------------------------------------------------------------
# Size to each host's disk and to the largest object you expect to serve; see
# docs/requirements.md for the disk formula. A node rejects any blob larger than
# decdn_max_blob_size_mb with BlobTooLarge, and decdn_cache_size_mb must be at
# least that. Override per host in host_vars when disks differ.
# https://github.com/decdn/devops/blob/main/docs/requirements.md for the disk formula.
# A node rejects any blob larger than decdn_max_blob_size_mb with BlobTooLarge, and
# decdn_cache_size_mb must be at least that. Override per host in host_vars when
# disks differ.
# decdn_max_blob_size_mb: 1024 # role default
# decdn_cache_size_mb: 10240 # role default (10 GiB)

Expand Down
2 changes: 1 addition & 1 deletion ansible/inventory/host_vars/decdn-node-1/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#
# CONTRACT ADDRESSES ARE NOT INVENTED HERE, and not copied here either:
# decdn_network below selects the role's mirror of upstream's deployment manifest
# (roles/decdn_node/vars/main/networks.yml, regenerated by
# (roles/decdn_node/vars/main/networks.yml, regenerated by decdn/devops's
# scripts/sync-network-profiles.py from contracts/deployments/<chainId>.json in
# decdn/decdn).
# It is the same manifest `decdn config init --chain arbitrum-sepolia` bakes in, and
Expand Down
2 changes: 1 addition & 1 deletion ansible/inventory/host_vars/origin-1/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# CONTRACT ADDRESSES ARE NOT INVENTED HERE, and not copied here either:
# decdn_network below selects the role's mirror of upstream's deployment manifest
# (roles/decdn_node/vars/main/networks.yml, regenerated by
# (roles/decdn_node/vars/main/networks.yml, regenerated by decdn/devops's
# scripts/sync-network-profiles.py from contracts/deployments/<chainId>.json in
# decdn/decdn).
# It is the same manifest `decdn config init --chain arbitrum-sepolia` bakes in, and
Expand Down
12 changes: 6 additions & 6 deletions ansible/inventory/hosts-node.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@
# affected host: `sudo --version` reports "sudo-rs" and/or /usr/bin/sudo.ws exists. Leave
# it commented on Debian bookworm / Ubuntu noble — that path does not exist there.

# Public deCDN cache nodes — provisioned by playbooks/node.yml (`make deploy-node`)
# and by site.yml (`make deploy`). A cache node has no origin backend: it fills a
# miss from other nodes (node-to-node pull-through). Origins are a publisher's
# (hosts-publisher.yml.example).
# Public deCDN cache nodes — provisioned by the node.yml playbook (which site.yml
# imports too). A cache node has no origin backend: it fills a miss from other nodes
# (node-to-node pull-through). Origins are a publisher's (hosts-publisher.yml.example).
# Per-node config (contract addresses, region, version, …) goes in the committed
# inventory/host_vars/<name>/main.yml; the one secret, rpc_url, goes in a git-ignored
# secret.yml beside it (copy the shipped secret.yml.example there).
# inventory/host_vars/<name>/main.yml; secrets (rpc_url, and any other
# secret.yml.example lists) go in a git-ignored secret.yml beside it (copy the shipped
# secret.yml.example there).
decdn_nodes:
hosts:
decdn-node-1:
Expand Down
Loading
Loading