Compare commits
10 Commits
9af76da900
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c9b686062 | ||
|
|
f601634857 | ||
|
|
f037b8f859 | ||
|
|
2c11e9db30 | ||
|
|
953cd9ced3 | ||
|
|
1c924a45fb | ||
|
|
276f77b7c2 | ||
|
|
84bcfca7af | ||
|
|
4d24766145 | ||
|
|
0337571499 |
@@ -1,5 +1,6 @@
|
|||||||
**
|
**
|
||||||
|
|
||||||
!Pipfile
|
!docker_nsupdate_ddns
|
||||||
!main.py
|
!pyproject.toml
|
||||||
!lib/
|
!README.md
|
||||||
|
!LICENSE.md
|
||||||
|
|||||||
15
.github/workflows/main.yml
vendored
15
.github/workflows/main.yml
vendored
@@ -1,6 +1,7 @@
|
|||||||
name: docker-build-and-push
|
name: docker-build-and-push
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
@@ -10,17 +11,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: merijntjetak/docker-nsupdate-ddns:latest
|
tags: ${{ secrets.DOCKERHUB_USERNAME }}/docker-nsupdate-ddns:latest
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
|||||||
23
Dockerfile
23
Dockerfile
@@ -1,24 +1,17 @@
|
|||||||
FROM python:alpine AS base
|
FROM python:3.12-alpine AS base
|
||||||
|
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
ENV PYTHONFAULTHANDLER 1
|
ENV PYTHONFAULTHANDLER 1
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
FROM base AS python-build
|
WORKDIR /build
|
||||||
RUN pip install pipenv
|
|
||||||
|
|
||||||
COPY Pipfile .
|
COPY . .
|
||||||
RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy --python /usr/local/bin/python
|
RUN pip install --no-cache-dir .
|
||||||
|
|
||||||
FROM base AS runtime
|
WORKDIR /
|
||||||
|
RUN rm -rf /build
|
||||||
|
|
||||||
# Copy virtual env from python-deps stage
|
ENTRYPOINT ["docker-nsupdate-ddns"]
|
||||||
COPY --from=python-build /.venv /.venv
|
|
||||||
ENV PATH="/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
RUN mkdir /app
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . /app/
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/main.py"]
|
|
||||||
|
|||||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Merijntje Tak
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
11
Pipfile
11
Pipfile
@@ -1,11 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
name = "pypi"
|
|
||||||
url = "https://pypi.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
docker = "*"
|
|
||||||
python-dotenv = "*"
|
|
||||||
dnspython = "*"
|
|
||||||
89
Pipfile.lock
generated
89
Pipfile.lock
generated
@@ -1,89 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"hash": {
|
|
||||||
"sha256": "2a9a9c218218349a7c550a97bb3bc3b6113227211c48e027755bdc8baf5822a1"
|
|
||||||
},
|
|
||||||
"pipfile-spec": 6,
|
|
||||||
"requires": {
|
|
||||||
"python_version": "3.6"
|
|
||||||
},
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"name": "pypi",
|
|
||||||
"url": "https://pypi.org/simple",
|
|
||||||
"verify_ssl": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"certifi": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
|
||||||
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
|
||||||
],
|
|
||||||
"version": "==2022.6.15"
|
|
||||||
},
|
|
||||||
"charset-normalizer": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
|
||||||
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3'",
|
|
||||||
"version": "==2.0.12"
|
|
||||||
},
|
|
||||||
"dnspython": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
|
|
||||||
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.2.1"
|
|
||||||
},
|
|
||||||
"docker": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7a79bb439e3df59d0a72621775d600bc8bc8b422d285824cb37103eab91d1ce0",
|
|
||||||
"sha256:d916a26b62970e7c2f554110ed6af04c7ccff8e9f81ad17d0d40c75637e227fb"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==5.0.3"
|
|
||||||
},
|
|
||||||
"idna": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
|
||||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3'",
|
|
||||||
"version": "==3.3"
|
|
||||||
},
|
|
||||||
"python-dotenv": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
|
|
||||||
"sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.20.0"
|
|
||||||
},
|
|
||||||
"requests": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
|
||||||
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
|
||||||
],
|
|
||||||
"version": "==2.27.1"
|
|
||||||
},
|
|
||||||
"urllib3": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec",
|
|
||||||
"sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"
|
|
||||||
],
|
|
||||||
"version": "==1.26.10"
|
|
||||||
},
|
|
||||||
"websocket-client": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:074e2ed575e7c822fc0940d31c3ac9bb2b1142c303eafcf3e304e6ce035522e8",
|
|
||||||
"sha256:6278a75065395418283f887de7c3beafb3aa68dada5cacbe4b214e8d26da499b"
|
|
||||||
],
|
|
||||||
"version": "==1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"develop": {}
|
|
||||||
}
|
|
||||||
57
README.md
57
README.md
@@ -4,40 +4,59 @@ This script pushes container/ip information from local Docker instance to a DNS
|
|||||||
|
|
||||||
Every REFRESH_INTERVAL seconds, it queries all the Docker containers on the local host, finds their IP and pushes it with the name to the DNS server.
|
Every REFRESH_INTERVAL seconds, it queries all the Docker containers on the local host, finds their IP and pushes it with the name to the DNS server.
|
||||||
|
|
||||||
## Running
|
## Configuration
|
||||||
|
The script takes environment variables or a config file. A sample config file is provided in [`sample.config.env`](sample.config.env).
|
||||||
|
|
||||||
The script takes environment variables or a config file. A sample config file is provided in `config.sample`. The file name of the config file can be passed as argument, but defaults to `config`.
|
The file name of the config file can be passed as argument, but defaults to `/config.env`.
|
||||||
|
|
||||||
The names of the environment variables is the same as in the config file. Environment variables have precendence over the config file.
|
The names of the environment variables is the same as in the config file. Environment variables have precendence over the config file.
|
||||||
|
|
||||||
|
| Config | Required | Default Value | Description |
|
||||||
|
|------------------|----------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| DOMAIN | Yes | | Sets the domain in which the records are created. Needs to match the Bind zone. |
|
||||||
|
| NAMESERVER | Yes | | Nameserver to push updates to. |
|
||||||
|
| TSIG_NAME | Yes | | TSIG key name for secure updates. |
|
||||||
|
| TSIG_KEY | Yes | | TSIG key value for secure updates. |
|
||||||
|
| DOCKER_SOCKET | No | /var/run/docker.sock | Sets the location of the Docker socket. |
|
||||||
|
| HOSTNAME_LABEL | No | nl.mtak.docker-nsupdate-ddns.hostname | Docker label to override the default record name with. Use with `docker --label=nl.mtak.docker-nsupdate-ddns.hostname=nginx` to get `nginx.int.mtak.nl` _If the label value present on the container, use it as hostname otherwise the container name._ |
|
||||||
|
| IGNORE_LABEL | No | nl.mtak.docker-nsupdate-ddns.ignore | Container label to exclude containers from DNS updates. |
|
||||||
|
| DNS_RECORD_TTL | No | 60 | Time to Live (TTL) for DNS records (seconds). |
|
||||||
|
| DEFAULT_NETWORK | No | | Preferred network name to find IP for, in case there are multiple networks. |
|
||||||
|
| REFRESH_INTERVAL | No | 60 | Interval between checks for container changes (seconds). |
|
||||||
|
| ONE_SHOT | No | False | Run once and exit instead of continuously monitoring. |
|
||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-e DOCKER_SOCKET=/var/run/docker.sock \
|
|
||||||
-e DOMAIN=int.mtak.nl \
|
-e DOMAIN=int.mtak.nl \
|
||||||
-e HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname \
|
|
||||||
-e DEFAULT_NETWORK=10.100.0.192/26 \
|
|
||||||
-e REFRESH_INTERVAL=5 \
|
|
||||||
-e ONE_SHOT=True \
|
|
||||||
-e NAMESERVER=10.100.0.11 \
|
-e NAMESERVER=10.100.0.11 \
|
||||||
-e TSIG_NAME=dck1 \
|
-e TSIG_NAME=dck1 \
|
||||||
-e TSIG_KEY=SyYXDCJ4kIs3qhvI= \
|
-e TSIG_KEY=SyYXDCJ4kIs3qhvI= \
|
||||||
|
-e DOCKER_SOCKET=/var/run/docker.sock \
|
||||||
|
-e HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname \
|
||||||
|
-e IGNORE_LABEL=nl.mtak.docker-nsupdate-ddns.ignore \
|
||||||
|
-e DNS_RECORD_TTL=60 \
|
||||||
|
-e DEFAULT_NETWORK=10.100.0.192/26 \
|
||||||
|
-e REFRESH_INTERVAL=60 \
|
||||||
|
-e ONE_SHOT=true \
|
||||||
merijntjetak/docker-nsupdate-ddns:latest
|
merijntjetak/docker-nsupdate-ddns:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Config file
|
### Config file
|
||||||
```bash
|
```bash
|
||||||
cat <<EOF > configfile
|
cat <<EOF > configfile
|
||||||
DOCKER_SOCKET=/var/run/docker.sock
|
|
||||||
DOMAIN=int.mtak.nl
|
DOMAIN=int.mtak.nl
|
||||||
HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname
|
|
||||||
DEFAULT_NETWORK=10.100.0.192/26
|
|
||||||
REFRESH_INTERVAL=5
|
|
||||||
ONE_SHOT=True
|
|
||||||
NAMESERVER=10.100.0.11
|
NAMESERVER=10.100.0.11
|
||||||
TSIG_NAME=dck1
|
TSIG_NAME=dck1
|
||||||
TSIG_KEY=SyYXDCJ4kIs3qhvI=
|
TSIG_KEY=SyYXDCJ4kIs3qhvI=
|
||||||
|
DOCKER_SOCKET=/var/run/docker.sock
|
||||||
|
HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname
|
||||||
|
IGNORE_LABEL=nl.mtak.docker-nsupdate-ddns.ignore
|
||||||
|
DNS_RECORD_TTL=60
|
||||||
|
DEFAULT_NETWORK=10.100.0.192/26
|
||||||
|
REFRESH_INTERVAL=60
|
||||||
|
ONE_SHOT=False
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
docker run -d \
|
docker run -d \
|
||||||
@@ -47,25 +66,13 @@ docker run -d \
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
- `DOCKER_SOCKET` - Sets the location of the Docker socket
|
|
||||||
- `DOMAIN` - Sets the domain in which the records are created. Needs to match the Bind zone.
|
|
||||||
- `HOSTNAME_LABEL` - Docker label to override the default record name with. Use with `docker --label=nl.mtak.docker-nsupdate-ddns.hostname=nginx` to get `nginx.int.mtak.nl`
|
|
||||||
- `DEFAULT_NETWORK` - Preferred network to find IP for, in case there are multiple networks
|
|
||||||
- `REFRESH_INTERVAL` - Interval between updates
|
|
||||||
- `ONE_SHOT` - Set to True for the script to update once and immediately quit (nice for debugging)
|
|
||||||
- `NAMESERVER` - Nameserver to push updates to
|
|
||||||
- `TSIG_NAME` - Name of the TSIG key
|
|
||||||
- `TSIG_KEY` - TSIG-KEY
|
|
||||||
|
|
||||||
### Bind9 integration
|
### Bind9 integration
|
||||||
|
|
||||||
1. Generate a key
|
1. Generate a key
|
||||||
|
|
||||||
`tsig-keygen clientname > /etc/bind/keys/clientname.key`
|
`tsig-keygen clientname > /etc/bind/keys/clientname.key`
|
||||||
|
|
||||||
2. Include keys in your Bind9 configuration
|
2. Include keys in your Bind9 `named.config` configuration file
|
||||||
|
|
||||||
`include "/etc/bind/keys/*";`
|
`include "/etc/bind/keys/*";`
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
DOCKER_SOCKET=/var/run/docker.sock
|
|
||||||
DOMAIN=int.mtak.nl
|
|
||||||
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
|
||||||
DEFAULT_NETWORK=10.100.0.192/26
|
|
||||||
REFRESH_INTERVAL=10
|
|
||||||
ONE_SHOT=True
|
|
||||||
NAMESERVER=10.100.0.11
|
|
||||||
TSIG_NAME=dck1
|
|
||||||
TSIG_KEY=SyYhvI=
|
|
||||||
7
docker_nsupdate_ddns/default.config.env
Normal file
7
docker_nsupdate_ddns/default.config.env
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
DOCKER_SOCKET=/var/run/docker.sock
|
||||||
|
HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname
|
||||||
|
IGNORE_LABEL=nl.mtak.docker-nsupdate-ddns.ignore
|
||||||
|
DNS_RECORD_TTL=60
|
||||||
|
REFRESH_INTERVAL=60
|
||||||
|
ONE_SHOT=False
|
||||||
|
DEFAULT_NETWORK=None
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_container_name(container):
|
def get_container_name(container):
|
||||||
"""
|
"""
|
||||||
@@ -45,6 +49,10 @@ def generate_container_list():
|
|||||||
ipam4 = {}
|
ipam4 = {}
|
||||||
|
|
||||||
for container in container_list:
|
for container in container_list:
|
||||||
|
if config['IGNORE_LABEL'] in container.attrs['Config']['Labels']:
|
||||||
|
LOG.debug(f"Ignoring container {container.attrs['Name']} as ignore label present")
|
||||||
|
continue
|
||||||
|
|
||||||
container_name = get_container_name(container)
|
container_name = get_container_name(container)
|
||||||
container_ip = get_container_ip(container)
|
container_ip = get_container_ip(container)
|
||||||
if container_ip:
|
if container_ip:
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import dns.update
|
import dns.update
|
||||||
import dns.query
|
import dns.query
|
||||||
import dns.tsigkeyring
|
import dns.tsigkeyring
|
||||||
@@ -5,12 +7,14 @@ import ipaddress
|
|||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def add_records(records):
|
def add_records(records):
|
||||||
keyring = dns.tsigkeyring.from_text(
|
keyring = dns.tsigkeyring.from_text({config['TSIG_NAME']: config['TSIG_KEY']})
|
||||||
{config['TSIG_NAME']: config['TSIG_KEY']})
|
|
||||||
for hostname, ip in records.items():
|
for hostname, ip in records.items():
|
||||||
print("Adding record for " + hostname + "(" + ip + ")")
|
LOG.info(f"Adding record for {hostname}({ip})")
|
||||||
|
|
||||||
rrtype = "A"
|
rrtype = "A"
|
||||||
address = ipaddress.ip_address(ip)
|
address = ipaddress.ip_address(ip)
|
||||||
@@ -20,15 +24,16 @@ def add_records(records):
|
|||||||
rrtype = "AAAA"
|
rrtype = "AAAA"
|
||||||
|
|
||||||
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
||||||
update.add(hostname, 60, rrtype, ip)
|
update.add(hostname, int(config['DNS_RECORD_TTL']), rrtype, ip)
|
||||||
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
||||||
|
|
||||||
|
|
||||||
def delete_records(records):
|
def delete_records(records):
|
||||||
keyring = dns.tsigkeyring.from_text(
|
keyring = dns.tsigkeyring.from_text({config['TSIG_NAME']: config['TSIG_KEY']})
|
||||||
{config['TSIG_NAME']: config['TSIG_KEY']})
|
|
||||||
for hostname, ip in records.items():
|
for hostname, ip in records.items():
|
||||||
print("Deleting record for " + hostname + "(" + ip + ")")
|
LOG.info(f"Deleting record for {hostname}({ip})")
|
||||||
|
|
||||||
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
||||||
update.delete(hostname)
|
update.delete(hostname)
|
||||||
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
||||||
105
docker_nsupdate_ddns/main.py
Executable file
105
docker_nsupdate_ddns/main.py
Executable file
@@ -0,0 +1,105 @@
|
|||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
import time
|
||||||
|
|
||||||
|
from docker_nsupdate_ddns.lib import *
|
||||||
|
import logging
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
ipam4_old = {}
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s [%(levelname)s] %(message)s')
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global config
|
||||||
|
config = get_config()
|
||||||
|
check_required_vars(config)
|
||||||
|
|
||||||
|
if not eval(config['ONE_SHOT']):
|
||||||
|
while True:
|
||||||
|
loop()
|
||||||
|
time.sleep(int(config['REFRESH_INTERVAL']))
|
||||||
|
|
||||||
|
loop()
|
||||||
|
LOG.info("Ending the process as ONE_SHOT is True")
|
||||||
|
|
||||||
|
|
||||||
|
def check_required_vars(_config):
|
||||||
|
# Check for all required config
|
||||||
|
required_vars = [
|
||||||
|
'DOMAIN',
|
||||||
|
'NAMESERVER',
|
||||||
|
'TSIG_NAME',
|
||||||
|
'DOCKER_SOCKET',
|
||||||
|
'HOSTNAME_LABEL',
|
||||||
|
'IGNORE_LABEL',
|
||||||
|
'DNS_RECORD_TTL',
|
||||||
|
'DEFAULT_NETWORK',
|
||||||
|
'REFRESH_INTERVAL',
|
||||||
|
'ONE_SHOT'
|
||||||
|
]
|
||||||
|
missing_vars = []
|
||||||
|
for item in required_vars:
|
||||||
|
if item in _config:
|
||||||
|
LOG.info(f"Detected config value: {item}={_config[item]}")
|
||||||
|
else:
|
||||||
|
missing_vars.append(item)
|
||||||
|
if 'TSIG_KEY' not in _config:
|
||||||
|
# Don't log it as it's a secret
|
||||||
|
missing_vars.append('TSIG_KEY')
|
||||||
|
if len(missing_vars) > 1:
|
||||||
|
LOG.error(f"Missing required config: {', '.join(missing_vars)}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Check if docker socket is correct
|
||||||
|
try:
|
||||||
|
if not stat.S_ISSOCK(os.stat(_config['DOCKER_SOCKET']).st_mode):
|
||||||
|
LOG.error(f"{_config['DOCKER_SOCKET']} not a docker socket file, exiting...")
|
||||||
|
exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"Docker socket {_config['DOCKER_SOCKET']} not found.", e)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def loop():
|
||||||
|
container.init(config)
|
||||||
|
ipam4 = container.generate_container_list()
|
||||||
|
global ipam4_old
|
||||||
|
|
||||||
|
additions4 = determine_additions(ipam4, ipam4_old)
|
||||||
|
deletions4 = determine_deletions(ipam4, ipam4_old)
|
||||||
|
|
||||||
|
nsupdate.init(config)
|
||||||
|
nsupdate.delete_records(deletions4)
|
||||||
|
nsupdate.add_records(additions4)
|
||||||
|
|
||||||
|
ipam4_old = ipam4
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
config_file = sys.argv[1] if len(sys.argv) >= 2 else 'config.env'
|
||||||
|
|
||||||
|
x = {
|
||||||
|
**dotenv_values(os.path.join(os.path.dirname(__file__), 'default.config.env')),
|
||||||
|
**dotenv_values(os.path.join(os.getcwd(), config_file)),
|
||||||
|
**os.environ
|
||||||
|
}
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def determine_additions(ipam, ipam_old):
|
||||||
|
return {k: v for k, v in ipam.items() if k not in ipam_old}
|
||||||
|
|
||||||
|
|
||||||
|
def determine_deletions(ipam, ipam_old):
|
||||||
|
return {k: v for k, v in ipam_old.items() if k not in ipam}
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
73
main.py
73
main.py
@@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import sys
|
|
||||||
from dotenv import dotenv_values
|
|
||||||
import time
|
|
||||||
|
|
||||||
from lib import *
|
|
||||||
|
|
||||||
config = {}
|
|
||||||
ipam4_old = {}
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
global config
|
|
||||||
config = get_config()
|
|
||||||
|
|
||||||
try:
|
|
||||||
stat.S_ISSOCK(os.stat(config['DOCKER_SOCKET']).st_mode)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print(
|
|
||||||
"Docker socket " +
|
|
||||||
config['DOCKER_SOCKET'] +
|
|
||||||
" not found, exiting...")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not config['ONE_SHOT']:
|
|
||||||
while True:
|
|
||||||
loop()
|
|
||||||
time.sleep(config['REFRESH_INTERVAL'])
|
|
||||||
|
|
||||||
loop()
|
|
||||||
|
|
||||||
|
|
||||||
def loop():
|
|
||||||
container.init(config)
|
|
||||||
ipam4 = container.generate_container_list()
|
|
||||||
global ipam4_old
|
|
||||||
|
|
||||||
additions4 = determine_additions(ipam4, ipam4_old)
|
|
||||||
deletions4 = determine_deletions(ipam4, ipam4_old)
|
|
||||||
|
|
||||||
nsupdate.init(config)
|
|
||||||
nsupdate.delete_records(deletions4)
|
|
||||||
nsupdate.add_records(additions4)
|
|
||||||
|
|
||||||
ipam4_old = ipam4
|
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
|
||||||
config_file = sys.argv[1] if len(sys.argv) >= 2 else 'config'
|
|
||||||
|
|
||||||
x = {
|
|
||||||
**dotenv_values(os.path.join(os.getcwd(), config_file)),
|
|
||||||
**os.environ
|
|
||||||
}
|
|
||||||
|
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
def determine_additions(ipam, ipam_old):
|
|
||||||
return {k: v for k, v in ipam.items() if k not in ipam_old}
|
|
||||||
|
|
||||||
|
|
||||||
def determine_deletions(ipam, ipam_old):
|
|
||||||
return {k: v for k, v in ipam_old.items() if k not in ipam}
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
39
pyproject.toml
Normal file
39
pyproject.toml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[project]
|
||||||
|
name = "docker-nsupdate-ddns"
|
||||||
|
version = "0.1"
|
||||||
|
description = "Docker DNS update in Bind9 with nsupdate based on docker container hostname or docker label"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3"
|
||||||
|
keywords = [
|
||||||
|
"nsupdate",
|
||||||
|
"docker",
|
||||||
|
"ddns",
|
||||||
|
"dns",
|
||||||
|
"bind9"
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Programming Language :: Python"
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"docker",
|
||||||
|
"python-dotenv",
|
||||||
|
"dnspython"
|
||||||
|
]
|
||||||
|
license = {file = "LICENSE.md"}
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = true
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["docker_nsupdate_ddns*"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
docker_nsupdate_ddns = ["default.config.env"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://github.com/mtak/docker-nsupdate-ddns"
|
||||||
|
repository = "https://github.com/mtak/docker-nsupdate-ddns"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
docker-nsupdate-ddns = "docker_nsupdate_ddns.main:main"
|
||||||
11
sample.config.env
Normal file
11
sample.config.env
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
DOMAIN=int.mtak.nl
|
||||||
|
NAMESERVER=10.100.0.11
|
||||||
|
TSIG_NAME=dck1
|
||||||
|
TSIG_KEY=SyYXDCJ4kIs3qhvI=
|
||||||
|
DOCKER_SOCKET=/var/run/docker.sock
|
||||||
|
HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname
|
||||||
|
IGNORE_LABEL=nl.mtak.docker-nsupdate-ddns.ignore
|
||||||
|
DNS_RECORD_TTL=60
|
||||||
|
DEFAULT_NETWORK=10.100.0.192/26
|
||||||
|
REFRESH_INTERVAL=60
|
||||||
|
ONE_SHOT=False
|
||||||
Reference in New Issue
Block a user