https://github.com/answerdotai/fastcaddy
A simple python wrapper for using the Caddy API
https://github.com/answerdotai/fastcaddy
Last synced: 8 months ago
JSON representation
A simple python wrapper for using the Caddy API
- Host: GitHub
- URL: https://github.com/answerdotai/fastcaddy
- Owner: AnswerDotAI
- License: apache-2.0
- Created: 2024-11-12T01:02:58.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-05-14T00:52:16.000Z (9 months ago)
- Last Synced: 2025-06-30T15:59:25.769Z (8 months ago)
- Language: Jupyter Notebook
- Homepage: https://answerdotai.github.io/fastcaddy/
- Size: 403 KB
- Stars: 19
- Watchers: 7
- Forks: 1
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# fastcaddy
## Usage
### Installation
Install from [pypi](https://pypi.org/project/fastcaddy/)
``` sh
$ 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
``` python
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:
``` python
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.
``` python
cf_token = os.environ['CLOUDFLARE_API_TOKEN']
```
We can now check that works OK:
``` python
cf = Cloudflare(api_token=cf_token)
zones = cf.zones.list()
len(zones.result)
```
8
Replace this with your domain name:
``` python
domain = 'answer.ai'
zones = cf.zones.list(name=domain)
assert len(zones.result)==1
```
``` python
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:
``` python
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`
``` python
from cloudflare.types.user.tokens import PermissionGroupListResponse
```
- client.user.tokens.permission_groups.list() -\>
SyncSinglePage\[object\]
``` python
from cloudflare.types.user.tokens import Value
```
- client.user.tokens.value.update(token_id, \*\*params) -\> str
We need these two permissions in our token:
``` python
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:
``` python
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:
``` sh
sudo add-apt-repository -y ppa:longsleep/golang-backports
sudo apt update
```
Now we can install xcaddy:
``` sh
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
```
Alternatively, you can download the latest xcaddy directly, e.g:
``` sh
# 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):
``` sh
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:
``` sh
./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:
``` sh
./setup_service.sh
```
If all went well, you should see output like this:
``` sh
● 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):
``` python
cf_token = os.environ.get('CADDY_CF_TOKEN', 'XXX')
```
We can now setup the basic routes needed for caddy:
``` python
setup_caddy(cf_token)
```
To view the configuration created, use
[`gcfg`](https://AnswerDotAI.github.io/fastcaddy/core.html#gcfg):
``` python
gcfg()
```
``` json
{ '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:
``` python
gcfg('/apps/http/servers')
```
``` json
{'srv0': {'listen': [':80', ':443'], 'routes': []}}
```
To add a reverse proxy, use
[`add_reverse_proxy`](https://AnswerDotAI.github.io/fastcaddy/core.html#add_reverse_proxy):
``` python
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`](https://AnswerDotAI.github.io/fastcaddy/core.html#gid):
``` python
gid('jph.answer.ai')
```
``` json
{ '@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:
``` python
add_reverse_proxy(host, 'localhost:8000')
gid('jph.answer.ai').handle[0]
```
``` json
{'handler': 'reverse_proxy', 'upstreams': [{'dial': 'localhost:8000'}]}
```
To remove a host, delete its id:
``` python
del_id(host)
```