From 4f82472c540523c5ff69db83013fdcf503366ce5 Mon Sep 17 00:00:00 2001 From: New Future Date: Thu, 29 May 2025 01:13:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96docker=E9=95=9C=E5=83=8F=20(#?= =?UTF-8?q?459)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 删除重复库, 减少docker体积 * 首次失败自动停止 * python3 优先 * 怎加python2检查 * Nuitka/Nuitka-Action Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .build/Dockerfile | 21 +++++---- .build/entrypoint.sh | 4 +- .build/remove_python2.py | 65 ++++++++++++++++++++++++++ .github/workflows/build.yml | 86 ++++++++++++++++++++++++++++------- .github/workflows/publish.yml | 3 ++ dns/alidns.py | 10 ++-- dns/callback.py | 10 ++-- dns/cloudflare.py | 10 ++-- dns/dnscom.py | 11 ++--- dns/dnspod.py | 11 ++--- dns/he.py | 10 ++-- dns/huaweidns.py | 11 ++--- run.py | 2 +- util/ip.py | 8 ++-- 14 files changed, 185 insertions(+), 77 deletions(-) create mode 100644 .build/remove_python2.py diff --git a/.build/Dockerfile b/.build/Dockerfile index d1eff1e14..1d1f9f276 100755 --- a/.build/Dockerfile +++ b/.build/Dockerfile @@ -1,9 +1,11 @@ -FROM alpine:latest +FROM alpine WORKDIR /build 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 -m nuitka run.py \ +RUN python3 .build/remove_python2.py +RUN python3 -O -m nuitka run.py \ --mode=onefile\ --output-dir=./dist\ --no-deployment-flag=self-execution\ @@ -11,15 +13,16 @@ RUN python3 -m nuitka run.py \ --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\ + --lto=yes \ + --onefile-tempdir-spec="{TEMP}/{PRODUCT}_{VERSION}" \ --python-flag=no_site,no_asserts,no_docstrings,isolated,static_hashes\ - --nofollow-import-to=unittest,pydoc\ - --onefile-tempdir-spec="{CACHE_DIR}/{PRODUCT}/{VERSION}" -RUN mkdir bin -RUN cp dist/ddns bin/ -RUN cp .build/entrypoint.sh bin/ + --nofollow-import-to=tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma \ + --noinclude-dlls=liblzma.so.* \ + $(cat nuitka_exclude_so.txt) +RUN mkdir docker-bin && cp dist/ddns docker-bin/ && cp .build/entrypoint.sh docker-bin/ -FROM alpine:latest +FROM alpine LABEL maintainer="NN708, newfuture" WORKDIR /ddns -COPY --from=0 /build/bin/* /bin/ +COPY --from=0 /build/docker-bin/* /bin/ ENTRYPOINT [ "/bin/entrypoint.sh" ] diff --git a/.build/entrypoint.sh b/.build/entrypoint.sh index 53bfacc49..57255b708 100755 --- a/.build/entrypoint.sh +++ b/.build/entrypoint.sh @@ -3,9 +3,7 @@ if [ $# -eq 0 ]; then printenv > /etc/environment echo "*/5 * * * * cd /ddns && /bin/ddns" > /etc/crontabs/root - /bin/ddns - echo "Cron daemon will run every 5 minutes..." - exec crond -f + /bin/ddns && echo "Cron daemon will run every 5 minutes..." && exec crond -f else first=`echo $1 | cut -c1` if [ "$first" = "-" ]; then diff --git a/.build/remove_python2.py b/.build/remove_python2.py new file mode 100644 index 000000000..0c1bdda96 --- /dev/null +++ b/.build/remove_python2.py @@ -0,0 +1,65 @@ +#!/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/workflows/build.yml b/.github/workflows/build.yml index b25e1838e..48d355f11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,23 @@ jobs: - name: check complexity and length # the GitHub editor is 127 chars wide run: flake8 . --count --max-complexity=12 --max-line-length=127 --statistics + python: + strategy: + fail-fast: false + matrix: + python-version: [ "2.7","3" ] + runs-on: ubuntu-22.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - run: sudo apt-get update && sudo apt-get install -y python${{ matrix.python-version }} + - name: test help command + run: python${{ matrix.python-version }} run.py -h + - name: test config generation + run: python${{ matrix.python-version }} run.py || test -e config.json + - name: test version + run: python${{ matrix.python-version }} run.py --version + pypi: runs-on: ubuntu-latest timeout-minutes: 5 @@ -55,6 +72,7 @@ jobs: path: dist/ retention-days: 5 + pyinstaller: strategy: # fail-fast: false @@ -95,6 +113,7 @@ jobs: retention-days: 3 nuitka: + needs: [ python ] strategy: fail-fast: false matrix: @@ -122,8 +141,9 @@ jobs: with: python-version: 3.x architecture: ${{ matrix.arch }} - - name: Install dependencies - run: python3 -m pip install -U nuitka + + - name: remove python2 code + run: python3 .build/remove_python2.py # Prepare build version and cert - name: Replace build version @@ -133,34 +153,67 @@ jobs: - name: Set up on Linux if: runner.os == 'Linux' run: | - sudo apt-get update - sudo apt-get install -y patchelf - echo " --static-libpython=yes --linux-icon=.build/icon.png" >> .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: Set up on macOS if: runner.os == 'macOS' - run: | - python3 -m pip install imageio - echo " --macos-app-name=DDNS --macos-app-icon=.build/icon.png" >> .build/nuitka.cmd - + run: python3 -m pip install imageio + - run: python3 ./run.py -h - - name: Package binary - run: ./.build/nuitka.cmd + - name: Build Executable + uses: Nuitka/Nuitka-Action@main + 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' && '.build/icon.png' || '' }} + static-libpython: ${{ runner.os == 'yes' || 'auto' }} + macos-app-name: ${{ runner.os == 'macOS' && 'DDNS' || '' }} + macos-app-icon: ${{ runner.os == 'macOS' && '.build/icon.png' || '' }} + - run: ./dist/ddns || test -e config.json - run: ./dist/ddns -h # Upload build result - - uses: actions/upload-artifact@v4 + - name: Upload Artifacts + uses: actions/upload-artifact@v4 with: name: ddns-${{ runner.os }}-${{ matrix.arch }} - path: dist/ - retention-days: 7 + if-no-files-found: error + path: | + dist/*.exe + dist/*.bin + dist/*.app + dist/ddns docker: if: github.event_name == 'pull_request' + needs: [ python ] strategy: matrix: platforms: [ linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/riscv64,linux/s390x ] @@ -189,7 +242,7 @@ jobs: preview-pypi: runs-on: ubuntu-latest if: github.event_name == 'push' - needs: [lint, pypi] + needs: [lint, pypi, python] timeout-minutes: 3 environment: name: preview @@ -207,8 +260,9 @@ jobs: print-hash: true preview-docker: - runs-on: ubuntu-latest if: github.event_name == 'push' + needs: [lint, python] + runs-on: ubuntu-latest timeout-minutes: 120 environment: name: preview diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 14a245f75..fdf5fae78 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -99,6 +99,9 @@ jobs: 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 + - name: setup on Linux if: runner.os == 'Linux' run: | diff --git a/dns/alidns.py b/dns/alidns.py index 50d94e50e..c0f696a58 100644 --- a/dns/alidns.py +++ b/dns/alidns.py @@ -14,14 +14,12 @@ from logging import debug, info, warning from datetime import datetime -try: - # python 2 - from httplib import HTTPSConnection - from urllib import urlencode, quote_plus, quote -except ImportError: - # python 3 +try: # python 3 from http.client import HTTPSConnection from urllib.parse import urlencode, quote_plus, quote +except ImportError: # python 2 + from httplib import HTTPSConnection + from urllib import urlencode, quote_plus, quote __author__ = 'New Future' # __all__ = ["request", "ID", "TOKEN", "PROXY"] diff --git a/dns/callback.py b/dns/callback.py index aa58f8055..f95aa1cc5 100644 --- a/dns/callback.py +++ b/dns/callback.py @@ -10,15 +10,13 @@ from logging import debug, info, warning from time import time -try: - # python 2 +try: # python 3 + from http.client import HTTPSConnection, HTTPConnection + from urllib.parse import urlencode, urlparse, parse_qsl +except ImportError: # python 2 from httplib import HTTPSConnection, HTTPConnection from urlparse import urlparse, parse_qsl from urllib import urlencode -except ImportError: - # python 3 - from http.client import HTTPSConnection, HTTPConnection - from urllib.parse import urlencode, urlparse, parse_qsl __author__ = '老周部落' diff --git a/dns/cloudflare.py b/dns/cloudflare.py index e43b49322..1ac2adf4b 100644 --- a/dns/cloudflare.py +++ b/dns/cloudflare.py @@ -9,14 +9,12 @@ from json import loads as jsondecode, dumps as jsonencode from logging import debug, info, warning -try: - # python 2 - from httplib import HTTPSConnection - from urllib import urlencode -except ImportError: - # python 3 +try: # python 3 from http.client import HTTPSConnection from urllib.parse import urlencode +except ImportError: # python 2 + from httplib import HTTPSConnection + from urllib import urlencode __author__ = 'TongYifan' diff --git a/dns/dnscom.py b/dns/dnscom.py index c1550ffe3..82eefdce5 100644 --- a/dns/dnscom.py +++ b/dns/dnscom.py @@ -13,15 +13,12 @@ from time import mktime from datetime import datetime -try: - # python 2 - from httplib import HTTPSConnection - from urllib import urlencode -except ImportError: - # python 3 +try: # python 3 from http.client import HTTPSConnection from urllib.parse import urlencode - +except ImportError: # python 2 + from httplib import HTTPSConnection + from urllib import urlencode __author__ = 'Bigjin' # __all__ = ["request", "ID", "TOKEN", "PROXY"] diff --git a/dns/dnspod.py b/dns/dnspod.py index f9a3b256c..ebf2b85df 100644 --- a/dns/dnspod.py +++ b/dns/dnspod.py @@ -9,14 +9,13 @@ from json import loads as jsondecode from logging import debug, info, warning from os import environ -try: - # python 2 - from httplib import HTTPSConnection - from urllib import urlencode -except ImportError: - # python 3 + +try: # python 3 from http.client import HTTPSConnection from urllib.parse import urlencode +except ImportError: # python 2 + from httplib import HTTPSConnection + from urllib import urlencode __author__ = 'New Future' diff --git a/dns/he.py b/dns/he.py index ef990fbb5..461eaf87d 100644 --- a/dns/he.py +++ b/dns/he.py @@ -8,14 +8,12 @@ from logging import debug, info, warning -try: - # python 2 - from httplib import HTTPSConnection - from urllib import urlencode -except ImportError: - # python 3 +try: # python 3 from http.client import HTTPSConnection from urllib.parse import urlencode +except ImportError: # python 2 + from httplib import HTTPSConnection + from urllib import urlencode __author__ = 'NN708' diff --git a/dns/huaweidns.py b/dns/huaweidns.py index eb43b7ee5..811848d0c 100644 --- a/dns/huaweidns.py +++ b/dns/huaweidns.py @@ -14,14 +14,13 @@ from logging import debug, info, warning from datetime import datetime -try: - # python 2 - from httplib import HTTPSConnection - from urllib import urlencode -except ImportError: - # python 3 +try: # python 3 from http.client import HTTPSConnection from urllib.parse import urlencode +except ImportError: # python 2 + from httplib import HTTPSConnection + from urllib import urlencode + __author__ = 'New Future' BasicDateFormat = "%Y%m%dT%H%M%SZ" diff --git a/run.py b/run.py index b91975abf..768175cd7 100755 --- a/run.py +++ b/run.py @@ -49,7 +49,7 @@ def get_ip(ip_type, index="default"): # EN: Catch exceptions value = None try: - debug(f"get_ip({ip_type}, {index})") + debug("get_ip(%s, %s)", ip_type, index) if index is False: # disabled return False elif isinstance(index, list): # 如果获取到的规则是列表,则依次判断列表中每一个规则,直到找到一个可以正确获取到的IP diff --git a/util/ip.py b/util/ip.py index b69d8c822..c5b59340d 100644 --- a/util/ip.py +++ b/util/ip.py @@ -4,12 +4,10 @@ from os import name as os_name, popen from socket import socket, getaddrinfo, gethostname, AF_INET, AF_INET6, SOCK_DGRAM from logging import debug, error -try: - # python2 - from urllib2 import urlopen, Request -except ImportError: - # python3 +try: # python3 from urllib.request import urlopen, Request +except ImportError: # python2 + from urllib2 import urlopen, Request # IPV4正则 IPV4_REG = r'((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])'