devconfig-gen-singbox 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/PKG-INFO +40 -6
  2. devconfig_gen_singbox-2.0.0/src/devconfig_gen_singbox.egg-info/PKG-INFO → devconfig_gen_singbox-2.1.0/README.md +36 -27
  3. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/pyproject.toml +5 -2
  4. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/__init__.py +1 -1
  5. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/provider.py +5 -0
  6. devconfig_gen_singbox-2.0.0/README.md → devconfig_gen_singbox-2.1.0/src/devconfig_gen_singbox.egg-info/PKG-INFO +61 -5
  7. devconfig_gen_singbox-2.1.0/src/devconfig_gen_singbox.egg-info/SOURCES.txt +86 -0
  8. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/entry_points.txt +1 -0
  9. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/requires.txt +4 -0
  10. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/top_level.txt +1 -0
  11. devconfig_gen_singbox-2.1.0/src/singbox_ops/__init__.py +29 -0
  12. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/__init__.py +27 -0
  13. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/acme/__init__.py +7 -0
  14. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/acme/base.py +16 -0
  15. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/acme/cloudflare_dns01.py +130 -0
  16. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/base.py +62 -0
  17. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/dns/__init__.py +7 -0
  18. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/dns/base.py +15 -0
  19. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/dns/cloudflare.py +138 -0
  20. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/export/__init__.py +7 -0
  21. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/export/local_files.py +62 -0
  22. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/__init__.py +17 -0
  23. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/nftables.py +99 -0
  24. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/packages.py +90 -0
  25. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/systemd.py +55 -0
  26. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/warp_watchdog.py +73 -0
  27. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/__init__.py +16 -0
  28. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/base.py +120 -0
  29. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/python_secrets.py +20 -0
  30. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/singbox_subprocess.py +96 -0
  31. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/static.py +47 -0
  32. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/state/__init__.py +7 -0
  33. devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/state/local_json.py +44 -0
  34. devconfig_gen_singbox-2.1.0/src/singbox_ops/cli.py +109 -0
  35. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/__init__.py +15 -0
  36. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/command.py +197 -0
  37. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/context_builder.py +155 -0
  38. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/exceptions.py +30 -0
  39. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/network.py +52 -0
  40. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/orchestrator.py +291 -0
  41. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/plan.py +253 -0
  42. devconfig_gen_singbox-2.1.0/src/singbox_ops/core/protocols.py +64 -0
  43. devconfig_gen_singbox-2.1.0/src/singbox_ops/templates/singbox_auto_update.py.template +112 -0
  44. devconfig_gen_singbox-2.1.0/src/singbox_ops/templates/warp_watchdog.sh.template +239 -0
  45. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_acme.py +66 -0
  46. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_cli.py +31 -0
  47. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_context_builder.py +60 -0
  48. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_dns.py +68 -0
  49. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_orchestrator.py +117 -0
  50. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_plan.py +70 -0
  51. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_runtime.py +120 -0
  52. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_secrets.py +86 -0
  53. devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_state_export.py +59 -0
  54. devconfig_gen_singbox-2.0.0/src/devconfig_gen_singbox.egg-info/SOURCES.txt +0 -43
  55. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/LICENSE +0 -0
  56. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/setup.cfg +0 -0
  57. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/cli.py +0 -0
  58. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/engine.py +0 -0
  59. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/formats.py +0 -0
  60. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/models.py +0 -0
  61. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/__init__.py +0 -0
  62. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/custom.py +0 -0
  63. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/env_provider.py +0 -0
  64. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/json_provider.py +0 -0
  65. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/__init__.py +0 -0
  66. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/data/rules.json +0 -0
  67. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/links.py +0 -0
  68. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/models.py +0 -0
  69. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/__init__.py +0 -0
  70. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/anytls.py +0 -0
  71. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/base.py +0 -0
  72. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/hysteria2.py +0 -0
  73. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/tuic.py +0 -0
  74. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/route.py +0 -0
  75. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/schema.py +0 -0
  76. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/py.typed +0 -0
  77. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/registry.py +0 -0
  78. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/validation.py +0 -0
  79. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/dependency_links.txt +0 -0
  80. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_api_parity.py +0 -0
  81. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_cli_e2e.py +0 -0
  82. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_custom_provider.py +0 -0
  83. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_devconfig_core.py +0 -0
  84. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_env_provider.py +0 -0
  85. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_formats.py +0 -0
  86. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_merge.py +0 -0
  87. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_provider_metadata.py +0 -0
  88. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_singbox_provider.py +0 -0
  89. {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devconfig_gen_singbox
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Provider-based developer configuration generation engine and CLI
5
5
  Author-email: henryliu443 <henryliu443@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -12,20 +12,23 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Provides-Extra: yaml
14
14
  Requires-Dist: PyYAML>=5.1; extra == "yaml"
15
+ Provides-Extra: ops
16
+ Requires-Dist: cryptography>=3.4; extra == "ops"
15
17
  Provides-Extra: dev
16
18
  Requires-Dist: build; extra == "dev"
17
19
  Requires-Dist: wheel; extra == "dev"
18
20
  Requires-Dist: PyYAML>=5.1; extra == "dev"
21
+ Requires-Dist: cryptography>=3.4; extra == "dev"
19
22
  Provides-Extra: docs
20
23
  Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
21
24
  Dynamic: license-file
22
25
 
23
- # DevConfig-Gen_SingBox
26
+ # devconfig_gen_singbox
24
27
 
25
28
  > **A sing-box domain implementation built on top of DevConfig-Gen.**
26
29
  > **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
27
30
 
28
- [![Version](https://img.shields.io/badge/version-2.0.0-blue.svg)](CHANGELOG.md)
31
+ [![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](CHANGELOG.md)
29
32
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
30
33
  ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)
31
34
  ![Tests](https://img.shields.io/badge/tests-136%20passing-brightgreen.svg)
@@ -43,7 +46,7 @@ DevConfig-Gen
43
46
  中立引擎 · Provider 契约 · 执行流水线
44
47
  │
45
48
  ▼
46
- DevConfig-Gen_SingBox
49
+ devconfig_gen_singbox
47
50
  sing-box 领域 · 校验 · 转换 · server/client 变体
48
51
  │
49
52
  ▼
@@ -148,7 +151,7 @@ DevConfig-Gen (父仓库 · 主)
148
151
  │ Provider 契约
149
152
  │ 执行流水线
150
153
  │
151
- └── DevConfig-Gen_SingBox (子仓库 · 兵)
154
+ └── devconfig_gen_singbox (子仓库 · 兵)
152
155
  │ sing-box 领域知识
153
156
  │ 领域校验
154
157
  │ 协议转换
@@ -239,6 +242,37 @@ devconfig-gen schema --provider singbox
239
242
  - **零副作用**:Provider 不读环境变量、不写 state、不调子进程;凭据与子域前缀
240
243
  全部显式输入。
241
244
 
245
+ ## 部署层 / Operations Layer(`singbox-ops`)
246
+
247
+ 生成与部署被刻意分开:`devconfig_gen` 只做纯函数配置生成;同仓库的
248
+ `singbox_ops` 包负责所有副作用(DNS、证书、安装、systemd、防火墙、watchdog),
249
+ 并通过**库调用**使用引擎,绝不注册为 provider。
250
+
251
+ ```bash
252
+ pip install -e ".[yaml,ops]"
253
+
254
+ # 只组装 context(无副作用,方便审阅)
255
+ singbox-ops context --plan examples/singbox-deploy.yaml > context.yaml
256
+
257
+ # 干跑:打印将执行的每一步,不碰系统
258
+ singbox-ops deploy --plan examples/singbox-deploy.yaml --dry-run
259
+
260
+ # 真实部署 / 反向清理
261
+ singbox-ops deploy --plan examples/singbox-deploy.yaml
262
+ singbox-ops destroy --plan examples/singbox-deploy.yaml
263
+ ```
264
+
265
+ - **适配器**:`secrets`(sing-box 子进程 + 纯 Python 兜底)、`dns`(Cloudflare)、
266
+ `acme`(acme.sh + Cloudflare DNS-01)、`state`(可选 local JSON)、
267
+ `export`(本地文件)、`runtime`(packages / systemd / nftables-basic / warp)。
268
+ - **可注入 runner**:所有副作用走 `CommandRunner`;`--dry-run` 使用记录器,
269
+ 测试全部 mock,不需要真实 VPS。
270
+ - **零污染**:`singbox_ops` 与 `devconfig_gen` 命名空间隔离,
271
+ 引擎的零副作用契约保持不变。
272
+
273
+ 详见 [`ARCHITECTURE.md`](ARCHITECTURE.md#operations-layer-singbox_ops) 与
274
+ [`examples/singbox-deploy.yaml`](examples/singbox-deploy.yaml)。
275
+
242
276
  ## 继承自 DevConfig-Gen 的引擎 / Inherited DevConfig-Gen Engine
243
277
 
244
278
  > 本仓库从 DevConfig-Gen 继承以下基础设施:
@@ -329,7 +363,7 @@ python3 scripts/smoke_rounds.py 8
329
363
 
330
364
  ## 仓库关系 / Repository Relationship
331
365
 
332
- - 本仓库是 **子仓库(child / fork)**:`DevConfig-Gen_SingBox`。
366
+ - 本仓库是 **子仓库(child / fork)**:`devconfig_gen_singbox`。
333
367
  - **父仓库(parent / upstream)** 为
334
368
  [`DevConfig-Gen`](https://github.com/henryliu443/DevConfig-Gen),拥有中立核心与
335
369
  [`PROVIDER_STANDARD.md`](PROVIDER_STANDARD.md)。
@@ -1,31 +1,9 @@
1
- Metadata-Version: 2.4
2
- Name: devconfig_gen_singbox
3
- Version: 2.0.0
4
- Summary: Provider-based developer configuration generation engine and CLI
5
- Author-email: henryliu443 <henryliu443@gmail.com>
6
- License-Expression: Apache-2.0
7
- Keywords: configuration,json,yaml,code-generation,cli
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.8
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Provides-Extra: yaml
14
- Requires-Dist: PyYAML>=5.1; extra == "yaml"
15
- Provides-Extra: dev
16
- Requires-Dist: build; extra == "dev"
17
- Requires-Dist: wheel; extra == "dev"
18
- Requires-Dist: PyYAML>=5.1; extra == "dev"
19
- Provides-Extra: docs
20
- Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
21
- Dynamic: license-file
22
-
23
- # DevConfig-Gen_SingBox
1
+ # devconfig_gen_singbox
24
2
 
25
3
  > **A sing-box domain implementation built on top of DevConfig-Gen.**
26
4
  > **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
27
5
 
28
- [![Version](https://img.shields.io/badge/version-2.0.0-blue.svg)](CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](CHANGELOG.md)
29
7
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
30
8
  ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)
31
9
  ![Tests](https://img.shields.io/badge/tests-136%20passing-brightgreen.svg)
@@ -43,7 +21,7 @@ DevConfig-Gen
43
21
  中立引擎 · Provider 契约 · 执行流水线
44
22
  │
45
23
  ▼
46
- DevConfig-Gen_SingBox
24
+ devconfig_gen_singbox
47
25
  sing-box 领域 · 校验 · 转换 · server/client 变体
48
26
  │
49
27
  ▼
@@ -148,7 +126,7 @@ DevConfig-Gen (父仓库 · 主)
148
126
  │ Provider 契约
149
127
  │ 执行流水线
150
128
  │
151
- └── DevConfig-Gen_SingBox (子仓库 · 兵)
129
+ └── devconfig_gen_singbox (子仓库 · 兵)
152
130
  │ sing-box 领域知识
153
131
  │ 领域校验
154
132
  │ 协议转换
@@ -239,6 +217,37 @@ devconfig-gen schema --provider singbox
239
217
  - **零副作用**:Provider 不读环境变量、不写 state、不调子进程;凭据与子域前缀
240
218
  全部显式输入。
241
219
 
220
+ ## 部署层 / Operations Layer(`singbox-ops`)
221
+
222
+ 生成与部署被刻意分开:`devconfig_gen` 只做纯函数配置生成;同仓库的
223
+ `singbox_ops` 包负责所有副作用(DNS、证书、安装、systemd、防火墙、watchdog),
224
+ 并通过**库调用**使用引擎,绝不注册为 provider。
225
+
226
+ ```bash
227
+ pip install -e ".[yaml,ops]"
228
+
229
+ # 只组装 context(无副作用,方便审阅)
230
+ singbox-ops context --plan examples/singbox-deploy.yaml > context.yaml
231
+
232
+ # 干跑:打印将执行的每一步,不碰系统
233
+ singbox-ops deploy --plan examples/singbox-deploy.yaml --dry-run
234
+
235
+ # 真实部署 / 反向清理
236
+ singbox-ops deploy --plan examples/singbox-deploy.yaml
237
+ singbox-ops destroy --plan examples/singbox-deploy.yaml
238
+ ```
239
+
240
+ - **适配器**:`secrets`(sing-box 子进程 + 纯 Python 兜底)、`dns`(Cloudflare)、
241
+ `acme`(acme.sh + Cloudflare DNS-01)、`state`(可选 local JSON)、
242
+ `export`(本地文件)、`runtime`(packages / systemd / nftables-basic / warp)。
243
+ - **可注入 runner**:所有副作用走 `CommandRunner`;`--dry-run` 使用记录器,
244
+ 测试全部 mock,不需要真实 VPS。
245
+ - **零污染**:`singbox_ops` 与 `devconfig_gen` 命名空间隔离,
246
+ 引擎的零副作用契约保持不变。
247
+
248
+ 详见 [`ARCHITECTURE.md`](ARCHITECTURE.md#operations-layer-singbox_ops) 与
249
+ [`examples/singbox-deploy.yaml`](examples/singbox-deploy.yaml)。
250
+
242
251
  ## 继承自 DevConfig-Gen 的引擎 / Inherited DevConfig-Gen Engine
243
252
 
244
253
  > 本仓库从 DevConfig-Gen 继承以下基础设施:
@@ -329,7 +338,7 @@ python3 scripts/smoke_rounds.py 8
329
338
 
330
339
  ## 仓库关系 / Repository Relationship
331
340
 
332
- - 本仓库是 **子仓库(child / fork)**:`DevConfig-Gen_SingBox`。
341
+ - 本仓库是 **子仓库(child / fork)**:`devconfig_gen_singbox`。
333
342
  - **父仓库(parent / upstream)** 为
334
343
  [`DevConfig-Gen`](https://github.com/henryliu443/DevConfig-Gen),拥有中立核心与
335
344
  [`PROVIDER_STANDARD.md`](PROVIDER_STANDARD.md)。
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devconfig_gen_singbox"
7
- version = "2.0.0"
7
+ version = "2.1.0"
8
8
  authors = [
9
9
  { name = "henryliu443", email = "henryliu443@gmail.com" },
10
10
  ]
@@ -21,15 +21,18 @@ dependencies = []
21
21
 
22
22
  [project.optional-dependencies]
23
23
  yaml = ["PyYAML>=5.1"]
24
- dev = ["build", "wheel", "PyYAML>=5.1"]
24
+ ops = ["cryptography>=3.4"]
25
+ dev = ["build", "wheel", "PyYAML>=5.1", "cryptography>=3.4"]
25
26
  docs = ["mkdocs-material>=9.5,<10"]
26
27
 
27
28
  [project.scripts]
28
29
  devconfig-gen = "devconfig_gen.cli:main"
29
30
  devconfig_gen_singbox = "devconfig_gen.cli:main"
31
+ singbox-ops = "singbox_ops.cli:main"
30
32
 
31
33
  [tool.setuptools.packages.find]
32
34
  where = ["src"]
33
35
 
34
36
  [tool.setuptools.package-data]
35
37
  devconfig_gen = ["py.typed", "providers/singbox/data/*.json"]
38
+ singbox_ops = ["templates/*.template"]
@@ -64,4 +64,4 @@ __all__ = [
64
64
  "loads",
65
65
  "validate_request",
66
66
  ]
67
- __version__ = "2.0.0"
67
+ __version__ = "2.1.0"
@@ -37,6 +37,11 @@ TUN_EXCLUDED_ROUTES = [
37
37
  # Android VpnService already excludes loopback/link-local; excluding them
38
38
  # explicitly makes `configure tun interface: Bad address` (sing-box #2030).
39
39
  "10.0.0.0/8",
40
+ # Parallels Desktop Shared Network (host 10.211.55.1, DHCP/DNS/NAT for
41
+ # guest VMs). Listed explicitly so macOS strict_route does not capture
42
+ # VM NAT/DNS traffic into the sing-box TUN.
43
+ "10.211.55.0/24",
44
+ "10.211.55.1/32",
40
45
  "100.64.0.0/10",
41
46
  "172.16.0.0/12",
42
47
  "192.168.0.0/16",
@@ -1,9 +1,34 @@
1
- # DevConfig-Gen_SingBox
1
+ Metadata-Version: 2.4
2
+ Name: devconfig_gen_singbox
3
+ Version: 2.1.0
4
+ Summary: Provider-based developer configuration generation engine and CLI
5
+ Author-email: henryliu443 <henryliu443@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Keywords: configuration,json,yaml,code-generation,cli
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Provides-Extra: yaml
14
+ Requires-Dist: PyYAML>=5.1; extra == "yaml"
15
+ Provides-Extra: ops
16
+ Requires-Dist: cryptography>=3.4; extra == "ops"
17
+ Provides-Extra: dev
18
+ Requires-Dist: build; extra == "dev"
19
+ Requires-Dist: wheel; extra == "dev"
20
+ Requires-Dist: PyYAML>=5.1; extra == "dev"
21
+ Requires-Dist: cryptography>=3.4; extra == "dev"
22
+ Provides-Extra: docs
23
+ Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
24
+ Dynamic: license-file
25
+
26
+ # devconfig_gen_singbox
2
27
 
3
28
  > **A sing-box domain implementation built on top of DevConfig-Gen.**
4
29
  > **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
5
30
 
6
- [![Version](https://img.shields.io/badge/version-2.0.0-blue.svg)](CHANGELOG.md)
31
+ [![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](CHANGELOG.md)
7
32
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
33
  ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)
9
34
  ![Tests](https://img.shields.io/badge/tests-136%20passing-brightgreen.svg)
@@ -21,7 +46,7 @@ DevConfig-Gen
21
46
  中立引擎 · Provider 契约 · 执行流水线
22
47
  │
23
48
  ▼
24
- DevConfig-Gen_SingBox
49
+ devconfig_gen_singbox
25
50
  sing-box 领域 · 校验 · 转换 · server/client 变体
26
51
  │
27
52
  ▼
@@ -126,7 +151,7 @@ DevConfig-Gen (父仓库 · 主)
126
151
  │ Provider 契约
127
152
  │ 执行流水线
128
153
  │
129
- └── DevConfig-Gen_SingBox (子仓库 · 兵)
154
+ └── devconfig_gen_singbox (子仓库 · 兵)
130
155
  │ sing-box 领域知识
131
156
  │ 领域校验
132
157
  │ 协议转换
@@ -217,6 +242,37 @@ devconfig-gen schema --provider singbox
217
242
  - **零副作用**:Provider 不读环境变量、不写 state、不调子进程;凭据与子域前缀
218
243
  全部显式输入。
219
244
 
245
+ ## 部署层 / Operations Layer(`singbox-ops`)
246
+
247
+ 生成与部署被刻意分开:`devconfig_gen` 只做纯函数配置生成;同仓库的
248
+ `singbox_ops` 包负责所有副作用(DNS、证书、安装、systemd、防火墙、watchdog),
249
+ 并通过**库调用**使用引擎,绝不注册为 provider。
250
+
251
+ ```bash
252
+ pip install -e ".[yaml,ops]"
253
+
254
+ # 只组装 context(无副作用,方便审阅)
255
+ singbox-ops context --plan examples/singbox-deploy.yaml > context.yaml
256
+
257
+ # 干跑:打印将执行的每一步,不碰系统
258
+ singbox-ops deploy --plan examples/singbox-deploy.yaml --dry-run
259
+
260
+ # 真实部署 / 反向清理
261
+ singbox-ops deploy --plan examples/singbox-deploy.yaml
262
+ singbox-ops destroy --plan examples/singbox-deploy.yaml
263
+ ```
264
+
265
+ - **适配器**:`secrets`(sing-box 子进程 + 纯 Python 兜底)、`dns`(Cloudflare)、
266
+ `acme`(acme.sh + Cloudflare DNS-01)、`state`(可选 local JSON)、
267
+ `export`(本地文件)、`runtime`(packages / systemd / nftables-basic / warp)。
268
+ - **可注入 runner**:所有副作用走 `CommandRunner`;`--dry-run` 使用记录器,
269
+ 测试全部 mock,不需要真实 VPS。
270
+ - **零污染**:`singbox_ops` 与 `devconfig_gen` 命名空间隔离,
271
+ 引擎的零副作用契约保持不变。
272
+
273
+ 详见 [`ARCHITECTURE.md`](ARCHITECTURE.md#operations-layer-singbox_ops) 与
274
+ [`examples/singbox-deploy.yaml`](examples/singbox-deploy.yaml)。
275
+
220
276
  ## 继承自 DevConfig-Gen 的引擎 / Inherited DevConfig-Gen Engine
221
277
 
222
278
  > 本仓库从 DevConfig-Gen 继承以下基础设施:
@@ -307,7 +363,7 @@ python3 scripts/smoke_rounds.py 8
307
363
 
308
364
  ## 仓库关系 / Repository Relationship
309
365
 
310
- - 本仓库是 **子仓库(child / fork)**:`DevConfig-Gen_SingBox`。
366
+ - 本仓库是 **子仓库(child / fork)**:`devconfig_gen_singbox`。
311
367
  - **父仓库(parent / upstream)** 为
312
368
  [`DevConfig-Gen`](https://github.com/henryliu443/DevConfig-Gen),拥有中立核心与
313
369
  [`PROVIDER_STANDARD.md`](PROVIDER_STANDARD.md)。
@@ -0,0 +1,86 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/devconfig_gen/__init__.py
5
+ src/devconfig_gen/cli.py
6
+ src/devconfig_gen/engine.py
7
+ src/devconfig_gen/formats.py
8
+ src/devconfig_gen/models.py
9
+ src/devconfig_gen/py.typed
10
+ src/devconfig_gen/registry.py
11
+ src/devconfig_gen/validation.py
12
+ src/devconfig_gen/providers/__init__.py
13
+ src/devconfig_gen/providers/custom.py
14
+ src/devconfig_gen/providers/env_provider.py
15
+ src/devconfig_gen/providers/json_provider.py
16
+ src/devconfig_gen/providers/singbox/__init__.py
17
+ src/devconfig_gen/providers/singbox/links.py
18
+ src/devconfig_gen/providers/singbox/models.py
19
+ src/devconfig_gen/providers/singbox/provider.py
20
+ src/devconfig_gen/providers/singbox/route.py
21
+ src/devconfig_gen/providers/singbox/schema.py
22
+ src/devconfig_gen/providers/singbox/data/rules.json
23
+ src/devconfig_gen/providers/singbox/plugins/__init__.py
24
+ src/devconfig_gen/providers/singbox/plugins/anytls.py
25
+ src/devconfig_gen/providers/singbox/plugins/base.py
26
+ src/devconfig_gen/providers/singbox/plugins/hysteria2.py
27
+ src/devconfig_gen/providers/singbox/plugins/tuic.py
28
+ src/devconfig_gen_singbox.egg-info/PKG-INFO
29
+ src/devconfig_gen_singbox.egg-info/SOURCES.txt
30
+ src/devconfig_gen_singbox.egg-info/dependency_links.txt
31
+ src/devconfig_gen_singbox.egg-info/entry_points.txt
32
+ src/devconfig_gen_singbox.egg-info/requires.txt
33
+ src/devconfig_gen_singbox.egg-info/top_level.txt
34
+ src/singbox_ops/__init__.py
35
+ src/singbox_ops/cli.py
36
+ src/singbox_ops/adapters/__init__.py
37
+ src/singbox_ops/adapters/base.py
38
+ src/singbox_ops/adapters/acme/__init__.py
39
+ src/singbox_ops/adapters/acme/base.py
40
+ src/singbox_ops/adapters/acme/cloudflare_dns01.py
41
+ src/singbox_ops/adapters/dns/__init__.py
42
+ src/singbox_ops/adapters/dns/base.py
43
+ src/singbox_ops/adapters/dns/cloudflare.py
44
+ src/singbox_ops/adapters/export/__init__.py
45
+ src/singbox_ops/adapters/export/local_files.py
46
+ src/singbox_ops/adapters/runtime/__init__.py
47
+ src/singbox_ops/adapters/runtime/nftables.py
48
+ src/singbox_ops/adapters/runtime/packages.py
49
+ src/singbox_ops/adapters/runtime/systemd.py
50
+ src/singbox_ops/adapters/runtime/warp_watchdog.py
51
+ src/singbox_ops/adapters/secrets/__init__.py
52
+ src/singbox_ops/adapters/secrets/base.py
53
+ src/singbox_ops/adapters/secrets/python_secrets.py
54
+ src/singbox_ops/adapters/secrets/singbox_subprocess.py
55
+ src/singbox_ops/adapters/secrets/static.py
56
+ src/singbox_ops/adapters/state/__init__.py
57
+ src/singbox_ops/adapters/state/local_json.py
58
+ src/singbox_ops/core/__init__.py
59
+ src/singbox_ops/core/command.py
60
+ src/singbox_ops/core/context_builder.py
61
+ src/singbox_ops/core/exceptions.py
62
+ src/singbox_ops/core/network.py
63
+ src/singbox_ops/core/orchestrator.py
64
+ src/singbox_ops/core/plan.py
65
+ src/singbox_ops/core/protocols.py
66
+ src/singbox_ops/templates/singbox_auto_update.py.template
67
+ src/singbox_ops/templates/warp_watchdog.sh.template
68
+ tests/test_api_parity.py
69
+ tests/test_cli_e2e.py
70
+ tests/test_custom_provider.py
71
+ tests/test_devconfig_core.py
72
+ tests/test_env_provider.py
73
+ tests/test_formats.py
74
+ tests/test_merge.py
75
+ tests/test_provider_metadata.py
76
+ tests/test_singbox_ops_acme.py
77
+ tests/test_singbox_ops_cli.py
78
+ tests/test_singbox_ops_context_builder.py
79
+ tests/test_singbox_ops_dns.py
80
+ tests/test_singbox_ops_orchestrator.py
81
+ tests/test_singbox_ops_plan.py
82
+ tests/test_singbox_ops_runtime.py
83
+ tests/test_singbox_ops_secrets.py
84
+ tests/test_singbox_ops_state_export.py
85
+ tests/test_singbox_provider.py
86
+ tests/test_validation.py
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
2
  devconfig-gen = devconfig_gen.cli:main
3
3
  devconfig_gen_singbox = devconfig_gen.cli:main
4
+ singbox-ops = singbox_ops.cli:main
@@ -3,9 +3,13 @@
3
3
  build
4
4
  wheel
5
5
  PyYAML>=5.1
6
+ cryptography>=3.4
6
7
 
7
8
  [docs]
8
9
  mkdocs-material<10,>=9.5
9
10
 
11
+ [ops]
12
+ cryptography>=3.4
13
+
10
14
  [yaml]
11
15
  PyYAML>=5.1
@@ -0,0 +1,29 @@
1
+ """Deployment/operations layer for the sing-box provider.
2
+
3
+ ``devconfig_gen`` stays a pure, side-effect-free configuration engine. This
4
+ sibling package owns everything that touches the outside world: DNS records,
5
+ TLS certificates, package installation, systemd, firewall rules, watchdogs and
6
+ state files.
7
+
8
+ The two packages are deliberately isolated:
9
+
10
+ - ``devconfig_gen`` -> context in, artifacts out. No I/O beyond explicit writes.
11
+ - ``singbox_ops`` -> orchestrates side effects and *calls* the engine.
12
+
13
+ Nothing in this package is registered as a ``ConfigProvider``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from .core.exceptions import AdapterError, CommandError, OpsError, PlanError
19
+ from .core.plan import DeployPlan, load_plan
20
+
21
+ __all__ = [
22
+ "AdapterError",
23
+ "CommandError",
24
+ "DeployPlan",
25
+ "OpsError",
26
+ "PlanError",
27
+ "load_plan",
28
+ ]
29
+ __version__ = "0.1.0"
@@ -0,0 +1,27 @@
1
+ """Adapters: the pluggable ports on the ops dock.
2
+
3
+ Each adapter is an ordinary Python class implementing a small protocol. We do
4
+ **not** use setuptools entry points or dynamic discovery here: a plain name ->
5
+ factory mapping keeps the two plugin systems (providers vs adapters) from
6
+ tangling.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .base import (
12
+ ACMEAdapter,
13
+ DNSAdapter,
14
+ ExportAdapter,
15
+ RuntimeAdapter,
16
+ SecretsAdapter,
17
+ StateAdapter,
18
+ )
19
+
20
+ __all__ = [
21
+ "ACMEAdapter",
22
+ "DNSAdapter",
23
+ "ExportAdapter",
24
+ "RuntimeAdapter",
25
+ "SecretsAdapter",
26
+ "StateAdapter",
27
+ ]
@@ -0,0 +1,7 @@
1
+ """ACME certificate adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .cloudflare_dns01 import CloudflareDNS01ACME
6
+
7
+ __all__ = ["CloudflareDNS01ACME"]
@@ -0,0 +1,16 @@
1
+ """ACME adapter contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Mapping, Protocol, Sequence, Tuple
6
+
7
+
8
+ class ACMEAdapter(Protocol):
9
+ def apply(
10
+ self,
11
+ hosts: Mapping[str, str],
12
+ protocols: Sequence[str],
13
+ *,
14
+ dry_run: bool = False,
15
+ ) -> Mapping[str, Tuple[str, str]]:
16
+ """Ensure certificates exist; return ``{protocol: (cert_path, key_path)}``."""
@@ -0,0 +1,130 @@
1
+ """acme.sh + Cloudflare DNS-01 certificate issuance.
2
+
3
+ Renewal is intentionally *not* handled here: the adapter only makes sure a valid
4
+ certificate exists. A systemd timer / cron job can call this adapter again, which
5
+ is idempotent (a valid, host-matching certificate is left untouched).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from typing import Mapping, Optional, Sequence, Tuple
12
+
13
+ from ...core.command import CommandRunner, LocalCommandRunner
14
+ from ...core.exceptions import AdapterError
15
+ from ...core.protocols import CERT_PATHS, TLS_PROTOCOLS
16
+
17
+ ACME_SH_PATH = "/root/.acme.sh/acme.sh"
18
+ ACME_INSTALL_URL = "https://get.acme.sh"
19
+ ACME_CA = "letsencrypt"
20
+ CERT_VALIDITY_WINDOW = 30 * 24 * 3600
21
+ CF_TOKEN_ENV = "CF_Token"
22
+ CF_ZONE_ID_ENV = "CF_Zone_ID"
23
+ RELOAD_CMD = "systemctl try-restart sing-box >/dev/null 2>&1 || true"
24
+
25
+ SUPPORTED_PROTOCOLS = TLS_PROTOCOLS
26
+
27
+
28
+ class CloudflareDNS01ACME:
29
+ def __init__(
30
+ self,
31
+ runner: Optional[CommandRunner] = None,
32
+ acme_sh: str = ACME_SH_PATH,
33
+ ca: str = ACME_CA,
34
+ token: Optional[str] = None,
35
+ zone_id: Optional[str] = None,
36
+ cert_paths: Optional[Mapping[str, Tuple[str, str]]] = None,
37
+ reload_cmd: str = RELOAD_CMD,
38
+ ):
39
+ self.runner = runner or LocalCommandRunner()
40
+ self.acme_sh = acme_sh
41
+ self.ca = ca
42
+ self.token = token
43
+ self.zone_id = zone_id
44
+ self.cert_paths = dict(cert_paths or CERT_PATHS)
45
+ self.reload_cmd = reload_cmd
46
+
47
+ # -- helpers ---------------------------------------------------------
48
+ def _dns_env(self) -> Mapping[str, str]:
49
+ token = (self.token or os.environ.get(CF_TOKEN_ENV, "")).strip()
50
+ zone_id = (self.zone_id or os.environ.get(CF_ZONE_ID_ENV, "")).strip()
51
+ if not token or not zone_id:
52
+ raise AdapterError(
53
+ "Cloudflare DNS-01 credentials missing; set CF_Token and CF_Zone_ID"
54
+ )
55
+ return {CF_TOKEN_ENV: token, CF_ZONE_ID_ENV: zone_id}
56
+
57
+ def cert_is_valid(self, cert_path: str, host: str) -> bool:
58
+ if not self.runner.exists(cert_path):
59
+ return False
60
+ check = self.runner.run(
61
+ ["openssl", "x509", "-in", cert_path, "-noout", "-checkend", str(CERT_VALIDITY_WINDOW)],
62
+ check=False,
63
+ )
64
+ if check.returncode != 0:
65
+ return False
66
+ san = self.runner.run(
67
+ ["openssl", "x509", "-in", cert_path, "-noout", "-ext", "subjectAltName"],
68
+ check=False,
69
+ )
70
+ return f"DNS:{host}" in (san.stdout or "")
71
+
72
+ def _ensure_acme_sh(self) -> str:
73
+ if self.runner.exists(self.acme_sh):
74
+ return self.acme_sh
75
+ self.runner.shell(f"curl -fsSL {ACME_INSTALL_URL} | sh", check=True)
76
+ if not self.runner.exists(self.acme_sh):
77
+ # Fall back to PATH lookup; the caller's runner may report a stub.
78
+ return "acme.sh"
79
+ return self.acme_sh
80
+
81
+ def _issue_and_install(self, host: str, cert_path: str, key_path: str) -> None:
82
+ env = self._dns_env()
83
+ if self.cert_is_valid(cert_path, host):
84
+ return
85
+ acme_sh = self._ensure_acme_sh()
86
+ self.runner.run([acme_sh, "--set-default-ca", "--server", self.ca], check=True)
87
+ self.runner.run(
88
+ [acme_sh, "--issue", "--dns", "dns_cf", "-d", host, "--keylength", "ec-256", "--server", self.ca],
89
+ check=True,
90
+ env=env,
91
+ )
92
+ self.runner.run(
93
+ [
94
+ acme_sh,
95
+ "--install-cert",
96
+ "-d",
97
+ host,
98
+ "--ecc",
99
+ "--fullchain-file",
100
+ cert_path,
101
+ "--key-file",
102
+ key_path,
103
+ "--reloadcmd",
104
+ self.reload_cmd,
105
+ ],
106
+ check=True,
107
+ )
108
+ self.runner.chmod(key_path, 0o600)
109
+
110
+ # -- adapter contract ------------------------------------------------
111
+ def apply(
112
+ self,
113
+ hosts: Mapping[str, str],
114
+ protocols: Sequence[str],
115
+ *,
116
+ dry_run: bool = False,
117
+ ) -> Mapping[str, Tuple[str, str]]:
118
+ wanted = [protocol for protocol in protocols if protocol in SUPPORTED_PROTOCOLS]
119
+ result: dict = {}
120
+ for protocol in wanted:
121
+ host = hosts.get(protocol)
122
+ if not host:
123
+ raise AdapterError(f"missing hostname for TLS protocol {protocol!r}")
124
+ cert_path, key_path = self.cert_paths[protocol]
125
+ if not dry_run:
126
+ self.runner.mkdir(os.path.dirname(cert_path), mode=0o700)
127
+ self.runner.mkdir(os.path.dirname(key_path), mode=0o700)
128
+ self._issue_and_install(host, cert_path, key_path)
129
+ result[protocol] = (cert_path, key_path)
130
+ return result