Compare commits

...

3 Commits

Author SHA1 Message Date
Jan Gaßner
6e40ecae05 Removed dualstack query parameter as it is unused and has no foreseeable use case 2023-07-11 00:23:43 +02:00
Jan Gaßner
0cd6d48bdd Very small improvements on logging & openapi docs phrasing 2023-07-11 00:22:58 +02:00
Jan Gaßner
4d5569b156 Added table of contents 2023-07-09 19:15:52 +02:00
5 changed files with 34 additions and 7 deletions

View File

@@ -1,6 +1,19 @@
DynDNS Helper
=============
- [DynDNS Helper](#dyndns-helper)
- [TODO](#todo)
- [Router DynDNS Examples](#router-dyndns-examples)
- [AVM Fritzbox](#avm-fritzbox)
- [IPv6 LAN Prefix \& Interface ID (IID)](#ipv6-lan-prefix--interface-id-iid)
- [Caveats](#caveats)
- [Configuration](#configuration)
- [Server](#server)
- [Vendors](#vendors)
- [Hetzner](#hetzner)
- [FQDNs](#fqdns)
Self-hosted python web application based on [FastAPI](https://github.com/tiangolo/fastapi) to update DNS records via API.
Vendor backends are easily extensible and include currently:
@@ -151,7 +164,7 @@ Additional options for Hetzner:
| api_url | 'https://dns.hetzner.com/api/v1' | false | Hetzner DNS API URL | |
| api_token | | true | default TTL to set for created records - if not defined in individual FQDN settings | 'secretapitoken' |
# FQDNs
## FQDNs
All FQDN config is defined under a key `fqdns` and the FQDN as a key, e.g.:
```json
{

View File

@@ -25,3 +25,7 @@ def run():
)
server = uvicorn.Server(config)
server.run()
if __name__ == 'main':
run()

View File

@@ -56,9 +56,8 @@ def check_credentials_for_fqdn(
async def dyndns(
username: Annotated[str, Depends(check_credentials_for_fqdn)],
fqdn_settings: Annotated[FQDNSettings, Depends(get_fqdn_settings)],
ip: Annotated[list[IPv4Address | IPv6Address | EmptyStr], Query()] = None,
ipv6lanprefix: Annotated[IPv6Network, Query()] = None,
dualstack: Annotated[bool, Query()] = None
ip: Annotated[list[IPv4Address | IPv6Address | EmptyStr], Query(description='IPv4 or IPv6 addresses')] = None,
ipv6lanprefix: Annotated[IPv6Network, Query(description='IPv6 Network with Prefix. Will be used to update AAAA records for configured IIDs')] = None
):
if ip:
# remove empty string items
@@ -72,7 +71,7 @@ async def dyndns(
)
if ip:
LOGGER.debug(f'DynDNS FQDN {fqdn_settings.fqdn} with IPs {", ".join(map(str, ip))}')
LOGGER.debug(f'{username} -DynDNS FQDN {fqdn_settings.fqdn} with IPs {", ".join(map(str, ip))}')
vendor = CONFIG.vendors.get_vendor(fqdn_settings.vendor)
await vendor.ensure_fqdn(
fqdn=fqdn_settings.fqdn,
@@ -86,7 +85,7 @@ async def dyndns(
# and we have to bitwise OR network address with iid address
netaddr_ipv6lanprefix_address = IPNetwork(str(ipv6lanprefix)).network
for iid_fqdn, iid in fqdn_settings.ipv6_lan_prefix_iid_map.items():
LOGGER.debug(f'DynDNS IID FQDN {iid_fqdn} {iid} with IPv6 LAN Prefix {ipv6lanprefix}')
LOGGER.debug(f'{username} - DynDNS IID FQDN {iid_fqdn} {iid} with IPv6 LAN Prefix {ipv6lanprefix}')
netaddr_iid_address = IPAddress(str(iid))
combined_address = IPv6Address(str(netaddr_ipv6lanprefix_address | netaddr_iid_address))

View File

@@ -116,6 +116,11 @@ class Vendors(BaseModel):
hetzner: HetznerSettings = Field(default_factory=HetznerSettings)
def get_vendor(self, name: VendorNames) -> Vendor:
"""
returns initialized vendor class
raises ValueError if vendor is not defined
"""
vendor_settings = getattr(self, name, None)
if not vendor_settings:
raise ValueError(f'No vendor settings found for name `{name}`')

View File

@@ -27,6 +27,9 @@ class HetznerZone(BaseModel):
name: str
ttl: int
def __str__(self):
return f'{self.id}/{self.name}'
@property
def update_dict(self):
return self.model_dump(
@@ -45,6 +48,9 @@ class HetznerRecord(BaseModel):
value: str
ttl: int
def __str__(self):
return f'{self.id}/{self.name}/{self.type}'
@property
def update_dict(self):
return self.model_dump(
@@ -139,7 +145,7 @@ class Hetzner(Vendor):
return HetznerRecord(**record)
async def update_record(self, record: HetznerRecord) -> HetznerRecord:
self.logger.info(f'Updating {record.id} / {record.type} record {record.name} in zone {record.zone_id}: {record.value}')
self.logger.info(f'Updating {record} in zone {record.zone_id}: {record.value}')
response = await self.api_put(
url=f'{self.settings.api_url}/records/{record.id}',
json=record.update_dict