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
9 changes: 2 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,10 @@ jobs:
run: make deps # must precede lint/syntax-check
- name: Lint (yamllint + ansible-lint)
run: make lint
- name: Syntax-check playbooks
- name: Syntax-check playbook
# Dummy inventory: no real hosts.yml is committed upstream (only hosts.yml.example),
# and --syntax-check only parses, it never connects.
run: |
for p in playbooks/site.yml playbooks/anvil.yml playbooks/add-dev-user.yml; do
echo "::group::syntax-check $p"
ansible-playbook "$p" --syntax-check -i localhost,
echo "::endgroup::"
done
run: ansible-playbook playbooks/site.yml --syntax-check -i localhost,

# Build the public `decdn.node` collection and run galaxy-importer's checks —
# the same validation Galaxy runs on upload (metadata, license, README, embedded
Expand Down
39 changes: 0 additions & 39 deletions .github/workflows/molecule.yml

This file was deleted.

23 changes: 2 additions & 21 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,14 @@
# These patterns are defense-in-depth in case anyone copies generated files in.
# ─────────────────────────────────────────────────────────────────────────────

# Shared anvil mnemonic / env (the master secret for the devnet)
**/anvil.env
# Environment files (may hold secrets)
.env
**/.env
!**/.env.example

# Caddy basic-auth material (bcrypt hashes + the username->hash registry)
**/*.basicauth
**/*.users.tsv
!**/*.example

# Cloudflare Tunnel credentials & cert
**/cloudflared/*.json
# Private keys & certs (defense-in-depth; real keystores live under /etc on the host)
**/cert.pem
**/*.pem
**/config.yml
!**/config.yml.example

# anvil chain state + deployment manifests (regenerated, machine-specific)
**/state.json
**/deployments.json

# Foundry build output (if contracts get compiled locally)
**/out/
**/cache/
**/broadcast/
**/.foundry/

# Local, per-developer Claude Code settings (not shared)
.claude/settings.local.json
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# pip install pre-commit && make hooks # one-time install
# make lint # run on all files
#
# Heavier Ansible checks (ansible-lint, syntax-check, KICS security scan,
# molecule) run in CI only — see .github/workflows/. ansible-lint's production
# profile already carries the Ansible security rules; this file is the fast
# local gate (hygiene, shellcheck, yamllint, markdown).
# Heavier Ansible checks (ansible-lint, syntax-check, KICS security scan) run in
# CI only — see .github/workflows/. ansible-lint's production profile already
# carries the Ansible security rules; this file is the fast local gate (hygiene,
# shellcheck, yamllint, markdown).
minimum_pre_commit_version: "3.5.0"
default_install_hook_types: [pre-commit]

Expand Down
80 changes: 33 additions & 47 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,35 @@ economic claims — those live in `decdn/adr/`. If something here states a proto

## Hard rules

1. **Never commit secrets.** No mnemonics, passwords, bcrypt hashes, private keys, API
tokens, or tunnel credentials in any tracked file. Secrets are *generated on the
target host* by the role tasks (e.g. basic-auth creds) and stored under `/etc/<svc>/` with `chmod 600`
and a dedicated owner. The repo ships `*.example` templates for secret files only
(non-secret config may be committed directly). The root `.gitignore` is a backstop —
do not rely on it; keep secrets out by design.
2. **Localhost-only by default.** Service daemons (anvil, etc.) bind `127.0.0.1`. The
*only* sanctioned public path is an explicit reverse proxy with auth + TLS in front
(e.g. the anvil devnet's public-HTTPS Caddy, or that same proxy behind an outbound
tunnel). Never bind a *backend* to `0.0.0.0` or expose its raw port — only the
auth-terminating proxy faces the internet.
1. **Never commit secrets.** No passwords, private keys, API tokens, or keystores in any
tracked file. Secrets are *generated on — or operator-provisioned to — the target host*
(e.g. the node's eth keystore, or `rpc_url` which may embed an API key) and stored under
`/etc/<svc>/` with `chmod 600` and a dedicated owner. The repo ships `*.example`
templates for secret files only (non-secret config may be committed directly). The root
`.gitignore` is a backstop — do not rely on it; keep secrets out by design.
2. **Localhost-only by default.** Service daemons bind `127.0.0.1` (e.g. the node's metrics
and admin RPC). A service that must accept public traffic declares exactly one hole (the
node's QUIC udp/4433) via `baseline_extra_inbound`; if a service ever needs an HTTP-facing
public path, front it with an explicit reverse proxy that terminates auth + TLS. Never
bind a *backend* to `0.0.0.0` or expose its raw port.
3. **`etc/` mirrors the target filesystem** (the `services/` convention, currently unused —
see Layout). Put a config where it installs:
`services/<svc>/etc/systemd/system/foo.service` → `/etc/systemd/system/foo.service`.
4. **Scripts are idempotent and fail loud.** `set -euo pipefail`, re-runnable, refuse to
overwrite existing secrets, and require typed confirmation before destructive ops
(e.g. wiping chain state).
overwrite existing secrets, and require typed confirmation before destructive ops.
5. **Show before installing.** When building or changing infra, present the files; the
playbook run on the target (`make deploy` / `deploy-anvil`) is what mutates a host — it
runs there, not here.
playbook run on the target (`make deploy`) is what mutates a host — it runs there, not
here.

## Layout

The repo's one active unit is the Ansible project; `services/` is a reserved convention:

```
ansible/ # the deployment project (DevSec-hardened, lean roles)
playbooks/ # site.yml (decdn node), anvil.yml (devnet), add-dev-user.yml
roles/ # baseline, decdn_node, anvil, caddy, contracts(stub)
inventory/ molecule/ galaxy/ # see ansible/README.md
playbooks/ # site.yml (decdn node)
roles/ # baseline, decdn_node
inventory/ galaxy/ # see ansible/README.md

services/<name>/ # reserved: future imperative bash + systemd units (currently empty)
README.md bin/ etc/ (filesystem-mirrored) contracts/
Expand All @@ -58,23 +57,15 @@ secrets; generated on host) and #2 (localhost-only by default) hold for both con

## Current services

- **`ansible/`** — the team's declarative deployment project. **Primary: the public deCDN
node** (`playbooks/site.yml` → baseline + `decdn-node`), installed from a pinned GitHub
release tarball under a hardened systemd unit; public QUIC udp/4433, loopback
metrics/admin, operator-provisioned eth keystore, required chain knobs (no baked protocol
facts — sourced from ADRs). **Internal: the anvil devnet** (`playbooks/anvil.yml` →
baseline + anvil + Caddy basic-auth; anvil stays loopback while Caddy fronts it on public
https/443 with auto-TLS — `caddy_public: true`, default; flip to loopback-only for a tunnel;
repeated basic-auth failures are fail2ban-banned via the public-only `caddy-rpc` jail)
— team tooling, not the product. Shared
DevSec-hardened `baseline`. See `ansible/README.md`. (On-chain node stake/registration,
ADR 019 Phase 2, is an operator step, not automated.)
- **`contracts` role** — intentional **stub**, out of scope for v1, not wired into any
playbook. CREATE2 deploys will plug in after the `anvil` role; until then it's a no-op.

The original bash + systemd `services/anvil-devnet/` devnet has been **removed** —
superseded by `ansible/` (the `anvil.yml` path). `services/` remains the documented
convention for any future imperative bash + systemd unit, but currently holds none.
- **`ansible/`** — the team's declarative deployment project. **The public deCDN node**
(`playbooks/site.yml` → baseline + `decdn-node`), installed from a pinned GitHub release
tarball under a hardened systemd unit; public QUIC udp/4433, loopback metrics/admin,
operator-provisioned eth keystore, required chain knobs (no baked protocol facts — sourced
from ADRs), over a shared DevSec-hardened `baseline`. See `ansible/README.md`. (On-chain
node stake/registration, ADR 019 Phase 2, is an operator step, not automated.)

`services/` remains the documented convention for any future imperative bash + systemd
unit, but currently holds none.

## Commands

Expand All @@ -87,27 +78,22 @@ make hooks # one-time: install pre-commit git hook (pip install pre-c
make lint # all pre-commit hooks on all files (hygiene, shellcheck, yamllint, markdown)
make lint-ansible # vendor collections + full ansible-lint (production profile)
make security # KICS IaC scan of ansible/ (pinned engine image)
make molecule # containerised converge/verify of the anvil stack (needs Docker)

# Ansible deploys — run from ansible/ (see ansible/README.md for the full flow)
cd ansible
make deps # vendor pinned Galaxy collections into ./collections
make check / deploy # deCDN node (site.yml): dry-run / provision
make check-anvil / deploy-anvil # anvil devnet (anvil.yml)
make add-dev USER_NAME=alice # mint + reveal an anvil basic-auth dev user
make check / deploy # deCDN node (site.yml): dry-run / provision
make build / galaxy-check # stage + build the decdn.node collection, then validate it
```

**Galaxy collection (`decdn.node`).** The public roles (`baseline` + `decdn_node`) ship as
a distributable collection; the internal anvil tooling does not. The overlay lives in
`ansible/galaxy/` and is staged into a clean collection tree by `galaxy/build.sh` — there is
**no** `galaxy.yml` at the `ansible/` root (that would make ansible-lint/molecule treat the
deploy project as a collection). Build/validate with `make build` / `make galaxy-check`;
**publishing is a manual step** (`ansible-galaxy collection publish`), not automated.
**Galaxy collection (`decdn.node`).** The two roles (`baseline` + `decdn_node`) ship as a
distributable collection. The overlay lives in `ansible/galaxy/` and is staged into a clean
collection tree by `galaxy/build.sh` — there is **no** `galaxy.yml` at the `ansible/` root
(that would make ansible-lint treat the deploy project as a collection). Build/validate with
`make build` / `make galaxy-check`; **publishing is a manual step**
(`ansible-galaxy collection publish`), not automated.

**Gotcha — pre-commit is local-only.** Hygiene/shellcheck/yamllint/markdown run via
`make hooks`/`make lint` on your machine, **not** in CI. CI (`.github/workflows/`) is the
blocking gate and runs `ansible-lint` + KICS + `galaxy-build` (on `ansible/**`) + `actionlint`. `ansible-lint`
is **not** a per-commit hook (it needs collections vendored) — run `make lint-ansible`.
A separate `molecule.yml` workflow runs the containerised converge/verify in CI too, so
`make molecule` is not purely local.
17 changes: 6 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,15 @@ tree), and `markdownlint`.
| `make lint` | run all pre-commit hooks on every file (the full local hygiene gate) |
| `make lint-ansible` | install Galaxy collections + run `ansible-lint` (its production profile includes the Ansible security rules) |
| `make security` | KICS IaC security scan of `ansible/` (engine image; CI uses the official KICS action) |
| `make molecule` | containerised converge/verify of the anvil stack (needs Docker) |

`ansible-lint` is **not** a per-commit hook (it needs the collections installed).
Run it on demand with `make lint-ansible`, or `pre-commit run ansible-lint --hook-stage manual`.

## CI overview

- **`ci.yml`** — `ansible-lint` + `kics` (on `ansible/**`) and `actionlint`.
Bash-only PRs skip the Ansible jobs. Hygiene/shellcheck/markdownlint run via
**pre-commit locally only** (`make hooks` / `make lint`), not in CI.
- **`molecule.yml`** — runs on `ansible/**` changes; **blocking**. Add it as a
required status check in branch protection (Settings → Branches) once proven,
alongside the `ci.yml` jobs.
- **`ci.yml`** — `ansible-lint` + `galaxy-build` + `kics` (on `ansible/**`) and
`actionlint`. Bash-only PRs skip the Ansible jobs. Hygiene/shellcheck/markdownlint
run via **pre-commit locally only** (`make hooks` / `make lint`), not in CI.

## Supply-chain / pinning rules

Expand All @@ -56,7 +52,6 @@ Run it on demand with `make lint-ansible`, or `pre-commit run ansible-lint --hoo

## Solidity

There is no Foundry project in the repo yet — deterministic on-devnet contract
deployment is a future Ansible role (`ansible/roles/contracts/`, currently a stub).
The `forge fmt` pre-commit hook and the CI `solidity` job are present but commented
out; they self-activate once real sources land.
There is no Foundry project in the repo yet. The `forge fmt` pre-commit hook and the
CI `solidity` job are present but commented out; they self-activate once real sources
land.
5 changes: 1 addition & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Convenience targets for the deCDN DevOps monorepo.
# Run from the repo root. Ansible-specific work is delegated to ansible/Makefile.
.PHONY: help hooks lint lint-ansible security molecule galaxy-build galaxy-check
.PHONY: help hooks lint lint-ansible security galaxy-build galaxy-check
SHELL := /bin/bash

# Local KICS runs use the engine image pinned by digest. CI runs the official
Expand Down Expand Up @@ -32,9 +32,6 @@ security: ## KICS IaC security scan of ansible/ (CI runs the official
--report-formats json --output-path /repo/kics-results \
--no-progress --fail-on high

molecule: ## containerised converge/verify of the anvil stack
$(MAKE) -C ansible molecule

galaxy-build: ## stage + build the decdn.node Galaxy collection artifact
$(MAKE) -C ansible build

Expand Down
Loading
Loading