[go: up one dir, main page]

Skip to content

AnswerDotAI/fastcaddy

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)