mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Add address capture deploy apply stage
This commit is contained in:
@@ -62,6 +62,7 @@ python3 -m ampg --config gateway.toml apply --dry-run --profile vps-full
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage state --dry-run --profile vps-full
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage supervisor --dry-run --profile vps-full
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage start --dry-run --profile vps-full
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage addresses --dry-run --profile vps-full
|
||||
```
|
||||
|
||||
`init site` writes a readable `gateway.toml` with selected transports enabled and common
|
||||
@@ -71,7 +72,8 @@ reachability hints. `apply --dry-run` prints the activation sequence and a prefl
|
||||
verdict without changing services. `deploy apply --stage state` copies approved
|
||||
managed-daemon config into AMPG-owned state. `deploy apply --stage supervisor` installs
|
||||
approved supervisor files after state exists. `deploy apply --stage start` starts
|
||||
AMPG-owned services after state and supervisor files are present.
|
||||
AMPG-owned services after state and supervisor files are present. `deploy apply --stage
|
||||
addresses` captures daemon-written public addresses into the address registry.
|
||||
|
||||
## Interaction tiers
|
||||
|
||||
@@ -141,6 +143,8 @@ python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage supe
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage supervisor --profile mobile-i2p --yes
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage start --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage start --profile mobile-i2p --yes
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage addresses --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage addresses --profile mobile-i2p --yes
|
||||
python3 -m ampg --config examples/wownero.gateway.toml build
|
||||
python3 -m ampg --config examples/wownero.gateway.toml build --profile tor-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml build --protocol tor --protocol i2p
|
||||
@@ -174,8 +178,9 @@ platform supervisor files, but are not installed or started. Managed daemon conf
|
||||
runtime state at `gateway.state_dir`.
|
||||
|
||||
Captured transport addresses are written under `gateway.state_dir` by
|
||||
`addresses capture` or `addresses set`. Fixture manifests and health plans use explicit
|
||||
config first, then captured addresses, then deterministic placeholders.
|
||||
`deploy apply --stage addresses`, `addresses capture`, or `addresses set`. Fixture
|
||||
manifests and health plans use explicit config first, then captured addresses, then
|
||||
deterministic placeholders.
|
||||
|
||||
`approvals list` reports whether generated artifact digests are missing, stale,
|
||||
unapproved, or approved. `approvals approve` records reviewed digests under
|
||||
@@ -204,6 +209,10 @@ requires `--yes`; it runs only structured platform commands for AMPG-named servi
|
||||
does not install packages, rewrite config, delete files, capture addresses, or run health
|
||||
checks.
|
||||
|
||||
`deploy apply --stage addresses --dry-run` shows daemon-written public addresses that can
|
||||
be recorded. The live form requires `--yes`; it writes captured addresses to
|
||||
`gateway.state_dir` and leaves health verification for the next stage.
|
||||
|
||||
`dns plan --free-domain-hints` prints optional community subdomain services that may help
|
||||
new users get a clearnet name without buying a domain. These are third-party registries;
|
||||
operators still need to review current terms, availability, content rules, and DNS
|
||||
@@ -232,4 +241,5 @@ Explicit `--protocol` and `--platform` flags override the profile when present.
|
||||
- [ ] Run `ampg deploy apply --stage state --dry-run`; confirm approved state copies.
|
||||
- [ ] Run `ampg deploy apply --stage supervisor --dry-run`; confirm approved service files.
|
||||
- [ ] Run `ampg deploy apply --stage start --dry-run`; confirm service-manager commands.
|
||||
- [ ] Run `ampg deploy apply --stage addresses --dry-run`; confirm captured transport addresses.
|
||||
- [ ] Run live apply after the preflight gate is ready.
|
||||
|
||||
+24
-10
@@ -185,27 +185,41 @@ def _protocol_specific_configured_url(protocol: ProtocolConfig) -> Any:
|
||||
return None
|
||||
|
||||
|
||||
def capture_addresses(config: GatewayConfig) -> list[AddressCaptureResult]:
|
||||
records = load_address_registry(config)
|
||||
def capture_address_results(config: GatewayConfig) -> list[AddressCaptureResult]:
|
||||
results: list[AddressCaptureResult] = []
|
||||
changed = False
|
||||
for site in config.sites:
|
||||
for protocol in site.protocols.values():
|
||||
if not protocol.enabled:
|
||||
continue
|
||||
result = _capture_protocol_address(config, site, protocol)
|
||||
results.append(result)
|
||||
if result.status == "captured":
|
||||
records[(site.id, protocol.name)] = AddressRecord(
|
||||
site_id=site.id,
|
||||
protocol=protocol.name,
|
||||
results.append(_capture_protocol_address(config, site, protocol))
|
||||
return results
|
||||
|
||||
|
||||
def write_captured_address_results(
|
||||
config: GatewayConfig,
|
||||
results: list[AddressCaptureResult],
|
||||
) -> Path | None:
|
||||
records = load_address_registry(config)
|
||||
changed = False
|
||||
for result in results:
|
||||
if result.status != "captured":
|
||||
continue
|
||||
records[(result.site_id, result.protocol)] = AddressRecord(
|
||||
site_id=result.site_id,
|
||||
protocol=result.protocol,
|
||||
url=result.url,
|
||||
address_status="captured",
|
||||
source=result.source,
|
||||
)
|
||||
changed = True
|
||||
if changed:
|
||||
write_address_registry(config, records)
|
||||
return write_address_registry(config, records)
|
||||
return None
|
||||
|
||||
|
||||
def capture_addresses(config: GatewayConfig) -> list[AddressCaptureResult]:
|
||||
results = capture_address_results(config)
|
||||
write_captured_address_results(config, results)
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ import shutil
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
from .addresses import (
|
||||
AddressCaptureResult,
|
||||
capture_address_results,
|
||||
write_captured_address_results,
|
||||
)
|
||||
from .config import GatewayConfig
|
||||
from .install_plan import (
|
||||
InstallStateCopy,
|
||||
@@ -70,6 +75,18 @@ class StartApplyResult:
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AddressApplyResult:
|
||||
site_id: str
|
||||
protocol: str
|
||||
status: str
|
||||
mode: str
|
||||
url: str
|
||||
source: str
|
||||
path: Path | None
|
||||
message: str
|
||||
|
||||
|
||||
CommandRunner = Callable[[tuple[str, ...]], CommandRunResult]
|
||||
|
||||
|
||||
@@ -253,6 +270,17 @@ def apply_start(
|
||||
return results
|
||||
|
||||
|
||||
def apply_addresses(
|
||||
config: GatewayConfig,
|
||||
*,
|
||||
dry_run: bool,
|
||||
) -> list[AddressApplyResult]:
|
||||
captures = capture_address_results(config)
|
||||
if not dry_run:
|
||||
write_captured_address_results(config, captures)
|
||||
return [_address_result(result, dry_run=dry_run) for result in captures]
|
||||
|
||||
|
||||
def _validate_state_copy(
|
||||
config: GatewayConfig,
|
||||
copy: InstallStateCopy,
|
||||
@@ -452,6 +480,36 @@ def _validate_start_action(
|
||||
)
|
||||
|
||||
|
||||
def _address_result(
|
||||
result: AddressCaptureResult,
|
||||
*,
|
||||
dry_run: bool,
|
||||
) -> AddressApplyResult:
|
||||
if result.status == "captured":
|
||||
status = "planned" if dry_run else "written"
|
||||
message = (
|
||||
"would record captured public transport address"
|
||||
if dry_run
|
||||
else "recorded captured public transport address"
|
||||
)
|
||||
elif result.status == "missing":
|
||||
status = "blocked"
|
||||
message = result.message
|
||||
else:
|
||||
status = "skipped"
|
||||
message = result.message
|
||||
return AddressApplyResult(
|
||||
site_id=result.site_id,
|
||||
protocol=result.protocol,
|
||||
status=status,
|
||||
mode="dry-run" if dry_run else "live",
|
||||
url=result.url,
|
||||
source=result.source,
|
||||
path=result.path,
|
||||
message=message,
|
||||
)
|
||||
|
||||
|
||||
def _result(
|
||||
copy: InstallStateCopy,
|
||||
*,
|
||||
|
||||
+38
-1
@@ -22,9 +22,11 @@ from .addresses import (
|
||||
set_address,
|
||||
)
|
||||
from .apply import (
|
||||
AddressApplyResult,
|
||||
StateApplyResult,
|
||||
StartApplyResult,
|
||||
SupervisorApplyResult,
|
||||
apply_addresses,
|
||||
apply_state,
|
||||
apply_start,
|
||||
apply_supervisor,
|
||||
@@ -160,7 +162,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
_add_target_selection(deploy_apply_parser)
|
||||
deploy_apply_parser.add_argument(
|
||||
"--stage",
|
||||
choices=("state", "supervisor", "start"),
|
||||
choices=("state", "supervisor", "start", "addresses"),
|
||||
required=True,
|
||||
help="Deployment stage to apply.",
|
||||
)
|
||||
@@ -642,6 +644,26 @@ def _cmd_deploy(config, args) -> int:
|
||||
f"blocked={len(blocked)}"
|
||||
)
|
||||
return 1 if blocked else 0
|
||||
if args.stage == "addresses":
|
||||
results = apply_addresses(config, dry_run=args.dry_run)
|
||||
for result in results:
|
||||
_print_address_apply_result(result)
|
||||
blocked = [result for result in results if result.status == "blocked"]
|
||||
written = [result for result in results if result.status == "written"]
|
||||
planned = [result for result in results if result.status == "planned"]
|
||||
skipped = [result for result in results if result.status == "skipped"]
|
||||
print(
|
||||
"AMPG_DEPLOY_APPLY_SUMMARY "
|
||||
f"stage=addresses "
|
||||
f"mode={'dry-run' if args.dry_run else 'live'} "
|
||||
f"results={len(results)} "
|
||||
f"planned={len(planned)} "
|
||||
f"written={len(written)} "
|
||||
f"skipped={len(skipped)} "
|
||||
f"blocked={len(blocked)} "
|
||||
f"registry=\"{_quote(str(address_registry_path(config)))}\""
|
||||
)
|
||||
return 1 if blocked else 0
|
||||
return 1
|
||||
|
||||
|
||||
@@ -1165,6 +1187,21 @@ def _print_start_apply_result(result: StartApplyResult) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _print_address_apply_result(result: AddressApplyResult) -> None:
|
||||
path = str(result.path) if result.path else "-"
|
||||
print(
|
||||
"AMPG_DEPLOY_ADDRESS "
|
||||
f"site={result.site_id} "
|
||||
f"protocol={result.protocol} "
|
||||
f"mode={result.mode} "
|
||||
f"status={result.status} "
|
||||
f"url=\"{_quote(result.url)}\" "
|
||||
f"source=\"{_quote(result.source)}\" "
|
||||
f"path=\"{_quote(path)}\" "
|
||||
f"message=\"{_quote(result.message)}\""
|
||||
)
|
||||
|
||||
|
||||
def _print_dns_record(record: DNSRecordPlan) -> None:
|
||||
print(
|
||||
"AMPG_DNS_RECORD "
|
||||
|
||||
+2
-2
@@ -82,7 +82,6 @@ def deploy_plan(
|
||||
daemon_probe=daemon_probe,
|
||||
),
|
||||
_artifact_step(config, command_context, platform_provider=platform_provider),
|
||||
_address_step(config, command_context),
|
||||
_apply_preflight_step(
|
||||
config,
|
||||
command_context,
|
||||
@@ -95,6 +94,7 @@ def deploy_plan(
|
||||
platform_provider=platform_provider,
|
||||
daemon_probe=daemon_probe,
|
||||
),
|
||||
_address_step(config, command_context),
|
||||
)
|
||||
status = _overall_status(steps)
|
||||
return DeployPlan(
|
||||
@@ -273,7 +273,7 @@ def _address_step(config: GatewayConfig, commands: _CommandContext) -> DeploySte
|
||||
return DeployStep(
|
||||
"addresses",
|
||||
"review",
|
||||
commands.command("addresses capture"),
|
||||
commands.command("deploy apply --stage addresses --dry-run"),
|
||||
f"capture or set generated transport address(es): {names}",
|
||||
)
|
||||
return DeployStep("addresses", "ready", commands.command("addresses list"), "addresses are known")
|
||||
|
||||
@@ -142,6 +142,10 @@ after the state files they reference exist. It does not start or reload services
|
||||
services after state and supervisor files exist. Address capture and health verification
|
||||
remain separate stages.
|
||||
|
||||
`ampg deploy apply --stage addresses` records daemon-written public addresses after the
|
||||
transport has produced them. It writes only the AMPG address registry; health
|
||||
verification remains a separate stage.
|
||||
|
||||
The dry-run command is intentionally stricter than `doctor`: missing generated output is
|
||||
blocked during activation, because AMPG must not point a transport at an unbuilt output
|
||||
root.
|
||||
|
||||
@@ -62,6 +62,8 @@ python3 -m ampg --config gateway.toml deploy apply --stage supervisor --dry-run
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage supervisor --profile mobile-i2p --yes
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage start --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage start --profile mobile-i2p --yes
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage addresses --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config gateway.toml deploy apply --stage addresses --profile mobile-i2p --yes
|
||||
python3 -m ampg --config gateway.toml doctor --platform android-termux
|
||||
```
|
||||
|
||||
@@ -99,9 +101,9 @@ daemon-written addresses under `gateway.state_dir`.
|
||||
which paths are AMPG-owned, daemon-written, required, or sensitive.
|
||||
|
||||
`addresses list` prints the effective public address for each enabled protocol:
|
||||
configured, captured, derived, or placeholder. `addresses capture` reads daemon-written
|
||||
address files from AMPG state or a protocol `address_file` override, then updates the
|
||||
address registry under `state_dir`.
|
||||
configured, captured, derived, or placeholder. `deploy apply --stage addresses` and
|
||||
`addresses capture` read daemon-written address files from AMPG state or a protocol
|
||||
`address_file` override, then update the address registry under `state_dir`.
|
||||
|
||||
`health-plan` prints fixture-based checks to run after services start. Published checks
|
||||
target real transport URLs; preview checks target local loopback preview endpoints.
|
||||
@@ -139,6 +141,11 @@ requires both state and supervisor files to exist, and runs only structured plat
|
||||
commands for AMPG-named services. It does not install packages, rewrite config, remove
|
||||
files, capture addresses, or run health checks.
|
||||
|
||||
`deploy apply --stage addresses` is the fourth live apply stage. Dry-run mode prints
|
||||
`AMPG_DEPLOY_ADDRESS` rows for configured, skipped, missing, and capturable addresses.
|
||||
Live mode requires `--yes` and writes captured transport addresses under
|
||||
`gateway.state_dir`. It does not rewrite daemon config or run health checks.
|
||||
|
||||
`--protocol` scopes operational commands to selected enabled protocols. A clearnet
|
||||
`adopt` failure will block a full activation run, but it will not block
|
||||
`apply --dry-run --protocol i2p` because clearnet is outside that selected run.
|
||||
|
||||
@@ -33,6 +33,7 @@ or deployment notes.
|
||||
python3 -m ampg --config examples/wownero.gateway.toml build
|
||||
python3 -m ampg --config examples/wownero.gateway.toml manifest
|
||||
python3 -m ampg --config examples/wownero.gateway.toml addresses list
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage addresses --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml addresses capture --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml preview manifest
|
||||
python3 -m ampg --config examples/wownero.gateway.toml health-plan
|
||||
@@ -62,10 +63,11 @@ Some transports may not have final public addresses when a fixture is generated.
|
||||
cases AMPG uses route-valid placeholders such as `http://wownero.onion/` so AMPB can still
|
||||
verify transport selection without contacting the network.
|
||||
|
||||
When a managed or adopted daemon writes an actual address, run `addresses capture`.
|
||||
Captured addresses are stored under `gateway.state_dir` and replace placeholders in
|
||||
fixture manifests and published health plans. Operators can also use `addresses set` to
|
||||
store an address manually.
|
||||
When a managed or adopted daemon writes an actual address, run
|
||||
`deploy apply --stage addresses --dry-run` before recording it. Captured addresses are
|
||||
stored under `gateway.state_dir` and replace placeholders in fixture manifests and
|
||||
published health plans. Operators can also use `addresses capture` for a direct write or
|
||||
`addresses set` to store an address manually.
|
||||
|
||||
## Interaction Policy
|
||||
|
||||
|
||||
@@ -61,3 +61,7 @@ explicit stage.
|
||||
`deploy apply --stage start` runs the service-manager start commands for AMPG-named
|
||||
services only after required state and supervisor files exist. It does not write or
|
||||
delete state files.
|
||||
|
||||
`deploy apply --stage addresses` reads daemon-written address files from the state
|
||||
contract and writes captured public addresses to `addresses.json` under
|
||||
`gateway.state_dir`.
|
||||
|
||||
@@ -141,8 +141,8 @@ daemon_policy = "auto"
|
||||
- `ampg build` writes a fixture manifest that AMPB can check.
|
||||
- `ampg preview manifest` writes loopback fixture URLs that AMPB can check before live
|
||||
transport daemons are installed or adopted.
|
||||
- `ampg deploy apply --stage state`, `--stage supervisor`, and `--stage start` stage
|
||||
Wownero transport services without mixing config writes and service start.
|
||||
- `ampg deploy apply --stage state`, `--stage supervisor`, `--stage start`, and
|
||||
`--stage addresses` stage Wownero transport publication without mixing concerns.
|
||||
- Tor output contains no `<script>` tags or inline event handlers.
|
||||
- Generated Gemini output has readable headings, paragraphs, and links.
|
||||
- Generated Micron output fits terminal-first browsing.
|
||||
@@ -167,6 +167,7 @@ python3 -m ampg --config examples/wownero.gateway.toml approvals approve --profi
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage state --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage supervisor --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage start --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/wownero.gateway.toml deploy apply --stage addresses --dry-run --profile mobile-i2p
|
||||
python3 -m ampg --config examples/i2p-only.gateway.toml plan
|
||||
python3 -m ampg --config examples/i2p-only.gateway.toml build
|
||||
```
|
||||
@@ -180,3 +181,4 @@ python3 -m ampg --config examples/i2p-only.gateway.toml build
|
||||
- [ ] Add Reticulum/Micron conversion fixtures.
|
||||
- [ ] Enable I2P and Reticulum daemon management only after static outputs are stable.
|
||||
- [ ] Start only AMPG-owned Wownero transport services after reviewed files are applied.
|
||||
- [ ] Capture Wownero transport addresses before published health checks.
|
||||
|
||||
@@ -4,6 +4,7 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from ampg.addresses import address_registry_path
|
||||
from ampg.apply import CommandRunResult, apply_start, apply_state, apply_supervisor
|
||||
from ampg.cli import main
|
||||
from ampg.config import load_config
|
||||
@@ -148,6 +149,13 @@ def _run_termux_supervisor_apply(config_path: Path, root: Path) -> None:
|
||||
raise AssertionError(blocked[0].message)
|
||||
|
||||
|
||||
def _write_i2p_address_file(root: Path, value: str = "captureexample.b32.i2p") -> Path:
|
||||
path = root / ".ampg/state/example/i2p/b32.txt"
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(value + "\n", encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
class DeployApplyTest(unittest.TestCase):
|
||||
def test_deploy_apply_state_blocks_unapproved_artifacts(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
@@ -479,6 +487,85 @@ class DeployApplyTest(unittest.TestCase):
|
||||
self.assertIn(("sv-enable", "ampg-example-i2p-i2pd"), commands)
|
||||
self.assertIn(("sv-enable", "ampg-example-i2p-nginx-loopback"), commands)
|
||||
|
||||
def test_deploy_apply_addresses_blocks_when_address_is_missing(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp).resolve()
|
||||
config_path = _init_i2p_config(root)
|
||||
|
||||
status, output, error = _run_cli(
|
||||
[
|
||||
"--config",
|
||||
str(config_path),
|
||||
"deploy",
|
||||
"apply",
|
||||
"--stage",
|
||||
"addresses",
|
||||
"--dry-run",
|
||||
"--profile",
|
||||
"mobile-i2p",
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(1, status)
|
||||
self.assertEqual("", error)
|
||||
self.assertIn("AMPG_DEPLOY_ADDRESS", output)
|
||||
self.assertIn("status=blocked", output)
|
||||
self.assertIn("no address file found", output)
|
||||
|
||||
def test_deploy_apply_addresses_dry_run_does_not_write_registry(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp).resolve()
|
||||
config_path = _init_i2p_config(root)
|
||||
_write_i2p_address_file(root)
|
||||
config = load_config(config_path)
|
||||
|
||||
status, output, error = _run_cli(
|
||||
[
|
||||
"--config",
|
||||
str(config_path),
|
||||
"deploy",
|
||||
"apply",
|
||||
"--stage",
|
||||
"addresses",
|
||||
"--dry-run",
|
||||
"--profile",
|
||||
"mobile-i2p",
|
||||
]
|
||||
)
|
||||
registry_exists = address_registry_path(config).exists()
|
||||
|
||||
self.assertEqual(0, status, error)
|
||||
self.assertIn("AMPG_DEPLOY_ADDRESS", output)
|
||||
self.assertIn("status=planned", output)
|
||||
self.assertIn("http://captureexample.b32.i2p/", output)
|
||||
self.assertFalse(registry_exists)
|
||||
|
||||
def test_deploy_apply_addresses_live_writes_registry(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp).resolve()
|
||||
config_path = _init_i2p_config(root)
|
||||
_write_i2p_address_file(root)
|
||||
config = load_config(config_path)
|
||||
|
||||
status, output, error = _run_cli(
|
||||
[
|
||||
"--config",
|
||||
str(config_path),
|
||||
"deploy",
|
||||
"apply",
|
||||
"--stage",
|
||||
"addresses",
|
||||
"--profile",
|
||||
"mobile-i2p",
|
||||
"--yes",
|
||||
]
|
||||
)
|
||||
registry_text = address_registry_path(config).read_text(encoding="utf-8")
|
||||
|
||||
self.assertEqual(0, status, error)
|
||||
self.assertIn("status=written", output)
|
||||
self.assertIn("http://captureexample.b32.i2p/", registry_text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -63,6 +63,7 @@ class DeployPlanTest(unittest.TestCase):
|
||||
self.assertIn("AMPG_DEPLOY_STEP stage=artifacts status=todo", output)
|
||||
self.assertIn("AMPG_DEPLOY_NEXT step=1 stage=build", output)
|
||||
self.assertIn("build --profile mobile-i2p", output)
|
||||
self.assertIn("deploy apply --stage addresses --dry-run --profile mobile-i2p", output)
|
||||
|
||||
def test_deploy_plan_shows_dns_review_for_clearnet_site(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
|
||||
Reference in New Issue
Block a user