mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 14:26:36 +08:00
454542c1d0
- README 默认英文:README.en.md → README.md(英文为默认),中文移至 README.zh-CN.md,语言切换链接同步 - 恢复被删除的 docs/en/ 英文文档(git 历史 cc5e53c5^),删除 4 篇已废弃文件 - 英文导航 config.ts 对齐中文结构(新增 Deployment/Changelog 侧栏,同步 Guide/Design 条目) - 翻译 15 篇中文新增文档:guide 5 篇(certificates/pages-usage/proxy-config/uptime-kuma/zone-domain-migration)+ design 10 篇(zone-design/cloudflare-pointing/waf-orchestration/origin-error-page/edge-cache-design/pages-design/logstore/kuma-design/login-captcha/observability 三篇) - en 首页更新(新增 Pages 特性、tagline 同步);changelog 英文入口指向中文版 - vitepress 构建验证:43 个英文页面全部渲染 注意:29 篇旧英文文档为恢复版,部分内容(如 deployment/server、reference/configuration)可能落后于中文,需后续逐篇同步
224 lines
9.9 KiB
Markdown
224 lines
9.9 KiB
Markdown
# Cloudflare DNS Pointing Design
|
|
|
|
## Goals
|
|
|
|
Point **ZoneDomains (explicit FQDNs)** in OpenFlare to edge node IPs quickly via the Cloudflare API, replacing manual A-record edits in the CF console. Users organize domains into **pointing groups**: each group configures a primary node, a backup node, and a default orange-cloud (proxied) policy; members can override orange-cloud individually. The system treats the database tables as the desired state and idempotently syncs remote DNS.
|
|
|
|
This module is an **optional integration capability**; it does not turn Zones into an authoritative DNS control plane. Zones still only handle root-domain boundaries, domains, certificates, and reverse-proxy associations; A record create/update/delete is driven by this module through Cloudflare.
|
|
|
|
## Scope and Phasing
|
|
|
|
### Phase 1 (this design's scope)
|
|
|
|
* Sidebar **Cloudflare** entry with a Token-ready gate
|
|
* Connection config: import from an existing DNS account **or** standalone entry within the module (mixed sources), stored encrypted
|
|
* Pointing group CRUD: primary node, backup node (reserved), group default orange-cloud
|
|
* Member management: add/remove at the granularity of `zone_domain_id`; member-level orange-cloud
|
|
* Sync: write each member as a **single A record** on Cloudflare → current active node IPv4
|
|
* Triggers: manual sync, member add, node/orange-cloud change, node IP change enqueue
|
|
* Async task batch sync; member sync status with readable errors
|
|
|
|
### Phase 2
|
|
|
|
* Agent heartbeat offline detection of primary node failure → `active_node` switches to backup → whole-group auto sync
|
|
* Optional auto failback, failure notification push
|
|
|
|
### Explicitly Out of Scope (later or permanent)
|
|
|
|
* Multiple parallel Cloudflare accounts (one global connection config)
|
|
* AAAA / multi-A load balancing / CNAME to node hostnames
|
|
* Managing MX/TXT/Page Rules and other non-module A records
|
|
* Non-Cloudflare DNS providers
|
|
* Merging DNS record management into the Zone core model
|
|
|
|
## Relationship with Existing Capabilities
|
|
|
|
| Existing Capability | Relationship |
|
|
| --- | --- |
|
|
| `of_zones` / `of_zone_domains` | Provide the pointable FQDN list; this module only references `zone_domain_id` |
|
|
| `of_nodes.ip` | Source of A record `content`; recommended to restrict to edge nodes with valid IPv4 |
|
|
| `of_dns_accounts` + `sealSensitive` | ACME DNS-01 already supports Cloudflare Token; this module can **import** the same account or store a Token standalone |
|
|
| lego Cloudflare provider | **Only** TXT/DNS-01; this module builds its own CF HTTP client for Zone/DNS Record APIs |
|
|
|
|
## Core Model
|
|
|
|
```mermaid
|
|
erDiagram
|
|
CF_CONNECTIONS ||--o| DNS_ACCOUNTS : optional_import
|
|
CF_POINTING_GROUPS ||--o{ CF_POINTING_MEMBERS : contains
|
|
ZONE_DOMAINS ||--o| CF_POINTING_MEMBERS : pointed_as
|
|
NODES ||--o{ CF_POINTING_GROUPS : primary
|
|
NODES ||--o{ CF_POINTING_GROUPS : backup
|
|
NODES ||--o{ CF_POINTING_GROUPS : active
|
|
|
|
CF_CONNECTIONS {
|
|
uint id PK
|
|
string source
|
|
uint dns_account_id
|
|
string authorization
|
|
string status
|
|
time verified_at
|
|
}
|
|
CF_POINTING_GROUPS {
|
|
uint id PK
|
|
string name
|
|
uint primary_node_id
|
|
uint backup_node_id
|
|
uint active_node_id
|
|
bool default_proxied
|
|
bool enabled
|
|
}
|
|
CF_POINTING_MEMBERS {
|
|
uint id PK
|
|
uint group_id
|
|
uint zone_domain_id UK
|
|
bool proxied
|
|
string cf_zone_id
|
|
string cf_record_id
|
|
string desired_ip
|
|
string sync_status
|
|
string last_error
|
|
time synced_at
|
|
}
|
|
```
|
|
|
|
### `of_cf_connections` (one valid connection globally)
|
|
|
|
| Field | Description |
|
|
| --- | --- |
|
|
| `source` | `dns_account` \| `standalone` |
|
|
| `dns_account_id` | references `of_dns_accounts` (type=cloudflare) when `source=dns_account` |
|
|
| `authorization` | encrypted storage when `source=standalone`, payload shape `{"api_token":"..."}`, consistent with DNS accounts; the API **never returns it** |
|
|
| `status` / `verified_at` | connectivity check result and time |
|
|
|
|
**Token resolution:** `dns_account` → decrypt the associated account; `standalone` → decrypt this row. Associated account deleted or validation failed → module not ready, sync forbidden.
|
|
|
|
**Recommended permissions:** Cloudflare API Token with `Zone:Read`, `DNS:Edit`.
|
|
|
|
### `of_cf_pointing_groups`
|
|
|
|
| Field | Description |
|
|
| --- | --- |
|
|
| `name` | display name |
|
|
| `primary_node_id` | primary node |
|
|
| `backup_node_id` | backup (nullable; phase 1 stores only) |
|
|
| `active_node_id` | currently effective node; equals primary in phase 1; rewritten by phase 2 failover |
|
|
| `default_proxied` | group default orange-cloud; **only affects newly added members** |
|
|
| `enabled` | whether to participate in sync |
|
|
|
|
Constraints: primary and backup must not be the same node; the node chosen as the active target must have a valid IPv4.
|
|
|
|
### `of_cf_pointing_members`
|
|
|
|
| Field | Description |
|
|
| --- | --- |
|
|
| `group_id` | owning group |
|
|
| `zone_domain_id` | globally unique: one domain belongs to at most one group |
|
|
| `proxied` | member orange-cloud (the only runtime basis) |
|
|
| `cf_zone_id` / `cf_record_id` | Cloudflare cache for idempotent updates |
|
|
| `desired_ip` / `sync_status` / `last_error` / `synced_at` | desired and sync state |
|
|
|
|
`sync_status`: `pending` \| `syncing` \| `ok` \| `error`.
|
|
|
|
No physical foreign keys; `zone_domain_id` unique index; query indexes on `group_id` etc.
|
|
|
|
## Orange-Cloud Priority
|
|
|
|
1. **Member `proxied`**: the only basis written to CF during sync.
|
|
2. **Group `default_proxied`**: copied to `proxied` when a member is **added**.
|
|
3. Later changes to the group default **do not rewrite** existing members.
|
|
|
|
## Sync Semantics
|
|
|
|
### Desired State
|
|
|
|
The OpenFlare DB tables are the Source of Truth. Each member expects:
|
|
|
|
| Item | Value |
|
|
| --- | --- |
|
|
| type | `A` |
|
|
| name | the ZoneDomain's FQDN |
|
|
| content | the group `active_node`'s IPv4 |
|
|
| proxied | member `proxied` |
|
|
| ttl | forced Auto by CF when orange-cloud is on; unified default (e.g. 300) when off |
|
|
|
|
Phase 1 does not write AAAA. Node IP not a valid IPv4 → that member is `error`.
|
|
|
|
### Triggers
|
|
|
|
| Trigger | Behavior |
|
|
| --- | --- |
|
|
| Manual sync (all / group / member) | reconcile |
|
|
| Member added | initialize `proxied`, then enqueue sync |
|
|
| Member removed / group deleted | delete the remote A managed by this module by default (configurable keep) |
|
|
| Primary node / active / member `proxied` changed | re-sync the corresponding scope |
|
|
| Node IP changed (heartbeat or manual) | enqueue members whose `active_node_id` points to that node |
|
|
| Token not ready | refuse sync |
|
|
|
|
Phase 1 does not do scheduled full reconciliation.
|
|
|
|
### Reconcile (single member, idempotent)
|
|
|
|
1. Resolve the CF Zone by the FQDN's registrable root domain, cache `cf_zone_id`.
|
|
2. With a `cf_record_id`, prefer Update; if stale, list by `name+type=A`.
|
|
3. **0 records** → Create; **exactly 1** → take over and Update; **multiple** → fail and tell the user to clean up in CF.
|
|
4. Write back `cf_record_id`, `desired_ip`, `sync_status`, `synced_at` / `last_error`.
|
|
5. On rate limiting, retry with bounded backoff.
|
|
|
|
**Ownership:** only manage records cached by this module or taken over as "the only same-name A"; do not clear the Zone or touch other record types. After a user edits in the CF console, the next sync overwrites with the OpenFlare desired state.
|
|
|
|
### Execution Carrier
|
|
|
|
* Single record: can sync on the request path.
|
|
* Whole group / per-node batch: Asynq tasks (`cloudflare:sync_member` / `sync_group` / `sync_by_node`), registered in `bootstrap`.
|
|
* Per-member mutex to prevent concurrent double-writes.
|
|
* Node IP change path delivers tasks **best-effort**, not blocking the heartbeat.
|
|
|
|
## API (Admin Panel)
|
|
|
|
Prefix: `/api/v1/d/cloudflare`, Session admin auth. Package: `internal/apps/openflare/cloudflare/`; routes: `internal/router/v1/openflare/register_cloudflare.go`.
|
|
|
|
| Resource | Method & Path |
|
|
| --- | --- |
|
|
| Connection | `GET/PUT /connection`, `POST /connection/verify`, `POST /connection/clear` |
|
|
| Overview | `GET /overview` |
|
|
| Groups | `GET/POST /groups`, `GET /groups/:id`, `POST /groups/:id/update|delete|sync` |
|
|
| Members | `GET/POST /groups/:id/members`, `POST .../members/:memberId/update|remove|sync` |
|
|
| Available domains | `GET /domains/available` |
|
|
|
|
* Success `response.OK`; failure `response.Abort*`; the Token is **never** returned in JSON.
|
|
* Handlers separated from `logics.go`; the CF client is abstracted behind an interface for replaceability.
|
|
|
|
## Frontend
|
|
|
|
* Navigation: `frontend/lib/navigation/openflare-nav.ts` adds **Cloudflare** → `/cloudflare` (near Website Management / DNS Accounts).
|
|
* Routes:
|
|
* `/cloudflare`: overview; guide to configure when not ready
|
|
* `/cloudflare/settings`: mixed Token config and test connection
|
|
* `/cloudflare/groups`, `/cloudflare/groups/[id]`: list and detail (members, orange-cloud, sync)
|
|
* Services: independent service under `frontend/lib/services/openflare/`, extending `BaseService`.
|
|
* Pages follow the existing title-bar and component-split conventions; destructive actions need double confirmation.
|
|
* Copy that must be visible: sync overwrites module-managed A records; multiple same-name A records need manual cleanup; removal deletes remote records by default; phase 1 has no automatic failover.
|
|
|
|
## Errors and Security
|
|
|
|
* User-visible copy is module-internal constants; internal errors log via `pkg/logger`.
|
|
* Typical: token not configured, invalid token, node without IP, no CF Zone, multiple same-name A records, rate limiting.
|
|
* Token is only decrypted server-side for use; responses and logs must never contain plaintext tokens.
|
|
|
|
## Data Migration
|
|
|
|
* goose both dialects (PG/SQLite) create the three tables; defaults match Go zero values.
|
|
|
|
## Key Decision Summary
|
|
|
|
| Decision | Conclusion |
|
|
| --- | --- |
|
|
| Module shape | standalone Cloudflare pointing module, not embedded Zone fields |
|
|
| Token | mixed: imported from DNS account or encrypted standalone |
|
|
| Domain granularity | ZoneDomain (FQDN) |
|
|
| Record shape | single A → active node IPv4 |
|
|
| Failover | phase 2; heartbeat offline; phase 1 only stores backup/active |
|
|
| Orange-cloud | member-level effective; group default only initializes |
|
|
| SoT | DB tables as desired state drive CF |
|