mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 15:41:41 +02:00
Generate daemon plan artifacts
This commit is contained in:
@@ -67,6 +67,11 @@ def _render_privacy_html_tree(
|
||||
files_skipped = 0
|
||||
stats = PrivacyRenderStats()
|
||||
max_asset_bytes = int(protocol.options.get("max_asset_bytes", 1024 * 1024))
|
||||
script_policy = str(protocol.options.get("script_policy", "strip"))
|
||||
if script_policy != "strip":
|
||||
raise ValueError(
|
||||
f"{site.id}/{protocol.name}: privacy-html only supports script_policy='strip'"
|
||||
)
|
||||
for source_path in _iter_source_files(site.source.path):
|
||||
target_path = output_root / source_path.relative_to(site.source.path)
|
||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
+15
-4
@@ -7,7 +7,7 @@ import sys
|
||||
from .build import build_gateway
|
||||
from .config import load_config
|
||||
from .docsgen import generate_docs
|
||||
from .plan import plan_gateway
|
||||
from .plan import plan_gateway, write_plan_artifacts
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
@@ -19,7 +19,12 @@ def main(argv: list[str] | None = None) -> int:
|
||||
help="Path to gateway TOML config.",
|
||||
)
|
||||
subcommands = parser.add_subparsers(dest="command", required=True)
|
||||
subcommands.add_parser("plan", help="Print a dry-run plan.")
|
||||
plan_parser = subcommands.add_parser("plan", help="Print a dry-run plan.")
|
||||
plan_parser.add_argument(
|
||||
"--write-artifacts",
|
||||
action="store_true",
|
||||
help="Write generated config snippets to the configured plan root.",
|
||||
)
|
||||
subcommands.add_parser("build", help="Build enabled protocol outputs.")
|
||||
docs_parser = subcommands.add_parser("docs", help="Generate or check generated docs.")
|
||||
docs_subcommands = docs_parser.add_subparsers(dest="docs_command", required=True)
|
||||
@@ -37,7 +42,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return _cmd_docs(args)
|
||||
config = load_config(args.config)
|
||||
if args.command == "plan":
|
||||
return _cmd_plan(config)
|
||||
return _cmd_plan(config, write_artifacts=args.write_artifacts)
|
||||
if args.command == "build":
|
||||
return _cmd_build(config)
|
||||
except Exception as exc: # noqa: BLE001 - CLI should print concise failures.
|
||||
@@ -46,8 +51,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _cmd_plan(config) -> int:
|
||||
def _cmd_plan(config, *, write_artifacts: bool) -> int:
|
||||
for line in plan_gateway(config):
|
||||
artifact_text = ",".join(str(path) for path in line.artifacts) if line.artifacts else "-"
|
||||
print(
|
||||
"AMPG_PLAN "
|
||||
f"site={line.site_id} "
|
||||
@@ -56,8 +62,13 @@ def _cmd_plan(config) -> int:
|
||||
f"output={line.output_root} "
|
||||
f"daemon={line.daemon} "
|
||||
f"policy={line.daemon_policy} "
|
||||
f"artifacts={artifact_text} "
|
||||
f"action=\"{line.action}\""
|
||||
)
|
||||
if write_artifacts:
|
||||
artifacts = write_plan_artifacts(config)
|
||||
for artifact in artifacts:
|
||||
print(f"AMPG_ARTIFACT path={artifact.path}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -33,6 +33,7 @@ class SourceConfig:
|
||||
@dataclass(frozen=True)
|
||||
class OutputConfig:
|
||||
root: Path
|
||||
plan_root: Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -82,6 +83,7 @@ def _parse_site(raw_site: dict[str, Any], base_dir: Path) -> SiteConfig:
|
||||
|
||||
source_path = _resolve_path(base_dir, _required_str(source, "path"))
|
||||
output_root = _resolve_path(base_dir, _required_str(outputs, "root"))
|
||||
plan_root = _resolve_path(base_dir, str(outputs.get("plan_root", "../dist/ampg-plan")))
|
||||
|
||||
parsed_protocols = {
|
||||
name: _parse_protocol(name, raw_protocol)
|
||||
@@ -97,7 +99,7 @@ def _parse_site(raw_site: dict[str, Any], base_dir: Path) -> SiteConfig:
|
||||
path=source_path,
|
||||
canonical_url=source.get("canonical_url"),
|
||||
),
|
||||
outputs=OutputConfig(root=output_root),
|
||||
outputs=OutputConfig(root=output_root, plan_root=plan_root),
|
||||
protocols=parsed_protocols,
|
||||
)
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ CONFIG_FIELDS = (
|
||||
ConfigField("site.source", "path", "path", True, "", "Source tree path."),
|
||||
ConfigField("site.source", "canonical_url", "url", False, "", "Canonical clearnet URL."),
|
||||
ConfigField("site.outputs", "root", "path", True, "", "Generated output root."),
|
||||
ConfigField("site.outputs", "plan_root", "path", False, "../dist/ampg-plan", "Generated plan artifact root."),
|
||||
ConfigField(
|
||||
"site.interactions",
|
||||
"default_tier",
|
||||
@@ -88,6 +89,22 @@ CONFIG_FIELDS = (
|
||||
"protocol default",
|
||||
"Whether AMPG adopts, manages, or only renders config.",
|
||||
),
|
||||
ConfigField(
|
||||
"site.protocols.<name>",
|
||||
"max_asset_bytes",
|
||||
"integer",
|
||||
False,
|
||||
"1048576",
|
||||
"Maximum asset size copied by privacy-html render targets.",
|
||||
),
|
||||
ConfigField(
|
||||
"site.protocols.<name>",
|
||||
"script_policy",
|
||||
"enum",
|
||||
False,
|
||||
"strip",
|
||||
"Script handling policy for privacy-html render targets.",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
+109
-1
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .config import GatewayConfig
|
||||
from .config import GatewayConfig, ProtocolConfig, SiteConfig
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -15,6 +15,13 @@ class PlanLine:
|
||||
daemon: str
|
||||
daemon_policy: str
|
||||
action: str
|
||||
artifacts: tuple[Path, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PlanArtifact:
|
||||
path: Path
|
||||
content: str
|
||||
|
||||
|
||||
def plan_gateway(config: GatewayConfig) -> list[PlanLine]:
|
||||
@@ -23,6 +30,7 @@ def plan_gateway(config: GatewayConfig) -> list[PlanLine]:
|
||||
for protocol in site.protocols.values():
|
||||
if not protocol.enabled:
|
||||
continue
|
||||
artifacts = tuple(artifact.path for artifact in plan_artifacts(site, protocol))
|
||||
lines.append(
|
||||
PlanLine(
|
||||
site_id=site.id,
|
||||
@@ -32,11 +40,43 @@ def plan_gateway(config: GatewayConfig) -> list[PlanLine]:
|
||||
daemon=protocol.daemon,
|
||||
daemon_policy=protocol.daemon_policy,
|
||||
action=_action_for_policy(protocol.daemon_policy),
|
||||
artifacts=artifacts,
|
||||
)
|
||||
)
|
||||
return lines
|
||||
|
||||
|
||||
def write_plan_artifacts(config: GatewayConfig) -> list[PlanArtifact]:
|
||||
artifacts: list[PlanArtifact] = []
|
||||
for site in config.sites:
|
||||
for protocol in site.protocols.values():
|
||||
if not protocol.enabled:
|
||||
continue
|
||||
artifacts.extend(plan_artifacts(site, protocol))
|
||||
for artifact in artifacts:
|
||||
artifact.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
artifact.path.write_text(artifact.content, encoding="utf-8")
|
||||
return artifacts
|
||||
|
||||
|
||||
def plan_artifacts(site: SiteConfig, protocol: ProtocolConfig) -> list[PlanArtifact]:
|
||||
if protocol.name == "clearnet" and protocol.daemon == "nginx":
|
||||
return [_nginx_clearnet_artifact(site, protocol)]
|
||||
if protocol.name == "tor":
|
||||
artifacts = []
|
||||
if protocol.daemon == "tor":
|
||||
artifacts.append(_tor_artifact(site, protocol))
|
||||
artifacts.append(_nginx_loopback_artifact(site, protocol))
|
||||
return artifacts
|
||||
if protocol.name == "i2p":
|
||||
artifacts = []
|
||||
if protocol.daemon == "i2pd":
|
||||
artifacts.append(_i2pd_artifact(site, protocol))
|
||||
artifacts.append(_nginx_loopback_artifact(site, protocol))
|
||||
return artifacts
|
||||
return []
|
||||
|
||||
|
||||
def _action_for_policy(policy: str) -> str:
|
||||
if policy == "external":
|
||||
return "render outputs and config snippets only"
|
||||
@@ -47,3 +87,71 @@ def _action_for_policy(policy: str) -> str:
|
||||
if policy == "auto":
|
||||
return "adopt healthy daemon, otherwise manage AMPG-owned instance"
|
||||
return "unknown policy; no apply support"
|
||||
|
||||
|
||||
def _protocol_plan_dir(site: SiteConfig, protocol: ProtocolConfig) -> Path:
|
||||
return site.outputs.plan_root / site.id / protocol.name
|
||||
|
||||
|
||||
def _nginx_clearnet_artifact(site: SiteConfig, protocol: ProtocolConfig) -> PlanArtifact:
|
||||
path = _protocol_plan_dir(site, protocol) / "nginx-server.conf"
|
||||
content = f"""# Generated by AMPG. Review before installing.
|
||||
server {{
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {site.domain} www.{site.domain};
|
||||
root {site.outputs.root / protocol.name};
|
||||
index index.html;
|
||||
|
||||
location / {{
|
||||
try_files $uri $uri/ /index.html;
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
return PlanArtifact(path=path, content=content)
|
||||
|
||||
|
||||
def _nginx_loopback_artifact(site: SiteConfig, protocol: ProtocolConfig) -> PlanArtifact:
|
||||
port = int(protocol.options.get("local_port", 18080 if protocol.name == "tor" else 18081))
|
||||
path = _protocol_plan_dir(site, protocol) / "nginx-loopback.conf"
|
||||
content = f"""# Generated by AMPG. Review before installing.
|
||||
server {{
|
||||
listen 127.0.0.1:{port};
|
||||
server_name _;
|
||||
root {site.outputs.root / protocol.name};
|
||||
index index.html;
|
||||
|
||||
location / {{
|
||||
try_files $uri $uri/ /index.html;
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
return PlanArtifact(path=path, content=content)
|
||||
|
||||
|
||||
def _tor_artifact(site: SiteConfig, protocol: ProtocolConfig) -> PlanArtifact:
|
||||
port = int(protocol.options.get("local_port", 18080))
|
||||
hidden_service_dir = protocol.options.get(
|
||||
"hidden_service_dir", f"/var/lib/ampg/tor/{site.id}"
|
||||
)
|
||||
path = _protocol_plan_dir(site, protocol) / "torrc-snippet.conf"
|
||||
content = f"""# Generated by AMPG. Review before installing.
|
||||
HiddenServiceDir {hidden_service_dir}/
|
||||
HiddenServicePort 80 127.0.0.1:{port}
|
||||
"""
|
||||
return PlanArtifact(path=path, content=content)
|
||||
|
||||
|
||||
def _i2pd_artifact(site: SiteConfig, protocol: ProtocolConfig) -> PlanArtifact:
|
||||
port = int(protocol.options.get("local_port", 18081))
|
||||
key_file = protocol.options.get("keys_file", f"{site.id}-web.dat")
|
||||
tunnel_name = protocol.options.get("tunnel_name", f"{site.id}-web")
|
||||
path = _protocol_plan_dir(site, protocol) / "i2pd-tunnel.conf"
|
||||
content = f"""# Generated by AMPG. Review before installing.
|
||||
[{tunnel_name}]
|
||||
type = server
|
||||
host = 127.0.0.1
|
||||
port = {port}
|
||||
keys = {key_file}
|
||||
"""
|
||||
return PlanArtifact(path=path, content=content)
|
||||
|
||||
Reference in New Issue
Block a user