from fastcore.utils import *fastcaddy
Usage
Installation
Install from pypi
$ pip install fastcaddyInstalling Caddy
This project is to help you use the caddy API, rather than a Caddyfile, to use caddy. To use the API, you need to install a plugin for your domain management service. We use Cloudflare, so we’ll document that here. For other domain services, see the Caddy docs for other plugins.
Cloudflare setup
You’ll need a token from Cloudflare with access to modify the necessary settings. Here’s the steps to create a token with the minimal privileges. You’ll need to install the cloudflare pip package, then import:
from cloudflare import CloudflareThen you’ll need create a Cloudflare API token for your user, which we’ll then use to create the less privileged token.
cf_token = os.environ['CLOUDFLARE_API_TOKEN']We can now check that works OK:
cf = Cloudflare(api_token=cf_token)
zones = cf.zones.list()
len(zones.result)8
Replace this with your domain name:
domain = 'answer.ai'
zones = cf.zones.list(name=domain)
assert len(zones.result)==1zone_id = zones.result[0].idHere’s the methods available for modifying DNS records:
client.dns.records.create(*, zone_id, **params) -> Optionalclient.dns.records.update(dns_record_id, *, zone_id, **params) -> Optionalclient.dns.records.list(*, zone_id, **params) -> SyncV4PagePaginationArray[Record]client.dns.records.delete(dns_record_id, *, zone_id) -> Optionalclient.dns.records.edit(dns_record_id, *, zone_id, **params) -> Optionalclient.dns.records.export(*, zone_id) -> strclient.dns.records.get(dns_record_id, *, zone_id) -> Optionalclient.dns.records.import\_(*, zone_id, **params) -> Optionalclient.dns.records.scan(*, zone_id, **params) -> Optional
…and here’s the methods for tokens:
from cloudflare.types.user import (CIDRList, Policy, Token, TokenCreateResponse, TokenUpdateResponse, TokenListResponse,
TokenDeleteResponse, TokenGetResponse, TokenVerifyResponse)client.user.tokens.create(**params) -> Optionalclient.user.tokens.update(token_id, **params) -> objectclient.user.tokens.list(**params) -> SyncV4PagePaginationArray[object]client.user.tokens.delete(token_id) -> Optionalclient.user.tokens.get(token_id) -> objectclient.user.tokens.verify() -> Optional
from cloudflare.types.user.tokens import PermissionGroupListResponse- client.user.tokens.permission_groups.list() -> SyncSinglePage[object]
from cloudflare.types.user.tokens import Value- client.user.tokens.value.update(token_id, **params) -> str
We need these two permissions in our token:
permission_groups = cf.user.tokens.permission_groups.list()
dns_write = next(group for group in permission_groups if group['name'] == 'DNS Write')
zone_read = next(group for group in permission_groups if group['name'] == 'Zone Read')Now we can create it:
new_token = cf.user.tokens.create(
name='caddy_dns',
policies=[{
"effect": "allow",
"resources": { f"com.cloudflare.api.account.zone.{zone_id}": "*" },
"permission_groups": [
{"id": zone_read['id'], "name": "Zone Read"},
{"id": dns_write['id'], "name": "DNS Write"}
]
}]
)
print(new_token.value)Make a copy of this value, which we’ll need for setting up caddy.
Installing caddy
To install caddy, we’ll use a tool called xcaddy. This is written in go. So first install go:
- Mac:
brew install go - Linux:
sudo apt install golang
Note that if you are not on the latest Ubuntu, you’ll need to setup the backport repo before installing go:
sudo add-apt-repository -y ppa:longsleep/golang-backports
sudo apt updateNow we can install xcaddy:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latestAlternatively, you can download the latest xcaddy directly, e.g:
# Change the OS and arch as needed, or remove them to view all options
wget -qO- https://latest.fast.ai/latest/caddyserver/xcaddy/linux_amd64.tar.gzThen we use that to compile caddy with our desired domain plugin (cloudflare, in this case):
mkdir -p ~/go/bin
cd ~/go/bin
./xcaddy build --with github.com/caddy-dns/cloudflareThis gives us a ~/go/bin/caddy binary we can run:
./caddy version
./caddy runSecurely run caddy on start
If you’re using a server or running caddy a lot, you’ll want it to run on start. And if you’re making it publicly accessible, you’ll want it to be secure. This isn’t needed otherwise – you can just ~/go/bin/caddy run to run it manually (you may want to add ~/go/bin to your PATH env var).
To set this up, run from this repo root:
./setup_service.shIf all went well, you should see output like this:
● caddy.service - Caddy
Loaded: loaded (/etc/systemd/system/caddy.service; enabled; preset: enabled)
Active: active (running) since Sat 2024-11-09 05:06:47 UTC; 2 days ago
Docs: https://caddyserver.com/docs/
Main PID: 138140 (caddy)
Tasks: 29 (limit: 154166)
Memory: 19.3M (peak: 28.8M)
CPU: 3min 37.216s
CGroup: /system.slice/caddy.service
└─138140 /usr/bin/caddy run --environHow to use
We will now show how to set up caddy as a reverse proxy for hosts added dynamically.
fastcaddy talks to caddy’s admin API, and config set that way is ephemeral: it doesn’t survive a caddy restart. So the recommended pattern is a setup script that runs on every deploy (e.g. from systemd): call reset(), then the functions below for everything you host. Each function is safe to re-run – routes are tagged with predictable @ids (the hostname, or wildcard-{domain}), and re-adding one replaces it rather than duplicating it – so the script is the single source of truth for your routing config.
Initial setup
We’ll grab our token from the previous step (assuming here that it’s stored in an env var):
cf_token = os.environ.get('AAI_CF_TOKEN', 'XXX')setup_caddy
setup_caddy (cf_token, srv_name='srv0')
Create SSL config and HTTP app skeleton
We can now setup the basic routes needed for caddy:
setup_caddy(cf_token)gcfg
gcfg (path='/', method='get')
Gets the config at path
To view the configuration created, use gcfg:
# gcfg()You can also view a sub-path of the configuration:
gcfg('/apps/http/servers'){'srv0': {'listen': [':80', ':443'], 'routes': []}}Reverse proxies
add_reverse_proxy
add_reverse_proxy (from_host, to_url)
Create a reverse proxy handler
To add a reverse proxy, use add_reverse_proxy:
host = 'jph.answer.ai'
add_reverse_proxy(host, 'localhost:5001')gid
gid (path='/')
Gets the id at path
This is automatically added with an id matching the host, which you can view with gid:
gid('jph.answer.ai'){ '@id': 'jph.answer.ai',
'handle': [{'handler': 'reverse_proxy', 'upstreams': [{'dial': 'localhost:5001'}]}],
'match': [{'host': ['jph.answer.ai']}],
'terminal': True}If you call this again with the same host, it will be replaced:
add_reverse_proxy(host, 'localhost:8000')
gid('jph.answer.ai').handle[1]{'handler': 'reverse_proxy', 'upstreams': [{'dial': 'localhost:8000'}]}del_id
del_id (id)
Delete route for id (e.g. a host)
To remove a host, delete its id:
del_id(host)Wildcard subdomains
Caddy can create a wildcard SSL cert. To do so, add a wildcard route (a no-op if it already exists, so existing subdomains inside it are never destroyed):
add_wildcard_route
add_wildcard_route (domain)
Add a wildcard subdomain
add_wildcard_route('something.fast.ai')Create reverse proxies in a wildcard domain requires using a special function:
add_sub_reverse_proxy
add_sub_reverse_proxy (domain, subdomain, port)
Add a reverse proxy to a wildcard subdomain
add_sub_reverse_proxy('something.fast.ai', 'foo', 5001)These subdomains can be deleted in the usual way:
del_id('foo.something.fast.ai')