Skip to content

Instantly share code, notes, and snippets.

@jens-struct
Last active November 30, 2025 15:46
Show Gist options
  • Select an option

  • Save jens-struct/371bf3b4ebc7f96c399c64320f34cc1e to your computer and use it in GitHub Desktop.

Select an option

Save jens-struct/371bf3b4ebc7f96c399c64320f34cc1e to your computer and use it in GitHub Desktop.
Setup Tailscale & Taildrop (Fedora)
#!/bin/sh
#
# Tailscale Installation and Setup with Taildrop Support
#
# Usage: ./install-tailscale.sh [OPTIONS]
#
# Options:
# --taildrop-dir DIR Directory for received files (default: ~/Downloads)
# --repo-url URL Tailscale repository URL
# --help Show this help message
#
# exit on error
set -e
#
# Default configuration
#
TAILSCALE_REPO_URL="https://pkgs.tailscale.com/stable/fedora/tailscale.repo"
TAILDROP_DIR="${HOME}/Downloads"
#
# Parse command line arguments
#
show_help() {
cat << HELP
Tailscale Installation and Setup with Taildrop Support
Usage: $0 [OPTIONS]
Options:
--taildrop-dir DIR Directory for received files (default: ~/Downloads)
--repo-url URL Tailscale repository URL (default: Fedora stable)
--help Show this help message
Example:
$0 --taildrop-dir ~/Documents/Taildrop
HELP
exit 0
}
while [ $# -gt 0 ]; do
case "$1" in
--taildrop-dir)
TAILDROP_DIR="$2"
shift 2
;;
--repo-url)
TAILSCALE_REPO_URL="$2"
shift 2
;;
--help|-h)
show_help
;;
*)
echo "Error: Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
#
# Validate configuration
#
echo "Configuration:"
echo " -> Tailscale Repository URL: ${TAILSCALE_REPO_URL}"
echo " -> Taildrop directory (for receiving files): ${TAILDROP_DIR}"
echo ""
printf "Continue with this configuration? (y/N): "
read -r CONFIRM
case "$CONFIRM" in
[yY]|[yY][eE][sS])
echo "Proceeding with installation..."
;;
*)
echo "Installation aborted."
exit 0
;;
esac
echo ""
# Create taildrop directory if it doesn't exist
mkdir -p "${TAILDROP_DIR}"
if [ ! -d "${TAILDROP_DIR}" ]; then
echo "Error: Could not create taildrop directory: ${TAILDROP_DIR}"
exit 1
fi
#
# install tailscale & dependencies
#
echo "Installing Tailscale..."
sudo dnf config-manager addrepo --from-repofile="${TAILSCALE_REPO_URL}"
sudo dnf install -y tailscale nautilus-python
#
# tailscale setup
#
echo "Setting up Tailscale service..."
sudo systemctl enable --now tailscaled
sudo systemctl status tailscaled --no-pager
echo "Initializing Tailscale (with user as operator)..."
sudo tailscale up --operator="${USER}"
#
# setup tailreceive service
#
echo "Setting up Taildrop receive service..."
# make sure systemd user service folder exists
mkdir -p ~/.config/systemd/user/
# create tailreceive systemd service file
cat <<EOF > ~/.config/systemd/user/tailreceive.service
[Unit]
Description=File Receiver Service for Taildrop
[Service]
UMask=0077
ExecStart=tailscale file get --verbose --loop --conflict=rename "${TAILDROP_DIR}"
[Install]
WantedBy=default.target
EOF
# reload systemd configuration (also checks everything is ok)
systemctl --user daemon-reload
# enable & start tailreceive service
systemctl --user enable --now tailreceive
# print tailreceive status
systemctl --user status tailreceive --no-pager
#
# setup taildrop nautilus extension
#
echo "Installing Nautilus extension..."
# make sure nautilus extensions folder exists
mkdir -p ~/.local/share/nautilus-python/extensions/
# create taildrop nautilus extension file
cat <<'EOF' > ~/.local/share/nautilus-python/extensions/taildrop.py
#!/usr/bin/env python3
"""
Taildrop support for Nautilus.
"""
import json
import subprocess
try:
from urllib import unquote
except ImportError:
from urllib.parse import unquote
from gi.repository import GObject, Nautilus
class Taildrop:
"""
Wrappers around taildrop.
"""
@staticmethod
def find_hosts():
"""
Discover the tailscale hosts that are online.
"""
process = subprocess.run(
['tailscale', 'status', '--json'],
capture_output=True,
check=False
)
status = json.loads(process.stdout)
items = []
for _host, data in status['Peer'].items():
if data['HostName'] == "funnel-ingress-node":
continue
items.append({
'hostname': data['DNSName'].split('.')[0],
'os': data['OS'],
'online': data['Online']
})
return items
@staticmethod
def send_file(path, host):
"""
Invoke the tailscale binary to send a file.
"""
subprocess.Popen(['tailscale', 'file', 'cp', path, host + ':'])
class TaildropMenuProvider(GObject.GObject, Nautilus.MenuProvider):
"""
Menu Provider for Taildrop
"""
def __init__(self):
pass
@staticmethod
def callback_send(_menu, hostname, files):
"""
Callback Handler for sending files to a host.
"""
for file in files:
filename = unquote(file.get_uri()[7:])
Taildrop.send_file(filename, hostname)
def get_file_items(self, files):
"""
Right click context menu for a batch of files.
"""
top_menuitem = Nautilus.MenuItem(
name='TaildropMenuProvider::Devices',
label='Taildrop',
tip='',
icon=''
)
submenu = Nautilus.Menu()
top_menuitem.set_submenu(submenu)
for idx, details in enumerate(Taildrop.find_hosts()):
if not details['online'] or details['hostname'] == 'diskstation':
continue
sub_menuitem = Nautilus.MenuItem(
name='TaildropMenuProvider::Device%i' % idx,
label="%s" % (details['hostname']),
tip='',
icon=''
)
sub_menuitem.connect(
'activate',
TaildropMenuProvider.callback_send, details['hostname'], files
)
submenu.append_item(sub_menuitem)
return (top_menuitem,)
EOF
echo ""
echo "Installation complete!"
echo ""
echo "Taildrop files will be saved to: ${TAILDROP_DIR}"
echo "You need to restart Nautilus ('nautilus -q') for the extension to appear."
@jens-struct
Copy link
Author

jens-struct commented Nov 30, 2025

Run with curl

GIST_URL=https://gist.githubusercontent.com/jens-struct/371bf3b4ebc7f96c399c64320f34cc1e/raw/install-tailscale.sh

# with defaults

bash <(curl -sL ${GIST_URL})

# set dir

bash <(curl -sL ${GIST_URL}) --taildrop-dir ~/Documents/Taildrop

# set repo

bash <(curl -sL ${GIST_URL})  --repo-url https://pkgs.tailscale.com/stable/fedora/tailscale.repo

Tailreceive service logs

journalctl --user-unit tailreceive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment