Skip to content

feat(core): an app-chosen hostname prefix, with app membership from Hostinfo.App (sidecar protocol 12) - #243

Merged
jamesyong-42 merged 1 commit into
mainfrom
feat/app-hostname-prefix
Nov 1, 2026
Merged

jamesyong-42 merged 1 commit into
mainfrom
feat/app-hostname-prefix

Conversation

@jamesyong-42

@jamesyong-42 jamesyong-42 commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

What

An app can choose the name its devices show on Tailscale's own pages. App membership moves off that name and onto a tag
the node announces in Hostinfo.App.

  • NodeBuilder::hostname_prefix("vibefield") names the node vibefield-{slug} instead of truffle-{app_id}-{slug}.
    • It is validated as a single lowercase DNS label of 1–61 characters, leaving room for the hyphen and a slug.
    • It is exclusive with hostname(): build() refuses both.
  • Every start announces Hostinfo.App = "truffle:{app_id}". tsnet:start carries app, and the sidecar calls
    hostinfo.SetApp before srv.Start(). SetApp is process-wide, and one sidecar process carries one node.
  • Every peer row (tsnet:peers, tsnet:peerChanged) carries app, the peer's Hostinfo.App as control relays it.
    • ipnstate.PeerStatus has no App, so the sidecar reads it through LocalClient.PeerByID once per peer.
    • After that, the IPN bus keeps the entry current: a Hostinfo change arrives as a full node in PeersChanged, which
      the bus watch already subscribes to.
    • A failed lookup is not cached. A node sent without Hostinfo has no app.
  • Admission is tag-first (is_app_row):
    • A tagged row is this app's only when its tag is exactly truffle:{app_id}, whatever its name. So a node of
      another app under the same prefix stays out.
    • An untagged row (a sidecar below 12, or a peer that announces nothing) falls back to the truffle-{app_id}-
      prefix, exactly as before.
    • The tag is the peer's own claim, just as its hostname is. It widens nothing a same-tailnet node could not already
      claim by its name.
  • A hello-less peer named {prefix}-{slug} resolves by its slug under the node's own prefix. The convention still
    resolves, for a peer on an older release.
  • Sidecar protocol 11 → 12. A sidecar below 12 ignores app. A node whose name is outside truffle-{app_id}-
    then logs a warning that its peers will not recognise it. The node still starts, as an explicit hostname() always
    has.

Additive. An app that sets no prefix keeps truffle-{app_id}-{slug} and today's discovery. TailscaleConfig is
unchanged: the tag is derived from its app_id.

Why: a real-tailnet probe first

The open question was whether control relays a peer's Hostinfo.App into the netmap a tsnet node receives.

  • tsnet sets only Package "tsnet".
  • On 2026-11-01, two ephemeral tsnet nodes on one tailnet answered it, using tailscale.com v1.104.1, the sidecar's
    own go.mod:
    • Node a called hostinfo.SetApp("truffle:vibefield-t8probe"); node b set nothing.
    • Each read the other through PeerByID.
[b]   Hostinfo.App     = "truffle:vibefield-t8probe"
[b]   Hostinfo.Package = ""
[b]   Hostinfo fields relayed: App, Hostname, OS, Services
[b] RESULT: peer truffle-t8probe-a Hostinfo.App RELAYED
[a]   Hostinfo fields relayed: Hostname, OS
[a] RESULT: peer truffle-t8probe-b Hostinfo.App NOT RELAYED (App empty)

So a peer's row keeps App, Hostname, OS and Services, and Package is stripped (no carrier). The tag shape stands.

Rollout hazard (read before turning a prefix on)

  • A device on an earlier release reads the name only. Its is_app_peer admits truffle-{app_id}- alone, so it
    will not see a node with a custom prefix. An app should turn hostname_prefix on only once every shipped device
    reads the tag: two releases, or one upgrade whose builder call waits a version.
  • The Apple plane is not in this PR. apple/Sources/Truffle/Identity/Slug.swift (Hostname.isAppPeer, used at
    MeshNode.swift:616 and :677) still filters by the prefix, and the Swift node announces no tag. A custom-prefix
    desktop is invisible to a Swift node until that plane reads and announces the tag, which is a follow-up.
  • Existing devices are renamed. tsnet re-sends the hostname on every start, so a device takes its new name in the
    admin console at its next start. Release notes should say so.
  • The napi option is a follow-up. NodeOptions.hostnamePrefix in truffle-napi regenerates index.d.ts, so it
    rides its own change.

Tests

The tests were written first and run red against stubs that ignored the tag and the prefix (logs kept):

  • Rust: 6 of 8 failed, each on its assertion. The wire parse and the untagged fallback passed, as they should.
  • Go: 3 of 4 failed. The start-data parse passed.

The new tests:

  • is_app_row: a present tag decides whatever the name; a foreign tag under our prefix is refused; a same-prefix
    node of another app is refused; an absent or empty tag falls back to the prefix.
  • classify_row admits a tagged custom-prefix row, and calls the same row untagged a stranger.
  • A peer row reads app from the wire; a protocol-11 row has none.
  • A scripted sidecar shows tsnet:start carries "app":"truffle:playground".
  • hostname_prefix composes {prefix}-{slug}. It cuts a long device name to 63 characters, rejects bad and over-long
    prefixes, and refuses one together with hostname().
  • hostname_slug learns the prefix and keeps the convention. A peer named vibefield-ec2-smoke shows ec2-smoke
    before hello and resolves by it.
  • Go: applyApp reaches hostinfo.New().App and clears. The app cache fetches once, follows full-node notifies,
    forgets removed peers, never caches a failure, and tolerates a node without Hostinfo. An App change counts as a
    peer change.
    • The full suite found that last case: peer_watch tests feed nodes without Hostinfo, and a bare .App() panicked.
  • The protocol pins moved to 12 (TestStatusEventAdvertisesProtocolVersion, the v11 status test).

Run locally at this commit, all green, with no tailnet key in the environment:

  • cargo fmt --check.
  • cargo clippy --locked --workspace --all-targets --exclude truffle-tauri-plugin --exclude truffle-napi --exclude truffle-bench-app -- -D warnings.
  • RUSTDOCFLAGS='-D warnings' cargo doc --locked --no-deps --workspace (the same excludes).
  • cargo test --locked --workspace (the same excludes): 21 suites, 773 passed, 0 failed, 30 ignored.
  • In packages/sidecar-slim: gofmt -l is empty; go vet ./... and go test ./... pass; go test -race -run 'App|Peer|Watch|Status' passes.

Not run here: CI's real-tailnet integration jobs. They need TRUFFLE_TEST_AUTHKEY, and CI runs them on this PR.

Petition: VibeField's T8 (track TS, TS-D10).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…ostinfo.App (sidecar protocol 12)

- NodeBuilder::hostname_prefix("vibefield") names the node vibefield-{slug}
  instead of truffle-{app_id}-{slug}; validated as a DNS label of 1-61
  chars, and exclusive with hostname().
- Every start announces Hostinfo.App = "truffle:{app_id}" (tsnet:start's
  app; the sidecar calls hostinfo.SetApp before srv.Start).
- Peer rows carry app, the peer's Hostinfo.App as control relays it. The
  sidecar reads it through LocalClient.PeerByID once per peer, and the IPN
  bus's full-node notifies keep it current.
- Admission is tag-first: a tagged row is ours only if its tag is
  truffle:{app_id}, whatever its name. An untagged row (an older sidecar)
  falls back to the truffle-{app_id}- prefix.
- A hello-less peer named {prefix}-{slug} resolves by its slug.
- Sidecar protocol 11 -> 12. A sidecar below 12 ignores app. A node whose
  name is outside the convention then logs a warning that its peers will
  not recognise it.
- A real-tailnet probe (2026-11-01, tailscale v1.104.1) found that a peer's
  netmap row keeps App, Hostname, OS and Services, and drops Package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jamesyong-42
jamesyong-42 merged commit 3ae234e into main Nov 1, 2026
14 checks passed
@jamesyong-42
jamesyong-42 deleted the feat/app-hostname-prefix branch November 1, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant