8.9 KiB
sshsync CLI Tool Guide
Complete reference for using sshsync with Tailscale SSH Sync Agent.
Table of Contents
Installation
Via pip
pip install sshsync
Verify Installation
sshsync --version
Configuration
1. SSH Config Setup
sshsync uses your existing SSH configuration. Edit ~/.ssh/config:
# Example host entries
Host homelab-1
HostName 100.64.1.10
User admin
IdentityFile ~/.ssh/id_ed25519
Port 22
Host prod-web-01
HostName 100.64.1.20
User deploy
IdentityFile ~/.ssh/id_rsa
Port 22
Host dev-laptop
HostName 100.64.1.30
User developer
Important Notes:
- sshsync uses the Host alias (e.g., "homelab-1"), not the actual hostname
- Ensure SSH key authentication is configured
- Test each host with
ssh host-aliasbefore using with sshsync
2. Initialize sshsync Configuration
First run:
sshsync sync
This will:
- Read all hosts from your SSH config
- Prompt you to assign hosts to groups
- Create
~/.config/sshsync/config.yaml
3. sshsync Config File
Location: ~/.config/sshsync/config.yaml
Structure:
groups:
production:
- prod-web-01
- prod-web-02
- prod-db-01
development:
- dev-laptop
- dev-desktop
homelab:
- homelab-1
- homelab-2
Manual Editing:
- Groups are arbitrary labels (use what makes sense for you)
- Hosts can belong to multiple groups
- Use consistent host aliases from SSH config
Core Commands
List Hosts
# List all configured hosts
sshsync ls
# List with online/offline status
sshsync ls --with-status
Output Example:
Host Status
homelab-1 online
homelab-2 offline
prod-web-01 online
dev-laptop online
Execute Commands
On All Hosts
# Execute on all configured hosts
sshsync all "df -h"
# With custom timeout (default: 10s)
sshsync all --timeout 20 "systemctl status nginx"
# Dry-run (preview without executing)
sshsync all --dry-run "reboot"
On Specific Group
# Execute on group
sshsync group production "uptime"
# With timeout
sshsync group web-servers --timeout 30 "npm run build"
# Filter with regex
sshsync group production --regex "web-.*" "df -h"
Regex Filtering:
- Filters group members by alias matching pattern
- Uses Python regex syntax
- Example:
--regex "web-0[1-3]"matches web-01, web-02, web-03
File Transfer
Push Files
# Push to specific host
sshsync push --host web-01 ./app /var/www/app
# Push to group
sshsync push --group production ./dist /var/www/app
# Push to all hosts
sshsync push --all ./config.yml /etc/app/config.yml
# Recursive push (directory with contents)
sshsync push --group web --recurse ./app /var/www/app
# Dry-run
sshsync push --group production --dry-run ./dist /var/www/app
Important:
- Local path comes first, remote path second
- Use
--recursefor directories - Dry-run shows what would be transferred without executing
Pull Files
# Pull from specific host
sshsync pull --host db-01 /var/log/mysql/error.log ./logs/
# Pull from group (creates separate directories per host)
sshsync pull --group databases /var/backups ./backups/
# Recursive pull
sshsync pull --host web-01 --recurse /var/www/app ./backup/
Pull Behavior:
- When pulling from groups, creates subdirectory per host
- Use
--recurseto pull entire directory trees - Destination directory created if doesn't exist
Group Management
Add Hosts to Group
# Interactive: prompts to select hosts
sshsync gadd production
# Follow prompts to select which hosts to add
Add Host to SSH Config
# Interactive host addition
sshsync hadd
# Follow prompts for:
# - Host alias
# - Hostname/IP
# - Username
# - Port (optional)
# - Identity file (optional)
Sync Ungrouped Hosts
# Assign groups to hosts not yet in any group
sshsync sync
Advanced Usage
Parallel Execution
sshsync automatically executes commands in parallel across hosts:
# This runs simultaneously on all hosts in group
sshsync group web-servers "npm run build"
Performance:
- Commands execute concurrently
- Results collected as they complete
- Timeout applies per-host independently
Timeout Strategies
Different operations need different timeouts:
# Quick checks (5-10s)
sshsync all --timeout 5 "hostname"
# Moderate operations (30-60s)
sshsync group web --timeout 60 "npm install"
# Long-running tasks (300s+)
sshsync group build --timeout 300 "docker build ."
Timeout Best Practices:
- Set timeout 20-30% longer than expected duration
- Use dry-run first to estimate timing
- Increase timeout for network-intensive operations
Combining with Other Tools
With xargs
# Get list of online hosts
sshsync ls --with-status | grep online | awk '{print $1}' | xargs -I {} echo "Host {} is online"
With jq (if using JSON output)
# Parse structured output (if sshsync supports --json flag)
sshsync ls --json | jq '.hosts[] | select(.status=="online") | .name'
In Shell Scripts
#!/bin/bash
# Deploy script using sshsync
echo "Deploying to staging..."
sshsync push --group staging --recurse ./dist /var/www/app
if [ $? -eq 0 ]; then
echo "Staging deployment successful"
echo "Running tests..."
sshsync group staging "cd /var/www/app && npm test"
if [ $? -eq 0 ]; then
echo "Tests passed, deploying to production..."
sshsync push --group production --recurse ./dist /var/www/app
fi
fi
Troubleshooting
Common Issues
1. "Permission denied (publickey)"
Cause: SSH key not configured or not added to ssh-agent
Solution:
# Add SSH key to agent
ssh-add ~/.ssh/id_ed25519
# Verify it's added
ssh-add -l
# Copy public key to remote
ssh-copy-id user@host
2. "Connection timed out"
Cause: Host is offline or network issue
Solution:
# Test connectivity
ping hostname
# Test Tailscale specifically
tailscale ping hostname
# Check Tailscale status
tailscale status
3. "Host not found in SSH config"
Cause: Host alias not in ~/.ssh/config
Solution:
# Add host to SSH config
sshsync hadd
# Or manually edit ~/.ssh/config
vim ~/.ssh/config
4. "Group not found"
Cause: Group doesn't exist in sshsync config
Solution:
# Add hosts to new group
sshsync gadd mygroup
# Or manually edit config
vim ~/.config/sshsync/config.yaml
5. File Transfer Fails
Cause: Insufficient permissions, disk space, or path doesn't exist
Solution:
# Check remote disk space
sshsync group production "df -h"
# Check remote path exists
sshsync group production "ls -ld /target/path"
# Check permissions
sshsync group production "ls -la /target/path"
Debug Mode
While sshsync doesn't have a built-in verbose mode, you can debug underlying SSH:
# Increase SSH verbosity
SSH_VERBOSE=1 sshsync all "uptime"
# Or use dry-run to see what would execute
sshsync all --dry-run "command"
Performance Issues
If operations are slow:
- Reduce parallelism (run on fewer hosts at once)
- Increase timeout for network-bound operations
- Check network latency:
sshsync all "echo $HOSTNAME" --timeout 5
Configuration Validation
# Verify SSH config is readable
cat ~/.ssh/config
# Verify sshsync config
cat ~/.config/sshsync/config.yaml
# Test hosts individually
for host in $(sshsync ls | awk '{print $1}'); do
echo "Testing $host..."
ssh $host "echo OK" || echo "FAILED: $host"
done
Best Practices
- Use meaningful host aliases in SSH config
- Organize groups logically (by function, environment, location)
- Always dry-run first for destructive operations
- Set appropriate timeouts based on operation type
- Test SSH keys before using sshsync
- Keep groups updated as infrastructure changes
- Use --with-status to check availability before operations
Integration with Tailscale
sshsync works seamlessly with Tailscale SSH:
# SSH config using Tailscale hostname
Host homelab-1
HostName homelab-1.tailnet.ts.net
User admin
# Or using Tailscale IP directly
Host homelab-1
HostName 100.64.1.10
User admin
Tailscale Advantages:
- No need for port forwarding
- Encrypted connections
- MagicDNS for easy hostnames
- Works across NATs
Verify Tailscale:
# Check Tailscale network
tailscale status
# Ping host via Tailscale
tailscale ping homelab-1
Summary
sshsync simplifies multi-host SSH operations:
- ✅ Execute commands across host groups
- ✅ Transfer files to/from multiple hosts
- ✅ Organize hosts into logical groups
- ✅ Parallel execution for speed
- ✅ Dry-run mode for safety
- ✅ Works great with Tailscale
For more help: sshsync --help