Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
# `tractor` over a WireGuard tunnel, declared as one maddr
|
|
|
|
|
|
|
|
|
|
A two-host LAN setup: a `tractor` actor tree on host A, dialed
|
|
|
|
|
from host B, with the endpoint declared as a single `wg`
|
|
|
|
|
multiaddr.
|
|
|
|
|
|
|
|
|
|
Supersedes the example set in gh
|
|
|
|
|
[#482](https://github.com/goodboy/tractor/issues/482) — see
|
|
|
|
|
[what changed](#what-changed-vs-482).
|
|
|
|
|
|
2026-08-13 00:08:08 +00:00
|
|
|
> **Why `examples/multihost/`?** `tests/test_docs_examples.py`
|
|
|
|
|
> walks `examples/` recursively and runs everything it collects
|
|
|
|
|
> as a subproc, asserting `rc == 0`. These need a real second
|
|
|
|
|
> host and a live `wg` tunnel, so they can't satisfy that;
|
|
|
|
|
> `'multihost' not in p[0]` is already in the test's exclusion
|
|
|
|
|
> list, which is what keeps them out of CI.
|
|
|
|
|
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
## the maddr form
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/ip4/192.168.1.50/udp/51820/wg/u<A_pub>/ip4/10.0.11.1/tcp/1616
|
|
|
|
|
\____ wg bearer ___________/\__ key __/\____ tractor ep _____/
|
|
|
|
|
underlay, wg `ListenPort` overlay, on the wg iface
|
2026-08-19 21:14:52 +00:00
|
|
|
(kernel owns the socket) (`MsgTransport` binds this)
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Three parts, three different owners:
|
|
|
|
|
|
2026-08-19 21:14:52 +00:00
|
|
|
| part | socket owner / provisioner | runtime role |
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
| --- | --- | --- |
|
2026-08-19 21:14:52 +00:00
|
|
|
| `/ip4/../udp/51820` bearer | kernel-owned; `wg-quick` now, tractor bindspace later | control-plane metadata |
|
|
|
|
|
| `/wg/u<key>` | nothing — it's an identity | parsed, verified explicitly |
|
|
|
|
|
| `/ip4/../tcp/1616` overlay | `tractor`'s `IPCServer` | application `MsgTransport` |
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
|
|
|
|
|
Verified against py-multiaddr
|
|
|
|
|
[#108](https://github.com/multiformats/py-multiaddr/pull/108):
|
|
|
|
|
this composed form parses and round-trips
|
|
|
|
|
(`['ip4','udp','wg','ip4','tcp']`).
|
|
|
|
|
|
|
|
|
|
## requirements
|
|
|
|
|
|
2026-08-14 13:51:36 +00:00
|
|
|
py-multiaddr #108 is **merged** (2026-07-28) but ships in no
|
|
|
|
|
release yet — the latest `0.2.0` (2026-03-17) predates it and has
|
2026-08-19 21:14:52 +00:00
|
|
|
no `wg` codec. So `pyproject.toml` temporarily pins the merge commit
|
|
|
|
|
in its PEP 621 dependency metadata, and a plain
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
|
|
|
|
|
```bash
|
2026-08-14 13:51:36 +00:00
|
|
|
uv sync
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
```
|
|
|
|
|
|
2026-08-14 13:51:36 +00:00
|
|
|
gets you a `wg`-aware `multiaddr`. That pin goes away once a
|
2026-08-19 21:14:52 +00:00
|
|
|
release carries the codec. `py-multibase` is a direct dependency.
|
2026-08-14 13:51:36 +00:00
|
|
|
|
Retract the hand-rolled tunnel peeler from plan-03
§3.2 specced a pure fn `_peel_tunnel_segs(proto_names) ->
(bearer_names, tunnel_specs, overlay_names)` to split a maddr at
its tunnel seg. It should never be written: `py-multiaddr` ships
that whole surface already and the plan simply missed it, even
though gh #443's 2nd bullet links the README sections in
question.
Replaced w/ a ⚠️ CORRECTION carrying the verified API table
(`.decapsulate_code(P_WG)` for the bearer, `.split()`/`.join()`
for a seg tail, `.value_for_protocol()` to read a value,
`.encapsulate()` to recompose) plus *why* it works on an infix
`/wg/` seg: the cut is by proto-code, never by matching an addr
value, and the key seg has no addr of its own.
Also,
- adopt `bearer`/`overlay` as the role names throughout, and say
plainly why not `inner`/`outer` — the call-stack reading of
"inner" is the exact opposite of the encapsulation one.
- warn that `value_for_protocol('ip4')` on a full tunnelled
maddr silently yields the *bearer's* host; only call it on a
peeled sub-maddr.
- note nesting (wg-in-wg) falls out of `.decapsulate_code()`
cutting at the *last* occurrence, so peel repeatedly rather
than recursing through a bespoke splitter.
- `mk_maddr()` for `TunnelledAddress` is `.encapsulate()`
composition, not `str` building.
- README: drop the "degrades to a plain segment split" para,
since that path is gone — no codec now means one actionable
raise.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-17 21:25:20 +00:00
|
|
|
Without the codec `parse_wg_maddr()` raises immediately with an
|
|
|
|
|
actionable message — there is deliberately **no** degraded
|
2026-08-19 21:14:52 +00:00
|
|
|
hand-split fallback. `_wg_proto_code()` performs the capability
|
|
|
|
|
check before parsing.
|
Retract the hand-rolled tunnel peeler from plan-03
§3.2 specced a pure fn `_peel_tunnel_segs(proto_names) ->
(bearer_names, tunnel_specs, overlay_names)` to split a maddr at
its tunnel seg. It should never be written: `py-multiaddr` ships
that whole surface already and the plan simply missed it, even
though gh #443's 2nd bullet links the README sections in
question.
Replaced w/ a ⚠️ CORRECTION carrying the verified API table
(`.decapsulate_code(P_WG)` for the bearer, `.split()`/`.join()`
for a seg tail, `.value_for_protocol()` to read a value,
`.encapsulate()` to recompose) plus *why* it works on an infix
`/wg/` seg: the cut is by proto-code, never by matching an addr
value, and the key seg has no addr of its own.
Also,
- adopt `bearer`/`overlay` as the role names throughout, and say
plainly why not `inner`/`outer` — the call-stack reading of
"inner" is the exact opposite of the encapsulation one.
- warn that `value_for_protocol('ip4')` on a full tunnelled
maddr silently yields the *bearer's* host; only call it on a
peeled sub-maddr.
- note nesting (wg-in-wg) falls out of `.decapsulate_code()`
cutting at the *last* occurrence, so peel repeatedly rather
than recursing through a bespoke splitter.
- `mk_maddr()` for `TunnelledAddress` is `.encapsulate()`
composition, not `str` building.
- README: drop the "degrades to a plain segment split" para,
since that path is gone — no codec now means one actionable
raise.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-17 21:25:20 +00:00
|
|
|
|
|
|
|
|
Every peel and re-compose here goes through `py-multiaddr`'s own
|
|
|
|
|
tunnel API (`.decapsulate_code()`, `.split()`, `.join()`,
|
|
|
|
|
`.encapsulate()`, `.value_for_protocol()`) rather than any
|
|
|
|
|
bespoke segment slicing — see its README "En/decapsulate" and
|
|
|
|
|
"Tunneling" sections. gh #429 was about *dropping* our NIH
|
|
|
|
|
parser, and that applies to peeling a tunnel stack just as much
|
|
|
|
|
as to decoding one proto.
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
|
|
|
|
|
## 0. tunnel setup (out-of-band, both hosts)
|
|
|
|
|
|
|
|
|
|
Host A is the service host (underlay e.g. `192.168.1.50`), host B
|
|
|
|
|
your workstation. Overlay net `10.0.11.0/24`.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
umask 077
|
|
|
|
|
wg genkey | tee wg_priv.key | wg pubkey > wg_pub.key
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`/etc/wireguard/wg0.conf` on **host A**:
|
|
|
|
|
|
|
|
|
|
```ini
|
|
|
|
|
[Interface]
|
|
|
|
|
PrivateKey = <A_priv>
|
|
|
|
|
Address = 10.0.11.1/24
|
|
|
|
|
ListenPort = 51820
|
|
|
|
|
```
|
|
|
|
|
```ini
|
|
|
|
|
[Peer]
|
|
|
|
|
PublicKey = <B_pub>
|
|
|
|
|
AllowedIPs = 10.0.11.2/32
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
on **host B**:
|
|
|
|
|
|
|
|
|
|
```ini
|
|
|
|
|
[Interface]
|
|
|
|
|
PrivateKey = <B_priv>
|
|
|
|
|
Address = 10.0.11.2/24
|
|
|
|
|
```
|
|
|
|
|
```ini
|
|
|
|
|
[Peer]
|
|
|
|
|
PublicKey = <A_pub>
|
|
|
|
|
Endpoint = 192.168.1.50:51820
|
|
|
|
|
AllowedIPs = 10.0.11.1/32
|
|
|
|
|
PersistentKeepalive = 25
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note how `ListenPort` and `Endpoint` are exactly the maddr's
|
|
|
|
|
bearer segment, and `[Interface] Address` is its overlay host.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sudo wg-quick up wg0 # both hosts
|
|
|
|
|
ping -c1 10.0.11.1 # from B
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 1. get your pubkey into the maddr
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
python -c "
|
2026-08-19 21:14:52 +00:00
|
|
|
from tractor.discovery import mb_pubkey
|
|
|
|
|
key = open('wg_pub.key').read().strip()
|
|
|
|
|
print(mb_pubkey(key))
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Paste the `u...` output into `WG_MADDR` in both scripts (they use
|
|
|
|
|
the same string — A's bearer, A's key, A's overlay ep).
|
|
|
|
|
|
|
|
|
|
## 2. run
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# host A
|
|
|
|
|
python host_a_srv.py
|
|
|
|
|
|
|
|
|
|
# host B
|
|
|
|
|
python host_b_client.py
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`host_a_srv.py` must be importable on host B too, since
|
|
|
|
|
`portal.run()` refs the fn by module path — standard `tractor`
|
|
|
|
|
RPC semantics.
|
|
|
|
|
|
|
|
|
|
## what changed vs #482
|
|
|
|
|
|
|
|
|
|
Four corrections, all from
|
|
|
|
|
`ai/tpt-backends/03_wg_tunnel_bindspace.md`:
|
|
|
|
|
|
|
|
|
|
1. **the maddr semantics were inverted.** #482 used
|
|
|
|
|
`/ip4/10.0.11.1/tcp/1616/wg/u<key>` — that parses, but it puts
|
|
|
|
|
the *overlay* addr where the bearer belongs and `tcp` where
|
|
|
|
|
wg's `udp` `ListenPort` goes, and it declares no overlay ep at
|
|
|
|
|
all. `parse_wg_maddr()` now rejects it with an actionable
|
|
|
|
|
error.
|
|
|
|
|
2. **parsing is pure.** #482's helper had the key-check adjacent
|
|
|
|
|
to the parse; `verify_wg_peer()` is now a separate, explicitly
|
|
|
|
|
composed step that the caller invokes. A parser that shells
|
|
|
|
|
out is a nasty surprise.
|
|
|
|
|
3. **no `sudo`.** #482 ran `sudo wg show`; a library/example must
|
|
|
|
|
never escalate. `wg show` works unprivileged for read on most
|
|
|
|
|
setups; if yours needs root, run the script as root rather
|
|
|
|
|
than embedding `sudo`.
|
|
|
|
|
4. **no new `Address` proto-type.** The tunnel rides *beside* the
|
2026-08-19 21:14:52 +00:00
|
|
|
overlay addr in a frozen `TunnelledAddress`, and only `.overlay`
|
Add a `wg`-tunnelled 2-host example set
Re-renders gh #482's examples w/ the corrected (infix) maddr
grammar, as the "layer A" slice of the wg plan: declarative
maddrs only, tunnel pre-provisioned out-of-band, zero runtime
changes.
- `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying
`bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr`
property that re-renders the canonical form, and pure
`mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser
rejects #482's inverted suffix form w/ an actionable error and
stays **side-effect free** — `verify_wg_peer()` is a separate,
explicitly impure step the caller composes, never something a
parse path shells out to.
- `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing
only `addr.inner` into `open_nursery()`/`open_root_actor()`,
which is the whole point — the bearer + key layers are already
established before any bind happens.
- `README.md`: the grammar + the 3-owners table, the `#108`
branch install line, tunnel setup, and a "what changed vs
#482" section enumerating the corrections.
Runnable-shaped but **not yet run against a live tunnel**; that's
next, and the reason these sit on the planning branch rather than
in `examples/` proper. `_segments()` marks its stopgap for when
the `wg` codec isn't installed.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-12 23:37:29 +00:00
|
|
|
crosses into `open_nursery()`. #482 §6 floated a `WGAddress`
|
|
|
|
|
registered in `_address_types` — that table is a `bidict`
|
|
|
|
|
(1:1 proto-key↔type) and `_addr_to_transport` wants a
|
|
|
|
|
`MsgTransport` per addr-type, which `wg` doesn't have.
|
|
|
|
|
|
|
|
|
|
## next
|
|
|
|
|
|
2026-08-19 21:14:52 +00:00
|
|
|
Layer A's `TunnelledAddress` and native maddr parser now live in
|
|
|
|
|
`tractor.discovery`. Next, replace this example's `wg(8)` verification
|
|
|
|
|
probe with `pyroute2`, then add `open_bindspace()` `@acm`s which
|
|
|
|
|
create/tear down the iface and netns.
|