From 91a82eb244c2d9ed06294506e022e3d4686489f8 Mon Sep 17 00:00:00 2001 From: New Future Date: Mon, 14 Jun 2021 08:39:04 +0000 Subject: [PATCH 1/4] feat(config): cli argument supports to set config --- run.py | 63 +++------------------------ util/config.py | 113 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 56 deletions(-) create mode 100644 util/config.py diff --git a/run.py b/run.py index 84d2ba8d..31e1b374 100755 --- a/run.py +++ b/run.py @@ -6,10 +6,8 @@ @modified: rufengsuixing """ from __future__ import print_function -from argparse import ArgumentParser, RawTextHelpFormatter -from json import load as loadjson, dump as dumpjson from time import ctime -from os import path, environ, stat, name as os_name +from os import path, environ, name as os_name from tempfile import gettempdir from logging import DEBUG, basicConfig, info, warning, error, debug from subprocess import check_output @@ -18,6 +16,7 @@ from util import ip from util.cache import Cache +from util.config import init_config, get_config __version__ = "${BUILD_SOURCEBRANCHNAME}@${BUILD_DATE}" # CI 时会被Tag替换 __description__ = "automatically update DNS records to dynamic local IP [自动更新DNS记录指向本地IP]" @@ -38,48 +37,6 @@ CACHE_FILE = path.join(gettempdir(), 'ddns.cache') -def get_config(key=None, default=None, path="config.json"): - """ - 读取配置 - """ - if not hasattr(get_config, "config"): - try: - with open(path) as configfile: - get_config.config = loadjson(configfile) - get_config.time = stat(path).st_mtime - except IOError: - error(' Config file `%s` does not exist!' % path) - with open(path, 'w') as configfile: - configure = { - "$schema": "https://ddns.newfuture.cc/schema/v2.8.json", - "id": "YOUR ID or EMAIL for DNS Provider", - "token": "YOUR TOKEN or KEY for DNS Provider", - "dns": "dnspod", - "ipv4": [ - "newfuture.cc", - "ddns.newfuture.cc" - ], - "ipv6": [ - "newfuture.cc", - "ipv6.ddns.newfuture.cc" - ], - "index4": "default", - "index6": "default", - "ttl": None, - "proxy": None, - "debug": False, - } - dumpjson(configure, configfile, indent=2, sort_keys=True) - sys.stdout.write("New template configure file `%s` is generated.\n" % path) - sys.exit(1) - except: - sys.exit('fail to load config from file: %s' % path) - if key: - return get_config.config.get(key, default) - else: - return get_config.config - - def get_ip(ip_type, index="default"): """ get IP address @@ -138,7 +95,7 @@ def update_ip(ip_type, cache, dns, proxy_list): index_rule = get_config('index' + ip_type, "default") # 从配置中获取index配置 address = get_ip(ip_type, index_rule) if not address: - error('Fail to get %s address!' ,ipname) + error('Fail to get %s address!', ipname) return False elif cache and (address == cache[ipname]): print('.', end=" ") # 缓存命中 @@ -157,14 +114,7 @@ def main(): """ 更新 """ - parser = ArgumentParser(description=__description__, - epilog=__doc__, formatter_class=RawTextHelpFormatter) - parser.add_argument('-v', '--version', - action='version', version=__version__) - parser.add_argument('-c', '--config', - default="config.json", help="run with config file [配置文件路径]") - config_file = parser.parse_args().config - get_config(path=config_file) + init_config(__description__, __doc__, __version__) # Dynamicly import the dns module as configuration dns_provider = str(get_config('dns', 'dnspod').lower()) dns = getattr(__import__('dns', fromlist=[dns_provider]), dns_provider) @@ -177,7 +127,8 @@ def main(): level=DEBUG, format='%(asctime)s <%(module)s.%(funcName)s> %(lineno)d@%(pathname)s \n[%(levelname)s] %(message)s') print("DDNS[", __version__, "] run:", os_name, sys.platform) - print("Configuration was loaded from <==", path.abspath(config_file)) + print("Configuration was loaded from <==", + path.abspath(get_config("config"))) print("=" * 25, ctime(), "=" * 25, sep=' ') proxy = get_config('proxy') or 'DIRECT' @@ -186,7 +137,7 @@ def main(): cache = get_config('cache', True) and Cache(CACHE_FILE) if cache is False: info("Cache is disabled!") - elif get_config.time >= cache.time: + elif get_config("config_modified_time") is None or get_config("config_modified_time") >= cache.time: warning("Cache file is out of dated.") cache.clear() elif not cache: diff --git a/util/config.py b/util/config.py new file mode 100644 index 00000000..7dda8d40 --- /dev/null +++ b/util/config.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +from argparse import ArgumentParser, ArgumentTypeError, Namespace, RawTextHelpFormatter +from json import load as loadjson, dump as dumpjson +from logging import error +from os import stat +from time import time + +import sys + +__cli_args = {} # type: Namespace +__config = {} # type: dict + + +def str2bool(v): + if isinstance(v, bool): + return v + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise ArgumentTypeError('Boolean value expected.') + + +def init_config(description, doc, version): + global __cli_args + """ + 配置 + """ + parser = ArgumentParser(description=description, + epilog=doc, formatter_class=RawTextHelpFormatter) + parser.add_argument('-v', '--version', + action='version', version=version) + parser.add_argument('-c', '--config', help="run with config file [配置文件路径]") + + # 参数定义 + parser.add_argument('--dns', help="DNS Provider [DNS服务提供商]", choices=[ + 'alidns', 'cloudflare', 'dnscom', 'dnspod', 'dnspod_com', 'he', 'huaweidns', 'callback']) + parser.add_argument('--id', help="api ID [授权账户]") + parser.add_argument('--token', help="api token or Secret key [授权访问凭证或密钥]") + parser.add_argument('--ipv4', nargs="*", + help="ipv4 domain list [IPV4域名列表]") + parser.add_argument('--ipv6', nargs="*", + help="ipv6 domain list [IPV6域名列表]") + parser.add_argument('--index4', help="the way to get ipv4 [IPV4 获取方式]") + parser.add_argument('--index6', help="the way to get ipv6 [IPV6获取方式]") + parser.add_argument('--ttl', type=int, help="ttl for DNS [DNS 解析 TTL 时间]") + parser.add_argument('--proxy', nargs="*", + help="https proxy [设置http 代理,多代理逐个尝试直到成功]") + parser.add_argument('--debug', type=str2bool, nargs='?', + const=True, help="debug mode [是否开启调试,默认否]", ) + parser.add_argument('--cache', type=str2bool, nargs='?', + const=True, help="eusing cache [是否缓存记录,默认是]") + + __cli_args = parser.parse_args() + has_cli_config = __cli_args.token or __cli_args.id + __load_config(__cli_args.config or "config.json", has_cli_config) + if __cli_args.config is None: + __cli_args.config = "config.json" + + +def __load_config(path="config.json", skip_auto_generation=False): + """ + 加载配置 + """ + global __config, config_modified_time + try: + with open(path) as configfile: + __config = loadjson(configfile) + __config["config_modified_time"] = stat(path).st_mtime + except IOError: + if skip_auto_generation: + __config["config_modified_time"] = time() + return + error(' Config file `%s` does not exist!' % path) + with open(path, 'w') as configfile: + configure = { + "$schema": "https://ddns.newfuture.cc/schema/v2.8.json", + "id": "YOUR ID or EMAIL for DNS Provider", + "token": "YOUR TOKEN or KEY for DNS Provider", + "dns": "dnspod", + "ipv4": [ + "newfuture.cc", + "ddns.newfuture.cc" + ], + "ipv6": [ + "newfuture.cc", + "ipv6.ddns.newfuture.cc" + ], + "index4": "default", + "index6": "default", + "ttl": None, + "proxy": None, + "debug": False, + } + dumpjson(configure, configfile, indent=2, sort_keys=True) + sys.stdout.write( + "New template configure file `%s` is generated.\n" % path) + sys.exit(1) + except: + sys.exit('fail to load config from file: %s' % path) + + +def get_config(key, default=None): + """ + 读取配置 + """ + if hasattr(__cli_args, key) and getattr(__cli_args, key) is not None: + return getattr(__cli_args, key) + if key in __config: + return __config.get(key) + return default From 98f83c5997af3e2f6544e1d296e8af5d1a03bea1 Mon Sep 17 00:00:00 2001 From: New Future Date: Mon, 14 Jun 2021 11:53:45 +0000 Subject: [PATCH 2/4] feat(config): support environment variables --- run.py | 10 +++++++--- util/config.py | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/run.py b/run.py index 31e1b374..539b5482 100755 --- a/run.py +++ b/run.py @@ -92,6 +92,8 @@ def update_ip(ip_type, cache, dns, proxy_list): domains = get_config(ipname) if not domains: return None + if not isinstance(domains, list): + domains = domains.strip('; ').replace(',',';').replace(' ',';').split(';') index_rule = get_config('index' + ip_type, "default") # 从配置中获取index配置 address = get_ip(ip_type, index_rule) if not address: @@ -127,12 +129,14 @@ def main(): level=DEBUG, format='%(asctime)s <%(module)s.%(funcName)s> %(lineno)d@%(pathname)s \n[%(levelname)s] %(message)s') print("DDNS[", __version__, "] run:", os_name, sys.platform) - print("Configuration was loaded from <==", - path.abspath(get_config("config"))) + if get_config("config"): + print("Configuration was loaded from <==", + path.abspath(get_config("config"))) print("=" * 25, ctime(), "=" * 25, sep=' ') proxy = get_config('proxy') or 'DIRECT' - proxy_list = proxy.strip('; ') .split(';') + proxy_list = proxy if isinstance( + proxy, list) else proxy.strip('; ').replace(',',';').split(';') cache = get_config('cache', True) and Cache(CACHE_FILE) if cache is False: diff --git a/util/config.py b/util/config.py index 7dda8d40..7575f39d 100644 --- a/util/config.py +++ b/util/config.py @@ -3,7 +3,7 @@ from argparse import ArgumentParser, ArgumentTypeError, Namespace, RawTextHelpFormatter from json import load as loadjson, dump as dumpjson from logging import error -from os import stat +from os import stat, environ from time import time import sys @@ -54,10 +54,10 @@ def init_config(description, doc, version): const=True, help="eusing cache [是否缓存记录,默认是]") __cli_args = parser.parse_args() - has_cli_config = __cli_args.token or __cli_args.id - __load_config(__cli_args.config or "config.json", has_cli_config) - if __cli_args.config is None: - __cli_args.config = "config.json" + is_configfile_optional = get_config("token") or get_config("id") + config_file = get_config("config"); + if not is_configfile_optional or config_file is not None: + __load_config(config_file, is_configfile_optional) def __load_config(path="config.json", skip_auto_generation=False): @@ -105,9 +105,17 @@ def __load_config(path="config.json", skip_auto_generation=False): def get_config(key, default=None): """ 读取配置 + 1. 命令行参数 + 2. 配置文件 + 3. 环境变量 """ if hasattr(__cli_args, key) and getattr(__cli_args, key) is not None: return getattr(__cli_args, key) if key in __config: return __config.get(key) + env_name = 'DDNS_'+key.upper() # type:str + if env_name in environ: # 大写环境变量 + return environ.get(env_name) + if env_name.lower() in environ: # 小写环境变量 + return environ.get(env_name.lower()) return default From 3eae884dfed79ef2428a41368874940d062a669b Mon Sep 17 00:00:00 2001 From: New Future Date: Mon, 14 Jun 2021 12:07:17 +0000 Subject: [PATCH 3/4] default config file --- util/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/config.py b/util/config.py index 7575f39d..7362148b 100644 --- a/util/config.py +++ b/util/config.py @@ -57,7 +57,8 @@ def init_config(description, doc, version): is_configfile_optional = get_config("token") or get_config("id") config_file = get_config("config"); if not is_configfile_optional or config_file is not None: - __load_config(config_file, is_configfile_optional) + __load_config(config_file or "config.json", is_configfile_optional) + __cli_args.config = config_file or "config.json" def __load_config(path="config.json", skip_auto_generation=False): From 9e54d4cca1c7959d5d5bc00d345e8499fbcdfdcb Mon Sep 17 00:00:00 2001 From: New Future Date: Mon, 14 Jun 2021 12:20:46 +0000 Subject: [PATCH 4/4] doc(readme): update config --- README.md | 6 ++++++ util/config.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff77b32f..bae23957 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ ## 详细配置 +所有字段可通过三种方式进行配置 + +1. 命令行参数 `ddns --key=value` (`ddns -h` 查看详情),优先级最高 +2. JSON配置文件(值为null认为是有效值,会覆盖环境变量的设置,如果没有对应的key则会尝试试用环境变量) +3. 环境变量DDNS_前缀加上key 全大写或者全小写 (`${ddns_key}` 或 `${DDNS_KEY}`) +
config.json 配置文件 diff --git a/util/config.py b/util/config.py index 7362148b..959c2742 100644 --- a/util/config.py +++ b/util/config.py @@ -55,9 +55,9 @@ def init_config(description, doc, version): __cli_args = parser.parse_args() is_configfile_optional = get_config("token") or get_config("id") - config_file = get_config("config"); + config_file = get_config("config") if not is_configfile_optional or config_file is not None: - __load_config(config_file or "config.json", is_configfile_optional) + __load_config(config_file or "config.json", is_configfile_optional) __cli_args.config = config_file or "config.json"