Farrier

Contributing

Farrier is MIT-licensed and contributions are welcome: connectors for more devices, fixes, docs and UI. This page covers how the repository is laid out and what a good pull request looks like.

Repository layout

farrier/                      Python engine (package "farrier")
├── api.py                  FastAPI app: REST API + background tick loop
├── cli.py                  `farrier` command
├── engine.py               renewal state machine (plan / advance / tick)
├── policy.py               CA/B Forum schedule + renewal timing maths
├── scheduler.py            change windows and daily-cap leveling
├── discovery.py            TLS scanning and target parsing
├── db.py                   SQLite store
├── vault.py                key sealing and secret references
├── issuers/                ACME (RFC 8555, DNS-01) and the local CA
├── connectors/             files, keystore, f5, fortigate, panos, manual
└── demo.py                 simulated estate for `farrier demo`
tests/                      pytest suite
examples/                   inventory and DNS hook examples
web/                        Next.js dashboard (what teams install)
└── src/
    ├── app/dashboard/      dashboard screens
    ├── app/api/farrier/      server-side proxy to the Farrier API (+ demo mode)
    ├── components/ui/      primitives: Button, Card, Table, Dialog…
    ├── components/         dashboard, charts, status vocabulary
    ├── lib/                API client, types, policy port, demo data
    └── proxy.ts            sign-in gate for the dashboard
site/                       Next.js public website, a static export (landing page, these docs)
└── src/
    ├── app/page.tsx        landing page
    ├── app/docs/           these docs (MDX)
    └── components/         landing, site header and footer, docs chrome

Development setup

Engine

python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
farrier demo

Dashboard, in a second terminal:

cd web
npm install
echo "FARRIER_API_URL=http://127.0.0.1:8740" > .env.local
npm run dev

Leave FARRIER_API_URL unset to work against the built-in demo data.

Running checks

python -m pytest                     # engine: unit, end-to-end, ACME and connector tests
cd web && npm run lint && npm run build

The engine tests need no network. ACME runs against an in-process RFC 8555 server, and appliance connectors run against mock management APIs (httpx.MockTransport). npm run build also type-checks and prerenders the docs.

Conventions

Python

The engine is grouped by responsibility:

farrier/
  api/          REST API, dashboard configuration routes, MCP server
  core/         renewal engine, policy, change windows, discovery, X.509
  db/           store, SQL dialects, migrations, leader election, backups
  security/     vault, secret store and backends, cloud identity, KMS
  config/       engine settings, dashboard-managed integrations
  monitoring/   health checks, zombie scanner, alert rules, notifications
  events/       CloudEvents and their publishers
  connectors/   F5, FortiGate, PAN-OS, keystores, files, change packs
  dns/          DNS-01 providers and accounts
  issuers/      ACME and the private CA
  storage/      object storage (file, S3, GCS, Azure Blob)
  • Python 3.11+, type hints, from __future__ import annotations.
  • Keep dependencies minimal: cryptography, httpx, fastapi, uvicorn.
  • Engine changes need a test. State-machine changes need an end-to-end test in tests/test_engine.py.
  • Never log or return secrets or private keys.

TypeScript / web

  • Components use design tokens, never raw hex. Read the Design system first.
  • Data fetching goes through the hooks in web/src/lib/api.ts. The browser only talks to /api/farrier/*.
  • web/src/lib/types.ts mirrors the API in farrier/api/. If you change an API response, update the types in the same PR, and update web/src/lib/demo.ts so demo mode stays faithful.
  • web/src/lib/policy.ts is a port of farrier/core/policy.py. Change both together.
  • Docs live in site/src/app/docs/<slug>/page.mdx (preview with npm run dev in site/, port 3100). Document every config key, route and state you add.

Adding a connector

  1. Write the class in farrier/connectors/<kind>.py following Writing a connector, with a module docstring listing its config keys (the API serves it to the dashboard).
  2. Register it in REGISTRY in farrier/connectors/__init__.py.
  3. Add tests against a mock transport in tests/test_appliances.py: deploy, rollback, and failure mid-deploy.
  4. Add a label to CONNECTOR_LABEL in web/src/components/status.tsx, and a section to connectors/page.mdx.
  5. Say in the PR whether you tested against real hardware, and which firmware version.

Pull request checklist

  • python -m pytest passes
  • npm run lint and npm run build pass in web/
  • New behaviour has tests
  • API changes are reflected in web/src/lib/types.ts, the demo data and the API reference
  • New config keys and defaults are documented
  • UI checked in light and dark mode, at mobile width, and with keyboard only
  • No secrets, keys or customer hostnames in code, tests or screenshots

Reporting security issues

Don't open a public issue. Follow the private process on the Security page.