Skip to content

Conversation

@lmagyar
Copy link
Collaborator

@lmagyar lmagyar commented Sep 4, 2025

Proposed Changes

This is a follow up after #463.

I've made all the optional config options mandatory, except exit node, becasue that is really an optional "string".

1 option was problematic: advertise_routes

Where:

  • undefined = use routes from the HA managed/supported interfaces
  • empty list [] = disabled

I've resolved it with a default special value (documented):

options:
  advertise_routes:
    - local_subnets
schema:
  advertise_routes:
    - "match(^(local.subnets|...)$)"

Related Issues

Summary by CodeRabbit

  • New Features

    • Option to auto-advertise local subnets via a "local_subnets" route setting; clearer warnings and restart guidance if none found.
  • Changes

    • Several networking/routing options now require explicit opt-in (disabled unless enabled).
    • Stricter validation for ports, tags, login server and related settings.
  • Documentation

    • Full step-by-step UI guide for enabling Exit Node, Subnet routes, and disabling key expiry; standardized default-value wording.

@coderabbitai
Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

Shifts many Tailscale defaults to explicit opt-in, tightens the config schema (required booleans, stricter patterns, new options/defaults), introduces local.subnets sentinel for route advertisement, changes runtime scripts to rely on explicit values, and rewrites docs/translations to reflect the new explicit semantics.

Changes

Cohort / File(s) Summary
Documentation
tailscale/DOCS.md
Reworked configuration guidance into step-by-step UI instructions; added anchor for Machines; standardized default-value phrasing; updated examples for Exit Node, Subnet routes, and key expiry.
Config schema & defaults
tailscale/config.yaml
Added top-level options defaults; converted many optional booleans to required booleans; tightened regexes and allowed values; added local_subnets to advertise_routes; set explicit defaults (e.g., log_level, login_server, share_on_port, SNAT/stateful/taildrop/userspace flags).
Startup/post-start flags
tailscale/rootfs/etc/s6-overlay/s6-rc.d/post-tailscaled/run
Switched to explicit enable/disable flags (emit --=true/false); narrowed exit-node conflict check; always passes --login-server; adjusted tag extraction and warning gating to use explicit config values.
Daemon TUN setup
tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run
Userspace networking (--tun=userspace-networking) enabled only when userspace_networking is explicitly true (removed default-on behavior).
Service gating (stage2)
tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
Tightened/changed removal conditions: many services now depend on explicit values; local-network kept only when advertise_routes equals local.subnets; forwarding/MSS-clamp removal tied to userspace_networking=true; share-homeassistant removed only if explicitly disabled.
Subnet advertisement logic
tailscale/rootfs/usr/bin/subnet-routes
advertised path unconditional; added support for local.subnets sentinel to collect local subnets (warn if none found); merges explicit CIDRs; extracts routes from addresses and deduplicates results; removed global no-local-subnets warning block.
Share Home Assistant service
tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-homeassistant/run
Dropped fallback to default port in the exec line; now uses only configured share_on_port.
Translations
tailscale/translations/en.yaml
Reworded many options to declarative default phrasing; documented default advertise_routes behavior (adds local_subnets); updated port/default text and several default-enabled/disabled lines.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant S6 as s6
  participant PT as post-tailscaled/run
  participant TS as tailscaled
  participant CFG as config.yaml
  participant LOG as Logger

  Note over CFG: Booleans now explicit required values
  S6->>TS: start tailscaled
  TS-->>S6: ready
  S6->>PT: run post-tailscaled
  PT->>CFG: read flags (accept_dns, accept_routes, advertise_*, userspace_networking...)
  alt flag == true
    PT->>TS: append --<flag>=true
  else flag == false
    PT->>TS: append --<flag>=false
  end
  PT->>TS: append --login-server=<url> (unconditional)
  alt advertise_exit_node=true and exit_node set
    PT->>LOG: warn exit-node conflict
  end
Loading
sequenceDiagram
  autonumber
  participant SR as subnet-routes
  participant CFG as config
  participant IF as Interfaces
  participant IPC as ipcalc
  participant LOG as Logger

  SR->>CFG: mode arg ("advertised" or not)
  alt mode = "advertised"
    SR->>CFG: read advertise_routes
    alt contains "local.subnets"
      SR->>IF: collect local subnets
      alt none
        SR->>LOG: warn "There are no local subnets to advertise!"
      end
    end
    SR->>SR: merge explicit CIDRs
  else
    SR->>IF: collect IP addresses
    SR->>IPC: extract networks from addresses
  end
  SR->>SR: deduplicate routes (sort -u)
  SR-->>Caller: emit routes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

breaking-change, documentation, enhancement

Suggested reviewers

  • frenck

Poem

I toggled flags with careful paws—click, click,
No defaults now; you pick each trick.
Subnets hop in local tracks,
Exit burrows mind their stacks.
Ports set true, my whiskers preen—hop hooray for routes unseen! 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Make all config options mandatory" directly and accurately describes the primary change across the entire changeset. The PR fundamentally transforms configuration from an optional schema (with ? markers and conditional logic) to a mandatory schema by removing optional quantifiers, adding default values, and updating run scripts to require explicit true/false values instead of defaulting based on absence. The title is specific, concise, and avoids vague terminology, enabling a developer scanning the commit history to immediately understand this PR's intent.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@frenck frenck added the refactor Improvement of existing code, not introducing new features. label Sep 14, 2025
@lmagyar lmagyar force-pushed the pr-mandatory-options branch from 0270a14 to d2acb2c Compare September 14, 2025 21:51
@lmagyar lmagyar marked this pull request as ready for review September 14, 2025 21:54
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tailscale/rootfs/usr/bin/subnet-routes (1)

50-58: Exclude loopback addresses to avoid advertising 127.0.0.0/8 or ::1/128.

Current filters skip link-local but not loopback. With local.subnets, this could generate bogus routes.

Apply this diff:

   # Extract routes from addresses
   for address in "${addresses[@]}"; do
     if bashio::var.has_value "${address}"; then
+      # Skip loopback addresses
+      if [[ "${address}" == 127.* ]] || [[ "${address:0:3}" == "::1" ]]; then
+        continue
+      fi
       # Skip local link addresses
       if [[ "${address:0:6}" == "fe80::" ]] || [[ "${address:0:8}" == "169.254." ]];
       then
         continue
       fi
🧹 Nitpick comments (2)
tailscale/DOCS.md (1)

66-86: Consider showing the local.subnets sentinel in the sample YAML.

Helps users discover the new default/shortcut.

Apply this diff:

 advertise_routes:
-  - 192.168.1.0/24
+  - local.subnets  # Advertise all local subnets discovered on supported interfaces
+  - 192.168.1.0/24
   - fd12:3456:abcd::/64
tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-homeassistant/run (1)

15-19: Gracefully handle share_homeassistant=disabled in case gating fails.

If stage removal ever regresses, this service would crash-loop on “disabled”. Early no-op is safer.

Apply this diff:

 # Validate share_homeassistant value
-if ! bashio::config.equals 'share_homeassistant' 'serve' && \
-  ! bashio::config.equals 'share_homeassistant' 'funnel'
-then
-  bashio::exit.nok "Invalid value '$(bashio::config 'share_homeassistant')' for share_homeassistant. Must be either 'serve' or 'funnel'"
-fi
+if bashio::config.equals 'share_homeassistant' 'disabled'; then
+  bashio::log.info "share_homeassistant=disabled; skipping Serve/Funnel setup."
+  exit 0
+fi
+if ! bashio::config.equals 'share_homeassistant' 'serve' && \
+   ! bashio::config.equals 'share_homeassistant' 'funnel'; then
+  bashio::exit.nok "Invalid value '$(bashio::config 'share_homeassistant')' for share_homeassistant. Must be 'serve', 'funnel', or 'disabled'."
+fi
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a30992 and d2acb2c.

📒 Files selected for processing (8)
  • tailscale/DOCS.md (12 hunks)
  • tailscale/config.yaml (1 hunks)
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/post-tailscaled/run (4 hunks)
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-homeassistant/run (1 hunks)
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run (1 hunks)
  • tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh (2 hunks)
  • tailscale/rootfs/usr/bin/subnet-routes (3 hunks)
  • tailscale/translations/en.yaml (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-11-15T17:38:56.576Z
Learnt from: lmagyar
PR: hassio-addons/addon-tailscale#419
File: tailscale/rootfs/command/with-contenv-merge:8-12
Timestamp: 2024-11-15T17:38:56.576Z
Learning: The script `with-contenv-merge` in `tailscale/rootfs/command/` is directly copied from s6 and should not be modified.

Applied to files:

  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-homeassistant/run
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/post-tailscaled/run
📚 Learning: 2024-10-09T10:32:14.110Z
Learnt from: mikz
PR: hassio-addons/addon-tailscale#407
File: tailscale/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run:24-29
Timestamp: 2024-10-09T10:32:14.110Z
Learning: In the Tailscale add-on for Home Assistant, the `tailscale/rootfs` directory is copied into the container's root directory via the Dockerfile, ensuring that files like `/etc/nginx/templates/homeassistant.gtpl` are present at runtime.

Applied to files:

  • tailscale/DOCS.md
📚 Learning: 2025-06-11T20:37:01.152Z
Learnt from: lmagyar
PR: hassio-addons/addon-tailscale#455
File: tailscale/rootfs/usr/bin/magicdns-ingress-proxy-forwarding:100-106
Timestamp: 2025-06-11T20:37:01.152Z
Learning: In the Home Assistant Tailscale add-on, DNATing DNS packets to port 0 (e.g. `--to-destination 127.0.0.1:0`) is an intentional technique to drop traffic. iptables accepts port 0, rewrites the destination port to 0, and the packet is rejected by the stack, effectively discarding it. This is valid and should not be flagged as an error in future reviews.

Applied to files:

  • tailscale/DOCS.md
🔇 Additional comments (19)
tailscale/translations/en.yaml (1)

8-8: LGTM on standardizing the “enabled/disabled by default” phrasing.

Consistent with the “mandatory config” direction and clearer for users.

Also applies to: 14-14, 21-21, 29-29, 63-63, 70-70, 78-78, 85-85, 96-96, 104-104

tailscale/DOCS.md (1)

294-294: LGTM: default port wording.

Matches the tightened schema and the run script using the configured port.

tailscale/rootfs/usr/bin/subnet-routes (2)

72-74: LGTM: unique the route list.

Ensures stable, deduplicated output for downstream consumers.


27-41: Approve — schema already allows literal local.subnets
config.yaml advertise_routes pattern includes the literal "local.subnets" (line 52); no schema change required.

tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-homeassistant/run (1)

72-72: LGTM: use configured HTTPS port.
Uses the schema-constrained value instead of hardcoding 443.
Stage gating verified: stage2 hook removes /etc/s6-overlay/s6-rc.d/user/contents.d/share-homeassistant when share_homeassistant is 'disabled'.

tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run (1)

26-29: LGTM — only enable userspace networking when explicitly true; default verified true

Verified: config.yaml contains userspace_networking: true (line 45) and the schema lists userspace_networking: bool (line 63).

tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh (4)

62-66: LGTM: gating for protect-subnets matches the new explicit semantics.


75-77: LGTM: forwarding removal correctly keys off userspace networking.


80-82: LGTM: mss-clamping removal aligns with userspace networking constraints.


90-92: LGTM: share-homeassistant only removed when explicitly disabled.

tailscale/rootfs/etc/s6-overlay/s6-rc.d/post-tailscaled/run (8)

19-25: LGTM: explicit accept-dns/accept-routes flags with explicit false when not enabled.

Also applies to: 26-32


34-38: LGTM: exit-node conflict guard and explicit advertise-exit-node toggling are correct.

Also applies to: 40-46


57-63: LGTM: advertise-connector is now explicit; good.


65-67: LGTM: login_server always passed; consistent with required schema default.


83-85: LGTM: tags join is safe; empty list yields empty string.


170-178: LGTM: userspace networking notice gated on explicit true.


68-74: No action needed — bundled Tailscale (v1.88.1) supports these flags.
tailscale/Dockerfile sets ARG TAILSCALE_VERSION="v1.88.1"; --stateful-filtering was added in v1.66.0 and --snat-subnet-routes is available, so these flags are supported and startup should not fail.


150-156: Sort both comm inputs to avoid false negatives

comm requires both inputs to be sorted; sort the local routes before comparing.

File: tailscale/rootfs/etc/s6-overlay/s6-rc.d/post-tailscaled/run (around lines 150-156)

-    comm -1 -2 \
-      <(subnet-routes local) \
-      <(/opt/tailscale status --json --peers=true --self=false \
+    comm -1 -2 \
+      <(subnet-routes local | sort -u) \
+      <(/opt/tailscale status --json --peers=true --self=false \
         | jq -rc '.Peer[] | select(has("PrimaryRoutes")) | .PrimaryRoutes[]' \
         | sort -u))

Run on a device with multiple peers to confirm expected warnings.

tailscale/config.yaml (1)

47-63: LGTM: booleans now required; patterns and enums look consistent with script expectations.

@lmagyar lmagyar marked this pull request as draft September 15, 2025 00:14
@lmagyar
Copy link
Collaborator Author

lmagyar commented Sep 17, 2025

I've reviewed all the AI reviews, it's ready for human review.

@lmagyar lmagyar marked this pull request as ready for review September 17, 2025 23:07
@github-actions
Copy link

There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. Thank you for your contributions.

@github-actions github-actions bot added stale There has not been activity on this issue or PR for quite some time. and removed stale There has not been activity on this issue or PR for quite some time. labels Oct 18, 2025
@lmagyar lmagyar added the no-stale This issue or PR is exempted from the stable bot. label Oct 22, 2025
@frenck frenck requested a review from Copilot October 27, 2025 05:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR makes configuration options mandatory rather than optional, standardizing the add-on's behavior by requiring explicit user choices for all settings. The change removes ambiguity about default behaviors and ensures consistent configuration across installations.

Key Changes:

  • Made all configuration options mandatory with explicit defaults (except exit_node which remains optional)
  • Introduced local_subnets as a special value for advertise_routes to auto-discover and advertise local subnet routes
  • Updated documentation to reflect that defaults are now explicitly set rather than implicitly applied when options are unset

Reviewed Changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tailscale/config.yaml Added options section with default values for all configuration parameters and removed ? optional markers from schema definitions
tailscale/translations/en.yaml Updated configuration descriptions to replace "When not set, this option is..." with "This option is..." for clarity
tailscale/DOCS.md Enhanced documentation with step-by-step UI instructions for enabling features and updated default value wording
tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh Simplified conditional logic by removing checks for undefined configuration values
tailscale/rootfs/etc/s6-overlay/s6-rc.d/tailscaled/run Removed checks for undefined userspace_networking configuration
tailscale/rootfs/etc/s6-overlay/s6-rc.d/post-tailscaled/run Simplified conditionals by removing undefined value checks and updated tags configuration handling
tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-homeassistant/run Removed default value fallback for share_on_port

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# If advertise_routes is configured, do not wait for the local network to be ready to collect subnet information
if bashio::config.exists "advertise_routes";
# If local subnets are not configured in advertise_routes, do not wait for the local network to be ready to collect subnet information
if ! bashio::config "advertise_routes" | grep -Eq "^local.subnets$";
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

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

The regex pattern ^local.subnets$ uses a literal dot which matches any character. It should be ^local_subnets$ to match the exact string 'local_subnets' as defined in the configuration.

Suggested change
if ! bashio::config "advertise_routes" | grep -Eq "^local.subnets$";
if ! bashio::config "advertise_routes" | grep -Eq "^local_subnets$";

Copilot uses AI. Check for mistakes.

# Get configured tags
tags=$(bashio::config "tags//[] | join(\",\")" "")
tags=$(bashio::config "tags | join(\",\")" "")
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

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

Removed the //[] fallback operator which handled the case when tags is not an array. Since tags now defaults to an empty array [] in config.yaml, this change appears safe. However, the second argument \"\" is now redundant since tags will always be an array and never undefined.

Suggested change
tags=$(bashio::config "tags | join(\",\")" "")
tags=$(bashio::config "tags | join(\",\")")

Copilot uses AI. Check for mistakes.
advertise_connector: bool
advertise_routes:
- "match(^(((25[0-5]|(2[0-4]|1\\d|[1-9]?)\\d)\\.){3}(25[0-5]|(2[0-4]|1\\d|[1-9]?)\\d)\\/(3[0-2]|[12]?\\d)|[a-fA-F\\d.:]+:[a-fA-F\\d.:]+\\/(12[0-8]|(1[01]|[1-9]?)\\d))$)?"
- "match(^(?:local.subnets|(?:(?:25[0-5]|(?:2[0-4]|1\\d|[1-9]?)\\d)\\.){3}(?:25[0-5]|(?:2[0-4]|1\\d|[1-9]?)\\d)\\/(?:3[0-2]|[12]?\\d)|[a-fA-F\\d.:]+:[a-fA-F\\d.:]+\\/(?:12[0-8]|(?:1[01]|[1-9]?)\\d))$)"
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

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

The regex pattern local.subnets uses a literal dot which matches any character. It should be local_subnets to match the exact string as used in the default configuration and documentation.

Suggested change
- "match(^(?:local.subnets|(?:(?:25[0-5]|(?:2[0-4]|1\\d|[1-9]?)\\d)\\.){3}(?:25[0-5]|(?:2[0-4]|1\\d|[1-9]?)\\d)\\/(?:3[0-2]|[12]?\\d)|[a-fA-F\\d.:]+:[a-fA-F\\d.:]+\\/(?:12[0-8]|(?:1[01]|[1-9]?)\\d))$)"
- "match(^(?:local_subnets|(?:(?:25[0-5]|(?:2[0-4]|1\\d|[1-9]?)\\d)\\.){3}(?:25[0-5]|(?:2[0-4]|1\\d|[1-9]?)\\d)\\/(?:3[0-2]|[12]?\\d)|[a-fA-F\\d.:]+:[a-fA-F\\d.:]+\\/(?:12[0-8]|(?:1[01]|[1-9]?)\\d))$)"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-stale This issue or PR is exempted from the stable bot. refactor Improvement of existing code, not introducing new features.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants