ソースを参照

chore(ci): 重构pipeline using prebuilt docker (#478)

* remove pyinstaller and use prebuilt docker
* Attest
* add prebuilt in production
* add lto
* auto add hidden modules
* add png

* Apply suggestions from code review

Co-authored-by: Copilot <[email protected]>

* fix build

---------

Co-authored-by: Copilot <[email protected]>
New Future 6 ヶ月 前
コミット
d673108f77

+ 0 - 1
.build/build.cmd

@@ -1 +0,0 @@
-pyinstaller --onefile --noconfirm --clean ./.build/ddns.spec

+ 0 - 50
.build/ddns.spec

@@ -1,50 +0,0 @@
-# -*- mode: python -*-
-
-block_cipher = None
-
-from PyInstaller.utils.hooks import exec_statement
-cert_datas = exec_statement(
-    'import ssl;print(ssl.get_default_verify_paths().cafile)').strip()
-if cert_datas and cert_datas != 'None':
-    cert_datas = [(f, 'lib') for f in cert_datas.split()]
-else:
-    cert_datas = []
-
-
-a = Analysis(['../run.py'],
-             pathex=[],
-             binaries=[],
-             datas=cert_datas,
-             hiddenimports=[
-                 'dns.dnspod', 
-                 'dns.alidns',
-                 'dns.dnspod_com',
-                 'dns.dnscom',
-                 'dns.cloudflare',
-                 'dns.he',
-                 'dns.huaweidns',
-                 'dns.callback'
-             ],
-             hookspath=[],
-             runtime_hooks=[],
-             excludes=[],
-             win_no_prefer_redirects=False,
-             win_private_assemblies=False,
-             cipher=block_cipher,
-             noarchive=False)
-pyz = PYZ(a.pure,
-          a.zipped_data,
-          cipher=block_cipher)
-exe = EXE(pyz,
-          a.scripts,
-          a.binaries,
-          a.zipfiles,
-          a.datas,
-          [],
-          name='ddns',
-          debug=False,
-          bootloader_ignore_signals=False,
-          strip=False,
-          upx=True,
-          runtime_tmpdir=None,
-          console=True)

+ 0 - 1
.build/nuitka.sh

@@ -1 +0,0 @@
-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

+ 57 - 20
.github/patch.py

@@ -44,7 +44,8 @@ def update_nuitka_version(pyfile):
         content = f.read()
 
     # 提取 __version__ 变量
-    version_match = re.search(r'__version__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
+    version_match = re.search(
+        r'__version__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
     if not version_match:
         print(f'No __version__ found in {pyfile}')
         return False
@@ -66,28 +67,60 @@ def update_nuitka_version(pyfile):
     return False
 
 
-def remove_windows_textiowrapper(pyfile):
+def add_nuitka_file_description(pyfile):
     """
-    如果当前系统不是 Windows,则删除 run.py 中的 TextIOWrapper 兼容代码块
+    添加 --file-description 配置,使用 __description__ 变量的值
     """
-    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}')
+    # 提取 __description__ 变量的值
+    desc_match = re.search(
+        r'__description__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
+    if not desc_match:
+        print(f'No __description__ found in {pyfile}')
+        return False
+
+    description = desc_match.group(1)
+    if not content.endswith('\n'):
+        content += '\n'
+    description_line = f'# nuitka-project: --file-description="{description}"\n'
+    if description_line not in content:
+        content += description_line
+
+    with open(pyfile, 'w', encoding='utf-8') as f:
+        f.write(content)
+    print(f'Added file-description to {pyfile}')
+    return True
+
+
+def add_nuitka_include_modules(pyfile):
+    """
+    读取 dns 目录下的所有 Python 模块,并添加到 run.py 末尾
+    """
+    dns_dir = os.path.join(ROOT, 'dns')
+    if not os.path.exists(dns_dir):
+        print(f'DNS directory not found: {dns_dir}')
+        return False
+
+    # 获取所有 Python 模块文件
+    modules = []
+    for filename in os.listdir(dns_dir):
+        if filename.endswith('.py') and filename != '__init__.py':
+            module_name = filename[:-3]  # 去掉 .py 扩展名
+            modules.append(f'dns.{module_name}')
+
+    if not modules:
+        print('No DNS modules found')
+        return False
+
+    # 直接在文件末尾追加配置行
+    with open(pyfile, 'a', encoding='utf-8') as f:
+        for module in sorted(modules):
+            f.write(f'# nuitka-project: --include-module={module}\n')
+
+    print(f'Added {len(modules)} DNS modules to {pyfile}: {", ".join(modules)}')
+    return True
 
 
 def remove_python2_compatibility(pyfile):
@@ -123,7 +156,8 @@ def remove_python2_compatibility(pyfile):
                     except_block.append(lines[i])
                     i += 1
                 # 添加try块内容,except块用空行替代
-                new_lines.extend(['\n'] + try_block + ['\n'] * (len(except_block) + 1))
+                new_lines.extend(['\n'] + try_block + ['\n']
+                                 * (len(except_block) + 1))
                 changed = True
             else:
                 # 没有except块,原样保留
@@ -143,7 +177,10 @@ def main():
     """
     遍历所有py文件并替换兼容导入,同时更新nuitka版本号
     """
-    update_nuitka_version(os.path.join(ROOT, "run.py"))
+    run_py_path = os.path.join(ROOT, "run.py")
+    update_nuitka_version(run_py_path)
+    add_nuitka_file_description(run_py_path)
+    add_nuitka_include_modules(run_py_path)
 
     changed_files = 0
     for dirpath, _, filenames in os.walk(ROOT):

+ 13 - 12
.github/workflows/build-nuitka-docker.yml

@@ -24,6 +24,7 @@ permissions:
   packages: write
 
 env:
+  DOCKER_IMAGE: hcr.io/newfuture/nuitka-builder
   NUITKA_VERSION: ${{ inputs.nuitka || 'main' }}
   PYTHON_VERSION: ${{ inputs.python || '3.8' }}
   FULL_TAG: ${{ inputs.nuitka || 'main' }}-py${{ inputs.python || '3.8' }}
@@ -60,7 +61,7 @@ jobs:
           platforms: ${{ env.platforms }}
           target: base-builder
           push: true
-          tags: ghcr.io/newfuture/nuitka-builder:${{ matrix.type }}-${{ matrix.host }}
+          tags: ${{env.DOCKER_IMAGE}}:${{ matrix.type }}-${{ matrix.host }}
           build-args: |
             PYTHON_VERSION=${{ env.PYTHON_VERSION }}
             NUITKA_VERSION=${{ env.NUITKA_VERSION }}
@@ -103,7 +104,7 @@ jobs:
           platforms: ${{ matrix.platforms }}
           target: base-builder
           push: true
-          tags: ghcr.io/newfuture/nuitka-builder:${{ matrix.host }}
+          tags: ${{env.DOCKER_IMAGE}}:${{ matrix.host }}
           build-args: |
             PYTHON_VERSION=${{ env.PYTHON_VERSION }}
             NUITKA_VERSION=${{ env.NUITKA_VERSION }}
@@ -131,10 +132,10 @@ jobs:
         run: |
           set -ex
           docker buildx imagetools create \
-            --tag ghcr.io/newfuture/nuitka-builder:${{matrix.type}}-${{ github.ref_name }} \
-            --tag ghcr.io/newfuture/nuitka-builder:${{ env.FULL_TAG }}-${{matrix.type}} \
-            ghcr.io/newfuture/nuitka-builder:${{matrix.type}}-amd \
-            ghcr.io/newfuture/nuitka-builder:${{matrix.type}}-arm \
+            --tag ${{env.DOCKER_IMAGE}}:${{matrix.type}}-${{ github.ref_name }} \
+            --tag ${{env.DOCKER_IMAGE}}:${{ env.FULL_TAG }}-${{matrix.type}} \
+            ${{env.DOCKER_IMAGE}}:${{matrix.type}}-amd \
+            ${{env.DOCKER_IMAGE}}:${{matrix.type}}-arm \
             --annotation "index:org.opencontainers.image.description={{matrix.type}} Nuitka Builder Image (multi-arch)" 
 
   merge-docker-builder:
@@ -157,10 +158,10 @@ jobs:
         run: |
           set -ex
           docker buildx imagetools create \
-            --tag ghcr.io/newfuture/nuitka-builder \
-            --tag ghcr.io/newfuture/nuitka-builder:${{ github.ref_name }} \
-            --tag ghcr.io/newfuture/nuitka-builder:${{ env.FULL_TAG }} \
-            ghcr.io/newfuture/nuitka-builder:amd \
-            ghcr.io/newfuture/nuitka-builder:arm \
-            ghcr.io/newfuture/nuitka-builder:qemu \
+            --tag ${{env.DOCKER_IMAGE}} \
+            --tag ${{env.DOCKER_IMAGE}}:${{ github.ref_name }} \
+            --tag ${{env.DOCKER_IMAGE}}:${{ env.FULL_TAG }} \
+            ${{env.DOCKER_IMAGE}}:amd \
+            ${{env.DOCKER_IMAGE}}:arm \
+            ${{env.DOCKER_IMAGE}}:qemu \
            --annotation "index:org.opencontainers.image.description=Alpine Nuitka Builder Image (multi-arch)"

+ 8 - 65
.github/workflows/build.yml

@@ -82,47 +82,8 @@ jobs:
           path: dist/
           retention-days: ${{ github.event_name == 'push' && 14 || 3 }}
 
-          
-  pyinstaller:
-    strategy:
-      matrix:
-        os: [macos, ubuntu]
-    runs-on: ${{ matrix.os }}-latest
-    timeout-minutes: 8
-    steps:
-      - uses: actions/checkout@v4
-      - name: Set up Python "3.x"
-        uses: actions/setup-python@v5
-        with:
-          python-version: "3.x"
-      - name: Install dependencies
-        run: pip install pyinstaller
-
-      # Prepare build version and cert
-      - 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: Copy cert on ubuntu
-        if: runner.os == 'Linux'
-        run: cp /etc/ssl/certs/ca-certificates.crt cert.pem && export SSL_CERT_FILE=${PWD}/cert.pem
-
-      - run: python ./run.py -h
-
-      - name: Package binary
-        run: python -O -m PyInstaller --noconfirm --clean .build/ddns.spec
-
-      - run: ./dist/ddns || test -f config.json
-      - run: ./dist/ddns -h
-
-      # Upload build result
-      - uses: actions/upload-artifact@v4
-        with:
-          name: PyInstaller-${{ matrix.os }}
-          path: dist/
-          retention-days: ${{ github.event_name == 'push' && 14 || 3 }}
-
   nuitka:
-    needs: [ python ]
+    needs: [ python, lint ]
     strategy:
       matrix:
         include:
@@ -162,9 +123,7 @@ jobs:
         
       - name: Set up on Linux
         if: runner.os == 'Linux'
-        run: |
-          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
+        run: sudo apt-get install -y --no-install-recommends patchelf
 
       - name: Set up on macOS
         if: runner.os == 'macOS'
@@ -179,31 +138,13 @@ jobs:
           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}"
+          lto: yes
+          file-description: "DDNS客户端[测试版 Alpha]"
           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' || '' }}
+          macos-app-icon: ${{ runner.os == 'macOS' && 'doc/img/ddns.png' || '' }}
 
       - run: ./dist/ddns || test -f config.json
       - run: ./dist/ddns -h
@@ -247,6 +188,7 @@ jobs:
           tags: ddnsbin
           target: export
           outputs: type=local,dest=./output
+          build-args: BUILDER=ghcr.io/newfuture/nuitka-builder:${{matrix.libc}}-master
       # 测试构建的二进制文件
       - name: Test built binaries
         run: |
@@ -310,7 +252,8 @@ jobs:
           push: false
           tags: ddns:test
           outputs: type=oci,dest=./multi-platform-image.tar
-      
+          build-args: BUILDER=ghcr.io/newfuture/nuitka-builder:master
+
       # 准备测试环境
       - name: Prepare test environment
         run: mkdir -p oci-image && tar -vxf multi-platform-image.tar -C oci-image  

+ 15 - 25
.github/workflows/publish.yml

@@ -17,6 +17,8 @@ jobs:
       url: https://hub.docker.com/r/newfuture/ddns
     permissions:
       packages: write
+      id-token: write
+      attestations: write
     env:
       platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/riscv64,linux/s390x
     steps:
@@ -49,6 +51,7 @@ jobs:
         env:
           DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
       - uses: docker/build-push-action@v6
+        id: push
         with:
           context: .
           file: docker/Dockerfile
@@ -57,7 +60,14 @@ jobs:
           tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}
           annotations: ${{ steps.meta.outputs.annotations }}
-      
+          build-args: BUILDER=ghcr.io/newfuture/nuitka-builder:master
+      - name: Attest
+        uses: actions/attest-build-provenance@v2
+        id: attest
+        with:
+          subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+          subject-digest: ${{ steps.push.outputs.digest }}
+          push-to-registry: true      
 
   publish-pypi:
     runs-on: ubuntu-latest
@@ -123,9 +133,7 @@ jobs:
 
       - name: Set up on Linux
         if: runner.os == 'Linux'
-        run: |
-          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
+        run: sudo apt-get install -y --no-install-recommends patchelf
       - name: Set up on macOS
         if: runner.os == 'macOS'
         run: python3 -m pip install imageio
@@ -140,31 +148,12 @@ jobs:
           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}"
+          lto: yes
           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' || '' }}
+          macos-app-icon: ${{ runner.os == 'macOS' && 'doc/img/ddns.png' || '' }}
 
       - run: ./dist/ddns || test -e config.json
       - run: ./dist/ddns -h
@@ -206,6 +195,7 @@ jobs:
           tags: ddnsbin
           target: export
           outputs: type=local,dest=./output
+          build-args: BUILDER=ghcr.io/newfuture/nuitka-builder:${{matrix.libc}}-master
       # 测试构建的二进制文件
       - name: Test built binaries
         run: |

BIN
doc/img/ddns.png


+ 2 - 12
docker/Dockerfile

@@ -28,20 +28,10 @@ COPY . .
 COPY --from=base /nuitka_exclude_so.txt nuitka_exclude_so.txt
 RUN python3 .github/patch.py
 RUN python3 -O -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\
+    --remove-output \
     --lto=yes \
-    --onefile-tempdir-spec="{TEMP}/{PRODUCT}_{VERSION}" \
-    --python-flag=no_site,no_asserts,no_docstrings,isolated,static_hashes\
-    --nofollow-import-to=tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma \
-    --noinclude-dlls=liblzma.so.* \
     $(cat nuitka_exclude_so.txt)
-RUN mkdir /output && cp dist/ddns /output/
+RUN mkdir /output && cp ddns /output/
 COPY docker/entrypoint.sh /output/
 
 FROM alpine:${HOST_VERSION}

+ 7 - 23
docker/glibc.Dockerfile

@@ -28,31 +28,15 @@ FROM ${BUILDER} AS builder
 # 拷贝项目文件
 COPY . .
 RUN python3 .github/patch.py
-# 构建二进制文件
-RUN python3 -O -m nuitka \
-    --onefile \
-    --output-dir=dist \
-    --output-filename=ddns \
+# 构建二进制文件,glibc arm下编译会报错,
+# collect2: fatal error: ld terminated with signal 11 [Segmentation fault], core dumped compilation terminated.
+# FATAL: Error, the C compiler 'gcc' crashed with segfault. Consider upgrading it or using '--clang' option.
+RUN apt-get update && apt-get install -y --no-install-recommends clang
+RUN python3 -O -m nuitka run.py \
     --remove-output \
-    --no-deployment-flag=self-execution \
-    --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 \
-    --nofollow-import-to=tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma \
-    --product-name=DDNS \
-    --onefile-tempdir-spec="{TEMP}/{PRODUCT}_{VERSION}" \
-    --python-flag=no_site,no_asserts,no_docstrings,isolated,static_hashes \
-    --file-description="DDNS Client 自动更新域名解析到本机IP" \
-    --company-name="New Future" \
     --linux-icon=doc/img/ddns.svg \
-    run.py
-RUN cp dist/ddns /bin/ddns \
-    && cp dist/ddns /ddns
+    $( [ "$(uname -m)" = "aarch64" ] || echo --lto=yes )
+RUN cp ddns /bin/ddns && cp ddns /ddns
 
 
 # export the binary

+ 3 - 14
docker/musl.Dockerfile

@@ -23,20 +23,9 @@ FROM ${BUILDER} AS builder
 COPY . .
 RUN python3 .github/patch.py
 RUN python3 -O -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\
-    --lto=yes \
-    --onefile-tempdir-spec="{TEMP}/{PRODUCT}_{VERSION}" \
-    --python-flag=no_site,no_asserts,no_docstrings,isolated,static_hashes\
-    --nofollow-import-to=tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma \
-    --noinclude-dlls=liblzma.so.*
-RUN cp dist/ddns /bin/ddns \
-    && cp dist/ddns /ddns
+    --remove-output \
+    --lto=yes
+RUN cp ddns /bin/ddns && cp ddns /ddns
 
 
 # export the binary

+ 25 - 12
run.py

@@ -6,8 +6,6 @@ DDNS
 @modified: rufengsuixing
 """
 
-# nuitka-project: --product-version=0.0.0
-
 from os import path, environ, name as os_name
 from io import TextIOWrapper
 from subprocess import check_output
@@ -21,7 +19,7 @@ from util.cache import Cache
 from util.config import init_config, get_config
 
 __version__ = "${BUILD_VERSION}@${BUILD_DATE}"  # CI 时会被Tag替换
-__description__ = "automatically update DNS records to dynamic local IP [自动更新DNS记录指向本地IP]"
+__description__ = "automatically update DNS records to my IP [域名自动指向本机IP]"
 __doc__ = """
 ddns[%s]
 (i) homepage or docs [文档主页]: https://ddns.newfuture.cc/
@@ -127,12 +125,6 @@ def main():
     更新
     """
     init_config(__description__, __doc__, __version__)
-    # Dynamicly import the dns module as configuration
-    dns_provider = str(get_config('dns', 'dnspod').lower())
-    dns = getattr(__import__('dns', fromlist=[dns_provider]), dns_provider)
-    dns.Config.ID = get_config('id')
-    dns.Config.TOKEN = get_config('token')
-    dns.Config.TTL = get_config('ttl')
 
     basicConfig(
         level=get_config('log.level'),
@@ -140,8 +132,15 @@ def main():
         datefmt='%m-%d %H:%M:%S',
         filename=get_config('log.file'),
     )
-
     info("DDNS[ %s ] run: %s %s", __version__, os_name, sys.platform)
+
+    # Dynamically import the dns module as configuration
+    dns_provider = str(get_config('dns', 'dnspod').lower())
+    dns = getattr(__import__('dns', fromlist=[dns_provider]), dns_provider)
+    dns.Config.ID = get_config('id')
+    dns.Config.TOKEN = get_config('token')
+    dns.Config.TTL = get_config('ttl')
+
     if get_config("config"):
         info('loaded Config from: %s', path.abspath(get_config('config')))
 
@@ -169,9 +168,23 @@ def main():
 
 
 if __name__ == '__main__':
-    encoding = sys.stdout.encoding
-    if encoding is not None and encoding.lower() != 'utf-8' and hasattr(sys.stdout, 'buffer'):
+    encode = sys.stdout.encoding
+    if encode is not None and encode.lower() != 'utf-8' and hasattr(sys.stdout, 'buffer'):
         # 兼容windows 和部分ASCII编码的老旧系统
         sys.stdout = TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
         sys.stderr = TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
     main()
+
+# Nuitka Project Configuration
+# nuitka-project: --mode=onefile
+# nuitka-project: --output-filename=ddns
+# nuitka-project: --product-name=DDNS
+# nuitka-project: --product-version=0.0.0
+# nuitka-project: --onefile-tempdir-spec="{TEMP}/{PRODUCT}_{VERSION}"
+# nuitka-project: --no-deployment-flag=self-execution
+# nuitka-project: --company-name="New Future"
+# nuitka-project: --copyright=https://ddns.newfuture.cc
+# nuitka-project: --assume-yes-for-downloads
+# nuitka-project: --python-flag=no_site,no_asserts,no_docstrings,isolated,static_hashes
+# nuitka-project: --nofollow-import-to=tkinter,unittest,pydoc,doctest,distutils,setuptools,lib2to3,test,idlelib,lzma
+# nuitka-project: --noinclude-dlls=liblzma.*