Skip to content

AnswerDotAI/fastcaddy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Apr 18, 2025
22ec3c2 · Apr 18, 2025

History

29 Commits
Feb 26, 2025
Apr 18, 2025
Apr 18, 2025
Nov 12, 2024
Apr 18, 2025
Nov 12, 2024
Nov 12, 2024
Nov 18, 2024
Apr 2, 2025
Apr 18, 2025
Nov 12, 2024
Nov 18, 2024

Repository files navigation

fastcaddy

Usage

Installation

Install from pypi

$ pip install fastcaddy

Installing 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

from fastcore.utils import *

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 Cloudflare

Then 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)==1
zone_id = zones.result[0].id

Here’s the methods available for modifying DNS records:

  • client.dns.records.create(*, zone_id, **params) -> Optional
  • client.dns.records.update(dns_record_id, *, zone_id, **params) -> Optional
  • client.dns.records.list(*, zone_id, **params) -> SyncV4PagePaginationArray[Record]
  • client.dns.records.delete(dns_record_id, *, zone_id) -> Optional
  • client.dns.records.edit(dns_record_id, *, zone_id, **params) -> Optional
  • client.dns.records.export(*, zone_id) -> str
  • client.dns.records.get(dns_record_id, *, zone_id) -> Optional
  • client.dns.records.import\_(*, zone_id, **params) -> Optional
  • client.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) -> Optional
  • client.user.tokens.update(token_id, **params) -> object
  • client.user.tokens.list(**params) -> SyncV4PagePaginationArray[object]
  • client.user.tokens.delete(token_id) -> Optional
  • client.user.tokens.get(token_id) -> object
  • client.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 update

Now we can install xcaddy:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

Alternatively, 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.gz

Then 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/cloudflare

This gives us a ~/go/bin/caddy binary we can run:

./caddy version
./caddy run

Securely 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.sh

If 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 --environ

How to use

We will now show how to set up caddy as a reverse proxy for hosts added dynamically. We’ll grab our token from the previous step (assuming here that it’s stored in an env var):

cf_token = os.environ.get('CADDY_CF_TOKEN', 'XXX')

We can now setup the basic routes needed for caddy:

setup_caddy(cf_token)

To view the configuration created, use gcfg:

gcfg()
{ 'apps': { 'http': { 'servers': { 'srv0': { 'listen': [':80', ':443'],
                                             'routes': []}}},
            'tls': { 'automation': { 'policies': [{'issuers': [{'challenges': {'dns': {'provider': {'api_token': 'XXX', 'name': 'cloudflare'}}}, 'module': 'acme'}]}]}}}}

You can also view a sub-path of the configuration:

gcfg('/apps/http/servers')
{'srv0': {'listen': [':80', ':443'], 'routes': []}}

To add a reverse proxy, use add_reverse_proxy:

host = 'jph.answer.ai'
add_reverse_proxy(host, 'localhost:5001')

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[0]
{'handler': 'reverse_proxy', 'upstreams': [{'dial': 'localhost:8000'}]}

To remove a host, delete its id:

del_id(host)