From 44541225fafeea00e55633d8b822ea08fd52c28c Mon Sep 17 00:00:00 2001 From: KamilKozakowski Date: Mon, 30 Oct 2023 18:13:10 +0100 Subject: [PATCH] first commit --- .gitignore | 164 +++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 6 ++ build.sh | 2 + env.env | 3 + main.py | 143 +++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + run.sh | 6 ++ 7 files changed, 326 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 build.sh create mode 100644 env.env create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57fe605 --- /dev/null +++ b/.gitignore @@ -0,0 +1,164 @@ +**/.DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +myenv.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3400c17 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11 +WORKDIR /myapp +COPY requirements.txt ./ +RUN pip install -r requirements.txt +ADD main.py / +CMD [ "python", "./main.py", ">> /volume/logs/script_logs.txt"] \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..34f80d9 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker build -f Dockerfile -t my_cloudflare_ip_update . \ No newline at end of file diff --git a/env.env b/env.env new file mode 100644 index 0000000..4dbac8d --- /dev/null +++ b/env.env @@ -0,0 +1,3 @@ +CLOUDFLARE_TOKEN= +ZONES= +DNS_A_RECORDS= \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..9a0ebcd --- /dev/null +++ b/main.py @@ -0,0 +1,143 @@ +import requests +import os +import sys +from time import gmtime, strftime +import crython +#SSL Cert Verification +# Requests verifies SSL certificates for HTTPS requests, just like a web browser. +# By default, SSL verification is enabled, and Requests will throw a SSLError +# if it’s unable to verify the certificate. + +def print_zone(x): + return f"{{name: {x['name']} , id: {x['id']}}}" + +def print_dns_record(x): + return f"{{name: {x['name']} , id: {x['id']} , type: {x['type']}}}" + + +def get_zones(auth_token): + def zone_to_id_name(x): + return { + 'id': x['id'], + 'name': x['name'] + } + api_url = "https://api.cloudflare.com/client/v4/zones" + response = requests.get(api_url, headers={ + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {auth_token}' + }) + response_json = response.json() + if response_json['success'] == False: + raise "" + zones = list(map(zone_to_id_name, response_json['result'])) + print('Zones: ') + print('\n'.join(map(print_zone, zones))) + active_zones = list(map(zone_to_id_name,filter(lambda x: x['status'] == 'active',response_json['result']))) + print('Active zones: ') + print('\n'.join(map(print_zone, active_zones))) + return (zones, active_zones) + +def get_dns_records(auth_token, active_zones): + def dns_record_to_id_name_type(x): + return { + 'id': x['id'], + 'name': x['name'], + 'zone_id': x['zone_id'], + 'zone_name': x['zone_name'], + 'type': x['type'] + } + dns_a_records: list = [] + dns_records: list = [] + for active_zone in active_zones: + api_url = f"https://api.cloudflare.com/client/v4/zones/{active_zone['id']}/dns_records" + response = requests.get(api_url, headers={ + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {auth_token}' + }) + response_json = response.json() + if response_json['success'] == False: + raise "" + dns_records.extend(list(map(dns_record_to_id_name_type, response_json['result']))) + print('Dns records: ') + print('\n'.join(map(print_dns_record, dns_records))) + dns_a_records.extend(list(map(dns_record_to_id_name_type,filter(lambda x: x['type'] == 'A',response_json['result'])))) + print('Dns records A: ') + print('\n'.join(map(print_dns_record, dns_a_records))) + return (dns_records, dns_a_records) + +# Crython supports seven fields +# (seconds, minutes, hours, day of month, month, weekday, year). +# https://crontab.guru/#*/5_*_*_*_* +old_ip: str = '0.0.0.0' + +@crython.job(expr='0 */5 * * * * *') +def main(): + TOKEN = os.environ.get('CLOUDFLARE_TOKEN') + DNS = os.environ.get('ZONES') + DNS_A = os.environ.get('DNS_A_RECORDS') + DNS = DNS.split(',') + DNS_A.split(',') + + + + current_time = strftime("%d %b, %Y %H:%M:%S", gmtime()) + print(f"{current_time}: EXECUTION STARTED") + my_public_ip = requests.get('https://ifconfig.me').text + + if old_ip == my_public_ip: + print("NOTHING TO UPDATE") + sys.exit(0) + print(f'PUBLIC IP: {my_public_ip}') + + print("Zones:") + (zones, active_zones) = get_zones(TOKEN) + active_zones = list(filter(lambda x: x['name'] in DNS, active_zones)) + + print("Active zones to update:") + print('\n'.join(map(print_zone,active_zones))) + + (dns_records, dns_a_records) = get_dns_records(TOKEN, active_zones) + + print(dns_a_records) + for dns_a_record in dns_a_records: + # TOOD + + api_url = f"https://api.cloudflare.com/client/v4/zones/{dns_a_record['zone_id']}/dns_records/{dns_a_record['id']}" + response = requests.patch(api_url, headers={ + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {TOKEN}' + }, data= { + 'content': my_public_ip, + 'type': 'A' + }) + response_json = response.json() + if response_json['success'] == False: + raise "" + print(f"Successfuly chcanged IP address of {dns_a_record['name']}") + print(response_json) + + current_time = strftime("%d %b, %Y %H:%M:%S", gmtime()) + + old_ip = my_public_ip + print(f"{current_time} FINISHED EXECUTION SUCCESSFULLY") + + + # api_url = f"https://api.cloudflare.com/client/v4/zones/{dns_a_record['zone_id']}/dns_records/{dns_a_record['id']}" + # response = requests.patch(api_url, headers={ + # 'Content-Type': 'application/json', + # 'Authorization': f'Bearer {TOKEN}' + # }, data= { + # 'content': my_public_ip, + # 'type': 'A' + # }) + # response_json = response.json() + # if response_json['success'] == False: + # raise "" + # print(f"Successfuly chcanged IP address of {dns_a_record['name']}") + + # api_url = f"https://api.cloudflare.com/client/v4/zones/{dns_a_record['zone_id']}/dns_records/{dns_a_record['id']}" + # response = requests.get(api_url, headers={ + # 'Content-Type': 'application/json', + # 'Authorization': f'Bearer {TOKEN}' + # }) + # response_json = response.json() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e29a922 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests +crython \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..d263bfb --- /dev/null +++ b/run.sh @@ -0,0 +1,6 @@ +#!/bin/bash +docker run \ + --detached \ + --env-file myenv.env \ + --volume ./volume:/volume \ + my_cloudflare_ip_update \ No newline at end of file