Skip to content

feat(hostinfo): detect Container + Env to match Go hostinfo.New#255

Merged
GeiserX merged 1 commit into
mainfrom
feat/hostinfo-container-env-detection
Jun 15, 2026
Merged

feat(hostinfo): detect Container + Env to match Go hostinfo.New#255
GeiserX merged 1 commit into
mainfrom
feat/hostinfo-container-env-detection

Conversation

@GeiserX

@GeiserX GeiserX commented Jun 15, 2026

Copy link
Copy Markdown
Owner

What

Fill the HostInfo.Container and HostInfo.Env fields the map-poll path was leaving at their wire-omitted defaults, mirroring Go hostinfo.New's inContainer() + getEnvType(). A real tailscaled in a container or a managed environment (k8s, Cloud Run, AWS Lambda/Fargate, fly.io, Heroku, Azure App Service, repl.it, Home Assistant, Docker Desktop) reports these — sending nothing while running in one is a fingerprint. (Remaining tsr-ajyc items after #238 shipped the macOS-OSVersion + Linux-Distro fields.)

How

  • in_container() (hostinfo.rs): Linux-only (None off Linux, matching Go's empty opt.Bool), Some(false) unless a container signal is present — /.dockerenv or /run/.containerenv exists, /proc/1/cgroup mentions /docker/ or /lxc/, or /proc/mounts carries the lxcfs cpuinfo bind. The no-network signals Go checks.
  • env_type() (hostinfo.rs): Go getEnvType()'s first-match env-var cascade, in Go's exact order. Factored into a pure env_type_from(get) core so the cascade is unit-tested without mutating the racy/unsafe/process-global real environment.
  • Threaded through MapRequestBuilder::host_environment (the map-poll path), alongside the existing distro fields. Register/logout keep their thinner identity-only HostInfo (they already omit distro too); Container/Env default to the wire-omitted values there → no regression.
  • Re-export ts_control_serde::EnvType (it was the type of the public HostInfo.env field but lived in a private module, unreachable by name).

Wire parity (verified by test)

Container marshals as the JSON bool true/false under the Go key Container (Go opt.Bool.MarshalJSON), Env as its short code (k8s, …) under Env; an unset container (None) and Unknown env are both omitted by the struct's apply(Option/_=>skip) rules, matching Go's json:",omitzero" (never "Container":null or "Env":"", which would be tells). The wire-key test asserts both the populated keys and the omit behavior.

Not filled (deliberate, honest)

  • GoArchVar (GOAMD64/GOARM): a Go-toolchain build var this non-Go fork can't report truthfully — leaving it empty is honest (a Go node with no micro-arch level also emits empty).
  • Cloud (cloudenv.Get): needs cloud-metadata probes (network); deferred. Both tracked as the tsr-ajyc tail.

Verification

cargo test -p geiserx_ts_control (234, incl. the env-cascade test) + geiserx_ts_control_serde (78, incl. the Container/Env wire-parity test) + geiserx_ts_runtime (328); clippy -D warnings (0); fmt; cargo run -p checks all green.

Created using Claude Code (Opus 4.8)

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced host environment detection: system now identifies whether it is running inside a container.
    • Automatic managed runtime environment detection: recognizes the runtime environment type in use.
    • Host information now includes container status and runtime environment details in system requests.

Fill the HostInfo.Container and HostInfo.Env fields the map-poll path was
leaving at their wire-omitted defaults, mirroring Go hostinfo.New's
inContainer() and getEnvType(). A real tailscaled in a container or a
managed environment (k8s, Cloud Run, Lambda, Fargate, fly.io, Heroku,
Azure App Service, repl.it, Home Assistant, Docker Desktop) reports these;
sending nothing while running in one is a fingerprint.

- inContainer() (hostinfo.rs): Linux-only (None off Linux, matching Go's
  empty opt.Bool), Some(false) unless a container signal is present —
  /.dockerenv or /run/.containerenv exists, /proc/1/cgroup mentions
  /docker/ or /lxc/, or /proc/mounts carries the lxcfs cpuinfo bind. All
  the no-network signals Go checks.
- env_type() (hostinfo.rs): Go getEnvType()'s first-match env-var cascade
  in Go's exact order. Factored into a pure env_type_from(get) core so the
  cascade is unit-tested without mutating the racy, unsafe, process-global
  real environment.
- Threaded through MapRequestBuilder::host_environment (the map-poll path),
  alongside the existing distro fields. Register/logout keep their thinner
  identity-only HostInfo (they already omit distro too) — Container/Env
  default to the wire-omitted values there, so no regression.
- Re-export ts_control_serde::EnvType (it was the type of the public
  HostInfo.env field but lived in a private module, unreachable by name).

Wire parity verified: Container marshals as the JSON bool true/false under
the Go key `Container` (Go opt.Bool.MarshalJSON), Env as its short code
under `Env`; an unset container (None) and Unknown env are BOTH omitted by
the struct's apply(Option/default => skip) rules, matching Go's `,omitzero`
(never `"Container":null` or `"Env":""`, which would themselves be tells).

GoArchVar and Cloud are deliberately NOT filled: GoArchVar (GOAMD64/GOARM)
is a Go-toolchain build var this non-Go fork cannot report truthfully, and
Cloud needs metadata probes (network); leaving them empty is honest and a
real possibility a Go node also emits. Tracked as the remaining tsr-ajyc
tail.

Signed-off-by: Sergio <sergio@geiser.cloud>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds container: Option<bool> and env: EnvType fields to HostInfoData. Detection uses Linux filesystem/cgroup signals for the container flag and an ordered environment-variable cascade for the env type. MapRequestBuilder::host_environment propagates both fields into HostInfo. Wire-format parity with Go is documented and tested in the serde crate.

Changes

Container and EnvType host detection

Layer / File(s) Summary
Serde contract: EnvType re-export and wire-format docs/tests
ts_control_serde/src/lib.rs, ts_control_serde/src/host_info.rs
EnvType is re-exported from the crate root; HostInfo::container docs describe omitzero wire semantics; new test validates Go-compatible JSON round-trips for Container and Env.
HostInfoData fields, detection helpers, and unit tests
ts_control/src/hostinfo.rs
Adds container and env fields to HostInfoData; implements in_container() from Linux cgroup/mount signals and env_type_from() as an injectable env-variable cascade over ten managed runtimes; both are wired into detect(); unit tests cover the cascade ordering, empty-value handling, and per-environment signatures.
MapRequest propagation
ts_control/src/map_request_builder.rs
host_environment copies container and env from HostInfoData into the HostInfo in built MapRequests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes the main change: adding Container and Env field detection to HostInfo to match Go's hostinfo.New behavior.
Description check ✅ Passed Description is comprehensive with What/How sections, implementation details, wire parity verification, and test confirmation, though missing explicit CHANGELOG reference.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/hostinfo-container-env-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
ts_control/src/map_request_builder.rs (1)

155-156: ⚡ Quick win

Add a regression assertion for these two new host-environment fields.

host_environment now copies container and env, but the existing test only checks older identity fields. Extend host_environment_populates_identity_fields to assert these two assignments too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ts_control/src/map_request_builder.rs` around lines 155 - 156, The code now
copies two new fields, container and env, from host to host_info, but the
existing test host_environment_populates_identity_fields does not verify these
assignments. Extend the host_environment_populates_identity_fields test to
include assertions that verify the container and env fields are correctly
populated in host_info after calling the function that performs these
assignments.
ts_control/src/hostinfo.rs (1)

467-557: ⚡ Quick win

Add an explicit Azure App Service case to the env cascade test.

env_type_from has a dedicated Azure branch, but this test doesn’t assert it. Add one case for APPSVC_RUN_ZIP + WEBSITE_STACK + WEBSITE_AUTH_AUTO_AAD to prevent silent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ts_control/src/hostinfo.rs` around lines 467 - 557, The
env_type_detects_known_environments test function does not include an assertion
for Azure App Service, even though env_type_from has a dedicated branch for it.
Add a new assert_eq! case in the test that passes the three Azure environment
variables (APPSVC_RUN_ZIP, WEBSITE_STACK, and WEBSITE_AUTH_AUTO_AAD) with
non-empty values to env_type_from and asserts the result equals the Azure
EnvType variant. Place this assertion among the other managed environment test
cases to ensure the Azure branch is properly covered and prevent silent
regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ts_control/src/hostinfo.rs`:
- Around line 467-557: The env_type_detects_known_environments test function
does not include an assertion for Azure App Service, even though env_type_from
has a dedicated branch for it. Add a new assert_eq! case in the test that passes
the three Azure environment variables (APPSVC_RUN_ZIP, WEBSITE_STACK, and
WEBSITE_AUTH_AUTO_AAD) with non-empty values to env_type_from and asserts the
result equals the Azure EnvType variant. Place this assertion among the other
managed environment test cases to ensure the Azure branch is properly covered
and prevent silent regressions.

In `@ts_control/src/map_request_builder.rs`:
- Around line 155-156: The code now copies two new fields, container and env,
from host to host_info, but the existing test
host_environment_populates_identity_fields does not verify these assignments.
Extend the host_environment_populates_identity_fields test to include assertions
that verify the container and env fields are correctly populated in host_info
after calling the function that performs these assignments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 388223bf-4379-4ae7-a615-83ce998911f9

📥 Commits

Reviewing files that changed from the base of the PR and between 3e81d86 and 7a41547.

📒 Files selected for processing (4)
  • ts_control/src/hostinfo.rs
  • ts_control/src/map_request_builder.rs
  • ts_control_serde/src/host_info.rs
  • ts_control_serde/src/lib.rs

@GeiserX GeiserX merged commit 26d5abc into main Jun 15, 2026
13 of 19 checks passed
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