Skip to content

Add support for advertising Tailscale Services - #715

Open
apfohl wants to merge 2 commits into
hassio-addons:mainfrom
apfohl:advertise-services
Open

Add support for advertising Tailscale Services#715
apfohl wants to merge 2 commits into
hassio-addons:mainfrom
apfohl:advertise-services

Conversation

@apfohl

@apfohl apfohl commented Jul 17, 2026

Copy link
Copy Markdown

Proposed Changes

This adds a new services configuration option that allows users to advertise local services running on the Home Assistant host as Tailscale Services.

A new S6 oneshot service share-services reads the configured services and calls tailscale serve --service=svc:<name> for each entry. The svc: prefix needs to be added in the config manually, following the tag:.

Supported protocols are http, https, tcp, and tls-terminated-tcp. Services must be defined and approved in the Tailscale admin console before they can accept traffic.

Also updates the app documentation and translations for the new option.

Related Issues

None. As far as I have looked into the issues.

Summary by CodeRabbit

  • New Features

    • Added support for exposing local services through Tailscale Services.
    • Configure service name, target, protocol, port, and optional path.
    • Supports HTTP, HTTPS, TCP, and TLS-terminated TCP services.
    • Added configuration guidance, validation, and setup instructions.
  • Documentation

    • Clarified that service sharing supports Tailscale Serve but not Funnel.
    • Documented service approval, migration behavior, and disabled-by-default settings.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a disabled-by-default services configuration for Tailscale Service advertisements, validates service definitions, documents usage, and runs startup synchronization that removes stale entries and advertises configured targets.

Changes

Tailscale Services

Layer / File(s) Summary
Service configuration and documentation
tailscale/config.yaml, tailscale/DOCS.md, tailscale/translations/en.yaml
Adds the services option and schema validation, documents service fields and protocol requirements, and adds the translated configuration description.
Service advertisement runtime
tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run
Synchronizes configured services with Tailscale Serve, removes stale entries, validates protocols and HTTPS capability, and advertises targets with ports and optional paths.
s6-overlay service wiring
tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/type, tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/up
Declares share-services as a oneshot service and connects its up action to the run script.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant share-services
  participant Tailscale Serve
  participant Tailscale status
  share-services->>Tailscale Serve: Read current serve configuration
  share-services->>Tailscale Serve: Remove stale service entries
  share-services->>Tailscale status: Check HTTPS capability when required
  share-services->>Tailscale Serve: Advertise configured target
Loading

Suggested labels: new-feature, no-stale

Suggested reviewers: frenck

Poem

A rabbit hops through YAML bright,
With service hosts all tucked in right.
Stale paths fade, new targets gleam,
Tailscale carries every stream.
“Oneshot magic!” thumps my feet.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support for advertising Tailscale Services.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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 (1)
tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh (1)

138-140: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use rm -f to prevent potential startup failures.

While the container filesystem is typically recreated on every start, adding the -f flag ensures the script won't fail and crash the startup process if the share-services contents file is already missing or cannot be removed.

🛠️ Proposed fix
 if ! bashio::config 'services' | jq -e '. | length > 0' > /dev/null; then
-    rm /etc/s6-overlay/s6-rc.d/user/contents.d/share-services
+    rm -f /etc/s6-overlay/s6-rc.d/user/contents.d/share-services
 fi
🤖 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 `@tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh` around lines 138 -
140, Update the cleanup command in the services configuration check of
stage2_hook.sh to use rm -f when removing the share-services contents file, so
an already-missing file does not fail startup.
🤖 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 `@tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh`:
- Around line 138-140: Update the cleanup command in the services configuration
check of stage2_hook.sh to use rm -f when removing the share-services contents
file, so an already-missing file does not fail startup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6fec5516-8be6-4b0a-b556-1608b8e2d9ce

📥 Commits

Reviewing files that changed from the base of the PR and between 2b00f29 and 2ca7ee2.

📒 Files selected for processing (9)
  • tailscale/DOCS.md
  • tailscale/config.yaml
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/dependencies.d/post-tailscaled
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/type
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/up
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/share-services
  • tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
  • tailscale/translations/en.yaml

@apfohl
apfohl force-pushed the advertise-services branch from 2ca7ee2 to 1575c1d Compare July 17, 2026 11:08

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@tailscale/DOCS.md`:
- Around line 342-344: Update the https service description in the Tailscale
documentation to state that tailnet HTTPS certificates and MagicDNS must be
enabled beforehand, while retaining the note that Tailscale provisions the TLS
certificate automatically once those prerequisites are met.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e896e75-70d3-41e7-acf1-5ba217281b30

📥 Commits

Reviewing files that changed from the base of the PR and between 2ca7ee2 and 1575c1d.

📒 Files selected for processing (9)
  • tailscale/DOCS.md
  • tailscale/config.yaml
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/dependencies.d/post-tailscaled
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/type
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/up
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/share-services
  • tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
  • tailscale/translations/en.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/type
  • tailscale/translations/en.yaml
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/up
  • tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run
  • tailscale/config.yaml

Comment thread tailscale/DOCS.md Outdated
@apfohl
apfohl force-pushed the advertise-services branch from 1575c1d to 0b889d6 Compare July 17, 2026 11:22

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@tailscale/config.yaml`:
- Line 84: Update the target regex in the configuration’s target validation to
enforce the same valid port range, 1–65535, instead of accepting any digits
after 127.0.0.1. Preserve support for both http(s) and tcp schemes and local
ports within the valid range.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5944bed-00f9-4282-9fd0-f289f39099c0

📥 Commits

Reviewing files that changed from the base of the PR and between 1575c1d and 0b889d6.

📒 Files selected for processing (9)
  • tailscale/DOCS.md
  • tailscale/config.yaml
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/dependencies.d/post-tailscaled
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/type
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/up
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/share-services
  • tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
  • tailscale/translations/en.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/up
  • tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/type
  • tailscale/DOCS.md
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run

Comment thread tailscale/config.yaml Outdated
This adds a new `services` configuration option that allows users to advertise local services running on the Home Assistant host as Tailscale Services.

A new S6 oneshot service `share-services` reads the configured services and calls `tailscale serve --service=svc:<name>` for each entry. The `svc:` prefix is added automatically; users configure the plain service name.

Supported protocols are `http`, `https`, `tcp`, and `tls-terminated-tcp`. Services must be defined and approved in the Tailscale admin console before they can accept traffic.

Also updates the app documentation and translations for the new option.
@apfohl
apfohl force-pushed the advertise-services branch from 0b889d6 to 3379686 Compare July 17, 2026 11:27
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/DOCS.md
Comment thread tailscale/DOCS.md Outdated
Comment thread tailscale/DOCS.md Outdated
Comment thread tailscale/config.yaml Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/scripts/stage2_hook.sh Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
@lmagyar

lmagyar commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

And now a general comment.

The share_homeassistant option worked similarly a few years ago ("recreating" TS serve/funnel config), and as far as I remember there were complaints, that it comflicted manually configured serve configs, that's why that was changed to a front running serve/funnel with --bg=false.

So, when this change is merged, it will delete previously manually configured serve configs for the services. Hmmm, I think we should mark it (potentially) breaking change. The removal is logged, so I think it's fine.

Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
Comment thread tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run Outdated
@apfohl

apfohl commented Jul 19, 2026

Copy link
Copy Markdown
Author

@lmagyar Thanks for all the good feedback. I will work it in over the next week.

@lmagyar
lmagyar marked this pull request as draft July 22, 2026 10:27
@lmagyar

lmagyar commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

I've just marked it draft until you work on it.

@apfohl

apfohl commented Jul 23, 2026

Copy link
Copy Markdown
Author

@lmagyar I implemented most of it now. But it feels a hard to keep track. Is it okay if I close the worked of conversations? Or do you want to close them during a review later? I still need to test all the variants to expose the services now.

@lmagyar

lmagyar commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Feel free to do as you wish. :) I will remember what I've commented.

Meanwhile I'm thinking to leave share_homeassistant option as-is: that can use funnel also, but funnel is incompatible with services, and the current foreground running service behind share_homeassistant doesn't interfere with services (the port thing).

Maybe we can add a comment to the share_homeassistant option to the docs, that if somebody wants to share HA with services, use the services option instead.

@apfohl

apfohl commented Jul 28, 2026

Copy link
Copy Markdown
Author

Tried my best, to address everything in a meaningful way. I need to still test all protocols though.

@apfohl

apfohl commented Jul 28, 2026

Copy link
Copy Markdown
Author

Alright, I created a test Home Assistant App providing HTTP and TCP ports and configured the corresponding services in my Tailnet:

services:
  - name: svc:http
    target: http://127.0.0.1:8090
    protocol: http
    port: 8090
  - name: svc:https
    target: http://127.0.0.1:8090
    protocol: https
    port: 443
  - protocol: tls-terminated-tcp
    name: svc:tls-tcp
    target: tcp://127.0.0.1:8092
    port: 8093
  - name: svc:tcp
    target: tcp://127.0.0.1:8092
    protocol: tcp
    port: 8092

And a client:

go run . --tailnet=tailaa.ts.net --http-service=http --http-port=8090 --https-service=https --tcp-service=tcp --tcp-port=8092 --tls-tcp-service=tls-tcp --tls-tcp-port=8093

ADDRESS                              PROTOCOL   RESULT DETAIL
http.tailaa.ts.net:8090              HTTP       PASS
https.tailaa.ts.net:443              HTTPS      PASS
tcp.tailaa.ts.net:8092               TCP        PASS
tls-tcp.tailaa.ts.net:8093           TLS-TCP    PASS

@apfohl
apfohl marked this pull request as ready for review July 28, 2026 12:52
@apfohl
apfohl requested a review from lmagyar July 28, 2026 12:52

@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.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run`:
- Around line 45-53: The stale-service reconciliation must operate once per
Service name rather than once per entry: at lines 45-53, retain the drain
operation without immediately clearing the service, and defer clear until
draining connections have completed; at lines 89-95, remove the unconditional
per-entry clear and group mappings by service name before reconciliation so
endpoints from earlier entries are not deleted prematurely.

In `@tailscale/translations/en.yaml`:
- Around line 82-83: Align the service-name contract across the supplied schema,
runtime normalization, and documentation: choose whether users provide the svc:
prefix or omit it, then update validation and normalization to accept and
produce that same form consistently. Update the service-name documentation near
this text to match the selected contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f350ff59-f1d0-4867-b278-c4349d84b34b

📥 Commits

Reviewing files that changed from the base of the PR and between 3379686 and 8bddad2.

📒 Files selected for processing (4)
  • tailscale/DOCS.md
  • tailscale/config.yaml
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run
  • tailscale/translations/en.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • tailscale/config.yaml
  • tailscale/DOCS.md

Comment on lines +45 to +53
for service_name in "${current_names[@]}"; do
if ! printf '%s\n' "${configured_names[@]}" | grep -qx "${service_name}"; then
bashio::log.info "Removing stale service host configuration for ${service_name}"
if ! /opt/tailscale serve drain "${service_name}" >/dev/null 2>&1; then
bashio::log.warning "Failed to drain stale service host ${service_name}"
fi
if ! /opt/tailscale serve clear "${service_name}" >/dev/null 2>&1; then
bashio::log.warning "Failed to clear stale service host ${service_name}"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Do not clear a Service immediately after draining it.

drain preserves existing connections until they close; calling clear immediately afterward terminates that grace period. The per-entry cleanup also deletes endpoints added by an earlier entry with the same Service name. Reconcile once per Service and wait for draining connections before clearing obsolete mappings. (tailscale.com)

  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run#L45-L53: defer clearing stale services until their drain completes.
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run#L89-L95: avoid unconditional per-entry clear; group mappings by service name before reconciliation.
📍 Affects 1 file
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run#L45-L53 (this comment)
  • tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run#L89-L95
🤖 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 `@tailscale/rootfs/etc/s6-overlay/s6-rc.d/share-services/run` around lines 45 -
53, The stale-service reconciliation must operate once per Service name rather
than once per entry: at lines 45-53, retain the drain operation without
immediately clearing the service, and defer clear until draining connections
have completed; at lines 89-95, remove the unconditional per-entry clear and
group mappings by service name before reconciliation so endpoints from earlier
entries are not deleted prematurely.

Comment thread tailscale/translations/en.yaml
@lmagyar

lmagyar commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Give me some time... :)

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.

2 participants