Last active
November 30, 2025 15:46
-
-
Save jens-struct/371bf3b4ebc7f96c399c64320f34cc1e to your computer and use it in GitHub Desktop.
Setup Tailscale & Taildrop (Fedora)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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." |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Run with curl
Tailreceive service logs