mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Add free domain DNS onboarding hints
This commit is contained in:
@@ -52,6 +52,7 @@ python3 -m ampg --config gateway.toml init site wownero \
|
|||||||
|
|
||||||
python3 -m ampg --config gateway.toml deploy plan --profile vps-full
|
python3 -m ampg --config gateway.toml deploy plan --profile vps-full
|
||||||
python3 -m ampg --config gateway.toml dns plan --mode dynamic --behind-router
|
python3 -m ampg --config gateway.toml dns plan --mode dynamic --behind-router
|
||||||
|
python3 -m ampg --config gateway.toml dns plan --free-domain-hints
|
||||||
python3 -m ampg --config gateway.toml build
|
python3 -m ampg --config gateway.toml build
|
||||||
python3 -m ampg --config gateway.toml doctor
|
python3 -m ampg --config gateway.toml doctor
|
||||||
python3 -m ampg --config gateway.toml install-plan --profile vps-full --write-artifacts
|
python3 -m ampg --config gateway.toml install-plan --profile vps-full --write-artifacts
|
||||||
@@ -91,6 +92,7 @@ API remains internal even when the public site is available on Tor or I2P.
|
|||||||
- [Architecture](docs/architecture.md)
|
- [Architecture](docs/architecture.md)
|
||||||
- [Configuration](docs/configuration.md)
|
- [Configuration](docs/configuration.md)
|
||||||
- [Daemon management](docs/daemon-management.md)
|
- [Daemon management](docs/daemon-management.md)
|
||||||
|
- [Domain onboarding](docs/domain-onboarding.md)
|
||||||
- [Fixture manifests](docs/fixture-manifests.md)
|
- [Fixture manifests](docs/fixture-manifests.md)
|
||||||
- [State contract](docs/state-contract.md)
|
- [State contract](docs/state-contract.md)
|
||||||
- [Interaction capabilities](docs/interaction-capabilities.md)
|
- [Interaction capabilities](docs/interaction-capabilities.md)
|
||||||
@@ -113,6 +115,7 @@ The current implementation is dependency-free Python:
|
|||||||
python3 -m ampg --config gateway.toml init site wownero --domain wownero.org --source ../wownero.org-website --preset full
|
python3 -m ampg --config gateway.toml init site wownero --domain wownero.org --source ../wownero.org-website --preset full
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml deploy plan --profile vps-full
|
python3 -m ampg --config examples/wownero.gateway.toml deploy plan --profile vps-full
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml dns plan --profile vps-full
|
python3 -m ampg --config examples/wownero.gateway.toml dns plan --profile vps-full
|
||||||
|
python3 -m ampg --config examples/wownero.gateway.toml dns plan --profile vps-full --free-domain-hints
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml dns plan --profile vps-full --mode dynamic --behind-router
|
python3 -m ampg --config examples/wownero.gateway.toml dns plan --profile vps-full --mode dynamic --behind-router
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml dns check --profile vps-full
|
python3 -m ampg --config examples/wownero.gateway.toml dns check --profile vps-full
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml plan
|
python3 -m ampg --config examples/wownero.gateway.toml plan
|
||||||
@@ -183,6 +186,11 @@ ready to copy into `gateway.state_dir`. The live form requires `--yes`; it creat
|
|||||||
AMPG-owned state directories and copies approved artifact contents. It does not install
|
AMPG-owned state directories and copies approved artifact contents. It does not install
|
||||||
packages, change adopted daemons, start services, or remove keys.
|
packages, change adopted daemons, start services, or remove keys.
|
||||||
|
|
||||||
|
`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
|
||||||
|
record support before relying on one.
|
||||||
|
|
||||||
Use `--protocol` to scope operational commands to one or more enabled protocols. This
|
Use `--protocol` to scope operational commands to one or more enabled protocols. This
|
||||||
lets a full site config build or activate only Tor, only I2P, or a selected subset without
|
lets a full site config build or activate only Tor, only I2P, or a selected subset without
|
||||||
unselected transports blocking the run.
|
unselected transports blocking the run.
|
||||||
|
|||||||
+24
@@ -43,6 +43,7 @@ from .dns import (
|
|||||||
DNS_MODES,
|
DNS_MODES,
|
||||||
DNSPlan,
|
DNSPlan,
|
||||||
DNSRecordPlan,
|
DNSRecordPlan,
|
||||||
|
FreeDomainHint,
|
||||||
dns_check,
|
dns_check,
|
||||||
dns_plan,
|
dns_plan,
|
||||||
)
|
)
|
||||||
@@ -197,6 +198,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Include NAT/router/tunnel reachability hints.",
|
help="Include NAT/router/tunnel reachability hints.",
|
||||||
)
|
)
|
||||||
|
dns_plan_parser.add_argument(
|
||||||
|
"--free-domain-hints",
|
||||||
|
action="store_true",
|
||||||
|
help="Include optional free subdomain registries to consider.",
|
||||||
|
)
|
||||||
dns_check_parser = dns_subcommands.add_parser(
|
dns_check_parser = dns_subcommands.add_parser(
|
||||||
"check",
|
"check",
|
||||||
help="Resolve clearnet domains and compare optional expected addresses.",
|
help="Resolve clearnet domains and compare optional expected addresses.",
|
||||||
@@ -598,11 +604,14 @@ def _cmd_dns(config, args) -> int:
|
|||||||
ipv6=args.ipv6,
|
ipv6=args.ipv6,
|
||||||
dynamic_hostname=args.dynamic_hostname,
|
dynamic_hostname=args.dynamic_hostname,
|
||||||
behind_router=args.behind_router,
|
behind_router=args.behind_router,
|
||||||
|
free_domain_hints=args.free_domain_hints,
|
||||||
)
|
)
|
||||||
for record in plan.records:
|
for record in plan.records:
|
||||||
_print_dns_record(record)
|
_print_dns_record(record)
|
||||||
for hint in plan.hints:
|
for hint in plan.hints:
|
||||||
_print_connectivity_hint(hint)
|
_print_connectivity_hint(hint)
|
||||||
|
for hint in plan.free_domains:
|
||||||
|
_print_free_domain_hint(hint)
|
||||||
_print_dns_summary(plan)
|
_print_dns_summary(plan)
|
||||||
return 1 if plan.status == "blocked" else 0
|
return 1 if plan.status == "blocked" else 0
|
||||||
|
|
||||||
@@ -1094,6 +1103,20 @@ def _print_connectivity_hint(hint: ConnectivityHint) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_free_domain_hint(hint: FreeDomainHint) -> None:
|
||||||
|
print(
|
||||||
|
"AMPG_FREE_DOMAIN_HINT "
|
||||||
|
f"provider=\"{_quote(hint.provider)}\" "
|
||||||
|
f"suffixes=\"{_quote(','.join(hint.suffixes))}\" "
|
||||||
|
f"fit=\"{_quote(hint.fit)}\" "
|
||||||
|
f"records=\"{_quote(hint.records)}\" "
|
||||||
|
f"workflow=\"{_quote(hint.workflow)}\" "
|
||||||
|
f"status={hint.status} "
|
||||||
|
f"url=\"{_quote(hint.url)}\" "
|
||||||
|
f"message=\"{_quote(hint.message)}\""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _print_dns_summary(plan: DNSPlan) -> None:
|
def _print_dns_summary(plan: DNSPlan) -> None:
|
||||||
statuses = [record.status for record in plan.records]
|
statuses = [record.status for record in plan.records]
|
||||||
statuses.extend(hint.status for hint in plan.hints)
|
statuses.extend(hint.status for hint in plan.hints)
|
||||||
@@ -1102,6 +1125,7 @@ def _print_dns_summary(plan: DNSPlan) -> None:
|
|||||||
f"status={plan.status} "
|
f"status={plan.status} "
|
||||||
f"records={len(plan.records)} "
|
f"records={len(plan.records)} "
|
||||||
f"hints={len(plan.hints)} "
|
f"hints={len(plan.hints)} "
|
||||||
|
f"free_domain_hints={len(plan.free_domains)} "
|
||||||
f"todo={statuses.count('todo')} "
|
f"todo={statuses.count('todo')} "
|
||||||
f"review={statuses.count('review')} "
|
f"review={statuses.count('review')} "
|
||||||
f"blocked={statuses.count('blocked')} "
|
f"blocked={statuses.count('blocked')} "
|
||||||
|
|||||||
+72
@@ -28,11 +28,24 @@ class ConnectivityHint:
|
|||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FreeDomainHint:
|
||||||
|
provider: str
|
||||||
|
suffixes: tuple[str, ...]
|
||||||
|
fit: str
|
||||||
|
records: str
|
||||||
|
workflow: str
|
||||||
|
status: str
|
||||||
|
url: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class DNSPlan:
|
class DNSPlan:
|
||||||
status: str
|
status: str
|
||||||
records: tuple[DNSRecordPlan, ...]
|
records: tuple[DNSRecordPlan, ...]
|
||||||
hints: tuple[ConnectivityHint, ...]
|
hints: tuple[ConnectivityHint, ...]
|
||||||
|
free_domains: tuple[FreeDomainHint, ...]
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
|
||||||
@@ -55,6 +68,7 @@ def dns_plan(
|
|||||||
ipv6: str | None = None,
|
ipv6: str | None = None,
|
||||||
dynamic_hostname: str | None = None,
|
dynamic_hostname: str | None = None,
|
||||||
behind_router: bool = False,
|
behind_router: bool = False,
|
||||||
|
free_domain_hints: bool = False,
|
||||||
) -> DNSPlan:
|
) -> DNSPlan:
|
||||||
if mode not in DNS_MODES:
|
if mode not in DNS_MODES:
|
||||||
raise ValueError(f"unsupported DNS mode {mode!r}")
|
raise ValueError(f"unsupported DNS mode {mode!r}")
|
||||||
@@ -64,6 +78,7 @@ def dns_plan(
|
|||||||
status="skipped",
|
status="skipped",
|
||||||
records=(),
|
records=(),
|
||||||
hints=(),
|
hints=(),
|
||||||
|
free_domains=(),
|
||||||
message="clearnet is not selected",
|
message="clearnet is not selected",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,15 +89,72 @@ def dns_plan(
|
|||||||
else:
|
else:
|
||||||
records.extend(_dynamic_records(site_id, domain, dynamic_hostname=dynamic_hostname))
|
records.extend(_dynamic_records(site_id, domain, dynamic_hostname=dynamic_hostname))
|
||||||
hints = _connectivity_hints(behind_router=behind_router)
|
hints = _connectivity_hints(behind_router=behind_router)
|
||||||
|
free_domains = FREE_DOMAIN_HINTS if free_domain_hints else ()
|
||||||
status = _plan_status([record.status for record in records] + [hint.status for hint in hints])
|
status = _plan_status([record.status for record in records] + [hint.status for hint in hints])
|
||||||
return DNSPlan(
|
return DNSPlan(
|
||||||
status=status,
|
status=status,
|
||||||
records=tuple(records),
|
records=tuple(records),
|
||||||
hints=tuple(hints),
|
hints=tuple(hints),
|
||||||
|
free_domains=free_domains,
|
||||||
message=_plan_message(status),
|
message=_plan_message(status),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FREE_DOMAIN_HINTS: tuple[FreeDomainHint, ...] = (
|
||||||
|
FreeDomainHint(
|
||||||
|
provider="is-a.dev",
|
||||||
|
suffixes=("is-a.dev",),
|
||||||
|
fit="developer personal sites and projects",
|
||||||
|
records="DNS records through reviewed repository changes",
|
||||||
|
workflow="fork repository, add domain config, open pull request",
|
||||||
|
status="community-reviewed",
|
||||||
|
url="https://github.com/is-a-dev/register",
|
||||||
|
message="good beginner option when a developer-branded subdomain is acceptable",
|
||||||
|
),
|
||||||
|
FreeDomainHint(
|
||||||
|
provider="JS.ORG",
|
||||||
|
suffixes=("js.org",),
|
||||||
|
fit="JavaScript ecosystem projects",
|
||||||
|
records="CNAME",
|
||||||
|
workflow="configure hosting custom domain, then open pull request",
|
||||||
|
status="strict-scope",
|
||||||
|
url="https://github.com/js-org/js.org",
|
||||||
|
message="only use for sites directly related to the JavaScript community",
|
||||||
|
),
|
||||||
|
FreeDomainHint(
|
||||||
|
provider="Open Domains",
|
||||||
|
suffixes=("is-cool.dev", "is-local.org", "is-not-a.dev", "localplayer.dev"),
|
||||||
|
fit="students and open-source projects",
|
||||||
|
records="A, AAAA, CNAME, NS, TXT, and related DNS records",
|
||||||
|
workflow="register through the current Open Domains web app",
|
||||||
|
status="verify-current-domains",
|
||||||
|
url="https://opendomains.andrewstech.me/",
|
||||||
|
message="GitHub registry moved to a web app; verify the current domain list before use",
|
||||||
|
),
|
||||||
|
FreeDomainHint(
|
||||||
|
provider="Community GitHub registries",
|
||||||
|
suffixes=(
|
||||||
|
"is-an.app",
|
||||||
|
"cluster.ws",
|
||||||
|
"wip.la",
|
||||||
|
"thedev.id",
|
||||||
|
"io.day",
|
||||||
|
"jsid.dev",
|
||||||
|
"is-a.co",
|
||||||
|
"is-a-good.dev",
|
||||||
|
"is-really.cool",
|
||||||
|
"js.cool",
|
||||||
|
),
|
||||||
|
fit="personal sites, hobby apps, and open-source projects",
|
||||||
|
records="varies by registry",
|
||||||
|
workflow="review registry terms, then open the requested issue or pull request",
|
||||||
|
status="verify-before-use",
|
||||||
|
url="https://github.com/tarampampam/domains",
|
||||||
|
message="treat as optional naming ideas; availability and rules can change",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def dns_check(
|
def dns_check(
|
||||||
config: GatewayConfig,
|
config: GatewayConfig,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ config artifacts.
|
|||||||
```sh
|
```sh
|
||||||
python3 -m ampg --config gateway.toml deploy plan --profile mobile-i2p
|
python3 -m ampg --config gateway.toml deploy plan --profile mobile-i2p
|
||||||
python3 -m ampg --config gateway.toml dns plan --profile vps-full
|
python3 -m ampg --config gateway.toml dns plan --profile vps-full
|
||||||
|
python3 -m ampg --config gateway.toml dns plan --profile vps-full --free-domain-hints
|
||||||
python3 -m ampg --config gateway.toml dns plan --profile vps-full --mode dynamic --behind-router
|
python3 -m ampg --config gateway.toml dns plan --profile vps-full --mode dynamic --behind-router
|
||||||
python3 -m ampg --config gateway.toml dns check --profile vps-full
|
python3 -m ampg --config gateway.toml dns check --profile vps-full
|
||||||
python3 -m ampg --config gateway.toml status
|
python3 -m ampg --config gateway.toml status
|
||||||
@@ -68,7 +69,9 @@ to run and does not change files or services.
|
|||||||
and `www` records. Dynamic mode supports a Dynamic DNS hostname and warns when apex
|
and `www` records. Dynamic mode supports a Dynamic DNS hostname and warns when apex
|
||||||
records require provider-specific ALIAS/ANAME or API-updated A/AAAA support. With
|
records require provider-specific ALIAS/ANAME or API-updated A/AAAA support. With
|
||||||
`--behind-router`, it also lists port forwarding, IPv6, explicit router mapping,
|
`--behind-router`, it also lists port forwarding, IPv6, explicit router mapping,
|
||||||
reverse-tunnel, and DNS-01 certificate options.
|
reverse-tunnel, and DNS-01 certificate options. With `--free-domain-hints`, it also
|
||||||
|
prints optional third-party community subdomain registries for operators who do not yet
|
||||||
|
own a domain.
|
||||||
|
|
||||||
`status` prints one row per enabled protocol with the selected platform provider,
|
`status` prints one row per enabled protocol with the selected platform provider,
|
||||||
adapter, installed/running daemon probe, action, and policy result.
|
adapter, installed/running daemon probe, action, and policy result.
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Domain Onboarding
|
||||||
|
|
||||||
|
> Status: draft | Updated 2026-07-06 | Applies to: clearnet DNS setup
|
||||||
|
|
||||||
|
AMPG treats clearnet naming as an operator choice. A site can use a normal owned domain,
|
||||||
|
a community free subdomain, a Dynamic DNS name, or no clearnet name at all when only
|
||||||
|
private transports are selected.
|
||||||
|
|
||||||
|
## Command
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m ampg --config gateway.toml dns plan
|
||||||
|
python3 -m ampg --config gateway.toml dns plan --free-domain-hints
|
||||||
|
python3 -m ampg --config gateway.toml dns plan --mode dynamic --behind-router
|
||||||
|
python3 -m ampg --config gateway.toml dns check
|
||||||
|
```
|
||||||
|
|
||||||
|
`dns plan --free-domain-hints` prints `AMPG_FREE_DOMAIN_HINT` rows for optional
|
||||||
|
third-party services that may provide free subdomains for personal sites, hobby apps, or
|
||||||
|
open-source projects. AMPG does not register these names automatically because each
|
||||||
|
provider has its own review process, terms, availability, and DNS record support.
|
||||||
|
|
||||||
|
## Choices
|
||||||
|
|
||||||
|
Owned domain:
|
||||||
|
: Best for production sites, long-lived projects, storefronts, and anything that needs
|
||||||
|
strong brand control.
|
||||||
|
|
||||||
|
Free community subdomain:
|
||||||
|
: Useful for experiments, personal projects, demos, and users who cannot buy a domain
|
||||||
|
yet. Review the provider's current rules before relying on it.
|
||||||
|
|
||||||
|
Dynamic DNS:
|
||||||
|
: Useful for laptops, home servers, old phones, or other hosts whose public IP can
|
||||||
|
change. Apex-domain support depends on the DNS provider.
|
||||||
|
|
||||||
|
Behind-router hosting:
|
||||||
|
: Use port forwarding, public IPv6, an explicit router mapping, a reverse tunnel, or
|
||||||
|
DNS-01 certificate validation when inbound port 80 is unavailable.
|
||||||
|
|
||||||
|
## Included Hints
|
||||||
|
|
||||||
|
AMPG includes conservative hints for:
|
||||||
|
|
||||||
|
- `is-a.dev`: developer personal sites and projects.
|
||||||
|
- `js.org`: JavaScript ecosystem projects; narrower content rules.
|
||||||
|
- Open Domains: student and open-source project subdomains through the current web app.
|
||||||
|
- Other community GitHub registries such as `is-an.app`, `wip.la`, `thedev.id`,
|
||||||
|
`io.day`, `jsid.dev`, `is-a.co`, `is-a-good.dev`, `is-really.cool`, and `js.cool`.
|
||||||
|
|
||||||
|
These hints are naming ideas, not guarantees. Verify current status before adding a
|
||||||
|
domain to a public deployment.
|
||||||
@@ -154,6 +154,7 @@ daemon_policy = "auto"
|
|||||||
python3 -m ampg --config gateway.toml init site wownero --domain wownero.org --source ../wownero.org-website --preset full
|
python3 -m ampg --config gateway.toml init site wownero --domain wownero.org --source ../wownero.org-website --preset full
|
||||||
python3 -m ampg --config gateway.toml deploy plan --profile vps-full
|
python3 -m ampg --config gateway.toml deploy plan --profile vps-full
|
||||||
python3 -m ampg --config gateway.toml dns plan --profile vps-full
|
python3 -m ampg --config gateway.toml dns plan --profile vps-full
|
||||||
|
python3 -m ampg --config gateway.toml dns plan --profile vps-full --free-domain-hints
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml plan
|
python3 -m ampg --config examples/wownero.gateway.toml plan
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml build
|
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 manifest
|
||||||
|
|||||||
@@ -93,6 +93,27 @@ class DNSTest(unittest.TestCase):
|
|||||||
self.assertIn("AMPG_CONNECTIVITY_HINT method=port-forward", output)
|
self.assertIn("AMPG_CONNECTIVITY_HINT method=port-forward", output)
|
||||||
self.assertIn("AMPG_CONNECTIVITY_HINT method=reverse-tunnel", output)
|
self.assertIn("AMPG_CONNECTIVITY_HINT method=reverse-tunnel", output)
|
||||||
|
|
||||||
|
def test_dns_plan_can_include_free_domain_hints(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
config_path = _init_config(Path(tmp).resolve())
|
||||||
|
|
||||||
|
status, output, error = _run_cli(
|
||||||
|
[
|
||||||
|
"--config",
|
||||||
|
str(config_path),
|
||||||
|
"dns",
|
||||||
|
"plan",
|
||||||
|
"--free-domain-hints",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(0, status, error)
|
||||||
|
self.assertIn("AMPG_FREE_DOMAIN_HINT", output)
|
||||||
|
self.assertIn('provider="is-a.dev"', output)
|
||||||
|
self.assertIn('suffixes="js.org"', output)
|
||||||
|
self.assertIn("status=verify-before-use", output)
|
||||||
|
self.assertIn("free_domain_hints=4", output)
|
||||||
|
|
||||||
def test_dns_plan_skips_when_clearnet_is_not_selected(self):
|
def test_dns_plan_skips_when_clearnet_is_not_selected(self):
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
config_path = _init_config(Path(tmp).resolve(), preset="i2p-only")
|
config_path = _init_config(Path(tmp).resolve(), preset="i2p-only")
|
||||||
|
|||||||
Reference in New Issue
Block a user