Skip to content

Commit

Permalink
Merge pull request #1 from M4RC0Sx/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
M4RC0Sx authored Feb 28, 2024
2 parents d1ae7ac + f41972c commit a44a17b
Show file tree
Hide file tree
Showing 13 changed files with 762 additions and 0 deletions.
160 changes: 160 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# 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/
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
PUBLIC_IP_PROVIDER=https://api.ipify.org
REFRESH_MINUTES=5
CF_API_URL=https://api.cloudflare.com/client/v4
CF_API_EMAIL=[email protected]
CF_API_KEY=cf_global_api_key
CF_ZONE=yourdomain.com
CF_RECORD=subdomain.yourdomain.com
30 changes: 30 additions & 0 deletions .github/workflows/semantic-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: semantic-release

on:
push:
branches:
- master

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.10.0
- name: Install semantic-release
run: npm install -g semantic-release @semantic-release/changelog conventional-changelog-conventionalcommits @semantic-release/git semantic-release-replace-plugin @codedependant/semantic-release-docker
- name: Docker login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Publish
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: npx semantic-release
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM python:3.12-bullseye AS builder

RUN pip install poetry==1.7.1

ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_CACHE_DIR='/tmp/poetry'

WORKDIR /app

COPY pyproject.toml poetry.lock ./
RUN touch README.md
RUN poetry install --no-dev --no-root
RUN rm -rf ${POETRY_CACHE_DIR}


FROM python:3.12-slim-bullseye AS runtime
WORKDIR /app

ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"

COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY clouddnsflare /app/clouddnsflare

ENTRYPOINT ["python", "-m", "clouddnsflare"]


Empty file added README.md
Empty file.
Empty file added clouddnsflare/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions clouddnsflare/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from os import getenv
import sys
import logging
from time import sleep


from clouddnsflare.defaults import DefaultConfig
from clouddnsflare.cloudflare_api import CloudflareAPI


logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logging.getLogger().addHandler(logging.StreamHandler())


PUBLIC_IP_PROVIDER = getenv(
"PUBLIC_IP_PROVIDER", DefaultConfig.PUBLIC_IP_PROVIDER.value
)
REFRESH_MINUTES = int(getenv("REFRESH_MINUTES", DefaultConfig.REFRESH_MINUTES.value))
CF_API_URL = getenv("CF_API_URL", DefaultConfig.CF_API_URL.value)
CF_API_EMAIL = getenv("CF_API_EMAIL", DefaultConfig.CF_API_EMAIL.value)
CF_API_KEY = getenv("CF_API_KEY", DefaultConfig.CF_API_KEY.value)
CF_ZONE = getenv("CF_ZONE", DefaultConfig.CF_ZONE.value)
CF_RECORD = getenv("CF_RECORD", DefaultConfig.CF_RECORD.value)


def main() -> None:
logging.info("ClouDDNSflare by M4RC0Sx (https://github.com/M4RC0Sx) is starting...")
logging.info(f"Public IP provider: {PUBLIC_IP_PROVIDER}")
logging.info(f"Refresh minutes: {REFRESH_MINUTES}")
logging.info(f"Cloudflare zone: {CF_ZONE}")
logging.info(f"Cloudflare record: {CF_RECORD}")

cf_api = CloudflareAPI(
email=CF_API_EMAIL,
key=CF_API_KEY,
zone=CF_ZONE,
record=CF_RECORD,
cf_api=CF_API_URL,
public_ip_provider=PUBLIC_IP_PROVIDER,
)

while True:
logging.info("Refreshing public IP...")

try:
cf_api.update_dns_record()
except Exception as e:
logging.error(f"There was an error updating DNS record: {e}. Exiting...")
sys.exit(1)

logging.info(f"Waiting {REFRESH_MINUTES} minutes before next refresh...")
sleep(REFRESH_MINUTES * 60)


if __name__ == "__main__":
main()
88 changes: 88 additions & 0 deletions clouddnsflare/cloudflare_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import logging

import requests


class CloudflareAPI:
def __init__(
self,
email: str,
key: str,
zone: str,
record: str,
cf_api: str,
public_ip_provider: str,
) -> None:
self.email = email
self.key = key
self.zone = zone
self.record = record
self.cf_api = cf_api

self.public_ip_provider = public_ip_provider

self.zone_id: str | None = None
self.record_id: str | None = None

def __get_cf_headers(self) -> dict[str, str]:
return {
"X-Auth-Email": self.email,
"X-Auth-Key": self.key,
"Content-Type": "application/json",
}

def __get_public_ip(self) -> str:
return requests.get(self.public_ip_provider).text

def __get_zone_id(self) -> str:
if self.zone_id:
return self.zone_id

url = f"{self.cf_api}/zones?name={self.zone}"
headers = self.__get_cf_headers()
response = requests.get(url, headers=headers)
return str(response.json()["result"][0]["id"])

def __get_record_id(self, zone_id: str) -> str:
if self.record_id:
return self.record_id

url = f"{self.cf_api}/zones/{zone_id}/dns_records"
headers = self.__get_cf_headers()
params = {"name": self.record, "type": "A"}
response = requests.get(url, headers=headers, params=params)
return str(response.json()["result"][0]["id"])

def update_dns_record(self) -> None:
zone_id = None
record_id = None
public_ip = None

try:
zone_id = self.__get_zone_id()
except Exception as e:
logging.error(f"Error getting zone ID: {e}")
raise e
self.zone_id = zone_id
try:
record_id = self.__get_record_id(zone_id)
except Exception as e:
logging.error(f"Error getting record ID: {e}")
raise e
self.record_id = record_id
try:
public_ip = self.__get_public_ip()
except Exception as e:
logging.error(f"Error getting public IP: {e}")
raise e

url = f"{self.cf_api}/zones/{zone_id}/dns_records/{record_id}"
headers = self.__get_cf_headers()
data = {
"type": "A",
"name": self.record,
"content": public_ip,
"proxied": False,
}
requests.put(url, headers=headers, json=data)
logging.info(f"DNS record updated to {public_ip}")
18 changes: 18 additions & 0 deletions clouddnsflare/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from enum import Enum


class DefaultConfig(Enum):
# Public IP service provider
PUBLIC_IP_PROVIDER = "https://api.ipify.org"
# Refresh minutes
REFRESH_MINUTES = 5
# Cloudflare API URL
CF_API_URL = "https://api.cloudflare.com/client/v4"
# Cloudflare API email
CF_API_EMAIL = ""
# Cloudflare API key
CF_API_KEY = ""
# Cloudflare zone
CF_ZONE = ""
# Cloudflare record
CF_RECORD = ""
Loading

0 comments on commit a44a17b

Please sign in to comment.