Files
DDNS/docs/en/dev/provider.md
0e466dc496 feat(docs): add automated documentation website builder using VitePress 2.0 (#634)
* Initial plan

* feat(docs): add static documentation website builder script

Add build_docs.py script to convert markdown docs to static HTML website with:
- Responsive navigation bar with collapsible directory tree
- In-page table of contents (right sidebar, hidden on small screens)
- Language switcher (Chinese/English)
- Pure static HTML output (no server-side processing)
- Footer with GitHub links and edit page link
- Mobile-friendly responsive design

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* style: format build_docs.py with ruff

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): use MkDocs with Material theme instead of custom parser

Replace custom markdown parser with industry-standard tools:
- MkDocs: Popular static site generator for documentation
- Material for MkDocs: Modern, feature-rich theme

Benefits:
- Mature, well-tested codebase used by thousands of projects
- Built-in features: search, navigation, TOC, code highlighting
- Active development with regular updates
- Extensive documentation and community support
- Beautiful, responsive design out of the box
- Easy to maintain with configuration-based approach

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): use VitePress instead of MkDocs for documentation

Replace MkDocs with VitePress - a Vue-powered static site generator:
- VitePress: Modern, fast, designed for documentation
- Vue 3 + Vite stack with instant HMR
- Built-in i18n, search, dark mode, responsive design
- Chinese-friendly (created by Evan You)

Features:
- Responsive navigation with collapsible sidebar
- In-page TOC automatically generated
- Bilingual support (Chinese/English)
- Local search with keyboard shortcuts
- Light/Dark mode toggle
- Edit page links to GitHub
- Pure static HTML output

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* feat(docs): add automated setup and build with npm run build

Add automated directory setup for VitePress build:
- Created scripts/setup-docs.mjs to auto-setup docs structure
- Updated package.json with 'build' command that runs setup + build
- Updated docs:dev to include setup automatically
- Simplified workflow to one command: npm run build

Changes:
- npm run build now handles everything automatically
- No need for separate bash script
- Directory structure created on-demand
- Updated BUILD_DOCS.md with simplified instructions

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): improve VitePress configuration per feedback

Addressed all feedback items:
1. Fixed edit link mapping: index.html now correctly maps to README.md/README.en.md in GitHub
2. Added schema directory to build output via public/ directory
3. Removed /doc/ prefix from all URLs - doc/ is now the web root
4. Fixed path references in README files during setup

Changes:
- Updated editLink to use function for proper README mapping
- Modified setup script to copy doc/ contents to docs root (not docs/doc/)
- Added schema/ to public/ directory for static asset serving
- Fixed /doc/ path references in copied README files
- Updated all navigation and sidebar paths to remove /doc/ prefix

URLs now work as:
- / (homepage from README.md)
- /docker, /install (no /doc/ prefix)
- /config/cli, /providers/dnspod (clean paths)
- /schema/v4.0.json (accessible in build)

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): preserve original /doc/ paths in documentation

Per user feedback, reverted path modifications:
- No longer replace /doc/ paths in README files
- Restored doc/ directory structure (docs/doc/)
- Updated VitePress navigation to use /doc/ prefix
- Edit links correctly map to source files in doc/ directory

Changes:
- setup-docs.mjs: removed path replacement, copy doc/ to docs/doc/
- config.mts: restored /doc/ prefix in all navigation and sidebar paths
- URLs now match original documentation: /doc/docker, /doc/providers/dnspod

This preserves the original documentation structure and paths as they
appear in the source repository.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): remove /doc/ prefix from URLs and ignore intermediate files

Per user feedback, changed URLs from /doc/xxx.html to /xxx.html:

Changes:
1. Updated setup-docs.mjs to copy doc/ contents to docs/ root (not docs/doc/)
2. Modified path replacement to handle both links and image src attributes
3. Updated VitePress config.mts to remove /doc/ prefix from all nav/sidebar paths
4. Updated .gitignore to exclude intermediate copied files from commits
5. Removed docs/index.md and docs/en/index.md from git tracking

URLs now:
- /docker.html instead of /doc/docker.html
- /providers/dnspod.html instead of /doc/providers/dnspod.html
- /config/cli.html instead of /doc/config/cli.html
- Images at /img/ddns.svg instead of /doc/img/ddns.svg
- Schema files remain at /schema/v4.0.json

Intermediate files generated during build are now gitignored and won't be committed.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): move build scripts to doc/ directory

Moved documentation build scripts from root and scripts/ to doc/ directory:
- Moved scripts/setup-docs.mjs → doc/setup-docs.mjs
- Moved build_docs.sh → doc/build_docs.sh
- Removed empty scripts/ directory
- Updated package.json to reference new script locations
- Updated BUILD_DOCS.md with new directory structure

This organizes all documentation-related files under doc/ directory,
keeping the repository root cleaner.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): consolidate into single doc/ directory

Eliminated docs/ directory, consolidated everything under doc/:
- Moved docs/.vitepress/ → doc/.vitepress/
- Removed docs/ directory entirely
- Updated package.json to use doc/ instead of docs/
- Updated .gitignore to reflect new structure (doc/.vitepress/dist/, doc/index.md, doc/en/, doc/public/)
- Updated setup-docs.mjs to generate files within doc/
- Updated BUILD_DOCS.md documentation

Structure now:
- doc/ contains source files, VitePress config, and generated intermediate files
- All build artifacts stay within doc/.vitepress/dist/
- Simpler single-directory structure without doc/docs nesting

Commands remain the same:
- npm run build
- npm run docs:dev
- npm run docs:preview

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): move package.json to doc/ directory

Moved package.json to doc/ directory to consolidate all documentation-related files:
- Moved package.json from root → doc/package.json
- Updated package.json scripts to use relative paths (. instead of doc)
- Updated BUILD_DOCS.md with new usage instructions (cd doc first)
- Updated build_docs.sh to work from within doc/ directory
- .gitignore already handles node_modules/ globally

All documentation dependencies and configuration now in single doc/ directory.

Usage:
```bash
cd doc
npm install
npm run build
```

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* feat(docs): add install.sh to public, auto-redirects, and ensure README→index.html mapping

Implemented three enhancements:

1. Copy install.sh to public/ directory
   - install.sh now accessible at /install.sh in build
   - Users can download directly from documentation site

2. Add automatic redirects for old URLs
   - Created custom VitePress theme with redirect logic
   - /doc/xxx URLs redirect to /xxx
   - /doc/xxx.en.html URLs redirect to /en/xxx.html
   - Maintains backward compatibility with old links

3. README.md renders as index.html with correct edit links
   - Already configured: index.md maps to README.md for editing
   - en/index.md maps to README.en.md for editing
   - Edit links work correctly for both Chinese and English homepages

Updated BUILD_DOCS.md with new features documentation.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* feat(docs): upgrade to VitePress 2.0 (next)

Upgraded VitePress from v1.x to v2.0 (next tag):
- Updated package.json to use "vitepress": "next"
- Currently using v2.0.0-alpha.15
- Tested build successfully - all features working
- Updated BUILD_DOCS.md to reflect VitePress 2.0

VitePress 2.0 benefits:
- Better performance and faster build times
- Improved developer experience
- Enhanced features and bug fixes
- Future-ready with latest Vue 3 + Vite improvements

Build tested and confirmed working with no breaking changes.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): remove redundant build script and fix copyright years

Changes per user feedback:
1. Removed doc/build_docs.sh (redundant - standard npm commands sufficient)
2. Updated copyright in config.mts line 133: "Copyright © 2016 ~ 2024 NewFuture"
3. Updated copyright in config.mts line 305: "Copyright © 2016 ~ 2024 NewFuture"

Usage now simplified to standard npm commands:
- cd doc && npm install
- npm run build
- npm run docs:dev
- npm run docs:preview

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): dynamic copyright year, move redirects to esa.js, update providers link

Per user feedback:
1. Made copyright year dynamic: `${new Date().getFullYear()}` (line 305)
2. Moved redirect logic from theme/index.ts to esa.js for server-side handling
3. Changed providers link from "概述 /providers/README" to "所有服务商 /providers/index" (line 70)

Changes:
- config.mts: Dynamic copyright calculation, updated providers navigation
- esa.js: Added redirect logic for /doc/ URLs (301 permanent redirects)
- theme/index.ts: Removed client-side redirect logic (now handled server-side)

Redirects now handled at edge function level for better SEO and performance.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): improve redirects, provider link, and add code-to-GitHub link conversion

Per user feedback (comment #3736692270):
1. Moved .en.html redirect logic before general /doc/ redirect in esa.js
2. Added /index.en.html → /en/ redirect
3. Changed providers/README URL to providers/ (line 39)
4. Added markdown transformer to convert code links (e.g., /ddns/provider/dnspod.py) to GitHub links

Changes:
- esa.js: Reordered redirect logic (en first), added index.en.html redirect
- config.mts: Changed providers link from /providers/README to /providers/
- config.mts: Added markdown.config to transform inline code paths to GitHub links

Code paths like `/ddns/provider/dnspod.py` in documentation will now automatically link to GitHub repository.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* feat(docs): enable dead link check, add sitemap, llms.txt, and GitHub Actions build

Per user feedback (comment #3736806631):
1. Changed redirects to 301 permanent redirects in esa.js (line 183)
2. Enabled dead link checking in config.mts (ignoreDeadLinks: false)
3. Added sitemap generation (sitemap.hostname: https://ddns.newfuture.cc)
4. Added llms.txt generation via buildEnd hook for AI/LLM context
5. Created GitHub Actions workflow (.github/workflows/build-docs.yml) to build docs on push

Changes:
- esa.js: Clarified 301 redirect comment
- config.mts: Enabled dead link check, added sitemap config, added buildEnd hook for llms.txt
- .github/workflows/build-docs.yml: New workflow to build and optionally deploy docs

GitHub Action triggers on:
- Push to master/main (doc/, README*, schema/ paths)
- Pull requests
- Manual workflow dispatch

Build output includes sitemap.xml and llms.txt for better SEO and AI integration.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): update Node.js to v24, retention to 7 days, simplify BUILD_DOCS

Per user feedback (comment #3736833266):
1. Updated Node.js version from v20 to v24 in build-docs.yml (line 33)
2. Changed artifact retention from 30 to 7 days (line 50)
3. Simplified BUILD_DOCS.md - removed verbose explanations, kept essentials

Changes:
- .github/workflows/build-docs.yml: Node 24, 7-day retention
- BUILD_DOCS.md: Condensed from 327 to 40 lines, focused on quick start and essential commands

BUILD_DOCS.md now provides concise reference instead of lengthy tutorial.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor(docs): simplify link transformer, extract llms.txt template, add docs agent

Per user feedback (comment #3737275691):
1. Simplified markdown transformer to only process link (a tag) hrefs instead of code_inline tokens
2. Extracted llms.txt content to separate template file (doc/llms.txt) with:
   - Complete documentation structure with all page URLs
   - All schema file URLs (v4.0, v2.8, v2)
   - Template variable {{DATE}} for build-time replacement
3. Deleted BUILD_DOCS.md and created .github/agents/docs.agent.md:
   - Comprehensive documentation maintenance agent
   - Full guide for updating docs, navigation, providers
   - Build/test procedures and common tasks
   - Guidelines for writing and link handling

Changes:
- config.mts: Transform link hrefs (not inline code), read llms.txt from template
- doc/llms.txt: New template with full doc/schema URLs
- .github/agents/docs.agent.md: New documentation maintenance agent
- BUILD_DOCS.md: Deleted (replaced by agent instructions)

Link transformer now only modifies existing <a> tags, preserving markdown styling.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): add metadata to docs agent, fix markdown syntax, add v4.1.json schema

Per user feedback (comment #3737334654):
1. Added YAML metadata to docs.agent.md (name, description) following new-provider.agent.md format
2. Fixed markdown syntax to be GitHub-compatible:
   - Changed inline code blocks in list items to properly formatted code blocks
   - Ensured all code examples are in fenced code blocks
3. Added missing v4.1.json schema to llms.txt (was only showing v4.0, v2.8, v2)

Changes:
- .github/agents/docs.agent.md: Added YAML frontmatter, fixed markdown formatting
- doc/llms.txt: Added v4.1 schema URL (first in list as newest version)

All schema files now documented: v4.1, v4.0, v2.8, v2

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): restructure to docs/ directory with cleaner organization

Per user feedback (comment #3737399407):
1. Renamed doc/ → docs/
2. Moved all .en.md files → docs/en/*.md (removed .en suffix)
3. Moved install.sh → docs/install.sh (source file, not copied)
4. Moved favicon.ico → docs/favicon.ico (source file)
5. Simplified setup-docs.mjs - no longer copies English files or install.sh
6. Updated all path references:
   - GitHub Actions workflow: doc/ → docs/
   - .gitignore: doc/ → docs/
   - VitePress config: edit links now map to docs/
   - Documentation agent: updated structure docs

New Structure:
- docs/ (was doc/)
  - config/, providers/, dev/ (Chinese markdown)
  - en/ (English markdown, no .en suffix)
    - config/, providers/, dev/
  - install.sh (source)
  - favicon.ico (source)
  - llms.txt (template)

Build command unchanged: cd docs && npm run build

All English documentation now sourced directly from docs/en/ instead of being generated.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): update documentation links from /doc/ to docs/ in README files

Per user feedback (comment #3737497400):
1. Updated setup-docs.mjs to properly replace /doc/ → docs/ → / in README processing
2. Updated all /doc/ links to docs/ in README.md:
   - Image src: /doc/img/ddns.svg → docs/img/ddns.svg
   - Docker docs: /doc/docker.md → docs/docker.md
   - Config docs: /doc/config/* → docs/config/*
   - Provider docs: /doc/providers/* → docs/providers/*
3. Updated all /doc/ links to docs/en/ in README.en.md:
   - Image src: /doc/img/ddns.svg → docs/img/ddns.svg
   - Docker docs: /doc/docker.en.md → docs/en/docker.md
   - Config docs: /doc/config/*.en.md → docs/en/config/*.md
   - Provider docs: /doc/providers/*.en.md → docs/en/providers/*.md

Changes:
- docs/setup-docs.mjs: Two-step replacement (/doc/ → docs/ → /) for GitHub links
- README.md: All documentation links now use docs/ prefix
- README.en.md: All documentation links now use docs/en/ prefix

Verified: No /doc/ links remain in README files or .github markdown files.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): simplify setup script and move to .vitepress directory

Per user feedback (comment #3737562776):
1. Moved setup-docs.mjs to docs/.vitepress/ directory
2. Simplified link replacement logic:
   - Removed unnecessary /doc/ processing code
   - Now only converts docs/ → / (Chinese version)
   - And docs/en/ → / (English version)
   - Since README files already use correct docs/ prefix
3. Updated package.json script paths to reference .vitepress/setup-docs.mjs
4. Updated path variables in script to work from .vitepress/ location

Changes:
- docs/.vitepress/setup-docs.mjs: New location, simplified logic
- docs/package.json: Updated setup script path
- Removed obsolete /doc/ → docs/ replacement steps

Build process now simpler and cleaner.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): fix all broken documentation links

Per user feedback (comment #3737640946):
Fixed all broken links throughout the documentation:

1. Fixed providers/README links → providers/ (clean URL)
2. Removed all .en.md suffixes from links (files now in docs/en/ with .md extension)
3. Fixed /doc/ prefix references → proper docs/ paths
4. Fixed cross-references between Chinese and English docs:
   - Chinese providers/README.md → ../en/providers/*.md for English docs
   - English docs use relative paths within en/ directory
5. Fixed redirect_to URLs in dnscom.md files
6. Fixed image URL: /doc/img/ → /img/

Changes affect 28 files:
- All config documentation (cli.md, env.md, json.md) in both languages
- All provider documentation in both languages
- Developer documentation (dev/config.md)
- Docker documentation
- Provider README files

All dead links now resolved. Documentation navigation works correctly.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): fix remaining dead links in generated index.md and provider files

Per user feedback (comment #3737692822):
Fixed all remaining dead links:

1. Enhanced setup-docs.mjs to handle ./doc/ prefix links:
   - Added replacement for (./doc/ → (/
   - Added replacement for /providers/index → /providers/
   - Now correctly converts README links to clean web URLs

2. Fixed provider documentation links:
   - callback.md and debug.md: Added "Return to All Providers" links
   - English versions: Added /en/providers/ links
   - Removed broken ./../../README links

All dead links now resolved:
- ./doc/install → /install
- ./doc/providers/* → /providers/*
- ./doc/config/* → /config/*
- /providers/index → /providers/
- Provider files now link to /providers/ and /en/providers/

Dead link checking should now pass successfully.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): fix all remaining dead links in documentation

Per user feedback (comment #3737728554):
Fixed all remaining dead links throughout documentation:

1. Enhanced setup-docs.mjs:
   - Added replacement for ](doc/ pattern
   - Handles more link format variations

2. Fixed config files (6 files):
   - config/cli.md: providers/ → ../providers/, providers/callback → ../providers/callback
   - config/json.md: docker.md → ../docker.md, providers/ → ../providers/
   - en/config/env.md: providers/callback → ../providers/callback, docker → ../docker, providers/ → ../providers/
   - en/config/json.md: docker.md → ../docker.md, providers/ → ../providers/

3. Fixed provider files (6 files):
   - providers/callback.md, debug.md, README.md: ../../README.md → /
   - en/providers/callback.md, debug.md: ../../README.md → /en/
   - en/providers/he.md: ../json.md → ../config/json.md

All dead links resolved:
✓ ./providers/index → ../providers/
✓ ./providers/callback → ../providers/callback
✓ ./docker → ../docker
✓ ./../../README → / (or /en/)
✓ ../json → ../config/json
✓ ](doc/ → ](/

Dead link checking should now pass successfully with zero errors.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): add VitePress rewrites to map providers/README.md to /providers/

Per user feedback (comment #3737780259):
Fixed `/providers/index` dead links by adding VitePress rewrites configuration.

Changes to docs/.vitepress/config.mts:
1. Added rewrites mapping:
   - providers/README.md → providers/index.md
   - en/providers/README.md → en/providers/index.md
2. Updated sidebar links:
   - /providers/index → /providers/
   - /en/providers/index → /en/providers/

This tells VitePress that the README.md files in providers directories should be accessible via the /providers/ URL (with cleanUrls enabled).

All /providers/index links now correctly resolve to /providers/

Dead link checking should now pass with zero errors.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): fix providers/index links and simplify setup script

Per user feedback (comment #3737836475):
1. Fixed dead links in config/env.md files:
   - ./providers/ → ../providers/ (both Chinese and English)
2. Simplified setup-docs.mjs:
   - Removed unnecessary doc/ processing (README no longer contains doc/)
   - Merged logic for both README files into single processReadme function
   - Cleaner, more maintainable code with less duplication

Changes:
- docs/config/env.md: Fixed providers/ link
- docs/en/config/env.md: Fixed providers/ link
- docs/.vitepress/setup-docs.mjs: Refactored with shared function

Dead link checking should now pass with zero errors.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): remove providers/index replacement from setup script

Per user feedback (comment #3737887893):
Removed `/providers/index` → `/providers/` replacement from setup-docs.mjs.
This should be fixed in markdown files instead, not in the setup script.

Simplified setup script by removing unnecessary transformation logic.

Changes:
- docs/.vitepress/setup-docs.mjs: Removed line 29 replacement

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): update remaining /doc/ references to docs/ in README files

Per user feedback (comment #3737970179):
Updated all remaining /doc/ references to docs/ or docs/en/ in both README files.

Changes in README.md:
- doc/install.md → docs/install.md
- All doc/providers/*.md → docs/providers/*.md (10 provider links)

Changes in README.en.md:
- doc/install.en.md → docs/en/install.md
- doc/providers/dnspod.en.md → docs/en/providers/dnspod.md
- doc/providers/alidns.en.md → docs/en/providers/alidns.md

All documentation links now correctly use docs/ prefix for GitHub navigation.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* fix(docs): update all remaining doc/ references to docs/ in README files

Per user feedback (comment #3738298603):
Updated ALL remaining doc/ references to docs/ or docs/en/ in both README files.

Changes in README.md (13 links):
- doc/providers/edgeone.md → docs/providers/edgeone.md
- doc/providers/noip.md → docs/providers/noip.md
- doc/config/cli.md → docs/config/cli.md (3 occurrences)
- doc/config/json.md → docs/config/json.md (2 occurrences)
- doc/config/env.md → docs/config/env.md (2 occurrences)
- doc/providers/ → docs/providers/ (3 occurrences)

Changes in README.en.md (13 links):
- doc/providers/*.en.md → docs/en/providers/*.md (9 provider links)
- doc/config/cli.en.md → docs/en/config/cli.md
- doc/config/json.en.md → docs/en/config/json.md
- doc/config/env.en.md → docs/en/config/env.md
- doc/providers/ → docs/en/providers/

All documentation links now use correct docs/ prefix with NO /doc/ references remaining.

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* refactor(docs): integrate setup logic into config.mts

Per user feedback (comment #3738600577):
Integrated setup-docs.mjs functionality into config.mts.

Changes:
1. Removed standalone setup-docs.mjs file
2. Added setupDocs() function in config.mts that runs before config export
3. Simplified package.json scripts - removed setup step
4. Setup now runs automatically when config loads (works for both dev and build)

Benefits:
- Single configuration file - easier to maintain
- No separate setup script needed
- Automatic execution on both dev and build
- Cleaner package.json scripts
- Same functionality, simpler implementation

Build command: `npm run build` or `npm run docs:dev`

Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>

* update link

* /doc/img/ddns.svg

* fix docs paths

* fix docs

* fix docs url

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: NewFuture <6290356+NewFuture@users.noreply.github.com>
Co-authored-by: New Future <NewFuture@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 23:48:37 +08:00

16 KiB
Raw Permalink Blame History

Development Guide: How to Implement a New DNS Provider

This guide explains how to quickly implement a custom DNS provider adapter class based on different abstract base classes to support dynamic DNS record creation and updates.

📦 Directory Structure

ddns/
├── provider/
│   ├── _base.py         # Abstract base classes SimpleProvider and BaseProvider, signature auth functions
│   └── myprovider.py    # Your new provider implementation
tests/
├── base_test.py         # Shared test utilities and base classes
├── test_provider_*.py   # Provider-specific test files
├── test_module_*.py     # Other tests
└── README.md            # Testing guide
doc/dev/
└── provider.md          # Provider development guide (this document)

🚀 Quick Start

DDNS provides two abstract base classes. Choose the appropriate base class based on your DNS provider's API characteristics:

1. SimpleProvider - Simple DNS Provider

Suitable for DNS providers that only offer simple update interfaces without support for querying existing records.

Required Methods:

Method Description Required
set_record(domain, value, record_type="A", ttl=None, line=None, **extra) Update or create DNS record ✅ Required
_validate() Validate authentication info ❌ Optional (has default implementation)

Use Cases:

  • DNS providers that only offer update interfaces (like HE.net)
  • Simple scenarios that don't need to query existing records
  • Debugging and testing purposes
  • Callback/Webhook type DNS updates

Suitable for standard DNS provider APIs that offer complete CRUD operations.

Required Methods:

Method Description Required
_query_zone_id(domain) Query zone ID for main domain ✅ Required
_query_record(zone_id, subdomain, main_domain, record_type, line=None, extra=None) Query current DNS record ✅ Required
_create_record(zone_id, subdomain, main_domain, value, record_type, ttl=None, line=None, extra=None) Create new record ✅ Required
_update_record(zone_id, old_record, value, record_type, ttl=None, line=None, extra=None) Update existing record ✅ Required
_validate() Validate authentication info ❌ Optional (default requires id and token)

Built-in Features:

  • ✅ All SimpleProvider functionality
  • 🎯 Automatic record management (complete query→create/update workflow)
  • 💾 Caching mechanism
  • 📝 Detailed operation logging and error handling

Use Cases:

  • DNS providers with complete REST APIs (like Cloudflare, Alibaba Cloud DNS)
  • Scenarios requiring query of existing record status
  • Support for precise record management and status tracking

🔧 Implementation Examples

SimpleProvider Example

Suitable for simple DNS providers, refer to existing implementations:

provider/mysimpleprovider.py

# coding=utf-8
"""
Custom simple DNS provider example
@author: YourGithubUsername
"""
from ._base import SimpleProvider, TYPE_FORM

class MySimpleProvider(SimpleProvider):
    """
    Example SimpleProvider implementation
    Supports simple DNS record updates, suitable for most simple DNS APIs
    """
    API = 'https://api.simpledns.com'
    content_type = TYPE_FORM          # or TYPE_JSON
    decode_response = False           # Set to False if returns plain text instead of JSON

    def _validate(self):
        """Validate authentication info (optional override)"""
        super(MySimpleProvider, self)._validate()
        # Add specific validation logic, like checking API key format
        if not self.token or len(self.token) < 16:
            raise ValueError("Invalid API token format")

    def set_record(self, domain, value, record_type="A", ttl=None, line=None, **extra):
        """Update DNS record - must implement"""
        # logic to update DNS record

BaseProvider Example

Suitable for standard DNS providers, refer to existing implementations:

provider/myprovider.py

# coding=utf-8
"""
Custom standard DNS provider example
@author: YourGithubUsername
"""
from ._base import BaseProvider, TYPE_JSON, hmac_sha256_authorization, sha256_hash

class MyProvider(BaseProvider):
    """
    Example BaseProvider implementation
    Suitable for DNS providers offering complete CRUD APIs
    """
    API = 'https://api.exampledns.com'
    content_type = TYPE_JSON  # or TYPE_FORM

    def _query_zone_id(self, domain):
        # type: (str) -> str | None
        """Query zone ID for main domain"""
        # Exact lookup or list matching

    def _query_record(self, zone_id, subdomain, main_domain, record_type, line=None, extra=None):
        # type: (str, str, str, str, str | None, dict | None) -> Any
        """Query existing DNS record"""

    def _create_record(self, zone_id, subdomain, main_domain, value, record_type, ttl=None, line=None, extra=None):
        # type: (str, str, str, str, str, int | str | None, str | None, dict | None) -> bool
        """Create new DNS record"""

    def _update_record(self, zone_id, old_record, value, record_type, ttl=None, line=None, extra=None):
        # type: (str, dict, str, str, int | str | None, str | None, dict | None) -> bool
        """Update existing DNS record"""

    def _request(self, action, **params):
        # type: (str, **(str | int | bytes | bool | None)) -> dict
        """[Recommended] Encapsulate common request logic, handle auth and common parameters"""
        # Build request parameters
        request_params = {
            "Action": action,
            "Version": "2023-01-01",
            "AccessKeyId": self.id,
            **{k: v for k, v in params.items() if v is not None}
        }

        res = self._http("POST", "/", params=request_params, headers=headers)
        return res.get("data", {})

✅ Development Best Practices

Choosing the Right Base Class

  1. SimpleProvider - For DNS providers with incomplete functionality

    • ✅ DNS provider only offers update API
    • ✅ No need to query existing records
  2. BaseProvider - Suitable for standard and complex scenarios

    • ✅ DNS provider offers complete query, create, modify APIs
    • ✅ Need precise record state management
    • ✅ Support complex domain resolution logic

General Development Recommendations

🌐 HTTP Request Handling

# Use built-in _http method, automatically handles proxy, encoding, logging
response = self._http("POST", path, params=params, headers=headers)

🔒 Format Validation

def _validate(self):
    """Authentication info validation example"""
    super(MyProvider, self)._validate()
    # Check API key format
    if not self.token or len(self.token) < 16:
        raise ValueError("API token must be at least 16 characters")

📝 Logging

if result:
    self.logger.info("DNS record got: %s", result.get("id"))
    return True
else:
    self.logger.warning("DNS record update returned false")

🧪 Testing and Debugging

Unit Testing

Each Provider should have comprehensive unit tests. The project provides unified test base classes and tools:

# tests/test_provider_myprovider.py
from base_test import BaseProviderTestCase, unittest, patch, MagicMock
from ddns.provider.myprovider import MyProvider

class TestMyProvider(BaseProviderTestCase):
    def setUp(self):
        super(TestMyProvider, self).setUp()
        # Provider-specific setup
    
    def test_init_with_basic_config(self):
        """Test basic initialization"""

Running Tests

# Run all tests
python -m unittest discover tests -v

# Run specific Provider tests
python -m unittest tests.test_provider_myprovider -v

# Run specific test method
python tests/test_provider_myprovider.py

📚 More Resources and Best Practices

🏗️ Project Structure Recommendations

ddns/
├── provider/
│   ├── _base.py              # Base class definitions
│   ├── myprovider.py         # Your Provider implementation
│   └── __init__.py           # Import and registration
tests/
├── base_test.py              # Shared test base class
├── test_provider_myprovider.py  # Your Provider tests
└── README.md                 # Testing guide

📖 Reference Implementations

SimpleProvider References:

BaseProvider References:


🔐 Cloud Provider Authentication Signature Algorithms

For cloud providers requiring signature authentication (like Alibaba Cloud, Huawei Cloud, Tencent Cloud), DDNS provides generic HMAC-SHA256 signature authentication functions.

Signature Authentication Utility Functions

hmac_sha256_authorization() - Generic Signature Generator

Generic cloud provider API authentication signature generation function, supporting Alibaba Cloud, Huawei Cloud, Tencent Cloud and other cloud providers. Uses HMAC-SHA256 algorithm to generate Authorization headers compliant with various cloud provider specifications. All cloud provider differences are passed through template parameters, achieving complete provider independence.

from ddns.provider._base import hmac_sha256_authorization, sha256_hash

# Generic signature function call example
authorization = hmac_sha256_authorization(
    secret_key=secret_key,                    # Signature key (already derived)
    method="POST",                            # HTTP method
    path="/v1/domains/records",               # API path
    query="limit=20&offset=0",                # Query string
    headers=request_headers,                  # Request headers dictionary
    body_hash=sha256_hash(request_body),      # Request body hash
    signing_string_format=signing_template,   # Signing string template
    authorization_format=auth_template        # Authorization header template
)

Function Parameters:

Parameter Type Description
secret_key str | bytes Signature key, already processed through key derivation
method str HTTP request method (GET, POST, etc.)
path str API request path
query str URL query string
headers dict[str, str] HTTP request headers
body_hash str SHA256 hash of request body
signing_string_format str Signing string template with {HashedCanonicalRequest} placeholder
authorization_format str Authorization header template with {SignedHeaders}, {Signature} placeholders

Template Variables:

  • {HashedCanonicalRequest} - SHA256 hash of canonical request
  • {SignedHeaders} - Alphabetically sorted list of signed headers
  • {Signature} - Final HMAC-SHA256 signature value

Cloud Provider Signature Implementation Examples

Alibaba Cloud (ACS3-HMAC-SHA256)

def _request(self, action, **params):
    # Build request headers
    headers = {
        "host": "alidns.aliyuncs.com",
        "x-acs-action": action,
        "x-acs-content-sha256": sha256_hash(body),
        "x-acs-date": timestamp,
        "x-acs-signature-nonce": nonce,
        "x-acs-version": "2015-01-09"
    }
    
    # Alibaba Cloud signature template
    auth_template = (
        "ACS3-HMAC-SHA256 Credential={access_key},"
        "SignedHeaders={{SignedHeaders}},Signature={{Signature}}"
    )
    signing_template = "ACS3-HMAC-SHA256\n{timestamp}\n{{HashedCanonicalRequest}}"
    
    # Generate signature
    authorization = hmac_sha256_authorization(
        secret_key=self.token,
        method="POST",
        path="/",
        query=query_string,
        headers=headers,
        body_hash=sha256_hash(body),
        signing_string_format=signing_template,
        authorization_format=auth_template
    )
    
    headers["authorization"] = authorization
    return self._http("POST", "/", body=body, headers=headers)

Tencent Cloud (TC3-HMAC-SHA256)

def _request(self, action, **params):
    # Tencent Cloud requires derived key
    derived_key = self._derive_signing_key(date, service, self.token)
    
    # Build request headers
    headers = {
        "content-type": "application/json",
        "host": "dnspod.tencentcloudapi.com",
        "x-tc-action": action,
        "x-tc-timestamp": timestamp,
        "x-tc-version": "2021-03-23"
    }
    
    # Tencent Cloud signature template
    auth_template = (
        "TC3-HMAC-SHA256 Credential={secret_id}/{date}/{service}/tc3_request, "
        "SignedHeaders={{SignedHeaders}}, Signature={{Signature}}"
    )
    signing_template = "TC3-HMAC-SHA256\n{timestamp}\n{date}/{service}/tc3_request\n{{HashedCanonicalRequest}}"
    
    # Generate signature
    authorization = hmac_sha256_authorization(
        secret_key=derived_key,  # Note: use derived key
        method="POST",
        path="/",
        query="",
        headers=headers,
        body_hash=sha256_hash(body),
        signing_string_format=signing_template,
        authorization_format=auth_template
    )
    
    headers["authorization"] = authorization
    return self._http("POST", "/", body=body, headers=headers)

Helper Utility Functions

sha256_hash() - SHA256 Hash Calculation

from ddns.provider._base import sha256_hash

# Calculate SHA256 hash of string
hash_value = sha256_hash("request body content")
# Calculate SHA256 hash of binary data
hash_value = sha256_hash(b"binary data")

hmac_sha256() - HMAC-SHA256 Signature Object

from ddns.provider._base import hmac_sha256

# Generate HMAC-SHA256 byte signature
# Get HMAC object, can call .digest() for bytes or .hexdigest() for hex string
hmac_obj = hmac_sha256("secret_key", "message_to_sign")
signature_bytes = hmac_obj.digest()        # Byte format
signature_hex = hmac_obj.hexdigest()       # Hex string format

  • Local development environment: VSCode
  • Online code editor: GitHub Codespaces or github.dev

🎯 Common Issue Solutions

  1. Q: Why choose SimpleProvider over BaseProvider?
    • A: If the DNS provider only offers update API without query API, SimpleProvider is simpler and more efficient

🎉 Summary

Quick Checklist

  • Choose appropriate base class (SimpleProvider vs BaseProvider)
  • Implement all required methods (with GPT or Copilot assistance)
  • Add appropriate error handling and logging
  • Write comprehensive unit tests (using GPT or Copilot generation)
  • Test various edge cases and error scenarios
  • Update relevant documentation

Happy Coding! 🚀