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.
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/PKG-INFO +40 -6
- devconfig_gen_singbox-2.0.0/src/devconfig_gen_singbox.egg-info/PKG-INFO → devconfig_gen_singbox-2.1.0/README.md +36 -27
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/pyproject.toml +5 -2
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/__init__.py +1 -1
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/provider.py +5 -0
- devconfig_gen_singbox-2.0.0/README.md → devconfig_gen_singbox-2.1.0/src/devconfig_gen_singbox.egg-info/PKG-INFO +61 -5
- devconfig_gen_singbox-2.1.0/src/devconfig_gen_singbox.egg-info/SOURCES.txt +86 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/entry_points.txt +1 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/requires.txt +4 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/top_level.txt +1 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/__init__.py +29 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/__init__.py +27 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/acme/__init__.py +7 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/acme/base.py +16 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/acme/cloudflare_dns01.py +130 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/base.py +62 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/dns/__init__.py +7 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/dns/base.py +15 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/dns/cloudflare.py +138 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/export/__init__.py +7 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/export/local_files.py +62 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/__init__.py +17 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/nftables.py +99 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/packages.py +90 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/systemd.py +55 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/runtime/warp_watchdog.py +73 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/__init__.py +16 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/base.py +120 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/python_secrets.py +20 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/singbox_subprocess.py +96 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/secrets/static.py +47 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/state/__init__.py +7 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/adapters/state/local_json.py +44 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/cli.py +109 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/__init__.py +15 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/command.py +197 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/context_builder.py +155 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/exceptions.py +30 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/network.py +52 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/orchestrator.py +291 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/plan.py +253 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/core/protocols.py +64 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/templates/singbox_auto_update.py.template +112 -0
- devconfig_gen_singbox-2.1.0/src/singbox_ops/templates/warp_watchdog.sh.template +239 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_acme.py +66 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_cli.py +31 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_context_builder.py +60 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_dns.py +68 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_orchestrator.py +117 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_plan.py +70 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_runtime.py +120 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_secrets.py +86 -0
- devconfig_gen_singbox-2.1.0/tests/test_singbox_ops_state_export.py +59 -0
- devconfig_gen_singbox-2.0.0/src/devconfig_gen_singbox.egg-info/SOURCES.txt +0 -43
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/LICENSE +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/setup.cfg +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/cli.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/engine.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/formats.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/models.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/__init__.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/custom.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/env_provider.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/json_provider.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/__init__.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/data/rules.json +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/links.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/models.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/__init__.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/anytls.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/base.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/hysteria2.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/plugins/tuic.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/route.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/providers/singbox/schema.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/py.typed +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/registry.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen/validation.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/src/devconfig_gen_singbox.egg-info/dependency_links.txt +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_api_parity.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_cli_e2e.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_custom_provider.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_devconfig_core.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_env_provider.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_formats.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_merge.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_provider_metadata.py +0 -0
- {devconfig_gen_singbox-2.0.0 → devconfig_gen_singbox-2.1.0}/tests/test_singbox_provider.py +0 -0
- {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.
|
|
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
|
-
#
|
|
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
|
-
[](CHANGELOG.md)
|
|
29
32
|
[](LICENSE)
|
|
30
33
|

|
|
31
34
|

|
|
@@ -43,7 +46,7 @@ DevConfig-Gen
|
|
|
43
46
|
中立引擎 · Provider 契约 · 执行流水线
|
|
44
47
|
│
|
|
45
48
|
▼
|
|
46
|
-
|
|
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
|
-
└──
|
|
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)**:`
|
|
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
|
-
|
|
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
|
-
[](CHANGELOG.md)
|
|
29
7
|
[](LICENSE)
|
|
30
8
|

|
|
31
9
|

|
|
@@ -43,7 +21,7 @@ DevConfig-Gen
|
|
|
43
21
|
中立引擎 · Provider 契约 · 执行流水线
|
|
44
22
|
│
|
|
45
23
|
▼
|
|
46
|
-
|
|
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
|
-
└──
|
|
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)**:`
|
|
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.
|
|
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
|
-
|
|
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"]
|
|
@@ -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
|
-
|
|
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
|
-
[](CHANGELOG.md)
|
|
7
32
|
[](LICENSE)
|
|
8
33
|

|
|
9
34
|

|
|
@@ -21,7 +46,7 @@ DevConfig-Gen
|
|
|
21
46
|
中立引擎 · Provider 契约 · 执行流水线
|
|
22
47
|
│
|
|
23
48
|
▼
|
|
24
|
-
|
|
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
|
-
└──
|
|
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)**:`
|
|
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
|
|
@@ -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,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
|