Skip to content

feat/add tailscale module#849

Open
dy-ma wants to merge 21 commits intocoder:mainfrom
dy-ma:feat/add-tailscale-module
Open

feat/add tailscale module#849
dy-ma wants to merge 21 commits intocoder:mainfrom
dy-ma:feat/add-tailscale-module

Conversation

@dy-ma
Copy link
Copy Markdown

@dy-ma dy-ma commented Apr 17, 2026

Description

Adds a Tailscale module that installs Tailscale and joins the workspace to a tailnet on startup.

Supports:

  • OAuth client credentials flow (default) or a pre-generated auth key
  • Kernel and userspace networking (auto-detected, or set explicitly)
  • Tailscale SSH, subnet route advertising, MagicDNS, ephemeral/persistent nodes
  • SOCKS5 and HTTP proxies in userspace mode

Tailscale seemed like an obvious fit for a Coder module and I was surprised one didn't already exist. I needed it for my own deployments so I built it.

This is my first open-source contribution. I've done my best to follow the contribution guidelines, but please tell me if I've done something horribly wrong.

Type of Change

  • New module
  • New template
  • Bug fix
  • Feature/enhancement
  • Documentation
  • Other

Module Information

Path: registry/dy-ma/modules/tailscale
New version: v1.0.0
Breaking change: [ ] Yes [x] No

Template Information

Path: registry/[namespace]/templates/[template-name]

Testing & Validation

  • Tests pass (bun test)
  • Code formatted (bun fmt)
  • Changes tested locally

Related Issues

None

Generated with Claude Code using Claude Sonnet 4.5

@35C4n0r 35C4n0r self-requested a review April 20, 2026 16:37
@35C4n0r
Copy link
Copy Markdown
Collaborator

35C4n0r commented Apr 20, 2026

@dy-ma can you fix the failing test ?

@dy-ma
Copy link
Copy Markdown
Author

dy-ma commented Apr 28, 2026

Fixed the ShellCheck failure - sudo doesn't affect redirects, so I changed line 96 from &> /tmp/tailscaled.log to use sudo tee instead.

The fix passes ShellCheck locally. Ready for another review when you have a chance.

@matifali
Copy link
Copy Markdown
Member

Hi @dy-ma, can you update the module to conform with instructions in AGENTS.md?

@dy-ma
Copy link
Copy Markdown
Author

dy-ma commented Apr 29, 2026

As far as I can see, the only non-conformance was the Tailscale icon being referanced via CDN link instead of local path. I initially wasn't sure of the correct approach for adding new icons, but since I had already added the icon to .icons/ for the README, I've updated the module to use the local path as well.

Also, I wasn't aware of the AI attribution policy, so I edited my comment. I appreciate the patience, as this is my first open source contribution.

data "coder_workspace" "me" {}

locals {
icon_url = "${path.module}/../../../../.icons/tailscale.svg"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will not work. Coder can only show bundled icons or something accessible over the network.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I see. The guidelines mention the README icon must be a local path. There isn't a bundled Tailscale icon. Is the correct approach for me to add the icon in .icons, but only reference it for the README, and use a network call for the module?

Comment on lines +182 to +209
resource "coder_script" "install_tailscale" {
agent_id = var.agent_id
display_name = "Tailscale"
icon = local.icon_url
script = templatefile("${path.module}/run.sh", {
TAILSCALE_API_URL = var.tailscale_api_url
AUTH_KEY = var.auth_key
OAUTH_CLIENT_ID = var.oauth_client_id
OAUTH_CLIENT_SECRET = var.oauth_client_secret
TAILNET = var.tailnet
HOSTNAME = local.hostname
TAGS_JSON = local.tags_json
TAGS_CSV = local.tags_csv
EPHEMERAL = var.ephemeral
PREAUTHORIZED = var.preauthorized
NETWORKING_MODE = var.networking_mode
SOCKS5_PORT = var.socks5_proxy_port
HTTP_PROXY_PORT = var.http_proxy_port
ACCEPT_DNS = var.accept_dns
ACCEPT_ROUTES = var.accept_routes
ADVERTISE_ROUTES = join(",", var.advertise_routes)
SSH = var.ssh
EXTRA_FLAGS = var.extra_flags
STATE_DIR = var.state_dir
})
run_on_start = true
run_on_stop = false
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You could try using the coder-utils module to manage installation and start the service.

This would also help comply with the suggested logs storage preference.

Copy link
Copy Markdown
Member

matifali commented Apr 29, 2026

.icons is for the registry.coder.com and this repo.
But for the scripts to show this icon on real coder deployments we need the icon refrenced from built-in icons at coder/coder or a network path.

@dy-ma
Copy link
Copy Markdown
Author

dy-ma commented Apr 29, 2026

I split run.sh into install.sh and start.sh, and used the coder_utils module to orchestrate them, and added a module directory for logging (I believe this is what you were mentioning by "log storage preference"?)

I considered using a post-install configuration, but in order to configure tailscale, the daemon needs to be running first, and I figure I'd want the flags to be reapplied on restart anyways. So I kept it to just a start.sh.

@dy-ma
Copy link
Copy Markdown
Author

dy-ma commented May 6, 2026

Upon further testing, I ran into an apt lock race condition between this module and KasmVNC, as both run apt-get update concurrently at boot, and whichever runs second fails with E: Could not get lock /var/lib/apt/lists/lock.

I looked into solutions and found coder/coder#22365 and coder/registry#82. The experimental coder exp sync feature could coordinate ordering but requires the template author to wire it up. It doesn't seem like I can have the module self-coordinate using it.

I'm currently testing a few approaches for handling this in install.sh:

  • Wrapping apt-get in /usr/local/bin with APT::Lock::Timeout so the timeout applies even through sudo (similar to tailscale/tailscale#12291
  • Doing the apt repo setup directly instead of piping to the official install script, so we can control the apt-get calls ourselves. But this requires branching the install methods for each distro, and introducing opportunities for drift between this module and the official installation methods.
  • Polling for the apt lock to be released between retries.

Happy to go with whatever approach you think fits best here, or if there's a preferred pattern for modules, please let me know.

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.

3 participants