diff --git a/tailscale-relay/backup.sh b/tailscale-relay/backup.sh new file mode 100755 index 0000000..f676595 --- /dev/null +++ b/tailscale-relay/backup.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "BACKUP_FILE" "TEMP_DIR" + +if ! lxc info "${CONTAINER_NAME}" &>/dev/null; then + _die "Container ${CONTAINER_NAME} does not exist." +fi + +mkdir -p "${TEMP_DIR}/backup" + +# Export tailscale state from the container +echo "Backing up Tailscale state..." +lxc exec "${CONTAINER_NAME}" -- tar -czf /tmp/tailscale-state.tgz -C /var/lib/tailscale . 2>/dev/null || _die "Failed to archive tailscale state" +lxc file pull "${CONTAINER_NAME}/tmp/tailscale-state.tgz" "${TEMP_DIR}/backup/tailscale-state.tgz" || _die "Failed to pull backup from container" +lxc exec "${CONTAINER_NAME}" -- rm -f /tmp/tailscale-state.tgz + +tar -czf "${BACKUP_FILE}" -C "${TEMP_DIR}/backup" . + +echo "Backup completed successfully" diff --git a/tailscale-relay/config/service.env b/tailscale-relay/config/service.env new file mode 100644 index 0000000..a44bd8f --- /dev/null +++ b/tailscale-relay/config/service.env @@ -0,0 +1,35 @@ +# Service settings for Tailscale Relay (LXC) +# (can also override anything in the template_info.env file 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= + +# REQUIRED: Network settings for the LXC container's static IP +# The container gets its own real IP on the host's physical network. +HOST_INTERFACE=eth0 +STATIC_IP=192.168.0.50/24 +GATEWAY=192.168.0.1 + +# Optional: DNS servers (comma-separated) +DNS_SERVERS="8.8.8.8,8.8.4.4" + +# REQUIRED: Peer relay UDP port +# This is the port used by Tailscale's peer relay feature. +# Forward this UDP port on your router to the container's STATIC_IP. +RELAY_PORT=3478 + +# Optional: Custom hostname for this node +# If not set, will use the container name +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= + +# Server Settings +SSH_USER="dropshell" diff --git a/tailscale-relay/destroy.sh b/tailscale-relay/destroy.sh new file mode 100755 index 0000000..797038a --- /dev/null +++ b/tailscale-relay/destroy.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +echo "Destroying Tailscale Relay service and all data..." + +if lxc info "${CONTAINER_NAME}" &>/dev/null; then + lxc stop "${CONTAINER_NAME}" --force 2>/dev/null || true + lxc delete "${CONTAINER_NAME}" --force 2>/dev/null || true + echo "Container deleted." +else + echo "Container does not exist." +fi + +echo "" +echo "Tailscale Relay 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" diff --git a/tailscale-relay/install.sh b/tailscale-relay/install.sh new file mode 100755 index 0000000..6f1e35f --- /dev/null +++ b/tailscale-relay/install.sh @@ -0,0 +1,155 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "LXC_IMAGE" "TAILSCALE_AUTH_KEY" "HOST_INTERFACE" "STATIC_IP" "GATEWAY" "RELAY_PORT" + +# Check LXC is available +if ! command -v lxc &>/dev/null; then + _die "LXC is not installed. Install it with: sudo snap install lxd --channel=latest/stable && sudo lxd init" +fi + +# Check LXC daemon is accessible +if ! lxc list &>/dev/null; then + _die "Cannot connect to LXD. Ensure the LXD daemon is running and your user is in the 'lxd' group." +fi + +echo "Installing Tailscale Relay (LXC)..." + +# Stop and remove existing container if it exists +if lxc info "${CONTAINER_NAME}" &>/dev/null; then + echo "Removing existing container..." + lxc stop "${CONTAINER_NAME}" --force 2>/dev/null || true + lxc delete "${CONTAINER_NAME}" --force 2>/dev/null || true +fi + +# Create the container (without starting) +echo "Creating LXC container from ${LXC_IMAGE}..." +lxc init "${LXC_IMAGE}" "${CONTAINER_NAME}" || _die "Failed to create LXC container" + +# Configure bridged networking on the host interface +echo "Configuring bridged networking on ${HOST_INTERFACE}..." +lxc config device remove "${CONTAINER_NAME}" eth0 2>/dev/null || true +lxc config device add "${CONTAINER_NAME}" eth0 nic \ + nictype=macvlan \ + parent="${HOST_INTERFACE}" || _die "Failed to configure network device" + +# Enable nesting (required when running LXC inside another LXC, e.g. Proxmox) +lxc config set "${CONTAINER_NAME}" security.nesting=true + +# Allow TUN device inside the container (needed by tailscale's WireGuard interface) +lxc config set "${CONTAINER_NAME}" raw.lxc "lxc.cgroup2.devices.allow = c 10:200 rwm" +lxc config device add "${CONTAINER_NAME}" tun unix-char path=/dev/net/tun 2>/dev/null || true + +# Start the container +echo "Starting container..." +lxc start "${CONTAINER_NAME}" || _die "Failed to start container" + +# Wait for container to be ready +echo "Waiting for container to be ready..." +READY=false +for i in $(seq 1 30); do + if lxc exec "${CONTAINER_NAME}" -- test -d /etc/netplan 2>/dev/null; then + READY=true + break + fi + sleep 1 +done +if [ "$READY" != "true" ]; then + _die "Container did not become ready in time" +fi + +# Extract IP without CIDR for display +CONTAINER_IP="${STATIC_IP%%/*}" + +# Configure static IP via netplan +echo "Configuring static IP ${STATIC_IP}..." +lxc exec "${CONTAINER_NAME}" -- bash -c "rm -f /etc/netplan/*.yaml" +lxc exec "${CONTAINER_NAME}" -- bash -c "cat > /etc/netplan/10-static.yaml << 'NETPLANEOF' +network: + version: 2 + ethernets: + eth0: + dhcp4: false + addresses: + - ${STATIC_IP} + routes: + - to: default + via: ${GATEWAY} + nameservers: + addresses: [${DNS_SERVERS}] +NETPLANEOF" +lxc exec "${CONTAINER_NAME}" -- chmod 600 /etc/netplan/10-static.yaml +lxc exec "${CONTAINER_NAME}" -- netplan apply 2>/dev/null || true + +# Wait for network to come up +sleep 3 + +# Verify network connectivity +echo "Verifying network connectivity..." +if ! lxc exec "${CONTAINER_NAME}" -- ping -c 1 -W 5 "${GATEWAY}" &>/dev/null; then + echo "Warning: Cannot reach gateway ${GATEWAY} from container. Check HOST_INTERFACE and network settings." +fi + +# Install Tailscale inside the container +echo "Installing Tailscale inside the container..." +lxc exec "${CONTAINER_NAME}" -- bash -c "curl -fsSL https://tailscale.com/install.sh | sh" || _die "Failed to install Tailscale" + +# Connect to Tailscale network +echo "Connecting to Tailscale network..." + +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 with retries +RETRY_COUNT=0 +MAX_RETRIES=5 +RETRY_DELAY=10 + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if lxc exec "${CONTAINER_NAME}" -- ${TAILSCALE_UP_CMD} 2>&1; then + echo "Successfully connected to Tailscale network!" + break + else + RETRY_COUNT=$((RETRY_COUNT + 1)) + if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then + echo "Connection attempt $RETRY_COUNT failed. Retrying in ${RETRY_DELAY} seconds..." + sleep $RETRY_DELAY + else + echo "Warning: Failed to connect after $MAX_RETRIES attempts." + echo "You may need to connect manually using:" + echo " lxc exec ${CONTAINER_NAME} -- tailscale up" + fi + fi +done + +# Enable peer relay on the configured port +echo "Enabling peer relay on port ${RELAY_PORT}/udp..." +lxc exec "${CONTAINER_NAME}" -- tailscale set --relay-server-port="${RELAY_PORT}" || echo "Warning: Failed to enable peer relay. You may need to enable it manually." + +# Get the Tailscale IP +TAILSCALE_IP=$(lxc exec "${CONTAINER_NAME}" -- tailscale ip -4 2>/dev/null || echo "not yet available") + +echo "" +echo "==========================================" +echo "Tailscale Relay installation complete!" +echo "==========================================" +echo "" +echo " LAN IP: ${CONTAINER_IP}" +echo " Tailscale IP: ${TAILSCALE_IP}" +echo " Relay port: ${RELAY_PORT}/udp" +echo " Container: ${CONTAINER_NAME}" +echo "" +echo "The container has its own IP (${CONTAINER_IP}) on your LAN." +echo "Forward ${RELAY_PORT}/udp on your router to ${CONTAINER_IP}." +echo "" +echo "You also need to add relay ACL grants in your tailnet policy." +echo "See: https://tailscale.com/kb/1403/peer-relay" +echo "" +echo "Manage at: https://login.tailscale.com/admin/machines" diff --git a/tailscale-relay/logs.sh b/tailscale-relay/logs.sh new file mode 100755 index 0000000..e96a0bb --- /dev/null +++ b/tailscale-relay/logs.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +if ! lxc info "${CONTAINER_NAME}" &>/dev/null; then + _die "Container ${CONTAINER_NAME} does not exist." +fi + +if ! lxc info "${CONTAINER_NAME}" 2>/dev/null | grep -q "Status: RUNNING"; then + _die "Container ${CONTAINER_NAME} is not running." +fi + +lxc exec "${CONTAINER_NAME}" -- journalctl -u tailscaled --no-pager -n 100 "$@" diff --git a/tailscale-relay/restore.sh b/tailscale-relay/restore.sh new file mode 100755 index 0000000..6b0f503 --- /dev/null +++ b/tailscale-relay/restore.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "BACKUP_FILE" "TEMP_DIR" + +if ! lxc info "${CONTAINER_NAME}" 2>/dev/null | grep -q "Status: RUNNING"; then + _die "Container ${CONTAINER_NAME} must be running for restore. Run install.sh first." +fi + +mkdir -p "${TEMP_DIR}/restore" +tar -xzf "${BACKUP_FILE}" -C "${TEMP_DIR}/restore" + +echo "Restoring Tailscale state..." + +# Stop tailscale before restoring state +lxc exec "${CONTAINER_NAME}" -- systemctl stop tailscaled 2>/dev/null || true + +# Push state archive into container and extract +lxc file push "${TEMP_DIR}/restore/tailscale-state.tgz" "${CONTAINER_NAME}/tmp/tailscale-state.tgz" || _die "Failed to push backup to container" +lxc exec "${CONTAINER_NAME}" -- bash -c "rm -rf /var/lib/tailscale/* && tar -xzf /tmp/tailscale-state.tgz -C /var/lib/tailscale" || _die "Failed to extract state" +lxc exec "${CONTAINER_NAME}" -- rm -f /tmp/tailscale-state.tgz + +# Restart tailscale +lxc exec "${CONTAINER_NAME}" -- systemctl start tailscaled + +echo "Restore completed successfully" diff --git a/tailscale-relay/ssh.sh b/tailscale-relay/ssh.sh new file mode 100755 index 0000000..d47dd19 --- /dev/null +++ b/tailscale-relay/ssh.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +if ! lxc info "${CONTAINER_NAME}" 2>/dev/null | grep -q "Status: RUNNING"; then + _die "Container ${CONTAINER_NAME} is not running. Start it first." +fi + +echo "Entering Tailscale Relay container shell..." +echo "Type 'exit' to leave the container." +echo "" +lxc exec "${CONTAINER_NAME}" -- /bin/bash diff --git a/tailscale-relay/start.sh b/tailscale-relay/start.sh new file mode 100755 index 0000000..0591742 --- /dev/null +++ b/tailscale-relay/start.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" "RELAY_PORT" + +# Check LXC is available +if ! command -v lxc &>/dev/null; then + _die "LXC is not installed." +fi + +echo "Starting Tailscale Relay container..." + +# Check container exists +if ! lxc info "${CONTAINER_NAME}" &>/dev/null; then + _die "Container ${CONTAINER_NAME} does not exist. Run install.sh first." +fi + +# Start if not already running +if lxc info "${CONTAINER_NAME}" 2>/dev/null | grep -q "Status: RUNNING"; then + echo "Container is already running." +else + lxc start "${CONTAINER_NAME}" || _die "Failed to start container" + sleep 3 +fi + +# Wait for tailscaled to be ready +echo "Waiting for Tailscale daemon..." +for i in $(seq 1 15); do + if lxc exec "${CONTAINER_NAME}" -- tailscale status &>/dev/null; then + break + fi + sleep 1 +done + +# Ensure relay is enabled +lxc exec "${CONTAINER_NAME}" -- tailscale set --relay-server-port="${RELAY_PORT}" 2>/dev/null || true + +CONTAINER_IP="${STATIC_IP%%/*}" +TAILSCALE_IP=$(lxc exec "${CONTAINER_NAME}" -- tailscale ip -4 2>/dev/null || echo "not yet available") + +echo "" +echo "Tailscale Relay started." +echo " LAN IP: ${CONTAINER_IP}" +echo " Tailscale IP: ${TAILSCALE_IP}" +echo " Relay port: ${RELAY_PORT}/udp" diff --git a/tailscale-relay/status.sh b/tailscale-relay/status.sh new file mode 100755 index 0000000..da67bfc --- /dev/null +++ b/tailscale-relay/status.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +if ! command -v lxc &>/dev/null; then + echo "Unknown" + exit 0 +fi + +if ! lxc info "${CONTAINER_NAME}" &>/dev/null; then + echo "Unknown" + exit 0 +fi + +if ! lxc info "${CONTAINER_NAME}" 2>/dev/null | grep -q "Status: RUNNING"; then + echo "Stopped" + exit 0 +fi + +# Container is running - check if tailscale is connected +if lxc exec "${CONTAINER_NAME}" -- tailscale status &>/dev/null; then + echo "Running" +else + echo "Error" +fi diff --git a/tailscale-relay/stop.sh b/tailscale-relay/stop.sh new file mode 100755 index 0000000..5b612b4 --- /dev/null +++ b/tailscale-relay/stop.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +echo "Stopping Tailscale Relay container..." + +if ! lxc info "${CONTAINER_NAME}" &>/dev/null; then + echo "Container ${CONTAINER_NAME} does not exist." + exit 0 +fi + +if lxc info "${CONTAINER_NAME}" 2>/dev/null | grep -q "Status: RUNNING"; then + # Gracefully disconnect from Tailscale network before stopping + echo "Disconnecting from Tailscale network..." + lxc exec "${CONTAINER_NAME}" -- tailscale down 2>/dev/null || true + sleep 2 + + lxc stop "${CONTAINER_NAME}" || _die "Failed to stop container" + echo "Tailscale Relay container stopped." +else + echo "Container is not running." +fi diff --git a/tailscale-relay/template_info.env b/tailscale-relay/template_info.env new file mode 100644 index 0000000..45b420f --- /dev/null +++ b/tailscale-relay/template_info.env @@ -0,0 +1,30 @@ +# 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-relay +REQUIRES_HOST_ROOT=false +REQUIRES_DOCKER=false +REQUIRES_DOCKER_ROOT=false + +# Service settings +CONTAINER_NAME=tailscale-relay + +# LXC image to use +LXC_IMAGE="ubuntu:24.04" + +# Tailscale defaults (to be overridden in service.env) +TAILSCALE_AUTH_KEY="" +TAILSCALE_HOSTNAME="" +TAILSCALE_EXTRA_ARGS="" + +# Relay port - the UDP port for the peer relay feature +# Forward this port on your router to the container's STATIC_IP +RELAY_PORT="3478" + +# Network defaults (to be overridden in service.env) +HOST_INTERFACE="" +STATIC_IP="" +GATEWAY="" +DNS_SERVERS="8.8.8.8,8.8.4.4" diff --git a/tailscale-relay/uninstall.sh b/tailscale-relay/uninstall.sh new file mode 100755 index 0000000..67bf45c --- /dev/null +++ b/tailscale-relay/uninstall.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# shellcheck disable=SC1091 +source "${AGENT_PATH}/common.sh" +_check_required_env_vars "CONTAINER_NAME" + +echo "Uninstalling Tailscale Relay service..." + +if lxc info "${CONTAINER_NAME}" &>/dev/null; then + bash ./stop.sh 2>/dev/null || true + echo "Stopping container..." + lxc stop "${CONTAINER_NAME}" --force 2>/dev/null || true +fi + +echo "Tailscale Relay service has been stopped." +echo "The container has been preserved. To completely remove it, run destroy.sh" +echo "" +echo "You may also want to remove this node from your Tailscale admin console."