Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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\
Expand Down
2 changes: 1 addition & 1 deletion .build/nuitka.cmd → .build/nuitka.sh
Original file line number Diff line number Diff line change
@@ -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}"
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
132 changes: 132 additions & 0 deletions .build/patch.py
Original file line number Diff line number Diff line change
@@ -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()
65 changes: 0 additions & 65 deletions .build/remove_python2.py

This file was deleted.

74 changes: 74 additions & 0 deletions .github/release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[![PyPI version](https://img.shields.io/badge/DDNS-${BUILD_VERSION}-1abc9c.svg?style=social)](https://pypi.org/project/ddns/${BUILD_VERSION}/) ![Deploy OK](https://img.shields.io/badge/release-success-brightgreen.svg?style=flat-square)

## 版本下载方式一览表

| 平台/方式 |架构支持 |
| ----------- |---------------------- |
| 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 (推荐) ![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/newfuture/ddns/${BUILD_VERSION}?style=flat-square)[![Docker Platforms](https://img.shields.io/badge/arch-amd64%20%7C%20arm64%20%7C%20arm%2Fv7%20%7C%20arm%2Fv6%20%7C%20ppc64le%20%7C%20s390x%20%7C%20386%20%7C%20mips64le-blue?logo=docker&style=flat-square)](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})

---

## 使用二进制文件 ![cross platform](https://img.shields.io/badge/system-windows_%7C%20linux_%7C%20osx-success.svg?style=flat-square)

各平台下载/使用方式

* ### 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 - Python Version](https://img.shields.io/pypi/pyversions/ddns/${BUILD_VERSION}.svg?style=flat-square) ![PyPI - Wheel](https://img.shields.io/pypi/wheel/ddns.svg?style=flat-square)

Pypi 安装当前版本或者更新最新版本

* 安装当前版本[current version]: `pip install ddns=${BUILD_VERSION}`
* 更新最新版[update latest version]: `pip install -U ddns`
14 changes: 7 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ jobs:
nuitka:
needs: [ python ]
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
Loading
Loading