mirror of
https://github.com/NewFuture/DDNS.git
synced 2026-10-19 16:15:45 +00:00
@@ -1,6 +1,5 @@
|
||||
config.*.json
|
||||
|
||||
.vscode
|
||||
*.temp
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-python.flake8",
|
||||
"ms-python.autopep8",
|
||||
"ms-python.python",
|
||||
"github.vscode-github-actions",
|
||||
]
|
||||
}
|
||||
@@ -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。
|
||||
> 代理模式,支持自动创建域名记录。
|
||||
|
||||
|
||||
[](https://pypi.org/project/ddns/)
|
||||
[](https://github.com/NewFuture/DDNS/actions/workflows/build.yml)
|
||||
[](https://github.com/NewFuture/DDNS/releases/latest)
|
||||
@@ -18,7 +16,7 @@
|
||||
- [x] 多系统兼容 
|
||||
- [x] python3 支持 (2.x支持python2和python3)
|
||||
- [x] PIP 安装 
|
||||
- [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`
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user