146 Commits

Author SHA1 Message Date
Timothy Miller
f0d9510fff Merge pull request #117 from arulrajnet/env-support
[feature] Support for environmental substitution in config.json
2024-08-23 13:55:33 -04:00
Timothy Miller
4ea9ba5745 Merge pull request #151 from 4n4n4s/dependabot-github-actions
Update github-actions
2023-12-10 16:51:21 -05:00
Timothy Miller
9a295bbf91 Merge pull request #127 from adamantike/fix/copy-dependencies-from-stage
Reduce Docker image size by only copying pip installed dependencies
2023-10-12 02:15:43 -04:00
Timothy Miller
fecf30cd2a Merge pull request #139 from Suyun114/ttl-patch
Add TTL set to 1 (auto)
2023-10-12 02:10:52 -04:00
Timothy Miller
f7d1ff8687 Merge pull request #140 from Nevah5/master
Fixed example config for load balancing support in README.md
2023-10-12 02:10:10 -04:00
4n4n4s
fa398b83fc Update github-actions 2023-09-16 16:52:56 +02:00
Timothy Miller
9eb395031e Merge pull request #137 from timothymiller/dependabot/pip/requests-2.31.0
Bump requests from 2.28.2 to 2.31.0
2023-07-23 16:15:58 -04:00
Nevah5
a8a7ed1e5f Fixed example config for load balancing support in README.md 2023-06-04 20:34:14 +02:00
Suyun
060257fe12 Add TTL set to 1 (auto) 2023-06-01 19:35:04 +08:00
dependabot[bot]
4be08d8811 Bump requests from 2.28.2 to 2.31.0
Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.28.2...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-23 06:13:44 +00:00
Michael Manganiello
0ca623329a Reduce Docker image size by only copying pip installed dependencies
Currently, the multi-stage Docker build makes the `release` stage
inherit from `dependencies`, which will include any files created by the
`pip install` process in the final image.

By using `pip install --user` to make dependencies be installed in
`~/.local`, we can only copy those files into the final image, reducing
the image size:

```
cloudflare-ddns-fix-applied     latest            68427bd7c88d   3 minutes ago   54.6MB
cloudflare-ddns-master          latest            2675320b651d   8 minutes ago   65.9MB
```

A good resource going deeper on how this approach works can be found at
https://pythonspeed.com/articles/multi-stage-docker-python/, solution 1.
2023-02-22 10:26:02 -03:00
Arul
d3fe3940f9 addressing review comments 2023-02-21 06:53:01 +05:30
Arul
fa79547f9b Merge branch 'master' into env-support 2023-02-21 06:40:37 +05:30
Timothy Miller
6e92fc0d09 Fix load balancer errors 2023-02-15 19:28:08 -05:00
Timothy Miller
82b97f9cda Updated Load Balancing docs 2023-02-15 17:32:14 -05:00
Timothy Miller
190b90f769 Merge pull request #120 from DeeeeLAN/master
[feature] Add load balancer support
2023-02-15 17:27:03 -05:00
Timothy Miller
fff882be11 Revert config-example.json options for netif 2023-02-15 17:26:55 -05:00
Timothy Miller
713f0de5b0 Updated README.md 2023-02-15 17:15:03 -05:00
Timothy Miller
414ef99f96 Updated docker compose version to 3.9 2023-02-15 17:13:42 -05:00
Timothy Miller
ed65aff55f Revert netif changes for now 2023-02-15 17:05:00 -05:00
Timothy Miller
cb7b1804cf [feature] Extract IP address from netif credit: @comicchang 2023-02-15 16:14:22 -05:00
Timothy Miller
c135a7d343 Merge pull request #121 from davide125/systemd
Add systemd service and timer
2023-02-15 15:39:26 -05:00
Timothy Miller
af347f89b9 Update interpreter of shebang to python3 2023-02-15 15:37:06 -05:00
Timothy Miller
9824815e12 Git history folder added to gitignore 2023-02-15 15:30:36 -05:00
Timothy Miller
0dbd2f7c2b Added dependabot 2023-02-15 15:28:58 -05:00
Timothy Miller
e913d94eb8 Fix wildcard subdomain support 2023-02-15 15:22:06 -05:00
Timothy Miller
83fa74831e Updated README.md 2023-02-15 15:17:08 -05:00
Timothy Miller
f22ec89f3e Updated README.md 2023-02-15 15:15:23 -05:00
Timothy Miller
bd3f4a94cb Updated README.md 2023-02-15 15:05:59 -05:00
Timothy Miller
7212161f7b Updated README.md 2023-02-15 15:01:28 -05:00
Timothy Miller
2ad7e57d65 Added support for secondary IP checks if primary fails (Fixes #111)
Updated requests module version
2023-02-15 14:07:17 -05:00
Timothy Miller
5c909e25cd Updated README.md 2023-02-15 13:25:07 -05:00
Davide Cavalca
2c2e929d17 Add systemd service and timer 2023-01-29 15:28:11 -08:00
Dillan Mills
d92976993d Add load balancer slupport 2023-01-27 15:08:10 -07:00
Arul
a1fa3b9714 support for environmental substitution in config.json using python Template
refer comment in #35
2022-11-13 21:37:44 +05:30
Timothy Miller
7e6d74f1f6 Onboarding experience improved 2022-10-30 17:54:32 -04:00
Timothy Miller
9855ca6249 Update documentation 2022-10-30 17:47:46 -04:00
Timothy Miller
e0f0280656 Upgrade requests to 2.28.1 2022-10-30 17:45:41 -04:00
Timothy Miller
e86695f77d Updated documentation 2022-10-30 17:43:04 -04:00
Timothy Miller
b0a396b8f1 Update README.md 2022-08-31 16:07:34 -04:00
Timothy Miller
c648b81b25 Fixed typo in README 2022-07-31 22:46:30 -04:00
Timothy Miller
ceeb011366 Updated the domain used to fetch IPv4 from cloudflare. 2022-07-31 15:55:59 -04:00
Timothy Miller
f0357c71c1 Cleaned up code smell 2022-07-31 03:32:05 -04:00
Timothy Miller
6933cbe27f Added compatibility for legacy configs 2022-07-31 03:06:08 -04:00
Timothy Miller
566ad3a7cf Cleaned up code smells 2022-07-31 02:44:38 -04:00
Timothy Miller
3287447e0a Added 🗣️ Call to action for Docker environment variable support 2022-07-30 21:52:27 -04:00
Timothy Miller
62c360cff2 Added 🗣️ Call to action for Docker environment variable support 2022-07-30 21:48:12 -04:00
Timothy Miller
ae7be14004 Synced repeat interval with TTL 2022-07-30 21:44:59 -04:00
Timothy Miller
cb539ad64d Fixed config path bugs in Docker 2022-07-30 21:24:20 -04:00
Timothy Miller
a4d29036c5 Updated cdn-cgi/trace domain from 1.1.1.1 to cloudflare.com 2022-07-30 21:22:54 -04:00
Timothy Miller
ef4e3a5787 Added configurable TTL option, plus documentation 2022-07-30 21:20:41 -04:00
Timothy Miller
2b9ebdeab2 Added exception handling for unhandled api requests 2022-07-30 20:28:54 -04:00
Timothy Miller
86976e5133 Added per-subdomain proxy flag to config.json 2022-07-30 20:24:27 -04:00
Timothy Miller
2401e7a995 Fixed directory not set when running script with crontab 2022-07-30 20:14:29 -04:00
Timothy Miller
8acd8e5f59 Added a catch all for * & @, which are common references to the root domain 2022-07-30 20:12:24 -04:00
Timothy Miller
0e0e9f9989 Fixed bug that caused the root domain to not update 2022-07-30 20:09:38 -04:00
Timothy Miller
464d2792b1 Fixed purgeUnknownRecords behavior 2022-07-30 20:07:36 -04:00
Timothy Miller
a9d25c743a Create CODE_OF_CONDUCT.md 2021-10-30 22:13:17 -04:00
Timothy Miller
254e978971 Merge pull request #67 from favonia/python-version-check 2021-10-30 22:03:03 -04:00
favonia
bf6135739d Simplify Python version checking 2021-10-30 16:15:37 -05:00
Timothy Miller
bc06202b35 Merge pull request #66 from rojosinalma/patch-1
Fixes Python version check
2021-10-30 16:22:46 -04:00
Rojo
eebbcfbbdf Fixes Python version check
This fixes the Python version check.

float() cuts trailing zeroes:

```python
import sys
```
2021-10-30 20:55:52 +02:00
Timothy Miller
2a4d9530dd Reduce unimportant logging
Original solution from here https://github.com/pypa/pip/issues/5900#issuecomment-490216395
2021-10-29 23:15:05 -04:00
Timothy Miller
6587d86c65 Reorganized folder structure 2021-10-29 22:56:13 -04:00
Timothy Miller
6e68d2623f Improved documentation around optional features 2021-10-29 22:22:57 -04:00
Timothy Miller
ffa4963ddd Merge pull request #62 from arpagon/master
K8S Compatability and Example
2021-10-29 22:06:12 -04:00
Timothy Miller
def75e282d Merge pull request #57 from omeganot/master
Config option for purge/delete of "stale" records
2021-10-29 21:54:53 -04:00
Timothy Miller
870da367a9 Merge pull request #59 from adamus1red/adamus1red-docker-ci-tweaks
Update GHA CI to use offical docker build/push actions and allow PR's to be tested
2021-10-29 21:52:31 -04:00
Timothy Miller
571a22ac22 Merge pull request #53 from zmilonas/patch-2
Do not wait before updating IPs for the first time (#51)
2021-10-29 21:42:47 -04:00
Sebastian Rojo
5136c925d2 FIX confi filename on Docs 2021-07-15 16:32:09 -05:00
Sebastian Rojo
96527aaab2 BETTER docs 2021-07-15 16:26:47 -05:00
Sebastian Rojo
f7d2e7dc00 Set default image to timothyjmiller/cloudflare-ddns:latest 2021-07-15 16:18:46 -05:00
Sebastian Rojo
4e4d3cebf1 BETTER docs 2021-07-15 16:17:35 -05:00
Sebastian Rojo
01993807a9 ADDED env Variable CONFIG_PATH for Kubernetes secret 2021-07-15 16:14:23 -05:00
adamus1red
0d9a9a0579 Update image.yml 2021-06-16 14:41:04 +01:00
Rich Visotcky
0a85b04287 Add config and option for purgeUnknownRecords 2021-06-02 09:18:56 -05:00
Zachary Milonas
1a6ffc9681 Do not wait before updating IPs for the first time (#51) 2021-04-11 15:44:18 +02:00
Timothy Miller
458559d52c 📈 Increase sync frequency to 5 minutes to prevent potential gateway timeout 2021-03-21 20:18:55 -04:00
Timothy Miller
1f6daa5968 Merge pull request #49 from immortaly007/feature/error-response-logging
🦢 Log the response text, in case the response indicated an error
2021-03-21 13:40:43 -04:00
Bas Dado
c34401c43f 🦢 Log the response text, in case the response indicated an error 2021-03-20 14:35:08 +01:00
Timothy Miller
9a8d7d57e1 🧹 Refactored code 2021-03-17 02:33:51 -04:00
Timothy Miller
04d87d3aa6 Improved error handling 2021-03-17 01:15:07 -04:00
Timothy Miller
bdf8c75cad 🧩 Disable IPv4 or IPv6 in config.json 2021-03-16 20:53:28 -04:00
Timothy Miller
6fe23a2aee 🦢 Improved error message handling 2021-03-16 14:46:29 -04:00
Timothy Miller
47ae1238e2 🦢 Graceful warnings when config.json path is not configured correctly 2021-03-12 15:58:36 -05:00
Timothy Miller
55b705072a 💨 Sped up shutdown
 Check every minute for changes
2021-03-11 20:34:05 -05:00
Timothy Miller
d3cc054b03 💹 Prevent rate limiting by increasing sync frequency to 15 minutes 2021-03-05 23:11:00 -05:00
Timothy Miller
6b25c64846 Revert merge pull request #39 2021-03-05 21:53:18 -05:00
Timothy Miller
378c600084 Merge pull request #39 from bjackman/set-config-path
🏁 Add a flag to modify config.json location
2021-03-03 22:16:30 -05:00
Timothy Miller
975fba4d42 🪵 Reduced duplicate logs [your SD card(s) will thank me] 2021-03-01 00:18:37 -05:00
Timothy Miller
3cd26feb03 🪵 Reduced duplicate logs [your SD card(s) will thank me] 2021-03-01 00:13:11 -05:00
Timothy Miller
1ca225b85c 🔬 Clarified config values for subdomains 2021-03-01 00:01:47 -05:00
Timothy Miller
80bd7801fe 🪵 Reduced duplicate logs [your SD card(s) will thank me] 2021-02-28 23:58:11 -05:00
Timothy Miller
000c833f43 🐳 CI Multi-Arch Docker Builds 2021-02-28 16:58:05 -05:00
Timothy Miller
29771030b1 🐳 CI Multi-Arch Docker builds 2021-02-28 16:39:14 -05:00
Timothy Miller
3753542dce 🐳 CI Multi-Arch Docker builds 2021-02-28 16:32:04 -05:00
Timothy Miller
c34ba8e94c 🐳 CI Multi-Arch Docker builds 2021-02-28 16:28:08 -05:00
Timothy Miller
6be8add640 🐳 CI Multi-Arch Docker Builds 2021-02-28 16:18:06 -05:00
Brendan Jackman
0f3708a482 Add a flag to modify config.json location
On Kubernetes, it's really awkward to write a Secret into the root directory:

https://www.jeffgeerling.com/blog/2019/mounting-kubernetes-secret-single-file-inside-pod

Therefore this adds support for reading the config from an arbitrary path. The
behaviour is unchanged if you don't set this new flag.
2021-02-28 18:24:25 +01:00
Brendan Jackman
8c55892f32 Switch to argparse
The next commit adds a second argument, so raw sys.argv parsing will be a bit
cumbersome. Switch to argparse instead.
2021-02-28 18:18:03 +01:00
Timothy Miller
86c935dea7 🐳 CI Multi-Arch Docker Builds
🗄️ Organized scripts
2021-02-28 12:06:38 -05:00
Timothy Miller
27ccdd0203 🦮 Strip whitespace from subdomain
📚 Improved documentation
2021-02-28 01:51:43 -05:00
Timothy Miller
a816fb6c3f 🧵 Type error resolved 2021-02-27 11:53:30 -05:00
Timothy Miller
e129789a85 🚀 Improvement: Update readme guide on multiple zones 2021-02-26 01:18:53 -05:00
Timothy Miller
4ffbb98f29 🚀 Improvement: Skip PUT request when IP does not change
🧑‍🚀 Improvement: Working graceful exit
🚀 Improvement: Update readme on multiple zones
🐛 Fix: Handle IP changes correctly https://github.com/timothymiller/cloudflare-ddns/issues/37
2021-02-26 01:15:35 -05:00
Timothy Miller
6140917119 Merge pull request #33 from markormesher/feat/handle-sigterm
handle sigterm and shutdown immediately
2021-01-23 11:50:23 -05:00
Mark Ormesher
d763be7931 handle sigterm and shutdown immediately 2021-01-20 18:50:36 +00:00
Timothy Miller
839ffe2551 Merge pull request #29 from xinxijishuwyq/master
Ignore case
2020-12-20 00:41:09 -05:00
KenWong
16352e4543 Ignore case
Signed-off-by: KenWong <xinxijishuwyq@gmail.com>
2020-12-20 13:37:14 +08:00
Timothy Miller
65d8c44ec3 Update docker-build.sh 2020-12-16 21:52:30 -05:00
Timothy Miller
de4e2ac5b6 Update docker-publish.sh 2020-12-16 21:52:18 -05:00
Timothy Miller
efefa0ae7a Update docker-run.sh 2020-12-16 21:52:04 -05:00
Timothy Miller
748170926c Update docker-build-all.sh 2020-12-16 21:51:50 -05:00
Timothy Miller
0ca979f91d Update docker-publish.sh 2020-12-16 19:15:41 -05:00
Timothy Miller
3b92c57a75 Update README.md 2020-12-16 18:55:57 -05:00
timothymiller
db5edef4f0 🖥️ Complete Official Python Docker Image support
📚 Updated README.md
2020-12-16 18:55:06 -05:00
Timothy Miller
1235464e18 Merge pull request #28 from wloot/patch-1
Use 1.1.1.1 api instead of dirty hack to get ip
2020-12-16 18:28:25 -05:00
Julian Liu
58c69e2c5f Use 1.1.1.1 api instead of dirty hack to get ip 2020-12-17 02:42:41 +08:00
timothymiller
3e1fcb13f3 🐳 Docker build scripts renamed 2020-12-16 05:52:41 -05:00
Timothy Miller
2b67615330 Merge pull request #27 from paz/master
dirty support to use cloudflare trace & force it to be ipv4/ipv6
2020-12-16 05:47:22 -05:00
root
344b056a6d use cloudflare trace & force it to be ipv4/ipv6 2020-12-16 18:01:40 +08:00
Timothy Miller
18ad6c6bc4 Merge pull request #25 from xinxijishuwyq/master
add option ttl
2020-12-12 15:22:40 -05:00
timothymiller
bc837c61a0 Merge branch 'master' of https://github.com/timothymiller/cloudflare-ddns 2020-12-12 14:53:03 -05:00
timothymiller
f63b0f13fc 🖼️ Added feature graphic 2020-12-12 14:52:52 -05:00
KenWong
cbfd628f22 add option ttl
Signed-off-by: KenWong <xinxijishuwyq@gmail.com>
2020-12-12 21:18:44 +08:00
Timothy Miller
3f2346db6f Update README.md 2020-12-08 22:35:10 -05:00
Timothy Miller
a8be42292b 📄 Updated Dockerhub links in README.md 2020-12-08 04:04:43 -05:00
timothymiller
f77a72f4e3 📄 Fixed Discord invite link in README.md 2020-12-08 03:58:21 -05:00
timothymiller
a633478239 🖥️ Pi-zero support (ARMv6)
📊 Docker Image Stats
💬 Official Discord Server for support
2020-12-08 03:07:40 -05:00
timothymiller
96f781f8b3 👨‍💻 Multi-arch support (ARMv7/ARMv8,AMD64)
Updated requests dependency
2020-12-07 20:50:15 -05:00
timothymiller
242575d7aa ⛏️ Fix: Gracefully handles all IPv4 or IPv6 connectivity scenarios 2020-10-04 13:54:01 -04:00
Timothy Miller
2ad3d6b564 Updated hidden project file default settings 2020-08-26 16:33:49 -04:00
Timothy Miller
d6d3cb54d2 update README.md 2020-08-26 16:20:03 -04:00
timothymiller
142fbaa8ba Merge branch 'master' of https://github.com/timothymiller/cloudflare-ddns 2020-08-26 05:51:28 -04:00
timothymiller
fa56332d18 Update README.md 2020-08-26 05:51:20 -04:00
Tim Miller
78042582bb Update FUNDING.yml 2020-08-16 15:14:10 -04:00
timothymiller
96d92accaa Added warnings for failure to detect ipv4 2020-08-15 22:42:43 -04:00
timothymiller
18654798e0 Update README.md 2020-08-13 23:15:27 -04:00
timothymiller
1e14700d4e Update README.md section on IPv6 inside Docker 2020-08-13 18:35:51 -04:00
timothymiller
3b9a961f61 Updated README.md 2020-08-13 18:33:52 -04:00
timothymiller
bd15e6f117 Fixed IPv6 support in Docker compose file. 2020-08-13 18:30:10 -04:00
timothymiller
5ac69b8274 Fixed IPv6 access inside Docker container 2020-08-13 18:26:43 -04:00
Tim Miller
e2deea1d6e Merge pull request #10 from merlinschumacher/patch-1
Fix image path in docker-compose example README.
2020-08-11 10:56:41 -04:00
Merlin Schumacher
ddc84cec96 Fix image path in docker-compose example 2020-08-11 14:09:19 +02:00
Tim Miller
33334a529f Merge pull request #9 from luigifcruz/patch-1
Fix docker compose username.
2020-08-07 19:19:31 -04:00
Luigi Cruz
86499b038a Update docker-compose.yml 2020-08-07 20:16:06 -03:00
22 changed files with 942 additions and 169 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,5 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [timothymiller] github: [timothymiller]
patreon: timknowsbest
custom: ['https://timknowsbest.com/donate']

16
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: 'pip'
directory: '/'
schedule:
interval: 'daily'
- package-ecosystem: 'docker'
directory: '/'
schedule:
interval: 'daily'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'

54
.github/workflows/image.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Build cloudflare-ddns Docker image (multi-arch)
on:
push:
branches: master
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
# https://github.com/docker/setup-qemu-action
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
# https://github.com/docker/setup-buildx-action
- name: Setting up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: timothyjmiller/cloudflare-ddns
sep-tags: ','
flavor: |
latest=false
tags: |
type=raw,enable=${{ steps.extract_branch.outputs.branch == 'master' }},value=latest
type=schedule
type=ref,event=pr
- name: Build and publish
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
platforms: linux/ppc64le,linux/s390x,linux/386,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/amd64
labels: |
org.opencontainers.image.source=${{ github.event.repository.html_url }}
org.opencontainers.image.created=${{ steps.meta.outputs.created }}
org.opencontainers.image.revision=${{ github.sha }}

3
.gitignore vendored
View File

@@ -58,3 +58,6 @@ venv/
ENV/ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
# Git History
**/.history/*

38
.vscode/settings.json vendored
View File

@@ -1,21 +1,21 @@
{ {
"files.exclude": { "files.exclude": {
"**/.git": true, "**/.git": true,
"**/.svn": true, "**/.svn": true,
"**/.hg": true, "**/.hg": true,
"**/CVS": true, "**/CVS": true,
"**/.DS_Store": true, "**/.DS_Store": true,
".github": true, "**/Thumbs.db": true,
".vscode": true, ".github": true,
"LICENSE": true, ".gitignore": true,
"requirements.txt": true, ".vscode": true,
"build-docker-image.sh": false, "Dockerfile": true,
".gitignore": true, "LICENSE": true,
"Dockerfile": false, "requirements.txt": true,
"start-sync.sh": false, "venv": true
"venv": true },
}, "explorerExclude.backup": {},
"explorerExclude.backup": null, "python.linting.pylintEnabled": true,
"python.linting.pylintEnabled": true, "python.linting.enabled": true,
"python.linting.enabled": true "python.formatting.provider": "autopep8"
} }

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -6,12 +6,13 @@ FROM python:alpine AS base
FROM base AS dependencies FROM base AS dependencies
# install dependencies # install dependencies
COPY requirements.txt . COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install --user -r requirements.txt
# #
# ---- Release ---- # ---- Release ----
FROM dependencies AS release FROM base AS release
# copy project file(s) # copy installed dependencies and project source file(s)
WORKDIR / WORKDIR /
COPY --from=dependencies /root/.local /root/.local
COPY cloudflare-ddns.py . COPY cloudflare-ddns.py .
CMD ["python", "/cloudflare-ddns.py", "--repeat"] CMD ["python", "-u", "/cloudflare-ddns.py", "--repeat"]

371
README.md
View File

@@ -1,12 +1,37 @@
# :rocket: Cloudflare DDNS <p align="center"><a href="https://timknowsbest.com/free-dynamic-dns" target="_blank" rel="noopener noreferrer"><img width="1024" src="feature-graphic.jpg" alt="Cloudflare DDNS"/></a></p>
Dynamic DNS service based on Cloudflare! Access your home network remotely via a custom domain name without a static IP! # 🚀 Cloudflare DDNS
## :us: Origin Access your home network remotely via a custom domain name without a static IP!
This script was written for the Raspberry Pi platform to enable low cost, simple self hosting to promote a more decentralized internet. On execution, the script fetches public IPv4 and IPv6 addresses and creates/updates DNS records for the subdomains in Cloudflare. Stale, duplicate DNS records are removed for housekeeping. ## ⚡ Efficiency
## :vertical_traffic_light: Getting Started - ❤️ Easy config. List your domains and you're done.
- 🔁 The Python runtime will re-use existing HTTP connections.
- 🗃️ Cloudflare API responses are cached to reduce API usage.
- 🤏 The Docker image is small and efficient.
- 0⃣ Zero dependencies.
- 💪 Supports all platforms.
- 🏠 Enables low cost self hosting to promote a more decentralized internet.
- 🔒 Zero-log IP provider ([cdn-cgi/trace](https://www.cloudflare.com/cdn-cgi/trace))
- 👐 GPL-3.0 License. Open source for open audits.
## 💯 Complete Support of Domain Names, Subdomains, IPv4 & IPv6, and Load Balancing
- 🌐 Supports multiple domains (zones) on the same IP.
- 📠 Supports multiple subdomains on the same IP.
- 📡 IPv4 and IPv6 support.
- 🌍 Supports all Cloudflare regions.
- ⚖️ Supports [Cloudflare Load Balancing](https://developers.cloudflare.com/load-balancing/understand-basics/pools/).
- 🇺🇸 Made in the U.S.A.
## 📊 Stats
| Size | Downloads | Discord |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [![cloudflare-ddns docker image size](https://img.shields.io/docker/image-size/timothyjmiller/cloudflare-ddns?style=flat-square)](https://hub.docker.com/r/timothyjmiller/cloudflare-ddns 'cloudflare-ddns docker image size') | [![Total DockerHub pulls](https://img.shields.io/docker/pulls/timothyjmiller/cloudflare-ddns?style=flat-square)](https://hub.docker.com/r/timothyjmiller/cloudflare-ddns 'Total DockerHub pulls') | [![Official Discord Server](https://img.shields.io/discord/785778163887112192?style=flat-square)](https://discord.gg/UgGmwMvNxm 'Official Discord Server') |
## 🚦 Getting Started
First copy the example configuration file into the real one. First copy the example configuration file into the real one.
@@ -16,17 +41,19 @@ cp config-example.json config.json
Edit `config.json` and replace the values with your own. Edit `config.json` and replace the values with your own.
### Authentication methods ### 🔑 Authentication methods
You can choose to use either the newer API tokens, or the traditional API keys You can choose to use either the newer API tokens, or the traditional API keys
To generate a new API tokens, go to https://dash.cloudflare.com/profile/api-tokens and create a token capable of **Edit DNS**. Then replace the value in To generate a new API tokens, go to your [Cloudflare Profile](https://dash.cloudflare.com/profile/api-tokens) and create a token capable of **Edit DNS**. Then replace the value in
```json ```json
"authentication": "authentication":
"api_token": "Your cloudflare API token, including the capability of **Edit DNS**" "api_token": "Your cloudflare API token, including the capability of **Edit DNS**"
``` ```
Alternatively, you can use the traditional API keys by setting appropriate values for: Alternatively, you can use the traditional API keys by setting appropriate values for:
```json ```json
"authentication": "authentication":
"api_key": "api_key":
@@ -34,40 +61,249 @@ Alternatively, you can use the traditional API keys by setting appropriate value
"account_email": "The email address you use to sign in to cloudflare", "account_email": "The email address you use to sign in to cloudflare",
``` ```
### Other values explained ### 📍 Enable or disable IPv4 or IPv6
Some ISP provided modems only allow port forwarding over IPv4 or IPv6. In this case, you would want to disable any interface not accessible via port forward.
```json
"a": true,
"aaaa": true
```
### 🎛️ Other values explained
```json ```json
"zone_id": "The ID of the zone that will get the records. From your dashboard click into the zone. Under the overview tab, scroll down and the zone ID is listed in the right rail", "zone_id": "The ID of the zone that will get the records. From your dashboard click into the zone. Under the overview tab, scroll down and the zone ID is listed in the right rail",
"subdomains": "Array of subdomains you want to update the A & where applicable, AAAA records. IMPORTANT! Only write subdomain name. Do not include the base domain name. (e.g. foo or an empty string to update the base domain)", "subdomains": "Array of subdomains you want to update the A & where applicable, AAAA records. IMPORTANT! Only write subdomain name. Do not include the base domain name. (e.g. foo or an empty string to update the base domain)",
"proxied": false (defaults to false. Make it true if you want CDN/SSL benefits from cloudflare. This usually disables SSH) "proxied": "Defaults to false. Make it true if you want CDN/SSL benefits from cloudflare. This usually disables SSH)",
"ttl": "Defaults to 300 seconds. Longer TTLs speed up DNS lookups by increasing the chance of cached results, but a longer TTL also means that updates to your records take longer to go into effect. You can choose a TTL between 30 seconds and 1 day. For more information, see [Cloudflare's TTL documentation](https://developers.cloudflare.com/dns/manage-dns-records/reference/ttl/)",
``` ```
## :fax: Hosting multiple domains on the same IP? ## 📠 Hosting multiple subdomains on the same IP?
You can save yourself some trouble when hosting multiple domains pointing to the same IP address (in the case of Traefik) by defining one A & AAAA record 'ddns.example.com' pointing to the IP of the server that will be updated by this DDNS script. For each subdomain, create a CNAME record pointing to 'ddns.example.com'. Now you don't have to manually modify the script config every time you add a new subdomain to your site!
## :whale: Deploy with Docker Compose This script can be used to update multiple subdomains on the same IP address.
Precompiled images are available via the official docker container [on DockerHub](https://hub.docker.com/r/timothyjmiller/cloudflare-ddns). For example, if you have a domain `example.com` and you want to host additional subdomains at `foo.example.com` and `bar.example.com` on the same IP address, you can use this script to update the DNS records for all subdomains.
### ⚠️ Note
Please remove the comments after `//` in the below example. They are only there to explain the config.
Do not include the base domain name in your `subdomains` config. Do not use the [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name).
### 👉 Example 🚀
```bash
{
"cloudflare": [
{
"authentication": {
"api_token": "api_token_here", // Either api_token or api_key
"api_key": {
"api_key": "api_key_here",
"account_email": "your_email_here"
}
},
"zone_id": "your_zone_id_here",
"subdomains": [
{
"name": "", // Root domain (example.com)
"proxied": true
},
{
"name": "foo", // (foo.example.com)
"proxied": true
},
{
"name": "bar", // (bar.example.com)
"proxied": true
}
]
}
],
"a": true,
"aaaa": true,
"purgeUnknownRecords": false,
"ttl": 300
}
```
## 🌐 Hosting multiple domains (zones) on the same IP?
You can handle ddns for multiple domains (cloudflare zones) using the same docker container by duplicating your configs inside the `cloudflare: []` key within `config.json` like below:
### ⚠️ Note:
If you are using API Tokens, make sure the token used supports editing your zone ID.
```bash
{
"cloudflare": [
{
"authentication": {
"api_token": "api_token_here",
"api_key": {
"api_key": "api_key_here",
"account_email": "your_email_here"
}
},
"zone_id": "your_first_zone_id_here",
"subdomains": [
{
"name": "",
"proxied": false
},
{
"name": "remove_or_replace_with_your_subdomain",
"proxied": false
}
]
},
{
"authentication": {
"api_token": "api_token_here",
"api_key": {
"api_key": "api_key_here",
"account_email": "your_email_here"
}
},
"zone_id": "your_second_zone_id_here",
"subdomains": [
{
"name": "",
"proxied": false
},
{
"name": "remove_or_replace_with_your_subdomain",
"proxied": false
}
]
}
],
"a": true,
"aaaa": true,
"purgeUnknownRecords": false
}
```
## ⚖️ Load Balancing
If you have multiple IP addresses and want to load balance between them, you can use the `loadBalancing` option. This will create a CNAME record for each subdomain that points to the subdomain with the lowest IP address.
### 📜 Example config to support load balancing
```json
{
"cloudflare": [
{
"authentication": {
"api_token": "api_token_here",
"api_key": {
"api_key": "api_key_here",
"account_email": "your_email_here"
}
},
"zone_id": "your_zone_id_here",
"subdomains": [
{
"name": "",
"proxied": false
},
{
"name": "remove_or_replace_with_your_subdomain",
"proxied": false
}
]
}
],{
"cloudflare": [
{
"authentication": {
"api_token": "api_token_here",
"api_key": {
"api_key": "api_key_here",
"account_email": "your_email_here"
}
},
"zone_id": "your_zone_id_here",
"subdomains": [
{
"name": "",
"proxied": false
},
{
"name": "remove_or_replace_with_your_subdomain",
"proxied": false
}
]
}
],
"load_balancer": [
{
"authentication": {
"api_token": "api_token_here",
"api_key": {
"api_key": "api_key_here",
"account_email": "your_email_here"
}
},
"pool_id": "your_pool_id_here",
"origin": "your_origin_name_here"
}
],
"a": true,
"aaaa": true,
"purgeUnknownRecords": false,
"ttl": 300
}
```
### Docker environment variable support
Define environmental variables starts with `CF_DDNS_` and use it in config.json
For ex:
```json
{
"cloudflare": [
{
"authentication": {
"api_token": "${CF_DDNS_API_TOKEN}",
```
### 🧹 Optional features
`purgeUnknownRecords` removes stale DNS records from Cloudflare. This is useful if you have a dynamic DNS record that you no longer want to use. If you have a dynamic DNS record that you no longer want to use, you can set `purgeUnknownRecords` to `true` and the script will remove the stale DNS record from Cloudflare.
## 🐳 Deploy with Docker Compose
Pre-compiled images are available via [the official docker container on DockerHub](https://hub.docker.com/r/timothyjmiller/cloudflare-ddns).
Modify the host file path of config.json inside the volumes section of docker-compose.yml. Modify the host file path of config.json inside the volumes section of docker-compose.yml.
```yml ```yml
version: "3.7" version: '3.9'
services: services:
cloudflare-ddns: cloudflare-ddns:
image: timothymiller/cloudflare-ddns:latest image: timothyjmiller/cloudflare-ddns:latest
container_name: cloudflare-ddns container_name: cloudflare-ddns
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
network_mode: 'host'
environment: environment:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
volumes: volumes:
- /EDIT/YOUR/PATH/HERE/config.json:/config.json - /YOUR/PATH/HERE/config.json:/config.json
restart: unless-stopped restart: unless-stopped
``` ```
### :running: Running ### ⚠️ IPv6
Docker requires network_mode be set to host in order to access the IPv6 public address.
### 🏃‍♂️ Running
From the project root directory From the project root directory
@@ -75,31 +311,42 @@ From the project root directory
docker-compose up -d docker-compose up -d
``` ```
### Building from source ## 🐋 Kubernetes
Create a config.json file with your production credentials. Create config File
Give build-docker-image.sh permission to execute.
```bash ```bash
sudo chmod +x ./build-docker-image.sh cp ../../config-example.json config.json
``` ```
At project root, run the build-docker-image.sh script. Edit config.jsonon (vim, nvim, nano... )
```bash ```bash
./build-docker-image.sh ${EDITOR} config.json
``` ```
#### Run the locally compiled version Create config file as Secret.
```bash ```bash
docker run -d timothyjmiller/cloudflare_ddns:latest kubectl create secret generic config-cloudflare-ddns --from-file=config.json --dry-run=client -oyaml -n ddns > config-cloudflare-ddns-Secret.yaml
``` ```
## :penguin: (legacy) Linux + cron instructions (all distros) apply this secret
### :running: Running ```bash
kubectl apply -f config-cloudflare-ddns-Secret.yaml
rm config.json # recomended Just keep de secret on Kubernetes Cluster
```
apply this Deployment
```bash
kubectl apply -f cloudflare-ddns-Deployment.yaml
```
## 🐧 Deploy with Linux + Cron
### 🏃 Running (all distros)
This script requires Python 3.5+, which comes preinstalled on the latest version of Raspbian. Download/clone this repo and give permission to the project's bash script by running `chmod +x ./start-sync.sh`. Now you can execute `./start-sync.sh`, which will set up a virtualenv, pull in any dependencies, and fire the script. This script requires Python 3.5+, which comes preinstalled on the latest version of Raspbian. Download/clone this repo and give permission to the project's bash script by running `chmod +x ./start-sync.sh`. Now you can execute `./start-sync.sh`, which will set up a virtualenv, pull in any dependencies, and fire the script.
@@ -117,16 +364,74 @@ crontab -e
*/15 * * * * /home/your_username_here/cloudflare-ddns/start-sync.sh */15 * * * * /home/your_username_here/cloudflare-ddns/start-sync.sh
``` ```
## Building from source
Create a config.json file with your production credentials.
### 💖 Please Note
The optional `docker-build-all.sh` script requires Docker experimental support to be enabled.
Docker Hub has experimental support for multi-architecture builds. Their official blog post specifies easy instructions for building with [Mac and Windows versions of Docker Desktop](https://docs.docker.com/docker-for-mac/multi-arch/).
1. Choose build platform
- Multi-architecture (experimental) `docker-build-all.sh`
- Linux/amd64 by default `docker-build.sh`
2. Give your bash script permission to execute.
```bash
sudo chmod +x ./docker-build.sh
```
```bash
sudo chmod +x ./docker-build-all.sh
```
3. At project root, run the `docker-build.sh` script.
Recommended for local development
```bash
./docker-build.sh
```
Recommended for production
```bash
./docker-build-all.sh
```
### Run the locally compiled version
```bash
docker run -d timothyjmiller/cloudflare_ddns:latest
```
## Supported Platforms
- [Docker](https://docs.docker.com/get-docker/)
- [Docker Compose](https://docs.docker.com/compose/install/)
- [Kubernetes](https://kubernetes.io/docs/tasks/tools/)
- [Python 3](https://www.python.org/downloads/)
- [Systemd](https://www.freedesktop.org/wiki/Software/systemd/)
## 📜 Helpful links
- [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens)
- [Cloudflare zone ID](https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-IP-address-)
- [Cloudflare zone DNS record ID](https://support.cloudflare.com/hc/en-us/articles/360019093151-Managing-DNS-records-in-Cloudflare)
## License ## License
This Template is licensed under the GNU General Public License, version 3 (GPLv3) and is distributed free of charge. This Template is licensed under the GNU General Public License, version 3 (GPLv3).
## Author ## Author
Timothy Miller Timothy Miller
GitHub: https://github.com/timothymiller 💡 [View my GitHub profile 💡](https://github.com/timothymiller)
Website: https://timknowsbest.com 💻 [View my personal website 💻](https://timknowsbest.com)
Donation: https://timknowsbest.com/donate 💸

View File

@@ -1 +0,0 @@
docker build -t timothyjmiller/cloudflare-ddns:latest .

View File

@@ -1,127 +1,319 @@
import requests, json, sys, os #!/usr/bin/env python3
import time, traceback # cloudflare-ddns.py
# Summary: Access your home network remotely via a custom domain name without a static IP!
# Description: Access your home network remotely via a custom domain
# Access your home network remotely via a custom domain
# A small, 🕵️ privacy centric, and ⚡
# lightning fast multi-architecture Docker image for self hosting projects.
PATH = os.getcwd() + "/" __version__ = "1.0.2"
version = float(str(sys.version_info[0]) + "." + str(sys.version_info[1]))
if(version < 3.5): from string import Template
raise Exception("This script requires Python 3.5+")
with open(PATH + "config.json") as config_file: import json
config = json.loads(config_file.read()) import os
import signal
import sys
import threading
import time
import requests
CONFIG_PATH = os.environ.get('CONFIG_PATH', os.getcwd())
# Read in all environment variables that have the correct prefix
ENV_VARS = {key: value for (key, value) in os.environ.items() if key.startswith('CF_DDNS_')}
class GracefulExit:
def __init__(self):
self.kill_now = threading.Event()
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
def exit_gracefully(self, signum, frame):
print("🛑 Stopping main thread...")
self.kill_now.set()
def deleteEntries(type):
# Helper function for deleting A or AAAA records
# in the case of no IPv4 or IPv6 connection, yet
# existing A or AAAA records are found.
for option in config["cloudflare"]:
answer = cf_api(
"zones/" + option['zone_id'] +
"/dns_records?per_page=100&type=" + type,
"GET", option)
if answer is None or answer["result"] is None:
time.sleep(5)
return
for record in answer["result"]:
identifier = str(record["id"])
cf_api(
"zones/" + option['zone_id'] + "/dns_records/" + identifier,
"DELETE", option)
print("🗑️ Deleted stale record " + identifier)
def getIPs(): def getIPs():
a = requests.get("https://api.ipify.org?format=json").json().get("ip") a = None
aaaa = requests.get("https://api6.ipify.org?format=json").json().get("ip") aaaa = None
ips = [] global ipv4_enabled
global ipv6_enabled
if(a.find(".") > -1): global purgeUnknownRecords
ips.append({ if ipv4_enabled:
try:
a = requests.get(
"https://1.1.1.1/cdn-cgi/trace").text.split("\n")
a.pop()
a = dict(s.split("=") for s in a)["ip"]
except Exception:
global shown_ipv4_warning
if not shown_ipv4_warning:
shown_ipv4_warning = True
print("🧩 IPv4 not detected via 1.1.1.1, trying 1.0.0.1")
# Try secondary IP check
try:
a = requests.get(
"https://1.0.0.1/cdn-cgi/trace").text.split("\n")
a.pop()
a = dict(s.split("=") for s in a)["ip"]
except Exception:
global shown_ipv4_warning_secondary
if not shown_ipv4_warning_secondary:
shown_ipv4_warning_secondary = True
print("🧩 IPv4 not detected via 1.0.0.1. Verify your ISP or DNS provider isn't blocking Cloudflare's IPs.")
if purgeUnknownRecords:
deleteEntries("A")
if ipv6_enabled:
try:
aaaa = requests.get(
"https://[2606:4700:4700::1111]/cdn-cgi/trace").text.split("\n")
aaaa.pop()
aaaa = dict(s.split("=") for s in aaaa)["ip"]
except Exception:
global shown_ipv6_warning
if not shown_ipv6_warning:
shown_ipv6_warning = True
print("🧩 IPv6 not detected via 1.1.1.1, trying 1.0.0.1")
try:
aaaa = requests.get(
"https://[2606:4700:4700::1001]/cdn-cgi/trace").text.split("\n")
aaaa.pop()
aaaa = dict(s.split("=") for s in aaaa)["ip"]
except Exception:
global shown_ipv6_warning_secondary
if not shown_ipv6_warning_secondary:
shown_ipv6_warning_secondary = True
print("🧩 IPv6 not detected via 1.0.0.1. Verify your ISP or DNS provider isn't blocking Cloudflare's IPs.")
if purgeUnknownRecords:
deleteEntries("AAAA")
ips = {}
if (a is not None):
ips["ipv4"] = {
"type": "A", "type": "A",
"ip": a "ip": a
}) }
if (aaaa is not None):
if(aaaa.find(":") > -1): ips["ipv6"] = {
ips.append({
"type": "AAAA", "type": "AAAA",
"ip": aaaa "ip": aaaa
}) }
return ips return ips
def commitRecord(ip): def commitRecord(ip):
stale_record_ids = [] global ttl
for c in config["cloudflare"]: for option in config["cloudflare"]:
subdomains = c["subdomains"] subdomains = option["subdomains"]
response = cf_api("zones/" + c['zone_id'], "GET", c) response = cf_api("zones/" + option['zone_id'], "GET", option)
if response is None or response["result"]["name"] is None:
time.sleep(5)
return
base_domain_name = response["result"]["name"] base_domain_name = response["result"]["name"]
for subdomain in subdomains: for subdomain in subdomains:
exists = False try:
name = subdomain["name"].lower().strip()
proxied = subdomain["proxied"]
except:
name = subdomain
proxied = option["proxied"]
fqdn = base_domain_name
# Check if name provided is a reference to the root domain
if name != '' and name != '@':
fqdn = name + "." + base_domain_name
record = { record = {
"type": ip["type"], "type": ip["type"],
"name": subdomain, "name": fqdn,
"content": ip["ip"], "content": ip["ip"],
"proxied": c["proxied"] "proxied": proxied,
"ttl": ttl
} }
list = cf_api( dns_records = cf_api(
"zones/" + c['zone_id'] + "/dns_records?per_page=100&type=" + ip["type"], "GET", c) "zones/" + option['zone_id'] +
"/dns_records?per_page=100&type=" + ip["type"],
full_subdomain = base_domain_name "GET", option)
if subdomain: identifier = None
full_subdomain = subdomain + "." + full_subdomain modified = False
duplicate_ids = []
dns_id = "" if dns_records is not None:
for r in list["result"]: for r in dns_records["result"]:
if (r["name"] == full_subdomain): if (r["name"] == fqdn):
exists = True if identifier:
if (r["content"] != ip["ip"]): if r["content"] == ip["ip"]:
if (dns_id == ""): duplicate_ids.append(identifier)
dns_id = r["id"] identifier = r["id"]
else:
duplicate_ids.append(r["id"])
else: else:
stale_record_ids.append(r["id"]) identifier = r["id"]
if(exists == False): if r['content'] != record['content'] or r['proxied'] != record['proxied']:
print("Adding new record " + str(record)) modified = True
if identifier:
if modified:
print("📡 Updating record " + str(record))
response = cf_api(
"zones/" + option['zone_id'] +
"/dns_records/" + identifier,
"PUT", option, {}, record)
else:
print(" Adding new record " + str(record))
response = cf_api( response = cf_api(
"zones/" + c['zone_id'] + "/dns_records", "POST", c, {}, record) "zones/" + option['zone_id'] + "/dns_records", "POST", option, {}, record)
elif(dns_id != ""): if purgeUnknownRecords:
# Only update if the record content is different for identifier in duplicate_ids:
print("Updating record " + str(record)) identifier = str(identifier)
response = cf_api( print("🗑️ Deleting stale record " + identifier)
"zones/" + c['zone_id'] + "/dns_records/" + dns_id, "PUT", c, {}, record) response = cf_api(
"zones/" + option['zone_id'] +
# Delete duplicate, stale records "/dns_records/" + identifier,
for identifier in stale_record_ids: "DELETE", option)
print("Deleting stale record " + str(identifier))
response = cf_api(
"zones/" + c['zone_id'] + "/dns_records/" + identifier, "DELETE", c)
return True return True
def updateLoadBalancer(ip):
for option in config["load_balancer"]:
pools = cf_api('user/load_balancers/pools', 'GET', option)
if pools:
idxr = dict((p['id'], i) for i, p in enumerate(pools['result']))
idx = idxr.get(option['pool_id'])
origins = pools['result'][idx]['origins']
idxr = dict((o['name'], i) for i, o in enumerate(origins))
idx = idxr.get(option['origin'])
origins[idx]['address'] = ip['ip']
data = {'origins': origins}
response = cf_api(f'user/load_balancers/pools/{option["pool_id"]}', 'PATCH', option, {}, data)
def cf_api(endpoint, method, config, headers={}, data=False): def cf_api(endpoint, method, config, headers={}, data=False):
api_token = config['authentication']['api_token'] api_token = config['authentication']['api_token']
if api_token != '' and api_token != 'api_token_here': if api_token != '' and api_token != 'api_token_here':
headers = { headers = {
"Authorization": "Bearer " + api_token, "Authorization": "Bearer " + api_token, **headers
**headers
} }
else: else:
headers = { headers = {
"X-Auth-Email": config['authentication']['api_key']['account_email'], "X-Auth-Email": config['authentication']['api_key']['account_email'],
"X-Auth-Key": config['authentication']['api_key']['api_key'], "X-Auth-Key": config['authentication']['api_key']['api_key'],
} }
if(data == False):
response = requests.request(
method, "https://api.cloudflare.com/client/v4/" + endpoint, headers=headers)
else:
response = requests.request(
method, "https://api.cloudflare.com/client/v4/" + endpoint, headers=headers, json=data)
return response.json()
def every(delay, task):
next_time = time.time() + delay
while True:
time.sleep(max(0, next_time - time.time()))
try: try:
task() if (data == False):
except Exception: response = requests.request(
traceback.print_exc() method, "https://api.cloudflare.com/client/v4/" + endpoint, headers=headers)
# in production code you might want to have this instead of course: else:
# logger.exception("Problem while executing repetitive task.") response = requests.request(
# skip tasks if we are behind schedule: method, "https://api.cloudflare.com/client/v4/" + endpoint,
next_time += (time.time() - next_time) // delay * delay + delay headers=headers, json=data)
def updateIPs(): if response.ok:
for ip in getIPs(): return response.json()
print("Checking " + ip["type"] + " records") else:
print("😡 Error sending '" + method +
"' request to '" + response.url + "':")
print(response.text)
return None
except Exception as e:
print("😡 An exception occurred while sending '" +
method + "' request to '" + endpoint + "': " + str(e))
return None
def updateIPs(ips):
for ip in ips.values():
commitRecord(ip) commitRecord(ip)
#updateLoadBalancer(ip)
if(len(sys.argv) > 1):
if(sys.argv[1] == "--repeat"): if __name__ == '__main__':
import threading shown_ipv4_warning = False
threading.Thread(target=lambda: every(60*15, updateIPs)).start() shown_ipv4_warning_secondary = False
updateIPs() shown_ipv6_warning = False
shown_ipv6_warning_secondary = False
ipv4_enabled = True
ipv6_enabled = True
purgeUnknownRecords = False
if sys.version_info < (3, 5):
raise Exception("🐍 This script requires Python 3.5+")
config = None
try:
with open(os.path.join(CONFIG_PATH, "config.json")) as config_file:
if len(ENV_VARS) != 0:
config = json.loads(Template(config_file.read()).safe_substitute(ENV_VARS))
else:
config = json.loads(config_file.read())
except:
print("😡 Error reading config.json")
# wait 10 seconds to prevent excessive logging on docker auto restart
time.sleep(10)
if config is not None:
try:
ipv4_enabled = config["a"]
ipv6_enabled = config["aaaa"]
except:
ipv4_enabled = True
ipv6_enabled = True
print("⚙️ Individually disable IPv4 or IPv6 with new config.json options. Read more about it here: https://github.com/timothymiller/cloudflare-ddns/blob/master/README.md")
try:
purgeUnknownRecords = config["purgeUnknownRecords"]
except:
purgeUnknownRecords = False
print("⚙️ No config detected for 'purgeUnknownRecords' - defaulting to False")
try:
ttl = int(config["ttl"])
except:
ttl = 300 # default Cloudflare TTL
print(
"⚙️ No config detected for 'ttl' - defaulting to 300 seconds (5 minutes)")
if ttl < 30:
ttl = 1 #
print("⚙️ TTL is too low - defaulting to 1 (auto)")
if (len(sys.argv) > 1):
if (sys.argv[1] == "--repeat"):
if ipv4_enabled and ipv6_enabled:
print(
"🕰️ Updating IPv4 (A) & IPv6 (AAAA) records every " + str(ttl) + " seconds")
elif ipv4_enabled and not ipv6_enabled:
print("🕰️ Updating IPv4 (A) records every " +
str(ttl) + " seconds")
elif ipv6_enabled and not ipv4_enabled:
print("🕰️ Updating IPv6 (AAAA) records every " +
str(ttl) + " seconds")
next_time = time.time()
killer = GracefulExit()
prev_ips = None
while True:
updateIPs(getIPs())
if killer.kill_now.wait(ttl):
break
else:
print("❓ Unrecognized parameter '" +
sys.argv[1] + "'. Stopping now.")
else:
updateIPs(getIPs())

View File

@@ -2,18 +2,27 @@
"cloudflare": [ "cloudflare": [
{ {
"authentication": { "authentication": {
"api_token": "api_token_here", "api_token": "api_token_here",
"api_key": { "api_key": {
"api_key": "api_key_here", "api_key": "api_key_here",
"account_email": "your_email_here" "account_email": "your_email_here"
} }
}, },
"zone_id": "your_zone_id_here", "zone_id": "your_zone_id_here",
"subdomains": [ "subdomains": [
"", {
"subdomain" "name": "",
], "proxied": false
"proxied": false },
{
"name": "remove_or_replace_with_your_subdomain",
"proxied": false
}
]
} }
] ],
"a": true,
"aaaa": true,
"purgeUnknownRecords": false,
"ttl": 300
} }

View File

@@ -1,13 +1,14 @@
version: "3.7" version: '3.9'
services: services:
cloudflare-ddns: cloudflare-ddns:
image: timothymiller/cloudflare-ddns:latest image: timothyjmiller/cloudflare-ddns:latest
container_name: cloudflare-ddns container_name: cloudflare-ddns
security_opt: security_opt:
- no-new-privileges:true - no-new-privileges:true
network_mode: 'host'
environment: environment:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
volumes: volumes:
- /EDIT/YOUR/PATH/HERE/config.json:/config.json - /YOUR/PATH/HERE/config.json:/config.json
restart: unless-stopped restart: unless-stopped

BIN
feature-graphic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

33
k8s/cloudflare-ddns.yml Normal file
View File

@@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflare-ddns
spec:
selector:
matchLabels:
app: cloudflare-ddns
template:
metadata:
labels:
app: cloudflare-ddns
spec:
containers:
- name: cloudflare-ddns
image: timothyjmiller/cloudflare-ddns:latest
resources:
limits:
memory: '32Mi'
cpu: '50m'
env:
- name: CONFIG_PATH
value: '/etc/cloudflare-ddns/'
volumeMounts:
- mountPath: '/etc/cloudflare-ddns'
name: config-cloudflare-ddns
readOnly: true
volumes:
- name: config-cloudflare-ddns
secret:
secretName: config-cloudflare-ddns

View File

@@ -1 +1 @@
requests==2.24.0 requests==2.31.0

4
scripts/docker-build-all.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
BASH_DIR=$(dirname $(realpath "${BASH_SOURCE}"))
docker buildx build --platform linux/ppc64le,linux/s390x,linux/386,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/amd64 --tag timothyjmiller/cloudflare-ddns:latest ${BASH_DIR}/../
# TODO: Support linux/riscv64

3
scripts/docker-build.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
BASH_DIR=$(dirname $(realpath "${BASH_SOURCE}"))
docker build --platform linux/amd64 --tag timothyjmiller/cloudflare-ddns:latest ${BASH_DIR}/../

3
scripts/docker-publish.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
BASH_DIR=$(dirname $(realpath "${BASH_SOURCE}"))
docker buildx build --platform linux/ppc64le,linux/s390x,linux/386,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/amd64 --tag timothyjmiller/cloudflare-ddns:latest --push ${BASH_DIR}/../

2
scripts/docker-run.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
docker run timothyjmiller/cloudflare-ddns:latest

View File

@@ -4,7 +4,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
python3 -m venv venv python3 -m venv venv
source ./venv/bin/activate source ./venv/bin/activate
pip3 install requests
cd $DIR cd $DIR
set -o pipefail; pip install -r requirements.txt | { grep -v "already satisfied" || :; }
python3 cloudflare-ddns.py python3 cloudflare-ddns.py

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Update DDNS on Cloudflare
ConditionPathExists=/etc/cloudflare-ddns/config.json
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
Environment=CONFIG_PATH=/etc/cloudflare-ddns
ExecStart=cloudflare-ddns
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Update DDNS on Cloudflare every 15 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=15m
[Install]
WantedBy=timers.target