Sharing Files Securely: Combining Python HTTPserver, Tailscale Funnel, and Magic Wormhole
For anyone in need of quickly sharing files across devices, especially within networks of varied operating systems, tools like Python HTTPserver, Tailscale Funnel, and Magic Wormhole can be invaluable. After some testing, I’ve found a combination of these tools to be particularly effective. Here’s a breakdown, including functions which i’ve now added to my dotfiles repo.
How to Use These Tools Together
-
Python HTTPserver:
- Use for serving files locally within a network.
- Combine with Tailscale Funnel for secure, internet-wide access.
-
Tailscale Funnel:
- Share services or files securely using a public URL.
- Use with Python HTTPserver for platform workarounds, especially on MacOS.
-
Magic Wormhole:
- Direct file transfers between two devices.
- Perfect for quick, secure sharing without setting up a server.
Choose the Right Tool for Your Needs
Each of these tools offers unique advantages:
- Python HTTPserver: Fast and simple for local networks.
- Tailscale Funnel: Secure and scalable for internet access.
- Magic Wormhole: Peer-to-peer simplicity for direct transfers.
Python HTTPserver: Local File Sharing Made Simple
The Python HTTPserver has long been a reliable tool for sharing files within a local network. By running a simple command:
python3 -m http.server 8080
You can serve files in the current directory over HTTP. However, there are limitations:
- Files are not encrypted and can be accessed by anyone on the local network.
- The service is confined to local traffic, with no straightforward way to share beyond the local network.
To enhance usability, I created a function to streamline the process, allowing you to specify files or directories for sharing. This function simplifies temporary setups and provides flexibility to manage shared content effectively.
pyserver(){
# usage: pyserver file1 file2 ...
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "Usage: pyserver file1 file2 ..."
return 0
fi
# check if python3 is installed
if ! command -v python3 &> /dev/null; then
echo "Error: Python 3 is not installed"
echo "You can install it with:"
echo 'brew install python'
return 1
fi
# set port for the python server
local port=8000
# create a python server for the passed files or dir
#get local ip
local_ip=$(hostname -I | awk '{print $1}')
# path for the server else use current dir
# if multiple files passed in arg then create tmp dir and add those passed files or dir via ln to the temp server dir
# if no files passed in arg then use current dir
if [ -n "$1" ]; then # if there are files passed in arg
# create temp dir
mkdir -p /tmp/pyserver
# add files or dir to the temp dir
for file in "$@"; do
ln -s "$file" /tmp/pyserver
done
# change dir to the temp dir
cd /tmp/pyserver
# start the python server
python3 -m http.server $port
else
# use current dir
cd .
# start the python server
python3 -m http.server $port
fi
}
Tailscale Funnel: Secure and Broader Access
Tailscale Funnel extends the functionality of local servers by creating a public URL for your service. It’s an excellent solution for securely sharing files or directories with anyone on the internet. However, there’s a caveat:
On MacOS, using the tailscale funnel
command for direct file sharing is restricted due to sandbox limitations. Instead, you can combine Tailscale Funnel with Python HTTPserver. This approach enables you to serve files locally and securely expose them via Tailscale.
Here’s the combined one-liner:
python3 -m http.server 8080 | tailscale funnel localhost:8080
For better management, I’ve written a function to automate this workflow. This method ensures secure file sharing with encrypted tunnels, leveraging Tailscale’s infrastructure.
funnel() {
# usage: funnel file1 file2 dir1 dir2 ...
# On MacOS you must be using the open source tailscaled distribution to use the funnel command for files.
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "Usage: funnel <target>"
echo "Funnel enables you to share a local server on the internet using Tailscale."
return 0
fi
local port=8080
use_python=true
# Check if tailscale is running
if ! tailscale status &> /dev/null; then
echo "Error: Tailscale is not running"
echo "You can start it with: tailscale start"
return 1
fi
# Use current directory if no target is specified
if [ -z "$1" ]; then
echo "No target specified. Using current directory as target."
target="."
else
target="$@"
fi
# if multiple files passed in arg then create tmp dir and add those passed files or dir via ln to the temp server dir
# if no files passed in arg then use current dir
if [ -n "$1" ]; then # if there are files passed in arg
# create temp dir
mkdir -p /tmp/funnel
# add files or dir to the temp dir
for file in "$@"; do
ln -s "$file" /tmp/funnel
done
# change dir to the temp dir
cd /tmp/funnel
if $use_python; then
# start the python server
python3 -m http.server $port&
# start the funnel
tailscale funnel localhost:$port
else
# start the funnel
tailscale funnel "$target"
fi
else
# use current dir
cd .
if $use_python; then
# start the python server
python3 -m http.server $port&
# start the funnel
tailscale funnel localhost:$port
else
# start the funnel
tailscale funnel "$target"
fi
fi
}
Magic Wormhole: Direct, Secure Transfers
Magic Wormhole is a command-line tool that enables secure and straightforward file transfers between devices. Unlike HTTP-based solutions, it creates a direct, encrypted connection, making it ideal for transferring sensitive files without relying on an intermediary server.
Wormhole Transfer Function
This function allows for flexible usage:
- Send multiple files or directories as a zipped archive.
- Encrypt files before sending, using
age
,gpg
, oropenssl
for maximum security.
wh-transfer(){
# Usage and options
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "Usage: wh-transfer [-e|--encrypt] path1 path2 ..."
echo "The default is to send the files as is"
echo "-e or --encrypt will encrypt the files before sending using age, gpg, or aes"
return 0
fi
# Encryption and sending
encrypt_and_send() {
local encrypt_tool="$1"
shift
local files=("$@")
local zip_file="./wormhole_$(date +%Y%m%d%H%M%S).zip"
zip "$zip_file" "${files[@]}"
local encrypted_file="${zip_file}.age"
if [[ "$encrypt_tool" == "gpg" ]]; then
encrypted_file="${zip_file}.gpg"
gpg --output "$encrypted_file" --symmetric "$zip_file"
elif [[ "$encrypt_tool" == "aes" ]]; then
encrypted_file="${zip_file}.aes"
openssl enc -aes-256-cbc -salt -in "$zip_file" -out "$encrypted_file"
else
age -o "$encrypted_file" -p "$zip_file"
fi
wormhole send "$encrypted_file"
rm -rf "$zip_file" "$encrypted_file"
}
if [[ "$1" == "-e" || "$1" == "--encrypt" ]]; then
shift
if command -v age &> /dev/null; then
encrypt_and_send "age" "$@"
elif command -v gpg &> /dev/null; then
encrypt_and_send "gpg" "$@"
else
encrypt_and_send "aes" "$@"
fi
else
if [[ "$#" -gt 1 ]]; then
local zip_file="./wormhole_$(date +%Y%m%d%H%M%S).zip"
zip "$zip_file" "$@"
wormhole send "$zip_file"
rm -rf "$zip_file"
else
wormhole send "$@"
fi
fi
}
Feel free to share your experiences and any other tools you’ve found useful. I hope to keep building and refining our workflows together!