diff --git a/.build/Dockerfile b/.build/Dockerfile
index 1d1f9f276..5647ba3c9 100755
--- a/.build/Dockerfile
+++ b/.build/Dockerfile
@@ -4,7 +4,7 @@ COPY . .
RUN find /lib /usr/lib -name '*.so*' | sed 's|.*/||' | awk '{print "--noinclude-dlls="$0}' > nuitka_exclude_so.txt
RUN apk add py3-pip python3-dev patchelf build-base libffi-dev
RUN pip3 install -U nuitka --break-system-packages
-RUN python3 .build/remove_python2.py
+RUN python3 .build/patch.py
RUN python3 -O -m nuitka run.py \
--mode=onefile\
--output-dir=./dist\
diff --git a/.build/nuitka.cmd b/.build/nuitka.sh
similarity index 88%
rename from .build/nuitka.cmd
rename to .build/nuitka.sh
index 4df5bf911..be6408db9 100755
--- a/.build/nuitka.cmd
+++ b/.build/nuitka.sh
@@ -1 +1 @@
-python3 -m nuitka run.py --mode=onefile --output-dir=./dist --no-deployment-flag=self-execution --output-filename=ddns --remove-output --include-module=dns.dnspod --include-module=dns.alidns --include-module=dns.dnspod_com --include-module=dns.dnscom --include-module=dns.cloudflare --include-module=dns.he --include-module=dns.huaweidns --include-module=dns.callback --product-name=DDNS --file-description="DDNS Client 自动更新域名解析到本机IP" --company-name="New Future" --copyright="https://ddns.newfuture.cc" --windows-icon-from-ico="favicon.ico" --assume-yes-for-downloads --lto=yes --nofollow-import-to=unittest,pydoc --onefile-tempdir-spec="{CACHE_DIR}/{PRODUCT}/{VERSION}"
\ No newline at end of file
+python3 -m nuitka run.py --mode=onefile --output-dir=./dist --no-deployment-flag=self-execution --output-filename=ddns --remove-output --include-module=dns.dnspod --include-module=dns.alidns --include-module=dns.dnspod_com --include-module=dns.dnscom --include-module=dns.cloudflare --include-module=dns.he --include-module=dns.huaweidns --include-module=dns.callback --product-name=DDNS --file-description="DDNS Client 自动更新域名解析到本机IP" --company-name="New Future" --copyright="https://ddns.newfuture.cc" --windows-icon-from-ico="favicon.ico" --assume-yes-for-downloads --lto=yes --nofollow-import-to=unittest,pydoc --onefile-tempdir-spec="{CACHE_DIR}/{PRODUCT}/{VERSION}" --linux-icon=doc/img/ddns.svg
\ No newline at end of file
diff --git a/.build/patch.py b/.build/patch.py
new file mode 100644
index 000000000..3560804f4
--- /dev/null
+++ b/.build/patch.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+"""
+自动将所有 try-except python2/3 兼容导入替换为 python3 only 导入,并显示处理日志
+"""
+import os
+import re
+
+ROOT = '.'
+# 匹配 try-except 块,去除导入前缩进,保证import顶格,删除的行用空行代替
+PATTERN = re.compile(
+ r'^[ \t]*try:[^\n]*python 3[^\n]*\n' # try: # python 3
+ r'((?:[ \t]+[^\n]*\n)+?)' # python3 导入内容
+ r'^[ \t]*except ImportError:[^\n]*\n' # except ImportError: # python 2
+ r'((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)', # except块内容
+ re.MULTILINE
+)
+
+
+def dedent_imports_with_blank(import_block, try_block, except_block):
+ """
+ 保留python3导入并去除缩进,try/except及except内容用空行代替
+ """
+ try_lines = try_block.count('\n')
+ except_lines = except_block.count('\n')
+ imports = ''.join(line.lstrip()
+ for line in import_block.splitlines(keepends=True))
+ return ('\n' * try_lines) + imports + ('\n' * except_lines)
+
+
+def extract_pure_version(version_str):
+ """
+ 提取前4组数字并用点拼接,如 v1.2.3.beta4.5 -> 1.2.3.4
+ """
+ import re
+ nums = re.findall(r'\d+', version_str)
+ return '.'.join(nums[:4]) if nums else "0.0.0"
+
+
+def update_nuitka_version(pyfile):
+ """
+ 读取 __version__ 并替换 nuitka-project 版本号
+ """
+ with open(pyfile, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # 提取 __version__ 变量
+ version_match = re.search(r'__version__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
+ if not version_match:
+ print(f'No __version__ found in {pyfile}')
+ return False
+
+ version_str = version_match.group(1)
+ pure_version = extract_pure_version(version_str)
+
+ # 替换 nuitka-project 行
+ new_content, n = re.subn(
+ r'(# nuitka-project: --product-version=)[^\n]*',
+ r'\g<1>' + pure_version,
+ content
+ )
+ if n > 0:
+ with open(pyfile, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+ print(f'update nuitka-project version: {pure_version} in {pyfile}')
+ return True
+ return False
+
+
+def remove_windows_textiowrapper(pyfile):
+ """
+ 如果当前系统不是 Windows,则删除 run.py 中的 TextIOWrapper 兼容代码块
+ """
+ if os.name == 'nt':
+ return # Windows 下不处理
+
+ with open(pyfile, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # 匹配并删除 if sys.version_info.major == 3 and os_name == 'nt': ... 代码块
+ pattern = re.compile(
+ r'(?m)^[ \t]*if sys\.version_info\.major == 3 and os_name == [\'"]nt[\'"]:\n'
+ r'(?:[ \t]+from io import TextIOWrapper\n)?'
+ r'(?:[ \t]+sys\.stdout = TextIOWrapper\(sys\.stdout\.detach\(\), encoding=[\'"]utf-8[\'"]\)\n)?'
+ r'(?:[ \t]+sys\.stderr = TextIOWrapper\(sys\.stderr\.detach\(\), encoding=[\'"]utf-8[\'"]\)\n)?'
+ )
+ new_content, n = pattern.subn('', content)
+ if n > 0:
+ with open(pyfile, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+ print(f'Removed Windows TextIOWrapper code from {pyfile}')
+
+
+def main():
+ """
+ 遍历所有py文件并替换兼容导入,同时更新nuitka版本号
+ """
+ changed_files = 0
+ # 先处理 run.py 的 nuitka-project 版本号
+ update_nuitka_version(os.path.join(ROOT, "run.py"))
+ # 非 Windows 平台,移除 TextIOWrapper 兼容代码
+ remove_windows_textiowrapper(os.path.join(ROOT, "run.py"))
+ for dirpath, _, filenames in os.walk(ROOT):
+ for fname in filenames:
+ if fname.endswith('.py'):
+ fpath = os.path.join(dirpath, fname)
+ with open(fpath, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ def repl(match):
+ try_block = re.match(
+ r'^[ \t]*try:[^\n]*python 3[^\n]*\n', match.group(0)
+ ).group(0)
+ except_block = re.search(
+ r'^[ \t]*except ImportError:[^\n]*\n((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)',
+ match.group(0), re.MULTILINE
+ )
+ except_block = except_block.group(
+ 0) if except_block else ''
+ return dedent_imports_with_blank(match.group(1), try_block, except_block)
+
+ new_content, n = PATTERN.subn(repl, content)
+ if n > 0:
+ with open(fpath, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+ print(f'change: {fpath}')
+ changed_files += 1
+ print('done')
+ print(f'Total changed files: {changed_files}')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.build/remove_python2.py b/.build/remove_python2.py
deleted file mode 100644
index 0c1bdda96..000000000
--- a/.build/remove_python2.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env python3
-"""
-自动将所有 try-except python2/3 兼容导入替换为 python3 only 导入,并显示处理日志
-"""
-import os
-import re
-
-ROOT = '.'
-# 匹配 try-except 块,去除导入前缩进,保证import顶格,删除的行用空行代替
-PATTERN = re.compile(
- r'^[ \t]*try:[^\n]*python 3[^\n]*\n' # try: # python 3
- r'((?:[ \t]+[^\n]*\n)+?)' # python3 导入内容
- r'^[ \t]*except ImportError:[^\n]*\n' # except ImportError: # python 2
- r'((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)', # except块内容
- re.MULTILINE
-)
-
-
-def dedent_imports_with_blank(import_block, try_block, except_block):
- """
- 保留python3导入并去除缩进,try/except及except内容用空行代替
- """
- try_lines = try_block.count('\n')
- except_lines = except_block.count('\n')
- imports = ''.join(line.lstrip()
- for line in import_block.splitlines(keepends=True))
- return ('\n' * try_lines) + imports + ('\n' * except_lines)
-
-
-def main():
- """
- 遍历所有py文件并替换兼容导入
- """
- changed_files = 0
- for dirpath, _, filenames in os.walk(ROOT):
- for fname in filenames:
- if fname.endswith('.py'):
- fpath = os.path.join(dirpath, fname)
- with open(fpath, 'r', encoding='utf-8') as f:
- content = f.read()
-
- def repl(match):
- try_block = re.match(
- r'^[ \t]*try:[^\n]*python 3[^\n]*\n', match.group(0)
- ).group(0)
- except_block = re.search(
- r'^[ \t]*except ImportError:[^\n]*\n((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)',
- match.group(0), re.MULTILINE
- )
- except_block = except_block.group(
- 0) if except_block else ''
- return dedent_imports_with_blank(match.group(1), try_block, except_block)
-
- new_content, n = PATTERN.subn(repl, content)
- if n > 0:
- with open(fpath, 'w', encoding='utf-8') as f:
- f.write(new_content)
- print(f'change: {fpath}')
- changed_files += 1
- print('done')
- print(f'Total changed files: {changed_files}')
-
-
-if __name__ == '__main__':
- main()
diff --git a/.github/release.md b/.github/release.md
new file mode 100644
index 000000000..82eb9ca61
--- /dev/null
+++ b/.github/release.md
@@ -0,0 +1,74 @@
+[](https://pypi.org/project/ddns/${BUILD_VERSION}/) 
+
+## 版本下载方式一览表
+
+| 平台/方式 |架构支持 |
+| ----------- |---------------------- |
+| Docker | `newfuture/ddns:${BUILD_VERSION}` 8种架构|
+| Windows | [64位(x64)](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-windows-x64.exe) / [32位(x86)](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-windows-x86.exe) / [arm64](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-windows-arm64.exe) |
+| Linux | [64位(x64)](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-linux-x64) / [arm64](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-linux-arm64) |
+| Mac OS X | [Apple Silicon (ARM64)](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-mac-arm64.app) / [Intel x64](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-mac-x64.app) |
+| Python/PIP | any |
+
+## Docker (推荐) [](https://hub.docker.com/r/newfuture/ddns)
+
+```bash
+# 当前版本
+docker --name ddns -v $(pwd)/:/DDNS newfuture/ddns:${BUILD_VERSION} -h
+# 最新版本(可能有缓存)
+docker --name ddns -v $(pwd)/:/DDNS newfuture/ddns -h
+```
+
+请将 `$(pwd)/` 替换为你的配置文件夹。
+
+* 命令行参数使用 `-h`
+* 配置文件config.json,使用vscode等编辑会有自动提示
+* 环境变量`DDNS_XXX`
+
+支持源:
+
+* Docker官方源: [docker.io/newfuture/ddns](https://hub.docker.com/r/newfuture/ddns)
+* Github镜像源: [ghcr.io/newfuture/ddns](https://github.com/NewFuture/DDNS/pkgs/container/ddns/426721813?tag=${BUILD_VERSION})
+
+---
+
+## 使用二进制文件 
+
+各平台下载/使用方式
+
+* ### Windows
+
+1. 下载 [`ddns-windows-x64.exe`](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-windows-x64.exe) 或者 [`ddns-windows-x32.exe`](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-windows-x32.exe) 或者[`ddns-windows-arm64.exe`](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-windows-arm64.exe) 保存为`ddns.exe` 运行
+2. [可选] 定时任务 下载 [`create-task.bat`](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/create-task.bat) 于**相同目录**,以管理员权限运行
+
+* ### Linux 系统
+
+```bash
+# 1. 下载ddns
+# x64 版本
+curl https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-linux-x64 -#SLo ddns && chmod +x ddns
+
+# arm64 版本
+curl https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-linux-arm64 -#SLo ddns && chmod +x ddns
+
+# 2. [可选] 定时任务(当前目录):
+curl -sSL https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/create-task.sh | bash
+```
+
+* ### Mac OSX
+
+```sh
+# 命令行下载
+# arm64
+curl https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-mac-arm64.app -#SLo ddns && chmod +x ddns
+
+# intel x64
+curl https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-mac-x64.app -#SLo ddns && chmod +x ddns
+```
+
+## 使用PIP 安装  
+
+Pypi 安装当前版本或者更新最新版本
+
+* 安装当前版本[current version]: `pip install ddns=${BUILD_VERSION}`
+* 更新最新版[update latest version]: `pip install -U ddns`
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d1bd91133..be011623d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -115,7 +115,6 @@ jobs:
nuitka:
needs: [ python ]
strategy:
- fail-fast: false
matrix:
include:
- os: windows-latest
@@ -143,7 +142,7 @@ jobs:
architecture: ${{ matrix.arch }}
- name: remove python2 code
- run: python3 .build/remove_python2.py
+ run: python3 .build/patch.py
# Prepare build version and cert
- name: Replace build version
@@ -180,7 +179,7 @@ jobs:
dns.he
dns.huaweidns
dns.callback
- file-description: "DDNS Client 更新域名解析本机IP"
+ file-description: "DDNS Client 更新域名解析本机IP-预览版"
product-name: DDNS
company-name: "New Future"
copyright: "https://ddns.newfuture.cc"
@@ -190,10 +189,10 @@ jobs:
nofollow-import-to: tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma
onefile-tempdir-spec: "{CACHE_DIR}/{PRODUCT}_{VERSION}"
windows-icon-from-ico: ${{ runner.os == 'Windows' && 'favicon.ico' || '' }}
- linux-icon: ${{ runner.os == 'Linux' && '.build/ddns.svg' || '' }}
+ linux-icon: ${{ runner.os == 'Linux' && 'doc/img/ddns.svg' || '' }}
static-libpython: ${{ runner.os == 'Linux' && 'yes' || 'auto' }}
macos-app-name: ${{ runner.os == 'macOS' && 'DDNS' || '' }}
- macos-app-icon: ${{ runner.os == 'macOS' && '.build/ddns.svg' || '' }}
+ macos-app-icon: ${{ runner.os == 'macOS' && 'doc/img/ddns.svg' || '' }}
- run: ./dist/ddns || test -e config.json
@@ -212,12 +211,13 @@ jobs:
dist/ddns
nuitka-linux:
+ needs: [ python ]
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, ubuntu-24.04-arm ]
runs-on: ${{ matrix.os }}
- timeout-minutes: 30
+ timeout-minutes: 10
steps:
- uses: actions/checkout@v4
@@ -238,7 +238,7 @@ jobs:
&& dnf repolist \
&& dnf install -y gcc strace patchelf ccache gdb make python3-devel python3-zstandard python3-ordered-set
python3 -m pip install nuitka
- cd /DDNS && python3 .build/remove_python2.py && .build/nuitka.cmd
+ cd /DDNS && python3 .build/patch.py && .build/nuitka.sh
- run: ./dist/ddns || test -e config.json
- run: ./dist/ddns -h
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 57780c05c..f5fb6c2be 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -17,7 +17,6 @@ jobs:
permissions:
packages: write
env:
- DOCKER_BUILD_RECORD_UPLOAD: false
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/riscv64,linux/s390x
steps:
- uses: actions/checkout@v4
@@ -41,6 +40,8 @@ jobs:
images: |
ghcr.io/newfuture/ddns
newfuture/ddns
+ env:
+ DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- uses: docker/build-push-action@v6
with:
context: .
@@ -49,10 +50,7 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- outputs: type=image,name=target,\
- annotation-index.org.opencontainers.image.description=DDNS alpine container,\
- annotation-index.org.opencontainers.image.source=https://github.com/NewFuture/DDNS,\
- annotation-index.org.opencontainers.image.licenses=MIT
+ annotations: ${{ steps.meta.outputs.annotations }}
publish-pypi:
@@ -80,56 +78,140 @@ jobs:
with:
print-hash: true
- build-binary:
+ publish-binary:
strategy:
- # fail-fast: false
+ fail-fast: false
matrix:
- os: [windows-latest, windows-11-arm, ubuntu-latest, ubuntu-24.04-arm, macos-13, macos-latest]
+ include:
+ - os: windows-latest
+ arch: x64
+ - os: windows-latest
+ arch: x86
+ - os: windows-11-arm
+ arch: arm64
+ - os: ubuntu-latest
+ arch: x64
+ - os: ubuntu-24.04-arm
+ arch: arm64
+ - os: macos-13
+ arch: x64
+ - os: macos-latest
+ arch: arm64
runs-on: ${{ matrix.os }}
timeout-minutes: 10
+ permissions:
+ contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- - name: Install dependencies
- run: pip install nuitka
+ architecture: ${{ matrix.arch }}
- name: Replace build version
run: sed -i.tmp -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" -e "s/\${BUILD_DATE}/$(date --iso-8601=seconds)/" run.py && rm run.py.tmp
shell: bash
- name: remove python2 code
- run: python3 .build/remove_python2.py
+ run: python3 .build/patch.py
- - name: setup on Linux
+ - name: Set up on Linux
if: runner.os == 'Linux'
run: |
- apt-get update && apt install -y patchelf ccache
- echo " --static-libpython=yes --linux-icon=.build/ddns.svg" >> .build/nuitka.cmd
+ sudo apt-get update && sudo apt-get install -y patchelf
cp /etc/ssl/certs/ca-certificates.crt cert.pem && export SSL_CERT_FILE=${PWD}/cert.pem
-
- - name: setup on macOS
+ - name: Set up on macOS
if: runner.os == 'macOS'
- run: |
- python3 -m pip install imageio
- echo " --macos-app-name=DDNS --macos-app-icon=.build/ddns.svg" >> .build/nuitka.cmd
+ run: python3 -m pip install imageio
- - name: Package binary
- run: ./.build/nuitka.cmd
+ - name: test run.py
+ run: python3 ./run.py -h
+
+ - name: Build Executable
+ uses: Nuitka/Nuitka-Action@v1.3
+ with:
+ nuitka-version: main
+ script-name: run.py
+ mode: onefile
+ output-dir: dist
+ output-file: ddns
+ no-deployment-flag: self-execution
+ include-module: |
+ dns.dnspod
+ dns.alidns
+ dns.dnspod_com
+ dns.dnscom
+ dns.cloudflare
+ dns.he
+ dns.huaweidns
+ dns.callback
+ file-description: "DDNS Client 更新域名解析本机IP"
+ product-name: DDNS
+ company-name: "New Future"
+ copyright: "https://ddns.newfuture.cc"
+ assume-yes-for-downloads: true
+ lto: auto
+ python-flag: no_site,no_asserts,no_docstrings,isolated,static_hashes
+ nofollow-import-to: tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma
+ onefile-tempdir-spec: "{CACHE_DIR}/{PRODUCT}_{VERSION}"
+ windows-icon-from-ico: ${{ runner.os == 'Windows' && 'favicon.ico' || '' }}
+ linux-icon: ${{ runner.os == 'Linux' && 'doc/img/ddns.svg' || '' }}
+ static-libpython: ${{ runner.os == 'Linux' && 'yes' || 'auto' }}
+ macos-app-name: ${{ runner.os == 'macOS' && 'DDNS' || '' }}
+ macos-app-icon: ${{ runner.os == 'macOS' && 'doc/img/ddns.svg' || '' }}
- run: ./dist/ddns || test -e config.json
- run: ./dist/ddns -h
- - name: Move and rename binary with lowercase OS and arch
- run: mv ./dist/ddns "./dist/ddns-$(echo ${{ runner.os }}-${{ runner.arch }} | tr '[:upper:]' '[:lower:]')"
- shell: bash
+ - run: mv ./dist/ddns ./dist/ddns-ubuntu24.04-${{ runner.arch }}
+ if: runner.os == 'Linux'
+ - run: mv ./dist/ddns ./dist/ddns-mac-${{ runner.arch }}.app
+ if: runner.os == 'macOS'
+ - run: mv ./dist/ddns.exe ./dist/ddns-windows-${{ runner.arch }}.exe
+ if: runner.os == 'Windows'
+ - name: Upload files
+ run: gh release upload ${{ github.ref_name }} dist/ddns* --clobber
+ env:
+ GH_TOKEN: ${{ github.token }}
- - uses: actions/upload-artifact@v4
+ publish-linux-general:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest, ubuntu-24.04-arm ]
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 10
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v4
+
+ # Prepare build
+ - name: Replace build version
+ run: sed -i.tmp -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" -e "s/\${BUILD_DATE}/$(date --iso-8601=seconds)/" run.py && rm run.py.tmp
+ shell: bash
+
+ # https://github.com/Nuitka/Nuitka/issues/2723#issuecomment-1960831891
+ - name: Run the build process with CentOS Docker
+ uses: addnab/docker-run-action@v3
with:
- name: ${{ runner.os }}
- path: dist/
- retention-days: 10
+ image: docker.io/centos:8
+ options: -v ${{ github.workspace }}:/DDNS
+ run: |
+ cd /etc/yum.repos.d/ && sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
+ dnf install -y epel-release \
+ && dnf repolist \
+ && dnf install -y gcc strace patchelf ccache gdb make python3-devel python3-zstandard python3-ordered-set
+ python3 -m pip install nuitka
+ cd /DDNS && python3 .build/patch.py && .build/nuitka.sh
+
+ - run: ./dist/ddns || test -e config.json
+ - run: ./dist/ddns -h
+ - run: mv ./dist/ddns ./dist/ddns-linux-${{ runner.arch }}
+ - name: Upload files
+ run: gh release upload ${{ github.ref_name }} dist/ddns* --clobber
+ env:
+ GH_TOKEN: ${{ github.token }}
github-release:
runs-on: ubuntu-latest
@@ -139,16 +221,13 @@ jobs:
url: https://github.com/NewFuture/DDNS/releases/tag/${{ github.ref_name }}
permissions:
contents: write
- needs: [publish-docker, publish-pypi, build-binary]
+ needs: [publish-docker, publish-pypi, publish-binary, publish-linux-general]
steps:
- uses: actions/checkout@v4
- - run: sed -i -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" .release/README.md
- - uses: actions/download-artifact@v4
- with:
- path: dist
+ - run: sed -i -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" .github/release.md
- name: Upload files
- run: gh release upload ${{ github.ref_name }} .release/*.sh .release/*.bat dist/*/* --clobber
+ run: gh release upload ${{ github.ref_name }} .release/*.sh .release/*.bat --clobber
env:
GH_TOKEN: ${{ github.token }}
@@ -156,6 +235,6 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
- gh release view ${{ github.ref_name }} --json body -q '.body' > .release/notes.md
- cat .release/README.md >> .release/notes.md
- gh release edit ${{ github.ref_name }} --notes-file .release/notes.md --latest --prerelease=false
+ gh release view ${{ github.ref_name }} --json body -q '.body' > release_notes.md
+ cat .github/release.md >> release_notes.md
+ gh release edit ${{ github.ref_name }} --notes-file release_notes.md
diff --git a/.release/README.md b/.release/README.md
deleted file mode 100644
index 0e984c7a7..000000000
--- a/.release/README.md
+++ /dev/null
@@ -1,32 +0,0 @@
-[](https://pypi.org/project/ddns/${BUILD_VERSION}/) 
-
-## 使用二进制文件 
-
-* Windows 下载 [ddns.exe](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns.exe)
-* Ubuntu 下载 [ddns](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns)
-* Mac OS X下载 [ddns-osx](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-osx)
-
-各平台下载/使用方式
-
-* ### Windows
- 1. 下载 [`ddns.exe`](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns.exe) 运行
- 2. [可选] 定时任务 下载 [`create-task.bat`](https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/create-task.bat) 于**相同目录**,以管理员权限运行
-* ### Ubuntu
-```bash
-# 1. 下载ddns
-curl https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns -#SLo ddns && chmod +x ddns
-# 2. [可选] 定时任务(当前目录):
-curl -sSL https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/create-task.sh | bash
-```
-* ### Mac OSX
-```sh
-# 命令行下载
-curl https://github.com/NewFuture/DDNS/releases/download/${BUILD_VERSION}/ddns-osx -#SLo ddns && chmod +x ddns
-```
-
-## 使用PIP 安装  
-
-Pypi 安装当前版本或者更新最新版本
-
-* 安装当前版本[current version]: `pip install ddns=${BUILD_VERSION}`
-* 更新最新版[update latest version]: `pip install -U ddns`
\ No newline at end of file
diff --git a/README.md b/README.md
index 35a3dbf92..dc73ad986 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
-# [DDNS](https://github.com/NewFuture/DDNS) [
](https://ddns.newfuture.cc)
+# [
](https://ddns.newfuture.cc) [DDNS](https://github.com/NewFuture/DDNS)
> 自动更新 DNS 解析 到本机 IP 地址,支持 ipv4 和 ipv6 以 本地(内网)IP 和 公网 IP。
> 代理模式,支持自动创建域名记录。
[](https://pypi.org/project/ddns/)
+[](https://hub.docker.com/r/newfuture/ddns)[](https://hub.docker.com/r/newfuture/ddns)
+[](https://pypi.org/project/ddns/)
[](https://github.com/NewFuture/DDNS/actions/workflows/build.yml)
[](https://github.com/NewFuture/DDNS/releases/latest)
@@ -12,42 +14,43 @@
## Features
- 兼容和跨平台:
- - [x] 可执行文件(无需 python 环境)
- - [x] 多系统兼容 
- - [x] python3 支持 (2.x支持python2和python3)
- - [x] PIP 安装 
- - [x] Docker 支持(@NN708)
+ - [Docker (@NN708)](https://hub.docker.com/r/newfuture/ddns) [](https://hub.docker.com/r/newfuture/ddns)[](https://hub.docker.com/r/newfuture/ddns)
+ - [PIP 安装 (兼容Python2)](https://pypi.org/project/ddns/)  
+ - [二进制文件](https://github.com/NewFuture/DDNS/releases/latest) 
+
- 域名支持:
- - [x] 多个域名支持
- - [x] 多级域名解析
- - [x] 自动创建新记录
+ - 多个域名支持
+ - 多级域名解析
+ - 自动创建新记录
- IP 类型:
- - [x] 内网 IPv4 / IPv6
- - [x] 公网 IPv4 / IPv6 (支持自定义 API)
- - [x] 自定义命令(shell)
- - [x] 正则选取支持(@rufengsuixing)
+ - 内网 IPv4 / IPv6
+ - 公网 IPv4 / IPv6 (支持自定义 API)
+ - 自定义命令(shell)
+ - 正则选取支持(@rufengsuixing)
- 网络代理:
- - [x] http 代理支持
- - [x] 多代理自动切换
+ - http 代理支持
+ - 多代理自动切换
- 服务商支持:
- - [x] [DNSPOD](https://www.dnspod.cn/)
- - [x] [阿里 DNS](http://www.alidns.com/)
- - [x] [DNS.COM](https://www.dns.com/)(@loftor-git)
- - [x] [DNSPOD 国际版](https://www.dnspod.com/)
- - [x] [CloudFlare](https://www.cloudflare.com/)(@tongyifan)
- - [x] [HE.net](https://dns.he.net/)(@NN708) (不支持自动创建记录)
- - [x] [华为云](https://huaweicloud.com/)(@cybmp3)
+ - [DNSPOD](https://www.dnspod.cn/)
+ - [阿里 DNS](http://www.alidns.com/)
+ - [DNS.COM](https://www.dns.com/)(@loftor-git)
+ - [DNSPOD 国际版](https://www.dnspod.com/)
+ - [CloudFlare](https://www.cloudflare.com/)(@tongyifan)
+ - [HE.net](https://dns.he.net/)(@NN708) (不支持自动创建记录)
+ - [华为云](https://huaweicloud.com/)(@cybmp3)
- 其他:
- - [x] 可设置定时任务
- - [x] TTL 配置支持
- - [x] 本地文件缓存(减少 API 请求)
- - [x] 地址变更时触发自定义回调API(与 DDNS 功能互斥)
+ - 可设置定时任务
+ - TTL 配置支持
+ - 本地文件缓存(减少 API 请求)
+ - 地址变更时触发自定义回调API(与 DDNS 功能互斥)
## 使用
### ① 安装
-根据需要选择一种方式: `二进制`版,`pip`版,`源码`运行,或者`Docker`
+根据需要选择一种方式: `二进制`版,`pip`版,`源码`运行,或者`Docker`.
+
+推荐docker版,兼容性最佳,体积小,性能优化。
- #### pip 安装(需要 pip 或 easy_install)
@@ -58,7 +61,7 @@
- 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)
+ - Mac OSX [ddns-mac](https://github.com/NewFuture/DDNS/releases/latest)
- #### 源码运行(无任何依赖, 需 python 环境)
@@ -69,7 +72,7 @@
- 使用环境变量:
- ```
+ ```sh
docker run -d \
-e DDNS_DNS=dnspod \
-e DDNS_ID=12345 \
@@ -82,7 +85,7 @@
- 使用配置文件(docker 工作目录`/ddds/`,默认配置位置`/ddns/config.json`):
- ```
+ ```sh
docker run -d \
-v /local/config/path/:/ddns/ \
--network host \
diff --git a/.build/ddns.svg b/doc/img/ddns.svg
similarity index 100%
rename from .build/ddns.svg
rename to doc/img/ddns.svg
diff --git a/run.py b/run.py
index 94bda2c40..72f284cd0 100755
--- a/run.py
+++ b/run.py
@@ -6,10 +6,7 @@
@modified: rufengsuixing
"""
-# nuitka-project-if: os.getenv("BUILD_VERSION") is not None:
-# nuitka-project: --product-version=${BUILD_VERSION}
-# nuitka-project-else:
-# nuitka-project: --product-version=0.0.0
+# nuitka-project: --product-version=0.0.0
from os import path, environ, name as os_name
from tempfile import gettempdir
diff --git a/util/config.py b/util/config.py
index 2647cf70d..1aa51240e 100644
--- a/util/config.py
+++ b/util/config.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
-from argparse import ArgumentParser, ArgumentTypeError, Namespace, RawTextHelpFormatter # noqa: F401
+from argparse import Action, ArgumentParser, Namespace, RawTextHelpFormatter
from json import load as loadjson, dump as dumpjson
from os import stat, environ
from logging import error, getLevelName
@@ -10,7 +10,7 @@
import sys
-__cli_args = {} # type: Namespace
+__cli_args = Namespace()
__config = {} # type: dict
log_levels = ['CRITICAL', 'FATAL', 'ERROR',
'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
@@ -53,14 +53,16 @@ def init_config(description, doc, version):
'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="*",
+ parser.add_argument('--index4', nargs="*", action=ExtendAction,
+ help="list to get ipv4 [IPV4 获取方式]")
+ parser.add_argument('--index6', nargs="*", action=ExtendAction,
+ help="list to get ipv6 [IPV6获取方式]")
+ parser.add_argument('--ipv4', nargs="*", action=ExtendAction,
help="ipv4 domain list [IPV4域名列表]")
- parser.add_argument('--ipv6', nargs="*",
+ parser.add_argument('--ipv6', nargs="*", action=ExtendAction,
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="*",
+ parser.add_argument('--proxy', nargs="*", action=ExtendAction,
help="https proxy [设置http 代理,多代理逐个尝试直到成功]")
parser.add_argument('--cache', type=str2bool, nargs='?',
const=True, help="cache flag [启用缓存,可配配置路径或开关]")
@@ -148,3 +150,20 @@ def get_config(key, default=None):
elif env_name.lower() in environ: # 小写环境变量
return environ.get(env_name.lower())
return default
+
+
+class ExtendAction(Action):
+ """
+ 兼容 Python <3.8 的 extend action
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = getattr(namespace, self.dest, None)
+ if items is None:
+ items = []
+ # values 可能是单个值或列表
+ if isinstance(values, list):
+ items.extend(values)
+ else:
+ items.append(values)
+ setattr(namespace, self.dest, items)