Initial Commit
This commit is contained in:
commit
9a2bf20eef
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Credentials file
|
||||||
|
/.credentials.secret
|
||||||
|
|
||||||
|
# PyCharm Editor
|
||||||
|
/.idea/
|
||||||
|
|
||||||
|
# PyCharm Virtual Environment
|
||||||
|
/venv*
|
||||||
|
/src/__pycache__/
|
10
default/.credentials.secret
Normal file
10
default/.credentials.secret
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# domain_name: Your domain name
|
||||||
|
# cf_api_key: This should appear when you create or roll your API
|
||||||
|
# cf_zone_id: This should appear on the dashboard of your API
|
||||||
|
# cf_record_id: Looking for the A record -> Use the id (not the zone_id, which will be the same as cf_zone_id
|
||||||
|
|
||||||
|
[CLOUDFLARE]
|
||||||
|
domain_name = yandere.cc
|
||||||
|
cf_api_key =
|
||||||
|
cf_zone_id =
|
||||||
|
cf_record_id =
|
1
requierments.txt
Normal file
1
requierments.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
requests
|
35
run.sh
Executable file
35
run.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
# Yandere Lewd Bot, an image posting bot for Pleroma
|
||||||
|
# Copyright (C) 2022 Anon
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Get the runtime path of the bot
|
||||||
|
ABS_PATH="$(readlink -f "$0")"
|
||||||
|
RUN_DIR="$(dirname "$ABS_PATH")"
|
||||||
|
|
||||||
|
# Relative paths to the virtual environment and main.py
|
||||||
|
VENV='./venv/bin/activate'
|
||||||
|
ENTRY='./src/main.py'
|
||||||
|
|
||||||
|
# cd into the bot's root path, set up the virtual environment, and run
|
||||||
|
cd "$RUN_DIR"
|
||||||
|
[ ! -f "$VENV" ] && echo "Virtual environment not found: ${VENV}" && cd - > /dev/null && exit 1
|
||||||
|
source "$VENV"
|
||||||
|
"$ENTRY" "$@"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
deactivate
|
||||||
|
cd - > /dev/null
|
215
src/main.py
Executable file
215
src/main.py
Executable file
@ -0,0 +1,215 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
# Yandere Lewd Bot, an image posting bot for Pleroma
|
||||||
|
# Copyright (C) 2022 Anon
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
import configparser
|
||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
def get_cf_dns_domain(cf_zone_id):
|
||||||
|
return "https://api.cloudflare.com/client/v4/zones/{}/dns_records".format(
|
||||||
|
cf_zone_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cf_dns_domain_record_id(cf_zone_id, cf_record_id):
|
||||||
|
return "/".join([
|
||||||
|
get_cf_dns_domain(cf_zone_id),
|
||||||
|
cf_record_id
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# !! Do not use this !! The global Cloudflair API is dangerous. Keeping this here for posterity and info
|
||||||
|
# def get_headers_global_api(cf_api_key, cf_email):
|
||||||
|
# return {
|
||||||
|
# "X-Auth-Key": cf_api_key,
|
||||||
|
# "X-Auth-Email": cf_email
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
def get_headers(cf_api_key):
|
||||||
|
return {
|
||||||
|
"Authorization": "Bearer {}".format(cf_api_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_record_id(cf_zone_id, cf_api_key):
|
||||||
|
resp = requests.get(
|
||||||
|
url=get_cf_dns_domain(cf_zone_id),
|
||||||
|
headers=get_headers(cf_api_key)
|
||||||
|
)
|
||||||
|
failed = resp.status_code != 200
|
||||||
|
if not failed:
|
||||||
|
print(json.dumps(resp.json(), indent=4, sort_keys=True))
|
||||||
|
return failed
|
||||||
|
|
||||||
|
|
||||||
|
# A hack to prevent pycharm from bitching
|
||||||
|
def dictionary_split(s, delim='='):
|
||||||
|
k, v = s.split(delim, 1)
|
||||||
|
return k, v
|
||||||
|
|
||||||
|
|
||||||
|
# Get the current ip address of the user
|
||||||
|
def get_new_ip():
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
a = requests.get("https://1.1.1.1/cdn-cgi/trace").text.split("\n")
|
||||||
|
ip = dict(dictionary_split(item) for item in a if item != "")["ip"]
|
||||||
|
return ip
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_old_ip(cf_zone_id, cf_record_id, cf_api_key):
|
||||||
|
resp = requests.get(
|
||||||
|
url=get_cf_dns_domain_record_id(cf_zone_id, cf_record_id),
|
||||||
|
headers=get_headers(cf_api_key)
|
||||||
|
)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
return resp.json()["result"]["content"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def put_new_ip(domain_name, ip, cf_zone_id, cf_record_id, cf_api_key):
|
||||||
|
json_payload = {
|
||||||
|
"type": 'A',
|
||||||
|
"name": domain_name,
|
||||||
|
"content": ip,
|
||||||
|
"proxied": True
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = requests.put(
|
||||||
|
url=get_cf_dns_domain_record_id(cf_zone_id, cf_record_id),
|
||||||
|
json=json_payload,
|
||||||
|
headers=get_headers(cf_api_key)
|
||||||
|
)
|
||||||
|
success = resp.status_code == 200
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def config_parser_has_option(config, profile, option):
|
||||||
|
# 'DEFAULT' is a special value in ConfigParser
|
||||||
|
config_name = "" if profile == "DEFAULT" else profile
|
||||||
|
if config.has_option(config_name, option):
|
||||||
|
return config[profile][option]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_path(filename):
|
||||||
|
paths = (".", os.environ["HOME"], "/root", "/")
|
||||||
|
for path in path:
|
||||||
|
full_path = os.path.join(path, filename)
|
||||||
|
if os.file.exists(full_path):
|
||||||
|
return full_path
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Logger - set to logging.INFO for production
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
# String constants
|
||||||
|
default_secret_file = ".credentials.secret"
|
||||||
|
default_profile = "CLOUDFLARE"
|
||||||
|
default_fullpath = get_defualt_path(default_secret_file)
|
||||||
|
|
||||||
|
# Find the default path to crdentials file
|
||||||
|
|
||||||
|
# Argument Parser
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="A utility for updating DNS records for dynamic IP addresses",
|
||||||
|
epilog="Configuration files should be owned by root with an octal permission of 600 (read/write by root only). Run this program as sudo.",
|
||||||
|
add_help=True
|
||||||
|
)
|
||||||
|
parser.add_argument("-i", "--get-record-ids", help="Print out (in JSON format) all records for your Cloudflair account", action="store_true")
|
||||||
|
parser.add_argument("-c", "--config", help="Specify configuration file (DEFAULT: '{}')".format(default_fullpath), default=default_fullpath)
|
||||||
|
parser.add_argument("-p", "--profile", help="Specify profile in configuration file (DEFAULT: '{}')".format(default_profile), default=default_profile)
|
||||||
|
parser.add_argument("-s", "--set", help="Set ip address to ip specified")
|
||||||
|
parser.add_argument("-g", "--get", help="Get old (from Cloudflare DNS records) and current ip address", action="store_true")
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
|
||||||
|
# Configuration file
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(arguments.config)
|
||||||
|
|
||||||
|
options = OrderedDict([
|
||||||
|
("domain_name", config_parser_has_option(config, arguments.profile, "domain_name")),
|
||||||
|
("cf_api_key", config_parser_has_option(config, arguments.profile, "cf_api_key")),
|
||||||
|
("cf_zone_id", config_parser_has_option(config, arguments.profile, "cf_zone_id")),
|
||||||
|
("cf_record_id", config_parser_has_option(config, arguments.profile, "cf_record_id"))
|
||||||
|
])
|
||||||
|
|
||||||
|
# Validate configuration file
|
||||||
|
missing_parameter = next((i for i in options if options[i] is None and i != "cf_record_id"), None)
|
||||||
|
if missing_parameter is not None:
|
||||||
|
logging.error("Configuration: {} | Profile: {} | Option: {}".format(
|
||||||
|
arguments.config, arguments.profile, missing_parameter
|
||||||
|
))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# If cf_zone_id is None, print out all record ids. The user will have to update this setting in their config file
|
||||||
|
# This will have to be done at least once
|
||||||
|
if arguments.get_record_ids or options["cf_record_id"] is None:
|
||||||
|
return int(get_record_id(options["cf_zone_id"], options["cf_api_key"]))
|
||||||
|
|
||||||
|
# Get the current dns record from cloudflair (old)
|
||||||
|
old_ip = get_old_ip(options["cf_zone_id"], options["cf_record_id"], options["cf_api_key"])
|
||||||
|
|
||||||
|
# Get the current ip address of the server (new)
|
||||||
|
new_ip = None
|
||||||
|
|
||||||
|
if arguments.set:
|
||||||
|
new_ip = arguments.set
|
||||||
|
else:
|
||||||
|
new_ip = get_new_ip()
|
||||||
|
|
||||||
|
if new_ip is None:
|
||||||
|
logging.error("Could not determine new IP")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# Print beginning of status line
|
||||||
|
if old_ip is None:
|
||||||
|
logging.warn("Could not determine old IP")
|
||||||
|
|
||||||
|
# Update cloudflair's dns record if necessary and print out the end of the status line
|
||||||
|
if arguments.get:
|
||||||
|
logging.info("Current IP address: {}".format(new_ip))
|
||||||
|
elif old_ip == new_ip:
|
||||||
|
logging.info("New IP matches old IP: {}".format(new_ip))
|
||||||
|
else:
|
||||||
|
if put_new_ip(options["domain_name"], new_ip, options["cf_zone_id"], options["cf_record_id"], options["cf_api_key"]):
|
||||||
|
logging.info("IP updated to: {} from: {}".format(old_ip, new_ip))
|
||||||
|
else:
|
||||||
|
logging.error("Failed to update to: {} from: {}".format(old_ip, new_ip))
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# Exit codes:
|
||||||
|
# 0 - Success
|
||||||
|
# 1 - An error occurred
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except Exception as e:
|
||||||
|
print("Exception!!", e)
|
||||||
|
sys.exit(1)
|
Loading…
Reference in New Issue
Block a user