更新配置文件模式至v4.0 重构日志配置,(#465)

Break changes
* 移除调试模式,

* 添加日志级别和输出文件
This commit is contained in:
New Future
2025-06-02 08:16:27 +00:00
committed by GitHub
parent 3baf48a21a
commit da19b8c0c4
7 changed files with 158 additions and 59 deletions
-1
View File
@@ -1,6 +1,5 @@
config.*.json
.vscode
*.temp
# Byte-compiled / optimized / DLL files
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"ms-python.flake8",
"ms-python.autopep8",
"ms-python.python",
"github.vscode-github-actions",
]
}
+21 -7
View File
@@ -1,10 +1,8 @@
# [DDNS](https://github.com/NewFuture/DDNS) [<img src=".build/ddns.svg" width="32px" height="32px"/>](https://ddns.newfuture.cc)
> 自动更新 DNS 解析 到本机 IP 地址,支持 ipv4 和 ipv6 以 本地(内网)IP 和 公网 IP。
> 代理模式,支持自动创建域名记录。
[![PyPI](https://img.shields.io/pypi/v/ddns.svg?label=DDNS&style=social)](https://pypi.org/project/ddns/)
[![Build Status](https://github.com/NewFuture/DDNS/actions/workflows/build.yml/badge.svg?event=push)](https://github.com/NewFuture/DDNS/actions/workflows/build.yml)
[![Publish Status](https://github.com/NewFuture/DDNS/actions/workflows/publish.yml/badge.svg)](https://github.com/NewFuture/DDNS/releases/latest)
@@ -18,7 +16,7 @@
- [x] 多系统兼容 ![cross platform](https://img.shields.io/badge/platform-windows_%7C%20linux_%7C%20osx-success.svg?style=social)
- [x] python3 支持 ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ddns.svg?style=social)(2.x支持python2和python3)
- [x] PIP 安装 ![PyPI - Wheel](https://img.shields.io/pypi/wheel/ddns.svg?style=social)
- [x] Docker 支持(@NN708)
- [x] Docker 支持(@NN708)
- 域名支持:
- [x] 多个域名支持
- [x] 多级域名解析
@@ -52,17 +50,25 @@
根据需要选择一种方式: `二进制`版,`pip`版,`源码`运行,或者`Docker`
- #### pip 安装(需要 pip 或 easy_install)
1. 安装 ddns: `pip install ddns` 或 `easy_install ddns`
2. 运行: `ddns`
- #### 二进制版(单文件,无需 python)
- Windows [ddns.exe](https://github.com/NewFuture/DDNS/releases/latest)
- Linux (仅 Ubuntu 测试) [ddns](https://github.com/NewFuture/DDNS/releases/latest)
- Mac OSX [ddns-osx](https://github.com/NewFuture/DDNS/releases/latest)
- #### 源码运行(无任何依赖, 需 python 环境)
1. clone 或者[下载此仓库](https://github.com/NewFuture/DDNS/archive/master.zip)并解压
2. 运行./run.py (widnows 双击`run.bat`或者运行`python run.py`)
- #### Docker(需要安装 Docker)
- 使用环境变量:
```
docker run -d \
-e DDNS_DNS=dnspod \
@@ -73,7 +79,9 @@
--network host \
newfuture/ddns
```
- 使用配置文件(docker 工作目录`/ddds/`,默认配置位置`/ddns/config.json`):
```
docker run -d \
-v /local/config/path/:/ddns/ \
@@ -102,7 +110,7 @@
1. 命令行参数 `ddns --key=value` (`ddns -h` 查看详情),优先级最高
2. JSON配置文件(值为null认为是有效值,会覆盖环境变量的设置,如果没有对应的key则会尝试试用环境变量)
3. 环境变量DDNS_前缀加上key 全大写或者全小写 (`${ddns_key}` 或 `${DDNS_KEY}`)
3. 环境变量DDNS_前缀加上key 全大写或者全小写,点转下划线 (`${ddns_id}` 或 `${DDNS_ID}`,`${DDNS_LOG_LEVEL}`)
<details open>
@@ -132,8 +140,9 @@ python run.py -c /path/to/config.json
| index6 | string\|int\|array | No | `"default"` | ipv6 获取方式 | 可设置`网卡`,`内网`,`公网`,`正则`等方式 |
| ttl | number | No | `null` | DNS 解析 TTL 时间 | 不设置采用 DNS 默认策略 |
| proxy | string | No | 无 | http 代理`;`分割 | 多代理逐个尝试直到成功,`DIRECT`为直连 |
| debug | bool | No | `false` | 是否开启调试 | 运行异常时,打开调试输出,方便诊断错误 |
| ~~debug~~ | bool | No | `false` | 是否开启调试 | v4 中弃用,请改用log.level=DEBUG |
| cache | string\|bool | No | `true` | 是否缓存记录 | 正常情况打开避免频繁更新,默认位置为临时目录下`ddns.cache`,<br>也可以指定一个具体文件实现自定义文件缓存位置 |
| log | {"level":string,"file":string} | No | `null` | 日志配置(可选) | 日志配置,日志级别和路径(默认命令行),<br>例如: `{ "level": "DEBUG", "file": "/path/to/logfile.log" }` |
#### index4 和 index6 参数说明
@@ -167,7 +176,7 @@ python run.py -c /path/to/config.json
```json
{
"$schema": "https://ddns.newfuture.cc/schema/v2.8.json",
"$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
"id": "12345",
"token": "mytokenkey",
"dns": "dnspod 或 dnspod_com 或 alidns 或 dnscom 或 cloudflare 或 he 或 huaweidns 或 callback",
@@ -177,7 +186,10 @@ python run.py -c /path/to/config.json
"index6": "public",
"ttl": 600,
"proxy": "127.0.0.1:1080;DIRECT",
"debug": false
"log": {
"level": "DEBUG",
"file": "dns.log"
},
}
```
@@ -200,12 +212,14 @@ python run.py -c /path/to/config.json
- 使用init.d和crontab:
`sudo ./task.sh`
- 使用systemd:
```bash
安装:
sudo ./systemd.sh install
卸载:
sudo ./systemd.sh uninstall
```
该脚本安装的文件符合 [Filesystem Hierarchy Standard (FHS)](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard):
可执行文件所在目录为 `/usr/share/DDNS`
配置文件所在目录为 `/etc/DDNS`
+14 -16
View File
@@ -11,11 +11,9 @@ DDNS
# nuitka-project-else:
# nuitka-project: --product-version=0.0.0
from __future__ import print_function
from time import ctime, asctime
from os import path, environ, name as os_name
from tempfile import gettempdir
from logging import DEBUG, basicConfig, info, warning, error, debug
from logging import basicConfig, info, warning, error, debug
from subprocess import check_output
import sys
@@ -82,8 +80,7 @@ def change_dns_record(dns, proxy_list, **kw):
else:
dns.Config.PROXY = proxy
record_type, domain = kw['record_type'], kw['domain']
print('\n%s %s(%s) ==> %s [via %s]' %
(asctime(), domain, record_type, kw['ip'], proxy))
info("%s(%s) ==> %s [via %s]", domain, record_type, kw['ip'], proxy)
try:
return dns.update_record(domain, kw['ip'], record_type=record_type)
except Exception as e:
@@ -108,7 +105,7 @@ def update_ip(ip_type, cache, dns, proxy_list):
error('Fail to get %s address!', ipname)
return False
elif cache and (address == cache[ipname]):
print('.', end=" ") # 缓存命中
info('%s address not changed, using cache.', ipname)
return True
record_type = (ip_type == '4') and 'A' or 'AAAA'
update_fail = False # https://github.com/NewFuture/DDNS/issues/16
@@ -132,16 +129,17 @@ def main():
dns.Config.ID = get_config('id')
dns.Config.TOKEN = get_config('token')
dns.Config.TTL = get_config('ttl')
if get_config('debug'):
ip.DEBUG = get_config('debug')
basicConfig(
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)
if get_config("config"):
print("Configuration was loaded from <==",
path.abspath(get_config("config")))
print("=" * 25, ctime(), "=" * 25, sep=' ')
basicConfig(
level=get_config('log.level'),
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%m-%d %H:%M:%S',
filename=get_config('log.file'),
)
info("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
if get_config("config"):
info("loaded Config from: %s", path.abspath(get_config('config')))
proxy = get_config('proxy') or 'DIRECT'
proxy_list = proxy if isinstance(
+4 -3
View File
@@ -176,13 +176,14 @@
"debug": {
"$id": "/properties/debug",
"type": "boolean",
"title": "Enable Debug Mode",
"description": "是否启用调试模式显示更多信息",
"title": "Enable Debug Mode (deprecated, use logger instead)",
"description": "是否启用调试模式显示更多信息(已废弃,请使用 logger 字段)",
"default": false,
"examples": [
false,
true
]
],
"deprecated": true
},
"cache": {
"$id": "/properties/cache",
+75 -20
View File
@@ -1,21 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://ddns.newfuture.cc/schema.json",
"description": "[Deprecated] 已弃用,请使用 https://ddns.newfuture.cc/schema/v2.json",
"$id": "https://ddns.newfuture.cc/schema/v4.0.json",
"description": "DNS 配置文件 https://github.com/NewFuture/DDNS",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"title": "please use https://ddns.newfuture.cc/schema/v2.json",
"description": "请更换为 https://ddns.newfuture.cc/schema/v2.json",
"title": "please use https://ddns.newfuture.cc/schema/v2.8.json",
"description": "请更换为 https://ddns.newfuture.cc/schema/v2.8.json",
"default": "https://ddns.newfuture.cc/schema/v2.8.json",
"enum": [
"https://ddns.newfuture.cc/schema/v2.json",
"http://ddns.newfuture.cc/schema/v2.json"
"https://ddns.newfuture.cc/schema/v2.8.json",
"http://ddns.newfuture.cc/schema/v2.8.json",
"./schema/v2.8.json"
]
},
"id": {
"$id": "/properties/id",
"type": "string",
"type": [
"string",
"null"
],
"title": "ID or Email",
"description": "DNS服务API认证的ID或者邮箱"
},
@@ -29,7 +34,7 @@
"$id": "/properties/dns",
"type": "string",
"title": "DNS Provider",
"description": "dns服务商:阿里为alidns,DNS.COM为dnscom,DNSPOD国际版为(dnspod_com),cloudflare,HE.net为he",
"description": "dns服务商:阿里为alidns,DNS.COM为dnscom,DNSPOD国际版为(dnspod_com),cloudflare,HE.net为he,华为DNS为huaweidns,自定义回调为callback",
"default": "dnspod",
"examples": [
"dnspod",
@@ -42,7 +47,9 @@
"cloudflare",
"dnspod_com",
"dnscom",
"he"
"he",
"huaweidns",
"callback"
]
},
"ipv4": {
@@ -55,7 +62,7 @@
"$id": "/properties/ipv4/items",
"title": "ipv4 domain for DDNS",
"type": "string",
"pattern": "^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,18}$",
"pattern": "^(?:\\*\\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,18}$",
"examples": [
"newfuture.cc",
"ipv4.example.newfuture.cc"
@@ -72,7 +79,7 @@
"$id": "/properties/ipv6/items",
"title": "The ipv6 domain for DDNS",
"type": "string",
"pattern": "^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}$",
"pattern": "^(?:\\*\\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,18}$",
"examples": [
"newfuture.cc",
"ipv6.example.newfuture.cc"
@@ -84,8 +91,19 @@
"type": [
"string",
"integer",
"boolean"
"boolean",
"array"
],
"items": {
"type": [
"string",
"integer"
],
"minimum": 0
},
"uniqueItems": true,
"minItems": 1,
"minimum": 0,
"title": "IPv4 address Setting",
"description": "本机 IPv4 获取方式设置",
"default": "default",
@@ -103,8 +121,19 @@
"type": [
"string",
"integer",
"boolean"
"boolean",
"array"
],
"items": {
"type": [
"string",
"integer"
],
"minimum": 0
},
"uniqueItems": true,
"minItems": 1,
"minimum": 0,
"title": "IPv6 address Setting",
"description": "本机 IPv6 获取方式设置",
"default": "default",
@@ -144,16 +173,42 @@
"127.0.0.1:1080;DIRECT"
]
},
"debug": {
"$id": "/properties/debug",
"type": "boolean",
"title": "Enable Debug Mode",
"description": "是否启用调试模式显示更多信息",
"default": false,
"cache": {
"$id": "/properties/cache",
"type": [
"string",
"boolean"
],
"title": "Enable Cache",
"description": "是否启用缓存记录以避免频繁更新",
"default": true,
"examples": [
true,
false,
true
"/path/to/cache/ddns.cache"
]
},
"log": {
"$id": "/properties/log",
"type": "object",
"title": "Log Config",
"description": "日志配置,支持自定义日志级别和输出位置。可通过命令行 --log.level, --log.file 或环境变量 DDNS_LOG_LEVEL, DDNS_LOG_FILE 设置。",
"properties": {
"level": {
"type": "string",
"title": "Log Level",
"description": "日志级别,如 DEBUG、INFO、WARNING、ERROR、CRITICAL",
"default": "INFO",
"enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
},
"file": {
"type": ["string", "null"],
"title": "Log Output File",
"description": "日志输出文件路径,留空或为null时输出到控制台"
}
},
"required": [],
"additionalProperties": false
}
},
"required": [
+36 -12
View File
@@ -2,8 +2,9 @@
# -*- coding:utf-8 -*-
from argparse import ArgumentParser, ArgumentTypeError, Namespace, RawTextHelpFormatter # noqa: F401
from json import load as loadjson, dump as dumpjson
from logging import error
from os import stat, environ
from logging import error, getLevelName
from time import time
import sys
@@ -11,6 +12,8 @@ import sys
__cli_args = {} # type: Namespace
__config = {} # type: dict
log_levels = ['CRITICAL', 'FATAL', 'ERROR',
'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
def str2bool(v):
@@ -24,7 +27,14 @@ def str2bool(v):
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise ArgumentTypeError('Boolean value expected.')
return v
def log_level(value):
"""
parse string to log level
"""
return getLevelName(value.upper())
def init_config(description, doc, version):
@@ -52,10 +62,12 @@ def init_config(description, doc, version):
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="enable cache [是否缓存记录,默认是]")
const=True, help="cache flag [启用缓存,可配配置路径或开关]")
parser.add_argument('--log.file', metavar="LOG_FILE",
help="log file [日志文件,默认标准输出]")
parser.add_argument('--log.level', type=log_level,
metavar="|".join(log_levels))
__cli_args = parser.parse_args()
is_configfile_optional = get_config("token") or get_config("id")
@@ -74,6 +86,13 @@ def __load_config(path="config.json", skip_auto_generation=False):
with open(path) as configfile:
__config = loadjson(configfile)
__config["config_modified_time"] = stat(path).st_mtime
if 'log' in __config:
if 'level' in __config['log'] and __config['log']['level'] is not None:
__config['log.level'] = log_level(__config['log']['level'])
if 'file' in __config['log']:
__config['log.file'] = __config['log']['file']
elif 'log.level' in __config:
__config['log.level'] = log_level(__config['log.level'])
except IOError:
if skip_auto_generation:
__config["config_modified_time"] = time()
@@ -81,7 +100,7 @@ def __load_config(path="config.json", skip_auto_generation=False):
error(' Config file `%s` does not exist!' % path)
with open(path, 'w') as configfile:
configure = {
"$schema": "https://ddns.newfuture.cc/schema/v2.8.json",
"$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
"id": "YOUR ID or EMAIL for DNS Provider",
"token": "YOUR TOKEN or KEY for DNS Provider",
"dns": "dnspod",
@@ -97,14 +116,17 @@ def __load_config(path="config.json", skip_auto_generation=False):
"index6": "default",
"ttl": None,
"proxy": None,
"debug": False,
"log": {
"level": "INFO",
"file": None
}
}
dumpjson(configure, configfile, indent=2, sort_keys=True)
sys.stdout.write(
"New template configure file `%s` is generated.\n" % path)
sys.exit(1)
except Exception:
sys.exit('fail to load config from file: %s' % path)
except Exception as e:
sys.exit('fail to load config from file: %s\n%s' % (path, e))
def get_config(key, default=None):
@@ -118,9 +140,11 @@ def get_config(key, default=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: # 大写环境变量
env_name = 'DDNS_' + key.replace('.', '_') # type:str
if env_name in environ: # 环境变量
return environ.get(env_name)
if env_name.lower() in environ: # 小写环境变量
elif env_name.upper() in environ: # 大写环境变量
return environ.get(env_name.upper())
elif env_name.lower() in environ: # 小写环境变量
return environ.get(env_name.lower())
return default