1
0
Fork 0

feat: update cloudflare dns records

This commit is contained in:
Arthur K. 2025-06-11 09:54:07 +03:00
parent 5830244af0
commit 5c906ac814
Signed by: wzray
GPG key ID: B97F30FDC4636357

66
app.py
View file

@ -1,7 +1,6 @@
import base64
import os
import re
import requests
import urllib3
from concurrent.futures import Future, ThreadPoolExecutor, wait as gather_futures
@ -12,18 +11,34 @@ from flask import Flask, request
NODE_NAME = (os.getenv('NODE_NAME') or '') + "_traefik"
TRAEFIK_INSTANCE = os.getenv('TRAEFIK_INSTANCE') or ''
TRAEFIK_HOST = os.getenv('TRAEFIK_HOST') or ''
EXTERNAL_HOST = os.getenv('EXTERNAL_HOST') or ''
INTERNAL_DESTS = (os.getenv('INTERNAL_DESTS') or '').split(',') or []
EXTERNAL_DESTS = (os.getenv('EXTERNAL_DESTS') or '').split(',') or []
PRIVATE_KEY = os.getenv('PRIVATE_KEY') or ''
CLOUDFLARE_API_KEY = os.getenv('CLOUDFLARE_API_KEY') or ''
CLOUDFLARE_ZONE_ID = os.getenv('CLOUDFLARE_ZONE_ID') or ''
HOST = os.getenv('HOST') or '0.0.0.0'
PORT = os.getenv('PORT') or '80'
assert NODE_NAME != '_traefik'
assert TRAEFIK_INSTANCE
assert TRAEFIK_HOST
assert INTERNAL_DESTS
assert EXTERNAL_DESTS
assert PRIVATE_KEY
for x in [
TRAEFIK_INSTANCE,
TRAEFIK_HOST,
EXTERNAL_HOST,
INTERNAL_DESTS,
EXTERNAL_DESTS,
PRIVATE_KEY,
CLOUDFLARE_API_KEY,
CLOUDFLARE_ZONE_ID,
HOST,
PORT,
]: assert x
app = Flask(__name__)
PATTERN = re.compile(r"Host\(`((?:[a-zA-Z0-9_-]+\.)*wzray\.com)`\)")
@ -42,6 +57,14 @@ def Connection(host: str, user: str = 'root', port = None) -> fabric.Connection:
{'auth_strategy': paramiko.auth_strategy.InMemoryPrivateKey(user, privkey)})
def cf_request(method: str, target: str, json = None):
r = urllib3.request(method, 'https://api.cloudflare.com/client/v4/' + target,
headers={'Authorization': f'Bearer {CLOUDFLARE_API_KEY}'}, json=json)
if r.status != 200:
raise Exception(f'Error {r.status}: {r.data.decode()}')
return r.json()['result']
class Observer:
internal: set[str] = set()
external: set[str] = set()
@ -111,10 +134,34 @@ class Observer:
return [cls.executor.submit(_update, x) for x in EXTERNAL_DESTS]
@classmethod
def update_cloudflare(cls):
url = f'zones/{CLOUDFLARE_ZONE_ID}/dns_records'
to_delete = [{'id': x['id']} for x in filter(
lambda x: x['comment'] == NODE_NAME, cf_request('GET', url))]
if not cls.external and not to_delete:
return
cf_request('POST', url + '/batch', {
'deletes': to_delete,
'posts': [{
'comment': NODE_NAME,
'content': EXTERNAL_HOST,
'type': 'A',
'proxied': True,
'name': x,
} for x in cls.external]
})
@classmethod
def update(cls, data):
def _job():
gather_futures(cls.update_external() + cls.update_internal())
gather_futures([
*cls.update_external(),
*cls.update_internal(),
cls.executor.submit(cls.update_cloudflare)
])
cls.job = None
cls.internal, cls.external = cls.parse_raw_data(data)
print(f"{cls.internal=}, {cls.external=}")
@ -126,7 +173,10 @@ class Observer:
@classmethod
def parse_raw_data(cls, data: dict):
http = data.get('http') or {}
flt = lambda ep: set([x.group(1) for x in [re.match(PATTERN, x['rule']) for x in http['routers'].values() if ep in x['entryPoints']] if x])
flt = lambda ep: set([z
for y in [re.findall(PATTERN, x['rule'])
for x in http['routers'].values() if ep in x['entryPoints']] if y for z in y])
internal = flt(INTERNAL_ENTRYPOINT)
external = flt(EXTERNAL_ENTRYPOINT)