Compare commits
No commits in common. "702cb85df8798954737c93a4bd978d9a711ed343" and "812eaa893dfdceaa813bd3ac20a1cb925e8312fc" have entirely different histories.
702cb85df8
...
812eaa893d
|
@ -1,3 +1 @@
|
||||||
*.secret
|
*.secret
|
||||||
**/__pycache__
|
|
||||||
certbot
|
|
41
README.md
41
README.md
|
@ -1,41 +0,0 @@
|
||||||
# hatecomputers.club dns updater & certbot plugin
|
|
||||||
|
|
||||||
this is a simple wrapper over hatecomputers.club's dns api
|
|
||||||
|
|
||||||
## dns creation steps
|
|
||||||
|
|
||||||
1. obtain an api key at [hatecomputers.club](https://hatecomputers.club)
|
|
||||||
2. put it in `apikey.secret`
|
|
||||||
3. modify `records.json` to your liking
|
|
||||||
4. `./main.py --create --records-file=records.json`
|
|
||||||
|
|
||||||
## certbot plugin
|
|
||||||
|
|
||||||
follow the above to generate an api key.
|
|
||||||
|
|
||||||
if you use the split-zone dns provided by hatecomputers.club and run your own certificate
|
|
||||||
authority, you can try something like:
|
|
||||||
```bash
|
|
||||||
REQUESTS_CA_BUNDLE=~/armin/roots.pem certbot certonly \
|
|
||||||
--manual --manual-auth-hook ./plugin.sh \
|
|
||||||
--preferred-challenges dns \
|
|
||||||
-d *.internal.simponic.xyz \
|
|
||||||
--config-dir ./certbot \
|
|
||||||
--work-dir ./certbot \
|
|
||||||
--logs-dir ./certbot \
|
|
||||||
--server https://ca.internal.simponic.xyz/acme/ACME/directory \
|
|
||||||
--email simponic@hatecomputers.club \
|
|
||||||
--agree-tos \
|
|
||||||
--no-eff-email
|
|
||||||
```
|
|
||||||
|
|
||||||
otherwise:
|
|
||||||
```bash
|
|
||||||
sudo certbot certonly \
|
|
||||||
--manual --manual-auth-hook ./plugin.sh \
|
|
||||||
--preferred-challenges dns \
|
|
||||||
-d *.simponic.hatecomputers.club \
|
|
||||||
--email simponic@hatecomputers.club \
|
|
||||||
--agree-tos \
|
|
||||||
--no-eff-email
|
|
||||||
```
|
|
59
args.py
59
args.py
|
@ -1,59 +0,0 @@
|
||||||
import argparse
|
|
||||||
|
|
||||||
|
|
||||||
def get_args():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--endpoint", default="https://hatecomputers.club", help="API endpoint"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--api-key-file",
|
|
||||||
default="apikey.secret",
|
|
||||||
help="path to file containing the API key",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--log-level",
|
|
||||||
default="INFO",
|
|
||||||
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--public-suffixes",
|
|
||||||
default="hatecomputers.club",
|
|
||||||
help="comma separated list of public suffixes",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--dns-propogate-time",
|
|
||||||
default=20,
|
|
||||||
type=int,
|
|
||||||
help="time to sleep to allow DNS to propogate",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--certbot", action="store_true", default=False, help="enable certbot mode"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--certbot-domain", required=False, help="splat/domain to validate with certbot"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--certbot-validation", required=False, help="validation token for certbot"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--create",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="upload records file to API to sync",
|
|
||||||
)
|
|
||||||
parser.add_argument("--records-file", default="records.json", help="records file")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if (args.certbot) and (not args.certbot_domain):
|
|
||||||
parser.error("--certbot-domain is required when --certbot is used")
|
|
||||||
if (args.certbot) and (not args.certbot_validation):
|
|
||||||
parser.error("--certbot-validation is required when --certbot is used")
|
|
||||||
if args.certbot:
|
|
||||||
args.public_suffixes = args.public_suffixes.split(",")
|
|
||||||
|
|
||||||
return args
|
|
57
main.py
57
main.py
|
@ -1,57 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
|
|
||||||
from updater.adapter import HatecomputersDNSAdapter
|
|
||||||
from updater.utils import record_transformer
|
|
||||||
from args import get_args
|
|
||||||
|
|
||||||
|
|
||||||
def certbot_mode(args, dns_api_adapter, record_transformer):
|
|
||||||
domain = args.certbot_domain
|
|
||||||
if domain.startswith("*."):
|
|
||||||
domain = domain[2:]
|
|
||||||
logging.info(f"processing domain {domain}")
|
|
||||||
|
|
||||||
record = {
|
|
||||||
"ttl": 60,
|
|
||||||
"name": "_acme-challenge." + domain,
|
|
||||||
"type": "TXT",
|
|
||||||
"content": args.certbot_validation,
|
|
||||||
}
|
|
||||||
record = record_transformer(record)
|
|
||||||
logging.info(f"creating record {record}")
|
|
||||||
dns_api_adapter.post_record(record)
|
|
||||||
|
|
||||||
logging.info(
|
|
||||||
f"eeping out for {args.dns_propogate_time}s, to allow DNS propogation. look at this cute little guy 🐢 until then!!"
|
|
||||||
)
|
|
||||||
time.sleep(args.dns_propogate_time)
|
|
||||||
|
|
||||||
logging.info(f"updating record for {domain} with {args.certbot_validation}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
args = get_args()
|
|
||||||
logging.basicConfig()
|
|
||||||
logging.root.setLevel(args.log_level)
|
|
||||||
|
|
||||||
api_key = open(args.api_key_file, "r").read().strip()
|
|
||||||
dns_api_adapter = HatecomputersDNSAdapter(args.endpoint, api_key)
|
|
||||||
|
|
||||||
if args.create:
|
|
||||||
records_file = open(args.records_file, "r")
|
|
||||||
dns_records = json.load(records_file)
|
|
||||||
dns_api_adapter.post_records(dns_records)
|
|
||||||
|
|
||||||
if args.certbot:
|
|
||||||
certbot_mode(
|
|
||||||
args,
|
|
||||||
dns_api_adapter,
|
|
||||||
record_transformer(args.public_suffixes),
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.info("done")
|
|
14
plugin.sh
14
plugin.sh
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
unset REQUESTS_CA_BUNDLE
|
|
||||||
|
|
||||||
API_KEY_FILE=/Users/lizzy/git/simponic/dns-updater/apikey.secret
|
|
||||||
ENDPOINT=https://hatecomputers.club
|
|
||||||
PUBLIC_SUFFIXES=.hatecomputers.club
|
|
||||||
|
|
||||||
./main.py --certbot \
|
|
||||||
--public-suffixes=$PUBLIC_SUFFIXES \
|
|
||||||
--certbot-domain=$CERTBOT_DOMAIN \
|
|
||||||
--certbot-validation=$CERTBOT_VALIDATION \
|
|
||||||
--endpoint=$ENDPOINT \
|
|
||||||
--api-key-file=$API_KEY_FILE
|
|
190
records.json
190
records.json
|
@ -1,115 +1,79 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "A",
|
"type": "A",
|
||||||
"name": "johan.internal.simponic.xyz.",
|
"name": "johan.internal.simponic.xyz.",
|
||||||
"content": "100.64.0.5",
|
"content": "100.64.0.5",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "A",
|
"type": "A",
|
||||||
"name": "europa.internal.simponic.xyz.",
|
"name": "europa.internal.simponic.xyz.",
|
||||||
"content": "100.64.0.8",
|
"content": "100.64.0.8",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "vaultwarden.internal.simponic.xyz.",
|
"name": "vaultwarden.internal.simponic.xyz.",
|
||||||
"content": "johan.internal.simponic.xyz.",
|
"content": "johan.internal.simponic.xyz",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "lldap.internal.simponic.xyz.",
|
"name": "lldap.internal.simponic.xyz.",
|
||||||
"content": "johan.internal.simponic.xyz.",
|
"content": "johan.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "ca.internal.simponic.xyz.",
|
"name": "ca.internal.simponic.xyz.",
|
||||||
"content": "johan.internal.simponic.xyz.",
|
"content": "johan.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "pihole.internal.simponic.xyz.",
|
"name": "pihole.internal.simponic.xyz.",
|
||||||
"content": "johan.internal.simponic.xyz.",
|
"content": "johan.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "owncloud.internal.simponic.xyz.",
|
"name": "owncloud.internal.simponic.xyz.",
|
||||||
"content": "europa.internal.simponic.xyz.",
|
"content": "europa.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "jellyfin.internal.simponic.xyz.",
|
"name": "jellyfin.internal.simponic.xyz.",
|
||||||
"content": "europa.internal.simponic.xyz.",
|
"content": "europa.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "drone.internal.simponic.xyz.",
|
"name": "drone.internal.simponic.xyz.",
|
||||||
"content": "europa.internal.simponic.xyz.",
|
"content": "europa.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "scurvy.internal.simponic.xyz.",
|
"name": "scurvy.internal.simponic.xyz.",
|
||||||
"content": "europa.internal.simponic.xyz.",
|
"content": "europa.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "CNAME",
|
"type": "CNAME",
|
||||||
"name": "roundcube.internal.simponic.xyz.",
|
"name": "roundcube.internal.simponic.xyz.",
|
||||||
"content": "europa.internal.simponic.xyz.",
|
"content": "europa.internal.simponic.xyz.",
|
||||||
"ttl": "43200",
|
"ttl": "43200",
|
||||||
"internal": "on"
|
"internal": "on"
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"type": "CNAME",
|
|
||||||
"name": "simponic.endpoints",
|
|
||||||
"content": "levi.simponic.xyz.",
|
|
||||||
"ttl": "43200",
|
|
||||||
"internal": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "CNAME",
|
|
||||||
"name": "simponic",
|
|
||||||
"content": "simponic.xyz.",
|
|
||||||
"ttl": "43200",
|
|
||||||
"internal": "off"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"type": "A",
|
|
||||||
"name": "armin.internal.simponic.xyz",
|
|
||||||
"content": "100.64.0.6",
|
|
||||||
"ttl": "43200",
|
|
||||||
"internal": "on"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "CNAME",
|
|
||||||
"name": "dev.armin.internal.simponic.xyz",
|
|
||||||
"content": "armin.internal.simponic.xyz",
|
|
||||||
"ttl": "43200",
|
|
||||||
"internal": "on"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "CNAME",
|
|
||||||
"name": "traefik.armin.internal.simponic.xyz.",
|
|
||||||
"content": "armin.internal.simponic.xyz.",
|
|
||||||
"ttl": "43200",
|
|
||||||
"internal": "on"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
requests==2.31.0
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
RECORDS_FILE = "records.json"
|
||||||
|
ENDPOINT = "https://hatecomputers.club"
|
||||||
|
API_KEY = open('apikey.secret', 'r').read().strip()
|
||||||
|
|
||||||
|
class HatecomputersDNSAdapter:
|
||||||
|
def __init__(self, endpoint, api_key):
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.headers = {'Authorization': 'Bearer ' + api_key}
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def post_record(self, record):
|
||||||
|
endpoint = self.endpoint + "/dns"
|
||||||
|
logging.info("adding", record, "at", endpoint)
|
||||||
|
|
||||||
|
self.session.post(endpoint, headers=self.headers, data=record)
|
||||||
|
|
||||||
|
def post_records(self, dns_entries, sleep_time=300):
|
||||||
|
for record in dns_entries:
|
||||||
|
self.post_record(record)
|
||||||
|
|
||||||
|
logging.info("sleeping", sleep_time)
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig()
|
||||||
|
logging.root.setLevel(logging.NOTSET)
|
||||||
|
|
||||||
|
records_file = open(RECORDS_FILE, 'r')
|
||||||
|
dns_records = json.load(records_file)
|
||||||
|
|
||||||
|
adapter = HatecomputersDNSAdapter(ENDPOINT, API_KEY)
|
||||||
|
adapter.post_records(dns_records)
|
|
@ -1,24 +0,0 @@
|
||||||
import requests
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
class HatecomputersDNSAdapter:
|
|
||||||
def __init__(self, endpoint, api_key, logger=None):
|
|
||||||
self.endpoint = endpoint
|
|
||||||
self.session = requests.Session()
|
|
||||||
self.headers = {"Authorization": "Bearer " + api_key}
|
|
||||||
self.logger = logger or logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def post_record(self, record):
|
|
||||||
endpoint = self.endpoint + "/dns"
|
|
||||||
self.logger.info(f"adding {record} to {endpoint}")
|
|
||||||
|
|
||||||
self.session.post(endpoint, headers=self.headers, data=record)
|
|
||||||
|
|
||||||
def post_records(self, dns_entries, eepy_time=0.25):
|
|
||||||
for record in dns_entries:
|
|
||||||
self.post_record(record)
|
|
||||||
|
|
||||||
self.logger.info(f"eeping out for {eepy_time}s")
|
|
||||||
time.sleep(eepy_time)
|
|
|
@ -1,21 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
def record_transformer(public_suffixes):
|
|
||||||
def transform(record):
|
|
||||||
name = record["name"]
|
|
||||||
suffixes = [suffix for suffix in public_suffixes if name.endswith(suffix)]
|
|
||||||
suffix = suffixes[0] if suffixes else None
|
|
||||||
|
|
||||||
if suffix:
|
|
||||||
logging.debug(f"stripping {suffix} from {name} as it is a public suffix")
|
|
||||||
|
|
||||||
record["name"] = name[: -len(suffix)]
|
|
||||||
record["internal"] = "off"
|
|
||||||
return record
|
|
||||||
|
|
||||||
logging.debug(f"keeping {name} as it is not a public suffix")
|
|
||||||
record["internal"] = "on"
|
|
||||||
return record
|
|
||||||
|
|
||||||
return transform
|
|
Loading…
Reference in New Issue