devconfig-gen-singbox 2.1.7__tar.gz → 2.1.8__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.1.7/src/devconfig_gen_singbox.egg-info → devconfig_gen_singbox-2.1.8}/PKG-INFO +2 -2
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/README.md +1 -1
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/pyproject.toml +1 -1
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/__init__.py +1 -1
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8/src/devconfig_gen_singbox.egg-info}/PKG-INFO +2 -2
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/cli.py +43 -6
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/context_builder.py +28 -13
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/plan.py +8 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/protocols.py +11 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_context_builder.py +23 -0
- devconfig_gen_singbox-2.1.8/tests/test_singbox_ops_wizard.py +71 -0
- devconfig_gen_singbox-2.1.7/tests/test_singbox_ops_wizard.py +0 -67
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/LICENSE +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/setup.cfg +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/cli.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/engine.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/formats.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/interactive.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/models.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/custom.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/env_provider.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/json_provider.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/data/rules.json +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/links.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/models.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/plugins/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/plugins/anytls.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/plugins/base.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/plugins/hysteria2.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/plugins/tuic.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/provider.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/route.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/singbox/schema.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/py.typed +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/registry.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/validation.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/web_ui.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen_singbox.egg-info/SOURCES.txt +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen_singbox.egg-info/dependency_links.txt +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen_singbox.egg-info/entry_points.txt +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen_singbox.egg-info/requires.txt +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen_singbox.egg-info/top_level.txt +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/acme/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/acme/base.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/acme/cloudflare_dns01.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/acme/prune.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/base.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/dns/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/dns/base.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/dns/cloudflare.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/export/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/export/local_files.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/runtime/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/runtime/auto_update.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/runtime/nftables.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/runtime/packages.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/runtime/systemd.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/runtime/warp_watchdog.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/secrets/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/secrets/base.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/secrets/python_secrets.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/secrets/singbox_subprocess.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/secrets/static.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/state/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/state/local_json.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/__init__.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/command.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/exceptions.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/network.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/orchestrator.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/ui.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/templates/singbox_auto_update.py.template +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/templates/warp_watchdog.sh.template +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_api_parity.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_cli_e2e.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_custom_provider.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_devconfig_core.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_env_provider.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_formats.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_interactive.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_merge.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_provider_metadata.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_acme.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_cli.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_dns.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_orchestrator.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_plan.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_runtime.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_secrets.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_state_export.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_provider.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_validation.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_web_ui.py +0 -0
- {devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_web_ui_widgets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devconfig_gen_singbox
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8
|
|
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
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
> **A sing-box domain implementation built on top of DevConfig-Gen.**
|
|
29
29
|
> **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
|
|
30
30
|
|
|
31
|
-
[](CHANGELOG.md)
|
|
32
32
|
[](LICENSE)
|
|
33
33
|

|
|
34
34
|

|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **A sing-box domain implementation built on top of DevConfig-Gen.**
|
|
4
4
|
> **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
|
|
5
5
|
|
|
6
|
-
[](CHANGELOG.md)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|

|
|
9
9
|

|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devconfig_gen_singbox
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.8
|
|
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
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
> **A sing-box domain implementation built on top of DevConfig-Gen.**
|
|
29
29
|
> **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
|
|
30
30
|
|
|
31
|
-
[](CHANGELOG.md)
|
|
32
32
|
[](LICENSE)
|
|
33
33
|

|
|
34
34
|

|
|
@@ -25,7 +25,12 @@ from .core.context_builder import build_context
|
|
|
25
25
|
from .core.exceptions import OpsError
|
|
26
26
|
from .core.plan import DeployPlan, load_plan
|
|
27
27
|
from .core.orchestrator import DRY_RUN_IP, build_suite, deploy, destroy, make_runner
|
|
28
|
-
from .core.protocols import
|
|
28
|
+
from .core.protocols import (
|
|
29
|
+
DEFAULT_ANYTLS_DECOY_PORT,
|
|
30
|
+
DEFAULT_ANYTLS_DECOY_SERVER,
|
|
31
|
+
DEFAULT_HY2_MASQUERADE,
|
|
32
|
+
PROTOCOLS,
|
|
33
|
+
)
|
|
29
34
|
from .core.ui import UI
|
|
30
35
|
|
|
31
36
|
PROG = "devconfig_gen_singbox"
|
|
@@ -151,7 +156,7 @@ def interactive_plan(domain=None, protocols=None, reader=None, writer=None, ui=N
|
|
|
151
156
|
ui = ui or UI(writer)
|
|
152
157
|
ui.banner(f"{PROG} · 交互式部署向导", "逐项回答;回车使用默认值。Ctrl-C 可随时退出。")
|
|
153
158
|
|
|
154
|
-
ui.section("1/
|
|
159
|
+
ui.section("1/5 基础")
|
|
155
160
|
while not domain:
|
|
156
161
|
domain = _ask(ui, "主域名", "例如 example.com;协议主机名 = <前缀>.<主域名>",
|
|
157
162
|
required=True, reader=reader, writer=writer)
|
|
@@ -167,7 +172,32 @@ def interactive_plan(domain=None, protocols=None, reader=None, writer=None, ui=N
|
|
|
167
172
|
)
|
|
168
173
|
server_ip = _ask(ui, "服务器公网 IP", "留空=自动探测", reader=reader, writer=writer)
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
protocol_list = [item.strip() for item in str(protocols).split(",") if item.strip()]
|
|
176
|
+
protocol_params: dict = {}
|
|
177
|
+
if "anytls" in protocol_list or "hysteria2" in protocol_list:
|
|
178
|
+
ui.section("2/5 协议参数")
|
|
179
|
+
if "anytls" in protocol_list:
|
|
180
|
+
protocol_params["anytls"] = {
|
|
181
|
+
"decoy_server": _ask(
|
|
182
|
+
ui, "REALITY 伪装站 (decoy server)",
|
|
183
|
+
f"REALITY 握手伪装的目标站点;证书 SNI 也用这个(默认 {DEFAULT_ANYTLS_DECOY_SERVER})",
|
|
184
|
+
default=DEFAULT_ANYTLS_DECOY_SERVER, reader=reader, writer=writer,
|
|
185
|
+
),
|
|
186
|
+
"decoy_port": _ask(
|
|
187
|
+
ui, "REALITY 伪装端口", f"通常 {DEFAULT_ANYTLS_DECOY_PORT}",
|
|
188
|
+
default=str(DEFAULT_ANYTLS_DECOY_PORT), reader=reader, writer=writer,
|
|
189
|
+
),
|
|
190
|
+
}
|
|
191
|
+
if "hysteria2" in protocol_list:
|
|
192
|
+
protocol_params["hysteria2"] = {
|
|
193
|
+
"masquerade": _ask(
|
|
194
|
+
ui, "Hysteria2 伪装网址 (masquerade)",
|
|
195
|
+
f"HTTP 伪装目标 URL(默认 {DEFAULT_HY2_MASQUERADE})",
|
|
196
|
+
default=DEFAULT_HY2_MASQUERADE, reader=reader, writer=writer,
|
|
197
|
+
),
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ui.section("3/5 Cloudflare(DNS 必填)")
|
|
171
201
|
ui.warn("DNS A 记录由 Cloudflare 管理,因此 CF 凭据必填(仅本次内存使用,不写盘)")
|
|
172
202
|
env = {
|
|
173
203
|
"CF_Token": _ask_secret(
|
|
@@ -185,23 +215,24 @@ def interactive_plan(domain=None, protocols=None, reader=None, writer=None, ui=N
|
|
|
185
215
|
default=True, reader=reader, writer=writer,
|
|
186
216
|
)
|
|
187
217
|
|
|
188
|
-
ui.section("
|
|
218
|
+
ui.section("4/5 运行时")
|
|
189
219
|
use_runtime = _ask_yes_no(
|
|
190
220
|
ui, "启用运行时适配器?",
|
|
191
221
|
"systemd / nftables / watchdog / 每日自动升级(生产建议 Y)",
|
|
192
222
|
default=True, reader=reader, writer=writer,
|
|
193
223
|
)
|
|
194
224
|
|
|
195
|
-
ui.section("
|
|
225
|
+
ui.section("5/5 产物路径")
|
|
196
226
|
server_out = _ask(ui, "服务端配置", "sing-box 主配置", default=DEFAULT_SERVER_OUT, reader=reader, writer=writer)
|
|
197
227
|
client_out = _ask(ui, "客户端配置", "可导入 GUI 的完整客户端配置", default=DEFAULT_CLIENT_OUT, reader=reader, writer=writer)
|
|
198
228
|
links_out = _ask(ui, "分享链接", "anytls:// tuic:// hy2:// 文本", default=DEFAULT_LINKS_OUT, reader=reader, writer=writer)
|
|
199
229
|
|
|
200
230
|
plan = {
|
|
201
231
|
"domain_root": domain,
|
|
202
|
-
"protocols":
|
|
232
|
+
"protocols": protocol_list,
|
|
203
233
|
"tunnel_mode": tunnel_mode,
|
|
204
234
|
"server_ip": server_ip or "auto",
|
|
235
|
+
"protocol_params": protocol_params,
|
|
205
236
|
"adapters": {
|
|
206
237
|
"secrets": "singbox-subprocess",
|
|
207
238
|
"dns": "cloudflare",
|
|
@@ -235,6 +266,12 @@ def _confirm(ui: UI, plan, dry_run: bool, reader=None, writer=None) -> bool:
|
|
|
235
266
|
ui.kv("DNS", adapters.get("dns") or "跳过(自己管理)")
|
|
236
267
|
ui.kv("证书", adapters.get("acme") or "跳过(自己管理)")
|
|
237
268
|
ui.kv("运行时", ", ".join(f"{k}={v}" for k, v in runtime.items() if v) or "关闭")
|
|
269
|
+
params = plan.protocol_params or {}
|
|
270
|
+
if params.get("anytls"):
|
|
271
|
+
anytls = params["anytls"]
|
|
272
|
+
ui.kv("REALITY decoy", f"{anytls.get('decoy_server')}:{anytls.get('decoy_port', 443)}")
|
|
273
|
+
if params.get("hysteria2"):
|
|
274
|
+
ui.kv("HY2 masquerade", params["hysteria2"].get("masquerade"))
|
|
238
275
|
ui.kv("服务端配置", plan.outputs.get("server_config", "?"))
|
|
239
276
|
if dry_run:
|
|
240
277
|
ui.warn("DRY-RUN:只打印将执行的步骤,不改动系统")
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/context_builder.py
RENAMED
|
@@ -12,19 +12,24 @@ from .exceptions import AdapterError
|
|
|
12
12
|
from .plan import DeployPlan
|
|
13
13
|
from .protocols import (
|
|
14
14
|
CERT_PATHS,
|
|
15
|
+
DEFAULT_ANYTLS_DECOY_PORT,
|
|
16
|
+
DEFAULT_ANYTLS_DECOY_SERVER,
|
|
17
|
+
DEFAULT_HY2_CLIENT_DOWN_MBPS,
|
|
18
|
+
DEFAULT_HY2_CLIENT_UP_MBPS,
|
|
19
|
+
DEFAULT_HY2_MASQUERADE,
|
|
20
|
+
DEFAULT_HY2_SERVER_DOWN_MBPS,
|
|
21
|
+
DEFAULT_HY2_SERVER_UP_MBPS,
|
|
15
22
|
HOST_KEYS,
|
|
16
23
|
PROTOCOL_PORTS,
|
|
17
24
|
TLS_PROTOCOLS,
|
|
18
25
|
requires_tls,
|
|
19
26
|
)
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
DEFAULT_CLIENT_UP_MBPS = 50
|
|
27
|
-
DEFAULT_CLIENT_DOWN_MBPS = 200
|
|
28
|
+
DEFAULT_MASQUERADE = DEFAULT_HY2_MASQUERADE
|
|
29
|
+
DEFAULT_SERVER_UP_MBPS = DEFAULT_HY2_SERVER_UP_MBPS
|
|
30
|
+
DEFAULT_SERVER_DOWN_MBPS = DEFAULT_HY2_SERVER_DOWN_MBPS
|
|
31
|
+
DEFAULT_CLIENT_UP_MBPS = DEFAULT_HY2_CLIENT_UP_MBPS
|
|
32
|
+
DEFAULT_CLIENT_DOWN_MBPS = DEFAULT_HY2_CLIENT_DOWN_MBPS
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
def _require(secrets: Mapping[str, Any], path: str, context_name: str) -> Any:
|
|
@@ -59,7 +64,8 @@ def build_context(
|
|
|
59
64
|
protocols = []
|
|
60
65
|
for protocol in plan.protocols:
|
|
61
66
|
credentials = secrets.get(protocol) or {}
|
|
62
|
-
|
|
67
|
+
params = dict(plan.protocol_params.get(protocol) or {})
|
|
68
|
+
entry = _build_protocol(protocol, credentials, prefixes, cert_paths, params)
|
|
63
69
|
protocols.append(entry)
|
|
64
70
|
|
|
65
71
|
client = {"fingerprint": plan.fingerprint}
|
|
@@ -97,7 +103,9 @@ def _build_protocol(
|
|
|
97
103
|
credentials: Mapping[str, Any],
|
|
98
104
|
prefixes: Mapping[str, str],
|
|
99
105
|
cert_paths: Optional[Mapping[str, str]],
|
|
106
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
100
107
|
) -> dict:
|
|
108
|
+
params = params or {}
|
|
101
109
|
host_key = HOST_KEYS[protocol]
|
|
102
110
|
if host_key not in prefixes:
|
|
103
111
|
raise AdapterError(f"subdomain prefix for {host_key!r} is required ({protocol})")
|
|
@@ -111,8 +119,11 @@ def _build_protocol(
|
|
|
111
119
|
if protocol == "anytls":
|
|
112
120
|
entry["auth"] = {"password": _require(credentials, "password", protocol)}
|
|
113
121
|
entry["reality"] = {
|
|
114
|
-
"decoy_server":
|
|
115
|
-
|
|
122
|
+
"decoy_server": params.get("decoy_server")
|
|
123
|
+
or credentials.get("decoy_server", DEFAULT_ANYTLS_DECOY_SERVER),
|
|
124
|
+
"decoy_port": int(
|
|
125
|
+
params.get("decoy_port") or credentials.get("decoy_port", DEFAULT_ANYTLS_DECOY_PORT)
|
|
126
|
+
),
|
|
116
127
|
"private_key": _require(credentials, "private_key", protocol),
|
|
117
128
|
"public_key": _require(credentials, "public_key", protocol),
|
|
118
129
|
"short_id": _require(credentials, "short_id", protocol),
|
|
@@ -130,10 +141,14 @@ def _build_protocol(
|
|
|
130
141
|
"obfs_password": _require(credentials, "obfs_password", protocol),
|
|
131
142
|
}
|
|
132
143
|
entry["bandwidth"] = {
|
|
133
|
-
"up_mbps": int(credentials.get("up_mbps", DEFAULT_SERVER_UP_MBPS)),
|
|
134
|
-
"down_mbps": int(
|
|
144
|
+
"up_mbps": int(params.get("up_mbps") or credentials.get("up_mbps", DEFAULT_SERVER_UP_MBPS)),
|
|
145
|
+
"down_mbps": int(
|
|
146
|
+
params.get("down_mbps") or credentials.get("down_mbps", DEFAULT_SERVER_DOWN_MBPS)
|
|
147
|
+
),
|
|
135
148
|
}
|
|
136
|
-
entry["masquerade"] =
|
|
149
|
+
entry["masquerade"] = (
|
|
150
|
+
params.get("masquerade") or credentials.get("masquerade", DEFAULT_MASQUERADE)
|
|
151
|
+
)
|
|
137
152
|
cert, key = _resolve_cert(protocol, cert_paths)
|
|
138
153
|
entry["tls"] = {"cert_path": cert, "key_path": key}
|
|
139
154
|
else: # pragma: no cover - guarded by DeployPlan validation
|
|
@@ -106,6 +106,7 @@ class DeployPlan:
|
|
|
106
106
|
subdomain_prefixes: Mapping[str, str] = field(default_factory=dict)
|
|
107
107
|
routing: Mapping[str, Any] = field(default_factory=dict)
|
|
108
108
|
dns: Mapping[str, Any] = field(default_factory=dict)
|
|
109
|
+
protocol_params: Mapping[str, Mapping[str, Any]] = field(default_factory=dict)
|
|
109
110
|
fingerprint: str = DEFAULT_FINGERPRINT
|
|
110
111
|
target: str = "both"
|
|
111
112
|
output_format: str = DEFAULT_FORMAT
|
|
@@ -150,6 +151,8 @@ class DeployPlan:
|
|
|
150
151
|
data["subdomain_prefixes"] = dict(self.subdomain_prefixes)
|
|
151
152
|
data.setdefault("routing", dict(self.routing))
|
|
152
153
|
data.setdefault("dns", dict(self.dns))
|
|
154
|
+
if self.protocol_params:
|
|
155
|
+
data["protocol_params"] = {key: dict(value) for key, value in self.protocol_params.items()}
|
|
153
156
|
client = dict(data.get("client") or {})
|
|
154
157
|
client.setdefault("fingerprint", self.fingerprint)
|
|
155
158
|
if self.server_ip:
|
|
@@ -231,6 +234,11 @@ class DeployPlan:
|
|
|
231
234
|
subdomain_prefixes=prefixes,
|
|
232
235
|
routing=dict(_as_mapping(raw.get("routing"), "routing")),
|
|
233
236
|
dns=dict(_as_mapping(raw.get("dns"), "dns")),
|
|
237
|
+
protocol_params={
|
|
238
|
+
str(proto): dict(value)
|
|
239
|
+
for proto, value in _as_mapping(raw.get("protocol_params"), "protocol_params").items()
|
|
240
|
+
if isinstance(value, Mapping)
|
|
241
|
+
},
|
|
234
242
|
fingerprint=_as_str(client.get("fingerprint"), "client.fingerprint")
|
|
235
243
|
or DEFAULT_FINGERPRINT,
|
|
236
244
|
target=target,
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/protocols.py
RENAMED
|
@@ -36,6 +36,17 @@ DEFAULT_FINGERPRINT = "chrome"
|
|
|
36
36
|
DEFAULT_TUNNEL_MODE = "proxy"
|
|
37
37
|
DEFAULT_FORMAT = "json"
|
|
38
38
|
|
|
39
|
+
# Single source of truth for protocol-level defaults. Anything that wants a
|
|
40
|
+
# fallback (context builder, wizard prompts, docs) reads these, so nothing is
|
|
41
|
+
# hard-coded in more than one place and operators can reason about one value.
|
|
42
|
+
DEFAULT_ANYTLS_DECOY_SERVER = "www.cloudflare.com"
|
|
43
|
+
DEFAULT_ANYTLS_DECOY_PORT = 443
|
|
44
|
+
DEFAULT_HY2_MASQUERADE = "https://www.cloudflare.com"
|
|
45
|
+
DEFAULT_HY2_SERVER_UP_MBPS = 500
|
|
46
|
+
DEFAULT_HY2_SERVER_DOWN_MBPS = 500
|
|
47
|
+
DEFAULT_HY2_CLIENT_UP_MBPS = 50
|
|
48
|
+
DEFAULT_HY2_CLIENT_DOWN_MBPS = 200
|
|
49
|
+
|
|
39
50
|
#: TCP / UDP classification, used for firewall rules.
|
|
40
51
|
UDP_PROTOCOLS: Tuple[str, ...] = ("tuic", "hysteria2")
|
|
41
52
|
|
|
@@ -49,6 +49,29 @@ class ContextBuilderTests(unittest.TestCase):
|
|
|
49
49
|
needed = needed_certificates(self.plan)
|
|
50
50
|
self.assertEqual(needed, {"tuic": "tuic", "hysteria2": "hy2"})
|
|
51
51
|
|
|
52
|
+
def test_protocol_params_override_defaults(self):
|
|
53
|
+
plan = DeployPlan.from_mapping(
|
|
54
|
+
{
|
|
55
|
+
"domain_root": "example.com",
|
|
56
|
+
"protocols": ["anytls", "hysteria2"],
|
|
57
|
+
"protocol_params": {
|
|
58
|
+
"anytls": {"decoy_server": "react.dev", "decoy_port": 8443},
|
|
59
|
+
"hysteria2": {"masquerade": "https://react.dev"},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
secrets = static_secrets(("anytls", "hysteria2"))
|
|
64
|
+
context = build_context(
|
|
65
|
+
plan,
|
|
66
|
+
secrets["credentials"],
|
|
67
|
+
"203.0.113.10",
|
|
68
|
+
subdomain_prefixes=secrets["subdomain_prefixes"],
|
|
69
|
+
)
|
|
70
|
+
protos = {item["type"]: item for item in context["network"]["protocols"]}
|
|
71
|
+
self.assertEqual(protos["anytls"]["reality"]["decoy_server"], "react.dev")
|
|
72
|
+
self.assertEqual(protos["anytls"]["reality"]["decoy_port"], 8443)
|
|
73
|
+
self.assertEqual(protos["hysteria2"]["masquerade"], "https://react.dev")
|
|
74
|
+
|
|
52
75
|
def test_missing_prefix_raises(self):
|
|
53
76
|
from singbox_ops.core.exceptions import AdapterError
|
|
54
77
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from singbox_ops.cli import interactive_plan
|
|
5
|
+
from singbox_ops.core.ui import UI
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_wizard(inputs):
|
|
9
|
+
reader = io.StringIO(inputs)
|
|
10
|
+
writer = io.StringIO()
|
|
11
|
+
return interactive_plan(reader=reader, writer=writer, ui=UI(writer))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WizardTests(unittest.TestCase):
|
|
15
|
+
def test_collects_dns_creds_and_protocol_params(self):
|
|
16
|
+
inputs = (
|
|
17
|
+
"example.com\n" # domain
|
|
18
|
+
"\n" # protocols (default: all)
|
|
19
|
+
"none\n" # tunnel mode
|
|
20
|
+
"\n" # server ip (auto)
|
|
21
|
+
"react.dev\n" # anytls decoy server
|
|
22
|
+
"443\n" # anytls decoy port
|
|
23
|
+
"https://react.dev\n" # hy2 masquerade
|
|
24
|
+
"tok123\n" # CF token
|
|
25
|
+
"zone456\n" # CF zone
|
|
26
|
+
"y\n" # acme
|
|
27
|
+
"y\n" # runtime
|
|
28
|
+
"\n\n\n" # outputs
|
|
29
|
+
)
|
|
30
|
+
plan, env = run_wizard(inputs)
|
|
31
|
+
self.assertEqual(plan["domain_root"], "example.com")
|
|
32
|
+
self.assertEqual(plan["adapters"]["dns"], "cloudflare")
|
|
33
|
+
self.assertEqual(env["CF_Token"], "tok123")
|
|
34
|
+
self.assertEqual(plan["protocol_params"]["anytls"]["decoy_server"], "react.dev")
|
|
35
|
+
self.assertEqual(plan["protocol_params"]["anytls"]["decoy_port"], "443")
|
|
36
|
+
self.assertEqual(plan["protocol_params"]["hysteria2"]["masquerade"], "https://react.dev")
|
|
37
|
+
|
|
38
|
+
def test_acme_can_be_skipped_but_dns_cannot(self):
|
|
39
|
+
inputs = (
|
|
40
|
+
"example.com\n\nnone\n\n"
|
|
41
|
+
"\n443\n\n" # decoy defaults, masquerade default
|
|
42
|
+
"tok\nzone\n"
|
|
43
|
+
"n\n" # no acme
|
|
44
|
+
"y\n" # runtime
|
|
45
|
+
"\n\n\n"
|
|
46
|
+
)
|
|
47
|
+
plan, env = run_wizard(inputs)
|
|
48
|
+
self.assertEqual(plan["adapters"]["dns"], "cloudflare")
|
|
49
|
+
self.assertIsNone(plan["adapters"]["acme"])
|
|
50
|
+
self.assertEqual(plan["protocol_params"]["anytls"]["decoy_server"], "www.cloudflare.com")
|
|
51
|
+
|
|
52
|
+
def test_domain_is_required(self):
|
|
53
|
+
inputs = (
|
|
54
|
+
"\n" # empty domain -> rejected
|
|
55
|
+
"example.com\n"
|
|
56
|
+
"\n" # protocols
|
|
57
|
+
"\n" # tunnel (default proxy)
|
|
58
|
+
"\n" # ip
|
|
59
|
+
"\n443\n\n" # protocol params defaults
|
|
60
|
+
"tok\nzone\n"
|
|
61
|
+
"n\nn\n" # no acme, no runtime
|
|
62
|
+
"\n\n\n"
|
|
63
|
+
)
|
|
64
|
+
plan, env = run_wizard(inputs)
|
|
65
|
+
self.assertEqual(plan["domain_root"], "example.com")
|
|
66
|
+
self.assertEqual(plan["tunnel_mode"], "proxy")
|
|
67
|
+
self.assertIsNone(plan["adapters"]["runtime"]["systemd"])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
unittest.main()
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import io
|
|
2
|
-
import unittest
|
|
3
|
-
|
|
4
|
-
from singbox_ops.cli import interactive_plan
|
|
5
|
-
from singbox_ops.core.ui import UI
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def run_wizard(inputs):
|
|
9
|
-
reader = io.StringIO(inputs)
|
|
10
|
-
writer = io.StringIO()
|
|
11
|
-
return interactive_plan(reader=reader, writer=writer, ui=UI(writer))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class WizardTests(unittest.TestCase):
|
|
15
|
-
def test_dns_is_always_cloudflare_and_creds_are_collected(self):
|
|
16
|
-
inputs = (
|
|
17
|
-
"example.com\n" # domain
|
|
18
|
-
"\n" # protocols (default)
|
|
19
|
-
"none\n" # tunnel mode
|
|
20
|
-
"\n" # server ip (auto)
|
|
21
|
-
"tok123\n" # CF token
|
|
22
|
-
"zone456\n" # CF zone
|
|
23
|
-
"y\n" # acme
|
|
24
|
-
"y\n" # runtime adapters
|
|
25
|
-
"\n\n\n" # output paths
|
|
26
|
-
)
|
|
27
|
-
plan, env = run_wizard(inputs)
|
|
28
|
-
self.assertEqual(plan["domain_root"], "example.com")
|
|
29
|
-
self.assertEqual(plan["tunnel_mode"], "none")
|
|
30
|
-
# DNS is mandatory, never optional
|
|
31
|
-
self.assertEqual(plan["adapters"]["dns"], "cloudflare")
|
|
32
|
-
self.assertEqual(env["CF_Token"], "tok123")
|
|
33
|
-
self.assertEqual(env["CF_Zone_ID"], "zone456")
|
|
34
|
-
self.assertEqual(plan["adapters"]["acme"], "cloudflare-dns01")
|
|
35
|
-
|
|
36
|
-
def test_acme_can_be_skipped_but_dns_cannot(self):
|
|
37
|
-
inputs = (
|
|
38
|
-
"example.com\n\nnone\n\n"
|
|
39
|
-
"tok\nzone\n"
|
|
40
|
-
"n\n" # no acme
|
|
41
|
-
"y\n" # runtime
|
|
42
|
-
"\n\n\n"
|
|
43
|
-
)
|
|
44
|
-
plan, env = run_wizard(inputs)
|
|
45
|
-
self.assertEqual(plan["adapters"]["dns"], "cloudflare")
|
|
46
|
-
self.assertIsNone(plan["adapters"]["acme"])
|
|
47
|
-
self.assertEqual(plan["adapters"]["runtime"]["auto_update"], "auto-update")
|
|
48
|
-
|
|
49
|
-
def test_domain_is_required(self):
|
|
50
|
-
inputs = (
|
|
51
|
-
"\n" # empty domain -> rejected
|
|
52
|
-
"example.com\n"
|
|
53
|
-
"\n" # protocols
|
|
54
|
-
"\n" # tunnel (default proxy)
|
|
55
|
-
"\n" # ip
|
|
56
|
-
"tok\nzone\n"
|
|
57
|
-
"n\nn\n" # no acme, no runtime
|
|
58
|
-
"\n\n\n"
|
|
59
|
-
)
|
|
60
|
-
plan, env = run_wizard(inputs)
|
|
61
|
-
self.assertEqual(plan["domain_root"], "example.com")
|
|
62
|
-
self.assertEqual(plan["tunnel_mode"], "proxy")
|
|
63
|
-
self.assertIsNone(plan["adapters"]["runtime"]["systemd"])
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if __name__ == "__main__":
|
|
67
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/interactive.py
RENAMED
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/__init__.py
RENAMED
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/devconfig_gen/providers/custom.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/acme/base.py
RENAMED
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/acme/prune.py
RENAMED
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/base.py
RENAMED
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/dns/__init__.py
RENAMED
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/dns/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/adapters/secrets/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/src/singbox_ops/core/orchestrator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_orchestrator.py
RENAMED
|
File without changes
|
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_runtime.py
RENAMED
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_secrets.py
RENAMED
|
File without changes
|
{devconfig_gen_singbox-2.1.7 → devconfig_gen_singbox-2.1.8}/tests/test_singbox_ops_state_export.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|