Initial commit

This commit is contained in:
2024-09-01 03:26:24 +09:00
commit 1786ce366b
11 changed files with 307 additions and 0 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
src/cloudflare_cname.py
README.md

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
src/cloudflare_cname.py

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM python:3.9-slim
# Install required packages
RUN apt-get update && apt-get install -y \
cron \
make \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/cloudflare-ddns/
WORKDIR /app/cloudflare-ddns
RUN mkdir -p /app/cloudflare-ddns/config
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
RUN cp /app/cloudflare-ddns/cron/cronjob /etc/cron.d/cloudflare-ddns
RUN touch /var/log/cron.log
RUN chmod +x /app/cloudflare-ddns/start.sh
RUN chmod +x /app/cloudflare-ddns/run_script.sh
VOLUME [ "/app/cloudflare-ddns/config" ]
ENTRYPOINT [ "bash", "-c" ]
CMD [ "/app/cloudflare-ddns/start.sh && cron -f" ]

47
Makefile Normal file
View File

@@ -0,0 +1,47 @@
IMAGE_NAME = "cloudflare-ddns"
default: build
.PHONY: build
build: stop
docker build -t $(IMAGE_NAME) .
.PHONY: run
run:
docker run --rm --privileged=true -d -v /opt/cloudflare-ddns/config:/app/cloudflare-ddns/config $(IMAGE_NAME)
.PHONY: stop
stop:
@container_id=$$(docker ps -q -f ancestor=$(IMAGE_NAME)); \
if [ -n "$$container_id" ]; then \
echo "Stopping container $$container_id"; \
docker stop $$container_id; \
else \
echo "No running container found for image $(IMAGE_NAME)"; \
fi
.PHONY: install
install:
@echo "Installing cloudflare-ddns to '/app/cloudflare-ddns'"
@mkdir -p /app/cloudflare-ddns
@cp -r ./* /app/cloudflare-ddns
@if [ ! -f /app/cloudflare-ddns/config/env.json ]; then \
echo "Creating default env.json"; \
mkdir -p /app/cloudflare-ddns/config; \
cp /app/cloudflare-ddns/init/default_env.json /app/cloudflare-ddns/config/env.json; \
echo "Please edit /app/cloudflare-ddns/config/env.json"; \
fi
@cp /app/cloudflare-ddns/cron/cronjob /etc/cron.d/cloudflare-ddns
@touch /var/log/cloudflare_ddns.log
@chmod +x /app/cloudflare-ddns/run_script.sh
@chmod +x /app/cloudflare-ddns/start.sh
@echo "Enter 'make configure' to configure the Cloudflare API key, domain and ZONE ID"
@echo "Done"
.PHONY: configure
configure:
@echo "Please edit /app/cloudflare-ddns/config/env.json"
@vi /app/cloudflare-ddns/config/env.json
@echo "Done"

40
README.md Normal file
View File

@@ -0,0 +1,40 @@
# Cloudflare DDNS Project
This project aims to implement a Dynamic DNS (DDNS) solution using Cloudflare's API.
## Overview
Dynamic DNS allows you to associate a domain name with a changing IP address, making it easier to access your network resources remotely. Cloudflare's API provides the necessary tools to automate this process.
## Prerequisites
Before getting started, make sure you have the following:
- A Cloudflare account
- A registered domain name
- API credentials from Cloudflare
## Installation
To install and set up the project, follow these steps:
1. Clone the repository: `git clone https://github.com/jung-geun/cloudflare-ddns.git`
2. Deploy the project to a server or cloud platform:
- You can deploy the project to a server or cloud platform of your choice.
- Make sure to install the necessary dependencies by running 'make install'
3. Configure the project:
- Create a configuration file named `config.json` in the initial
'make configure' will create a configuration file with default settings.
- Add your Cloudflare API credentials and domain information to the configuration file.
## Usage
Once the project is up and running, it will periodically check for IP address changes and update the DNS records accordingly. You can customize the update frequency and other settings in the configuration file.
## Contributing
Contributions are welcome! If you have any suggestions or improvements, feel free to open an issue or submit a pull request.
## License
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more details.
## 기여하기
기여는 환영합니다! 제안이나 개선 사항이 있으시면 이슈를 열거나 풀 리퀘스트를 제출해 주세요.
## 라이선스
이 프로젝트는 MIT 라이선스로 배포됩니다. 자세한 내용은 [LICENSE](./LICENSE) 파일을 참조하세요.

1
cron/cronjob Normal file
View File

@@ -0,0 +1 @@
*/5 * * * * root /app/cloudflare-ddns/run_script.sh

5
init/default_env.json Normal file
View File

@@ -0,0 +1,5 @@
{
"CLOUDFLARE_API_KEY": "",
"CLOUDFLARE_DOMAIN": "",
"CLOUDFLARE_ZONE_ID": ""
}

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
requests==2.25.1

7
run_script.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
export CLOUDFLARE_API_KEY=$API_KEY
export CLOUDFLARE_DOMAIN=$DOMAIN_NAME
export CLOUDFLARE_ZONE_ID=$ZONE_ID
# Run the script
python3 /app/cloudflare-ddns/src/update_dns.py $@

168
src/update_dns.py Normal file
View File

@@ -0,0 +1,168 @@
import logging.handlers
import os
import sys
import requests
import json
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
request_log = logging.getLogger("requests").setLevel(logging.WARNING)
fileHandler = logging.handlers.RotatingFileHandler(
"/var/log/cloudflare_ddns.log", maxBytes=100000, backupCount=5
)
fileHandler.setFormatter(format)
logger.addHandler(fileHandler)
logger.addHandler(logging.StreamHandler())
def load_config():
config = {}
required_keys = ["CLOUDFLARE_API_KEY", "CLOUDFLARE_DOMAIN", "CLOUDFLARE_ZONE_ID"]
# 1. env.json 파일에서 설정 로드 (있는 경우)
json_file = "/app/cloudflare-ddns/config/env.json"
if os.path.exists(json_file):
with open(json_file, "r") as file:
config = json.load(file)
# 2. 환경 변수에서 설정 로드 (파일에 없는 경우에만)
for key in required_keys:
if key not in config and key in os.environ:
config[key] = os.getenv(key)
# 3. 필수 키가 모두 있는지 확인
missing_keys = [key for key in required_keys if key not in config]
if missing_keys:
raise ValueError(f"Missing required configuration: {', '.join(missing_keys)}")
return config
def get_ip():
try:
response = requests.get("https://ifconfig.me")
return response.text
except Exception as e:
logger.error(f"Error: {e}")
# print(f"Error: {e}")
return None
def previous_ip():
try:
if os.path.exists("/tmp/external_ip.txt"):
with open("/tmp/external_ip.txt", "r") as file:
return file.read()
else:
os.mknod("/tmp/external_ip.txt")
except Exception as e:
logger.error(f"Error: {e}")
# print(f"Error: {e}")
return None
def update_ip(ip):
try:
with open("/tmp/external_ip.txt", "w") as file:
file.write(ip)
except Exception as e:
logger.error(f"Error: {e}")
# print(f"Error: {e}")
return None
def get_record(zone_id, domain_name, api_key):
try:
url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
params = {
"type": "A",
"name": domain_name,
}
response = requests.get(url, headers=headers, params=params)
records = response.json()["result"]
return records if records else None
except Exception as e:
logger.error(f"Error: {e}")
logger.warning("recommendation: check the environment variables")
# print(f"Error: {e}")
return None
def update_dns_record(zone_id, record_id, record_name, ip_address, api_key):
try:
url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{record_id}"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
data = {
"type": "A",
"name": record_name,
"content": ip_address,
"ttl": 1,
"proxied": True,
}
response = requests.put(url, headers=headers, data=json.dumps(data))
success = response.json()["success"]
return success if success else None
except Exception as e:
logger.error(f"Error: {e}")
logger.warning("recommendation: check the environment variables")
# print(f"Error: {e}")
return None
if __name__ == "__main__":
config = load_config()
external_ip = get_ip()
logger.info(f"External IP: {external_ip}")
# print(f"External IP: {external_ip}")
previous_ip_ = previous_ip()
logger.info(f"Previous IP: {previous_ip_}")
# print(f"Previous IP: {previous_ip_}")
if external_ip != previous_ip_:
logger.info("IP has changed")
# print("IP has changed")
records = get_record(
config["CLOUDFLARE_ZONE_ID"],
config["CLOUDFLARE_DOMAIN"],
config["CLOUDFLARE_API_KEY"],
)
if not records:
logger.warning("No records found")
# print("No records found")
sys.exit(0)
record_id = records[0]["id"]
result = update_dns_record(
config["CLOUDFLARE_ZONE_ID"],
record_id,
config["CLOUDFLARE_DOMAIN"],
external_ip,
config["CLOUDFLARE_API_KEY"],
)
if not result:
logger.error("Failed to update DNS record")
# print("Failed to update DNS record")
sys.exit(0)
update_ip(external_ip)
logger.info("IP has been updated")
# print("IP has been updated")
else:
logger.info("IP has not changed")
# print("IP has not changed")
sys.exit(0)

8
start.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
if [ ! -f /app/cloudflare-ddns/config/env.json ]; then
cp /app/cloudflare-ddns/init/default_env.json /app/cloudflare-ddns/config/env.json
fi
sh /app/cloudflare-ddns/run_script.sh
# tail -f /var/log/cloudflare_ddns.log