From d920052082837b3dc41eca71e22735467e0a6367 Mon Sep 17 00:00:00 2001 From: ptrcnull Date: Tue, 23 Jul 2024 14:29:22 +0200 Subject: [PATCH] feat: add porkbun handler --- nyacme/handlers/__init__.py | 4 ++- nyacme/handlers/porkbun.py | 61 +++++++++++++++++++++++++++++++++++++ nyacme/hook.py | 3 +- 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 nyacme/handlers/porkbun.py diff --git a/nyacme/handlers/__init__.py b/nyacme/handlers/__init__.py index 4047715..c62b352 100644 --- a/nyacme/handlers/__init__.py +++ b/nyacme/handlers/__init__.py @@ -1,9 +1,11 @@ from .cloudflare import CloudflareHandler from .hetzner import HetznerHandler from .http import HTTPHandler +from .porkbun import PorkbunHandler __all__ = [ 'CloudflareHandler', 'HetznerHandler', - 'HTTPHandler' + 'HTTPHandler', + 'PorkbunHandler', ] diff --git a/nyacme/handlers/porkbun.py b/nyacme/handlers/porkbun.py new file mode 100644 index 0000000..32b9023 --- /dev/null +++ b/nyacme/handlers/porkbun.py @@ -0,0 +1,61 @@ +import json +import urllib.request +from typing import Any, Optional + +from ..config import Config +from .base import Handler + + +class PorkbunHandler(Handler): + # discovered + nameservers: list[str] + + def __init__(self, zone_name: str, config: Config, token: str) -> None: + super().__init__(zone_name, config, token) + self.apikey = config.get_secret('porkbun.apikey') + self.secretapikey = config.get_secret('porkbun.secretapikey') + + self.nameservers = self.fetch(f'/domain/getNs/{self.zone}')['ns'] + + def fetch(self, url: str, data: Optional[dict[str, Any]] = None) -> Any: + req = urllib.request.Request('https://api.porkbun.com/api/json/v3' + url) + req.add_header('Auth-API-Token', self.secret) + if not data: + data = {} + + data['apikey'] = self.apikey + data['secretapikey'] = self.secretapikey + req.data = json.dumps(data).encode('utf-8') + req.add_header('Content-Type', 'application/json;charset=utf-8') + req.method = 'POST' + try: + with urllib.request.urlopen(req) as f: + data = json.load(f) + if data['status'] != 'SUCCESS': + raise Exception(data['message']) + return data + except urllib.error.HTTPError as ex: + self.log.error('cannot %s %s: %s', req.method, url, ex) + res = ex.fp.read().decode('utf-8') + try: + raise Exception(json.loads(res)['message']) + except json.JSONDecodeError: + raise Exception(res) + + def create(self, record_name: str, record_value: str) -> None: + self.remove(record_name) + self.log.info('creating %s with value %s', record_name, record_value) + self.fetch(f'/dns/create/{self.zone}', { + 'name': record_name, + 'type': 'TXT', + 'content': record_value, + 'ttl': 300, + }) + + def remove(self, record_name: str) -> None: + full_record_name = record_name + '.' + self.zone + records = self.fetch(f'/dns/retrieve/{self.zone}')['records'] + for record in records: + if record['name'] == full_record_name: + self.log.info('removing %s', full_record_name) + self.fetch(f'/dns/delete/{self.zone}/{record['id']}') diff --git a/nyacme/hook.py b/nyacme/hook.py index 038af20..ef7fbda 100644 --- a/nyacme/hook.py +++ b/nyacme/hook.py @@ -6,7 +6,7 @@ from itertools import chain import dns.resolver from .config import read_config -from .handlers import CloudflareHandler, HetznerHandler, HTTPHandler +from .handlers import CloudflareHandler, HetznerHandler, HTTPHandler, PorkbunHandler logging.basicConfig(level=logging.INFO, format='> [%(levelname)s] %(name)s: %(message)s') log = logging.getLogger('nyacme_hook') @@ -16,6 +16,7 @@ handlers = { 'cloudflare': CloudflareHandler, 'hetzner': HetznerHandler, 'http': HTTPHandler, + 'porkbun': PorkbunHandler, } class Args: