From 9aa6168f76677b2a324f5938b81f4c9a09d7e1ff Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 7 Sep 2025 22:52:10 +1200 Subject: [PATCH] Add tailscale! --- tailscale/README.txt | 104 ++++++++++++++++++++++++++++ tailscale/config/.template_info.env | 27 ++++++++ tailscale/config/service.env | 25 +++++++ tailscale/destroy.sh | 35 ++++++++++ tailscale/install.sh | 45 ++++++++++++ tailscale/logs.sh | 26 +++++++ tailscale/ssh.sh | 17 +++++ tailscale/start.sh | 90 ++++++++++++++++++++++++ tailscale/status.sh | 62 +++++++++++++++++ tailscale/stop.sh | 26 +++++++ tailscale/uninstall.sh | 29 ++++++++ versions.json | 1 + 12 files changed, 487 insertions(+) create mode 100644 tailscale/README.txt create mode 100644 tailscale/config/.template_info.env create mode 100644 tailscale/config/service.env create mode 100755 tailscale/destroy.sh create mode 100755 tailscale/install.sh create mode 100755 tailscale/logs.sh create mode 100755 tailscale/ssh.sh create mode 100755 tailscale/start.sh create mode 100755 tailscale/status.sh create mode 100755 tailscale/stop.sh create mode 100755 tailscale/uninstall.sh diff --git a/tailscale/README.txt b/tailscale/README.txt new file mode 100644 index 0000000..1dde772 --- /dev/null +++ b/tailscale/README.txt @@ -0,0 +1,104 @@ +Tailscale VPN Service Template +============================== + +This template deploys Tailscale as a Docker container, providing secure network access +to your server through Tailscale's zero-config VPN. + +REQUIREMENTS +------------ +* A Tailscale account (free at https://tailscale.com) +* An auth key from https://login.tailscale.com/admin/settings/keys +* Docker installed on the host system + +CONFIGURATION +------------- +Before installation, you MUST set the following in your service configuration: + +1. TAILSCALE_AUTH_KEY (required) + - Generate at: https://login.tailscale.com/admin/settings/keys + - Can be reusable or one-time use + - Required for automatic connection + +2. TAILSCALE_HOSTNAME (optional) + - Custom hostname for this node in your Tailscale network + - If not set, uses the system hostname + +3. TAILSCALE_EXTRA_ARGS (optional) + - Additional Tailscale arguments + - Examples: + --advertise-exit-node (make this an exit node) + --accept-routes (accept advertised routes) + --advertise-routes=10.0.0.0/24 (advertise local routes) + +4. TAILSCALE_USERSPACE (optional) + - Set to "true" for environments without TUN device support + - Useful for some container platforms or restricted environments + +DEFAULT SETTINGS +---------------- +* Container name: tailscale +* Image: tailscale/tailscale:stable +* State volume: tailscale_state (persistent across restarts) +* Network mode: Host networking with NET_ADMIN capability + +USAGE +----- +After installation, your server will be accessible through your Tailscale network: + +1. Access by Tailscale IP: + - Find IP with: ./status.sh + - Connect via: ssh user@100.x.x.x + +2. Access by MagicDNS name: + - Enable MagicDNS in Tailscale admin console + - Connect via: ssh user@hostname + +3. Use as exit node (if configured): + - Configure with: --advertise-exit-node in TAILSCALE_EXTRA_ARGS + - Approve in Tailscale admin console + - Route traffic through this server + +COMMANDS +-------- +* ./install.sh - Install and start Tailscale +* ./start.sh - Start the Tailscale container +* ./stop.sh - Stop the Tailscale container +* ./status.sh - Check Tailscale connection status +* ./logs.sh - View Tailscale logs +* ./ssh.sh - Access container shell +* ./uninstall.sh - Remove container (preserves state) +* ./destroy.sh - Complete removal including state + +TROUBLESHOOTING +--------------- +1. Container won't start: + - Check TAILSCALE_AUTH_KEY is set correctly + - Verify Docker has necessary permissions + - Review logs with ./logs.sh + +2. Not connecting to network: + - Ensure auth key is valid and not expired + - Check if key is reusable if using multiple times + - Verify no firewall blocking outbound connections + +3. Can't create TUN device: + - Set TAILSCALE_USERSPACE=true for userspace mode + - This is slower but works in restricted environments + +4. Need to re-authenticate: + - Generate new auth key + - Update configuration + - Restart with ./start.sh + +SECURITY NOTES +-------------- +* Auth keys should be kept secret +* Use ephemeral keys for temporary access +* Regularly review connected devices in admin console +* Consider using ACLs to restrict access + +RESOURCES +--------- +* Tailscale Documentation: https://tailscale.com/kb/ +* Admin Console: https://login.tailscale.com/admin/ +* ACL Guide: https://tailscale.com/kb/1018/acls/ \ No newline at end of file diff --git a/tailscale/config/.template_info.env b/tailscale/config/.template_info.env new file mode 100644 index 0000000..2373cb5 --- /dev/null +++ b/tailscale/config/.template_info.env @@ -0,0 +1,27 @@ +# DO NOT EDIT THIS FILE FOR YOUR SERVICE! +# This file is replaced from the template whenever there is an update. +# Edit the service.env file to make changes. + +# Template to use - always required! +TEMPLATE=tailscale +REQUIRES_HOST_ROOT=false +REQUIRES_DOCKER=true +REQUIRES_DOCKER_ROOT=false + +# Service settings +CONTAINER_NAME=tailscale + +# Image settings +IMAGE_REGISTRY="docker.io" +IMAGE_REPO="tailscale/tailscale" +IMAGE_TAG="stable" + +# Volumes for persistent state +STATE_VOLUME="${CONTAINER_NAME}_state" + +# Tailscale settings (to be overridden in service.env) +TAILSCALE_AUTH_KEY="" +TAILSCALE_HOSTNAME="" +TAILSCALE_EXTRA_ARGS="" +TAILSCALE_USERSPACE="false" + diff --git a/tailscale/config/service.env b/tailscale/config/service.env new file mode 100644 index 0000000..d0903ee --- /dev/null +++ b/tailscale/config/service.env @@ -0,0 +1,25 @@ +# Service settings for Tailscale +# (can also override anything in the .template_info.env file in the template to make it specific to this server) + +# REQUIRED: Your Tailscale authentication key +# Get this from: https://login.tailscale.com/admin/settings/keys +# Can be reusable or one-time use +TAILSCALE_AUTH_KEY= + +# Optional: Custom hostname for this node +# If not set, will use the system hostname +TAILSCALE_HOSTNAME= + +# Optional: Additional Tailscale arguments +# Examples: +# TAILSCALE_EXTRA_ARGS="--advertise-exit-node" +# TAILSCALE_EXTRA_ARGS="--accept-routes" +# TAILSCALE_EXTRA_ARGS="--advertise-routes=10.0.0.0/24" +TAILSCALE_EXTRA_ARGS= + +# Optional: Enable userspace networking (for environments without TUN support) +# Set to "true" if running in restricted environments +TAILSCALE_USERSPACE=false + +# Server Settings +SSH_USER="dropshell" \ No newline at end of file diff --git a/tailscale/destroy.sh b/tailscale/destroy.sh new file mode 100755 index 0000000..6cc97e5 --- /dev/null +++ b/tailscale/destroy.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "STATE_VOLUME" + +# Tailscale Destroy Script - Complete removal including data + +echo "WARNING: This will completely remove Tailscale and all its data!" +echo "This includes the Tailscale state and authentication." +echo "" +read -p "Are you sure you want to continue? (yes/no): " confirmation + +if [ "$confirmation" != "yes" ]; then + echo "Destruction cancelled." + exit 0 +fi + +echo "" +echo "Destroying Tailscale service and data..." + +# First run uninstall +${SERVICE_PATH}/uninstall.sh + +# Remove the state volume +echo "Removing Tailscale state volume..." +if docker volume ls | grep -q ${STATE_VOLUME}; then + docker volume rm ${STATE_VOLUME} || echo "Warning: Could not remove volume ${STATE_VOLUME}" +else + echo "Volume ${STATE_VOLUME} does not exist." +fi + +echo "" +echo "Tailscale has been completely destroyed." +echo "Note: You should also remove this node from your Tailscale admin console at:" +echo " https://login.tailscale.com/admin/machines" \ No newline at end of file diff --git a/tailscale/install.sh b/tailscale/install.sh new file mode 100755 index 0000000..d766bac --- /dev/null +++ b/tailscale/install.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG" "TAILSCALE_AUTH_KEY" + +# Check if auth key is set +if [ -z "$TAILSCALE_AUTH_KEY" ] || [ "$TAILSCALE_AUTH_KEY" = "" ]; then + _die "TAILSCALE_AUTH_KEY is not set in config/service.env! Please add your Tailscale auth key." +fi + +_check_docker_installed || _die "Docker test failed, aborting installation..." + +echo "Installing Tailscale service..." + +echo "Pulling Tailscale image..." +docker pull "$IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" || _die "Failed to pull image $IMAGE_REGISTRY/$IMAGE_REPO:$IMAGE_TAG" + +# Create volume for persistent state +echo "Creating volume for Tailscale state..." +if ! docker volume create ${STATE_VOLUME}; then + echo "Volume ${STATE_VOLUME} may already exist, continuing..." +fi + +# Stop and remove existing container if it exists +if _is_container_exists "$CONTAINER_NAME"; then + echo "Removing existing container..." + bash ./stop.sh 2>/dev/null || true + _remove_container "$CONTAINER_NAME" || true +fi + +# Start the tunnel +bash ./start.sh || _die "Failed to start Tailscale" + +echo "" +echo "==========================================" +echo "Tailscale installation complete!" +echo "==========================================" +echo "" +echo "Next steps:" +echo "1. Check connection status: ds status [server] tailscale" +echo "2. View logs: ds logs [server] tailscale" +echo "3. Manage device in Tailscale admin console:" +echo " https://login.tailscale.com/admin/machines" +echo "" +echo "Your device should appear as connected in the admin console." \ No newline at end of file diff --git a/tailscale/logs.sh b/tailscale/logs.sh new file mode 100755 index 0000000..e7333e2 --- /dev/null +++ b/tailscale/logs.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +# Tailscale Logs Script + +# Optional: follow logs with -f flag +FOLLOW="" +if [ "$1" = "-f" ] || [ "$1" = "--follow" ]; then + FOLLOW="-f" +fi + +if ! _is_container_exists "$CONTAINER_NAME"; then + echo "Error: Tailscale container does not exist." + exit 1 +fi + +echo "Fetching logs from Tailscale container..." +echo "=========================================" +docker logs ${FOLLOW} ${CONTAINER_NAME} 2>&1 + +if [ -z "$FOLLOW" ]; then + echo "" + echo "Tip: Use '${SERVICE_PATH}/logs.sh -f' to follow logs in real-time." +fi \ No newline at end of file diff --git a/tailscale/ssh.sh b/tailscale/ssh.sh new file mode 100755 index 0000000..ad84818 --- /dev/null +++ b/tailscale/ssh.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +# Tailscale SSH Script - Access container shell + +if ! _is_container_running "$CONTAINER_NAME"; then + echo "Error: Tailscale container is not running." + echo "Start it with: ${SERVICE_PATH}/start.sh" + exit 1 +fi + +echo "Entering Tailscale container shell..." +echo "Type 'exit' to leave the container." +echo "" +docker exec -it ${CONTAINER_NAME} /bin/sh \ No newline at end of file diff --git a/tailscale/start.sh b/tailscale/start.sh new file mode 100755 index 0000000..f5d5e24 --- /dev/null +++ b/tailscale/start.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "IMAGE_REGISTRY" "IMAGE_REPO" "IMAGE_TAG" "TAILSCALE_AUTH_KEY" + +# Check if auth key is set +if [ -z "$TAILSCALE_AUTH_KEY" ] || [ "$TAILSCALE_AUTH_KEY" = "" ]; then + _die "TAILSCALE_AUTH_KEY is not set in config/service.env! Please add your Tailscale auth key." +fi + +echo "Starting Tailscale container..." + +# Determine if we should use userspace networking +NETWORK_MODE="" +CAP_ADD="" +DEVICE_MOUNT="" + +if [ "$TAILSCALE_USERSPACE" = "true" ]; then + echo "Using userspace networking mode..." + TAILSCALE_EXTRA_ARGS="--tun=userspace-networking ${TAILSCALE_EXTRA_ARGS}" +else + # Standard mode with TUN device + CAP_ADD="--cap-add=NET_ADMIN --cap-add=SYS_MODULE" + DEVICE_MOUNT="--device=/dev/net/tun:/dev/net/tun" +fi + +# Build hostname argument if provided +HOSTNAME_ARG="" +if [ -n "$TAILSCALE_HOSTNAME" ]; then + HOSTNAME_ARG="--hostname=${TAILSCALE_HOSTNAME}" +fi + +# Build the Docker run command +DOCKER_RUN_CMD="docker run -d \ + --restart unless-stopped \ + --name ${CONTAINER_NAME} \ + --network=host \ + -v ${STATE_VOLUME}:/var/lib/tailscale \ + -v /dev/net/tun:/dev/net/tun \ + ${CAP_ADD} \ + ${DEVICE_MOUNT} \ + -e TS_AUTHKEY=${TAILSCALE_AUTH_KEY} \ + -e TS_STATE_DIR=/var/lib/tailscale \ + -e TS_USERSPACE=${TAILSCALE_USERSPACE} \ + ${HOSTNAME_ARG} \ + ${IMAGE_REGISTRY}/${IMAGE_REPO}:${IMAGE_TAG} \ + tailscaled" + +# Create and start the container +if ! _create_and_start_container "$DOCKER_RUN_CMD" "$CONTAINER_NAME"; then + _die "Failed to start Tailscale container" +fi + +# Give it a moment to initialize +sleep 2 + +# Check if the container is still running (didn't crash immediately) +if ! _is_container_running "$CONTAINER_NAME"; then + echo "Container failed to start. Checking logs..." + docker logs "$CONTAINER_NAME" 2>&1 | tail -20 + _die "Tailscale container exited unexpectedly. Check the TAILSCALE_AUTH_KEY and logs above." +fi + +# Connect to Tailscale network +echo "Connecting to Tailscale network..." + +# Build tailscale up command +TAILSCALE_UP_CMD="tailscale up --authkey=${TAILSCALE_AUTH_KEY}" + +if [ -n "$TAILSCALE_HOSTNAME" ]; then + TAILSCALE_UP_CMD="${TAILSCALE_UP_CMD} --hostname=${TAILSCALE_HOSTNAME}" +fi + +if [ -n "$TAILSCALE_EXTRA_ARGS" ]; then + TAILSCALE_UP_CMD="${TAILSCALE_UP_CMD} ${TAILSCALE_EXTRA_ARGS}" +fi + +# Execute tailscale up command +if ! docker exec ${CONTAINER_NAME} ${TAILSCALE_UP_CMD}; then + echo "Warning: Failed to connect to Tailscale network automatically." + echo "You may need to connect manually using:" + echo " docker exec ${CONTAINER_NAME} tailscale up" +fi + +echo "" +echo "Tailscale started successfully!" +echo "Container: ${CONTAINER_NAME}" +echo "" +echo "The device should appear as connected in your Tailscale admin console." +echo "Manage at: https://login.tailscale.com/admin/machines" \ No newline at end of file diff --git a/tailscale/status.sh b/tailscale/status.sh new file mode 100755 index 0000000..22e30e8 --- /dev/null +++ b/tailscale/status.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +# Tailscale Status Script + +echo "Tailscale Service Status" +echo "========================" + +# Check if container exists +if ! _is_container_exists "$CONTAINER_NAME"; then + echo "Status: Not Installed" + echo "The Tailscale container does not exist." + exit 1 +fi + +# Check if container is running +if ! _is_container_running "$CONTAINER_NAME"; then + echo "Status: Stopped" + echo "The Tailscale container exists but is not running." + echo "" + echo "To start the service, run:" + echo " ${SERVICE_PATH}/start.sh" + exit 1 +fi + +echo "Status: Running" +echo "" + +# Get Tailscale connection status +echo "Tailscale Connection Status:" +echo "----------------------------" +if docker exec ${CONTAINER_NAME} tailscale status --json >/dev/null 2>&1; then + # Get basic status + docker exec ${CONTAINER_NAME} tailscale status 2>/dev/null || { + echo "Unable to get Tailscale status. The daemon may still be starting." + } + + echo "" + echo "Tailscale IP Address:" + docker exec ${CONTAINER_NAME} tailscale ip -4 2>/dev/null || echo "Not connected" + + echo "" + echo "Device Name:" + docker exec ${CONTAINER_NAME} tailscale status --json 2>/dev/null | \ + docker exec -i ${CONTAINER_NAME} sh -c 'cat | grep -o "\"Self\":{[^}]*}" | grep -o "\"HostName\":\"[^\"]*\"" | cut -d":" -f2 | tr -d "\""' 2>/dev/null || echo "Unknown" +else + echo "Tailscale is running but not yet connected to the network." + echo "This might be normal if the container was just started." +fi + +echo "" +echo "Container Information:" +echo "----------------------" +docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Status}}\t{{.Ports}}" + +echo "" +echo "To view detailed logs, run:" +echo " ${SERVICE_PATH}/logs.sh" + +exit 0 \ No newline at end of file diff --git a/tailscale/stop.sh b/tailscale/stop.sh new file mode 100755 index 0000000..74fbae0 --- /dev/null +++ b/tailscale/stop.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +# Tailscale Stop Script + +echo "Stopping Tailscale container..." + +if _is_container_running "$CONTAINER_NAME"; then + # Gracefully disconnect from Tailscale network before stopping + echo "Disconnecting from Tailscale network..." + docker exec ${CONTAINER_NAME} tailscale down 2>/dev/null || true + + # Give it a moment to cleanly disconnect + sleep 2 + + # Stop the container + if _stop_container "$CONTAINER_NAME"; then + echo "Tailscale container stopped successfully." + else + _die "Failed to stop Tailscale container" + fi +else + echo "Tailscale container is not running." +fi \ No newline at end of file diff --git a/tailscale/uninstall.sh b/tailscale/uninstall.sh new file mode 100755 index 0000000..6b8b2d2 --- /dev/null +++ b/tailscale/uninstall.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "STATE_VOLUME" + +# Tailscale Uninstallation Script + +echo "Uninstalling Tailscale service..." + +# Stop the container if it's running +if _is_container_running "$CONTAINER_NAME"; then + echo "Stopping Tailscale container..." + _stop_container "$CONTAINER_NAME" +fi + +# Remove the container +if _is_container_exists "$CONTAINER_NAME"; then + echo "Removing Tailscale container..." + _remove_container "$CONTAINER_NAME" +fi + +echo "Tailscale service has been uninstalled." +echo "" +echo "Note: The Tailscale state volume (${STATE_VOLUME}) has been preserved." +echo "This maintains your Tailscale node configuration and keys." +echo "To completely remove all data, run:" +echo " docker volume rm ${STATE_VOLUME}" +echo "" +echo "You may also want to remove this node from your Tailscale admin console." \ No newline at end of file diff --git a/versions.json b/versions.json index f7f5fc8..9cc4abe 100644 --- a/versions.json +++ b/versions.json @@ -4,5 +4,6 @@ "gitea-runner-docker": "1.0.0", "simple-object-server": "1.0.0", "static-website": "1.0.0", + "tailscale": "1.0.0", "watchtower": "1.0.0" } \ No newline at end of file