Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
159baf9
feat: migrate tailscale configuration to YAML and update proxy settings
atj4me Sep 12, 2025
9935792
fix: update project_files to reference correct configuration file
atj4me Sep 12, 2025
97fa037
fix: update web_extra_exposed_ports to use string format
atj4me Sep 12, 2025
4fe2ef6
fix: remove unused web_extra_volumes configuration
atj4me Sep 12, 2025
fd30bff
fix: remove web_extra_exposed_ports configuration
atj4me Sep 12, 2025
876ea00
feat: add Dockerfile for Tailscale installation and update project files
atj4me Sep 12, 2025
b6e41c4
fix: update project_files to reference correct Dockerfile path and ad…
atj4me Sep 12, 2025
a936675
feat: update install.yaml to reference Dockerfile.tailscale and add D…
atj4me Sep 12, 2025
d6f5403
feat: simplify Tailscale command in config and add environment variab…
atj4me Sep 12, 2025
58eca19
fix: add missing comments to Dockerfile.tailscale for clarity
atj4me Sep 12, 2025
7b7864a
fix: add missing newline for clarity in config.tailscale.yaml
atj4me Sep 12, 2025
5c26b7e
fix: improve clarity in config.tailscale.yaml by adding comments and …
atj4me Sep 12, 2025
b69781d
fix: correct paths in config.tailscale.yaml and add docker-compose.ta…
atj4me Sep 12, 2025
13e2176
feat: add Tailscale configuration and Dockerfile for routing capabili…
atj4me Sep 12, 2025
e917db2
fix: update project file reference from config.tailscale.yaml to conf…
atj4me Sep 12, 2025
dca717e
feat: enhance Tailscale routing setup with improved configuration and…
atj4me Sep 12, 2025
53886d9
feat: add 'share' command to Tailscale functionality and remove obsol…
atj4me Sep 12, 2025
dc46227
fix: remove obsolete Tailscale configuration files from install.yaml
atj4me Sep 12, 2025
96931a4
chore: update configuration for Tailscale routing capabilities
atj4me Sep 12, 2025
000a123
feat: add support for sharing with public flag and forward arbitrary …
atj4me Sep 12, 2025
ecb3771
feat: update Tailscale share command documentation and remove obsolet…
atj4me Sep 13, 2025
bbcb5a3
fix: update ownership command in Dockerfile to use dynamic username v…
atj4me Sep 13, 2025
5e03fe1
Update config.tailscale-router.yaml
atj4me Sep 13, 2025
78e8aa4
Update config.tailscale-router.yaml
atj4me Sep 13, 2025
34ce943
Update commands/host/tailscale
atj4me Sep 13, 2025
666e005
Update config.tailscale-router.yaml
atj4me Sep 13, 2025
2bce8a3
Update commands/host/tailscale
atj4me Sep 13, 2025
9ef1771
Refactor Tailscale command and update configuration for hostname
atj4me Sep 13, 2025
acee5df
Update commands/host/tailscale
atj4me Sep 13, 2025
68f090c
Apply suggestion from @Copilot
atj4me Sep 13, 2025
4638319
Apply suggestion from @Copilot
atj4me Sep 13, 2025
b78154c
Apply suggestion from @Copilot
atj4me Sep 13, 2025
0cb927e
Update Tailscale configuration and Dockerfile for improved setup
atj4me Sep 15, 2025
f3fbc9a
Merge branch 'atj4me/issue19' of https://github.com/atj4me/ddev-tails…
atj4me Sep 15, 2025
cd82437
Fix docker-compose file reference in install.yaml
atj4me Sep 15, 2025
92b3b38
Add traefik configuration file to project files in install.yaml
atj4me Sep 15, 2025
e02732c
Add ddev-generated comments to Traefik configuration files
atj4me Sep 15, 2025
7692914
Fix typo in install.yaml: change 'global_config' to 'global_files'
atj4me Sep 15, 2025
50d9d28
Update install.yaml for improved installation instructions
atj4me Sep 15, 2025
eb1c853
Fix file paths in project_files section of install.yaml
atj4me Sep 15, 2025
7a36f58
Add README.txt for custom certificates and update project_files in in…
atj4me Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,39 @@ Access all [Tailscale CLI](https://tailscale.com/kb/1080/cli) commands plus help
| ------- | ----------- |
| `ddev tailscale <anything>` | Run any Tailscale CLI command |
| `ddev tailscale launch` | Launch your project's Tailscale URL in browser |
| `ddev tailscale share [--bg] [--public]` | Start sharing your project (use `--public` for Funnel, `--bg` to run in background) |
| `ddev tailscale status` | Show Tailscale status |
| `ddev tailscale ping <device>` | Ping a Tailscale device |
| `ddev tailscale stat` | Show status with self and active peers only |
| `ddev tailscale proxy` | Show funnel status |
| `ddev tailscale url` | Get your project's Tailscale URL |
| `ddev logs -s tailscale-router` | Show logs for the Tailscale router service |

## Advanced Customization
You can run any [Tailscale CLI](https://tailscale.com/kb/1080/cli) command directly, and use the special `--public` flag to share via Funnel:

To change the used Docker image:
| Command | Description |
| ------- | ----------- |
| `ddev tailscale <any tailscale command> [flags]` | Run any Tailscale CLI command (all arguments except `--public` are passed through) |

```bash
ddev dotenv set .ddev/.env.tailscale-router --ts-docker-image=tailscale/tailscale:latest
ddev restart
```
**Note:**
- The `--public` flag is handled by the wrapper and will switch to Tailscale Funnel mode (public sharing). It is not passed to the Tailscale CLI.
- All other arguments and flags are forwarded to the Tailscale CLI as-is.

All customization options (use with caution):
**Examples:**

| Variable | Flag | Default |
| -------- | ---- | ------- |
| `TS_DOCKER_IMAGE` | `--ts-docker-image` | `tailscale/tailscale:latest` |
| `TS_AUTHKEY` | `--ts-authkey` | (none, required, not recommended to set in `.ddev/.env.tailscale-router`) |
| `TS_PRIVACY` | `--ts-privacy` | `private` (`private`/`public`) |
```bash
# Launch private share (default)
ddev tailscale launch
# Launch public share (Funnel)
ddev tailscale launch --public
# Start sharing in background (private)
ddev tailscale share --bg
# Start sharing in background (public)
ddev tailscale share --bg --public
# Run any Tailscale CLI command
ddev tailscale status
ddev tailscale ping <device>
```

## Components of the Repository

Expand Down
89 changes: 74 additions & 15 deletions commands/host/tailscale
Original file line number Diff line number Diff line change
@@ -1,25 +1,84 @@
#!/usr/bin/env bash

## #ddev-generated: If you want to edit and own this file, remove this line.
## Description: Tailscale command with launch functionality
## Usage: tailscale [launch|stat|proxy|url|args...]
## Example: "ddev tailscale launch"
## Description: Tailscale command with launch/share functionality
## Usage: tailscale [launch|share|stat|proxy|url|args...]
## Example: "ddev tailscale launch" or "ddev tailscale share"

if [ "$1" = "launch" ] || [ $# -eq 0 ]; then
TAILSCALE_URL=$(ddev exec -s tailscale-router tailscale funnel status | grep -o 'https://[^[:space:]]*' | head -1)


# Helper to run tailscale in the web container
tailscale_web() {
ddev exec -s web tailscale "$@"
}

# Helper to extract the Tailscale URL from status
get_tailscale_url() {
tailscale_web "$1" status | grep -o 'https://[^[:space:]]*' | head -1
}

# Helper to run tailscale share/funnel/serve with options
run_tailscale_share() {
local cmd="$1"
local bg_flag="$2"
tailscale_web $cmd $bg_flag 127.0.0.1:$DDEV_ROUTER_HTTP_PORT
}

stop_tailscale_share() {
local cmd="$1"
tailscale_web $cmd --https=443 off
}

# Parse args, handle --public, and forward all other args
CMD="serve"
LABEL="private"
ARGS=()
for arg in "$@"; do
if [ "$arg" = "--public" ]; then
CMD="funnel"
LABEL="public"
else
ARGS+=("$arg")
fi
done

# If no args, default to launching a share
if [ ${#ARGS[@]} -eq 0 ] || [ "${ARGS[0]}" = "launch" ]; then
TAILSCALE_URL=$(get_tailscale_url $CMD)
if [ -z "$TAILSCALE_URL" ]; then
echo "No share found, creating one..."
run_tailscale_share "$CMD" "--bg"
sleep 2
TAILSCALE_URL=$(get_tailscale_url $CMD)
fi
if [ -z "$TAILSCALE_URL" ]; then
echo "Error: Could not retrieve Tailscale URL after $CMD."
exit 1
fi
echo "Tailscale $LABEL URL: $TAILSCALE_URL"
ddev launch "$TAILSCALE_URL"
elif [ "${ARGS[0]}" = "share" ]; then
BG_FLAG=""
for arg in "${ARGS[@]}"; do
if [ "$arg" = "--bg" ]; then
BG_FLAG="--bg"
fi
done
run_tailscale_share "$CMD" "$BG_FLAG"
TAILSCALE_URL=$(get_tailscale_url $CMD)
if [ -z "$TAILSCALE_URL" ]; then
echo "Error: Could not retrieve Tailscale URL."
echo "Error: Could not retrieve Tailscale URL after $CMD."
exit 1
fi

echo "Tailscale $LABEL URL: $TAILSCALE_URL"
ddev launch "$TAILSCALE_URL"
elif [ "$1" = "stat" ]; then
ddev exec -s tailscale-router tailscale status --self --peers=false --active=true
elif [ "$1" = "proxy" ]; then
ddev exec -s tailscale-router tailscale funnel status
elif [ "$1" = "url" ]; then
ddev exec -s tailscale-router tailscale funnel status | grep -o 'https://[^[:space:]]*' | head -1
elif [ "${ARGS[0]}" = "stop" ] ; then
stop_tailscale_share "${ARGS[0]}"
elif [ "${ARGS[0]}" = "stat" ]; then
tailscale_web status --self --peers=false --active=true
elif [ "${ARGS[0]}" = "proxy" ]; then
tailscale_web $CMD status
elif [ "${ARGS[0]}" = "url" ]; then
get_tailscale_url $CMD
else
ddev exec -s tailscale-router tailscale "$@"
tailscale_web "${ARGS[@]}"
fi
9 changes: 9 additions & 0 deletions config.tailscale-router.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ddev-generated
# This configuration adds Tailscale routing capabilities to the web container.
web_extra_daemons:
- name: tailscale-service
command: tailscaled --statedir=${TS_STATE_DIR} --tun=userspace-networking
directory: /usr/sbin
- name: "tailscale-router"
command: tailscale up --auth-key=$TS_AUTHKEY --hostname=$TS_HOSTNAME $TS_EXTRA_ARGS
directory: /var/www/html
40 changes: 9 additions & 31 deletions docker-compose.tailscale-router.yaml
Original file line number Diff line number Diff line change
@@ -1,37 +1,15 @@
#ddev-generated
services:
tailscale-router:
image: ${TS_DOCKER_IMAGE:-tailscale/tailscale:latest}-${DDEV_SITENAME}-built
build:
dockerfile_inline: |
ARG TS_DOCKER_IMAGE=scratch
FROM $$TS_DOCKER_IMAGE
RUN apk add --no-cache socat
args:
TS_DOCKER_IMAGE: ${TS_DOCKER_IMAGE:-tailscale/tailscale:latest}
hostname: ${DDEV_SITENAME}
container_name: ddev-${DDEV_SITENAME}-tailscale-router
web:
environment:
TS_AUTHKEY: ${TS_AUTHKEY:-}
TS_HOSTNAME: ${DDEV_SITENAME}
TS_EXTRA_ARGS: --accept-routes --ssh
TS_STATE_DIR: /var/lib/tailscale
TS_USERSPACE: "true"
TS_PRIVACY: ${TS_PRIVACY:-private}
TS_SERVE_CONFIG: /config/tailscale-${TS_PRIVACY:-private}.json
- TS_AUTHKEY=${TS_AUTHKEY}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_AUTHKEY=${TS_AUTHKEY:-}

- TS_HOSTNAME=${DDEV_SITENAME}
- TS_EXTRA_ARGS=--accept-routes --ssh
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_PRIVACY=${TS_PRIVACY:-private}
- TS_SERVE_CONFIG=/config/tailscale-${TS_PRIVACY}.json
volumes:
- tailscale-router-state:/var/lib/tailscale
- ./tailscale-router/config:/config:ro
- .:/mnt/ddev_config
- ddev-global-cache:/mnt/ddev-global-cache
restart: "no"
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
depends_on:
- web
post_start:
- command: ["sh", "-c", "socat TCP-LISTEN:8080,reuseaddr,fork TCP:web:${DDEV_ROUTER_HTTP_PORT} >> /var/log/ddev.log 2>&1 &"]

- tailscale-router-state:/var/lib/tailscale
volumes:
tailscale-router-state:
6 changes: 3 additions & 3 deletions install.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: tailscale-router

project_files:
- tailscale-router/config/tailscale-private.json
- tailscale-router/config/tailscale-public.json
- docker-compose.tailscale-router.yaml
- config.tailscale-router.yaml
- commands/host/tailscale
- web-build/Dockerfile.tailscale-router
- docker-compose.tailscale-router.yaml

ddev_version_constraint: '>= v1.24.3'

Expand Down
20 changes: 0 additions & 20 deletions tailscale-router/config/tailscale-private.json

This file was deleted.

20 changes: 0 additions & 20 deletions tailscale-router/config/tailscale-public.json

This file was deleted.

76 changes: 21 additions & 55 deletions tests/test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,16 @@ teardown() {
fi
}


@test "install from directory" {
set -eu -o pipefail
echo "# ddev add-on get ${DIR} with project ${PROJNAME} in $(pwd)" >&3
run ddev add-on get "${DIR}"
assert_success
run ddev restart -y
assert_success
health_checks
}

# bats test_tags=release
@test "install from release" {
set -eu -o pipefail
echo "# ddev add-on get ${GITHUB_REPO} with project ${PROJNAME} in $(pwd)" >&3
run ddev add-on get "${GITHUB_REPO}"
assert_success
run ddev restart -y
assert_success
health_checks
}

@test "tailscale command exists and responds" {
set -eu -o pipefail
Expand All @@ -87,35 +77,13 @@ teardown() {
run ddev restart -y
assert_success

# Test tailscale command exists (without --help which may not work)
# Test tailscale command exists (should not crash)
run ddev tailscale status
# Command should execute (may show error but shouldn't crash)
}

@test "tailscale service container is running" {
set -eu -o pipefail
run ddev add-on get "${DIR}"
assert_success
run ddev restart -y
assert_success

# Check if tailscale-router container exists
run docker ps -a --filter "name=ddev-${PROJNAME}-tailscale-router" --format "{{.Names}}"
assert_success
assert_output "ddev-${PROJNAME}-tailscale-router"
}

@test "configuration files are properly installed" {
set -eu -o pipefail
run ddev add-on get "${DIR}"
assert_success

# Check if config files exist
assert_file_exists ".ddev/tailscale-router/config/tailscale-private.json"
assert_file_exists ".ddev/tailscale-router/config/tailscale-public.json"
assert_file_exists ".ddev/docker-compose.tailscale-router.yaml"
assert_file_exists ".ddev/commands/host/tailscale"
}

@test "tailscale command shortcuts work" {
set -eu -o pipefail
Expand All @@ -131,38 +99,36 @@ teardown() {
# Test proxy command
run ddev tailscale proxy
# Command should execute
}

@test "docker-compose file has required services and volumes" {
# Test share and launch commands (should not crash)
run ddev tailscale share --bg
run ddev tailscale launch
}
@test "tailscale share --public forwards correctly" {
set -eu -o pipefail
run ddev add-on get "${DIR}"
assert_success

# Check docker-compose file contains required elements
run grep -q "tailscale-router:" ".ddev/docker-compose.tailscale-router.yaml"
assert_success

run grep -q "tailscale-router-state:" ".ddev/docker-compose.tailscale-router.yaml"
run ddev restart -y
assert_success

run grep -q "image: \${TS_DOCKER_IMAGE:-tailscale/tailscale:latest}-\${DDEV_SITENAME}-built" ".ddev/docker-compose.tailscale-router.yaml"
assert_success
# Should use funnel (public) and not pass --public to tailscale CLI
run ddev tailscale share --public --bg
# Command should execute (may show error if not logged in, but shouldn't crash)
}

@test "configuration supports both private and public modes" {
@test "tailscale arbitrary args are forwarded" {
set -eu -o pipefail
run ddev add-on get "${DIR}"
assert_success

# Check private config
run grep -q '"AllowFunnel"' ".ddev/tailscale-router/config/tailscale-private.json"
assert_success
run grep -q 'false' ".ddev/tailscale-router/config/tailscale-private.json"
run ddev restart -y
assert_success

# Check public config
run grep -q '"AllowFunnel"' ".ddev/tailscale-router/config/tailscale-public.json"
assert_success
run grep -q 'true' ".ddev/tailscale-router/config/tailscale-public.json"
assert_success
# Should forward all args except --public
run ddev tailscale status
# Command should execute (may show error if not logged in, but shouldn't crash)

run ddev tailscale ping 127.0.0.1
# Command should execute (may show error if not logged in, but shouldn't crash)
}


Loading
Loading