SyncArr is a high-performance Go application that synchronizes labeled movies and TV shows between Plex Media Servers. It provides fast file transfers using rsync, comprehensive metadata synchronization, and intelligent content matching to keep your Plex libraries perfectly synchronized.
version: '3.8'
services:
syncarr:
image: syncarr:latest
container_name: syncarr
restart: unless-stopped
environment:
# Source Plex Server
SOURCE_PLEX_REQUIRES_HTTPS: "true"
SOURCE_PLEX_HOST: "192.168.1.10"
SOURCE_PLEX_PORT: "32400"
SOURCE_PLEX_TOKEN: "your-source-plex-token"
# Destination Plex Server
DEST_PLEX_REQUIRES_HTTPS: "true"
DEST_PLEX_HOST: "192.168.1.20"
DEST_PLEX_PORT: "32400"
DEST_PLEX_TOKEN: "your-destination-plex-token"
# SSH Configuration (choose password OR key-based auth)
SSH_USER: "your-ssh-user"
SSH_PASSWORD: "your-ssh-password" # For password auth
# SSH_KEY_PATH: "/keys/id_rsa" # For key-based auth
SSH_PORT: "22"
# Sync Configuration
SYNC_LABEL: "Sync2Secondary" # Label to identify content to sync
SYNC_INTERVAL: "60" # Minutes between sync cycles
LOG_LEVEL: "INFO" # DEBUG, INFO, WARN, ERROR
DRY_RUN: "false" # Set to "true" for testing
# Path Mapping
SOURCE_REPLACE_FROM: "/data/Media" # Source path prefix to strip for destination
SOURCE_REPLACE_TO: "/media/source" # Local container path (or leave empty for same-volume mounting)
DEST_ROOT_DIR: "/mnt/data" # Destination server root path
volumes:
# Mount your media directories (adjust paths as needed)
- "/path/to/your/media:/media/source:ro" # Read-only source media
# Alternative: Same-volume mounting (leave SOURCE_REPLACE_TO empty)
# - "/data/Media:/data/Media:ro"
# For SSH key authentication (uncomment if using keys)
# - "/path/to/ssh/keys:/keys:ro"
# Use host networking to access local Plex servers
network_mode: "host"
# Health check
healthcheck:
test: ["CMD", "./syncarr", "--validate"]
interval: 30s
timeout: 10s
retries: 3💡 Pro Tip: Start with
DRY_RUN: "true"to test your configuration without making any changes!
🎯 Core Synchronization Features
- 🏷️ Label-based Sync: Automatically sync only media items with specific Plex labels
- ⚡ High-Performance Transfers: Uses rsync for fast, resumable file transfers
- 🔄 7-Phase Sync Process: Content discovery → Cleanup → File transfer → Library refresh → Content matching → Metadata sync
- 📊 Comprehensive Metadata Sync: Titles, summaries, ratings, genres, labels, collections, artwork, and more
- 👁️ Watched State Sync: Keep viewing progress synchronized between servers
- 🔄 Incremental Updates: Only transfer changed or new content
- 📁 Automatic Directory Creation: Creates destination directories as needed
🔐 Authentication & Security
- 🔑 Dual SSH Authentication: Support for both SSH keys and password authentication
- 🔒 Secure Transfers: All file transfers use encrypted SSH connections
- 🛡️ Non-interactive Operation: Uses sshpass for automated password authentication
⚠️ Dry Run Mode: Test configurations without making any changes
🛠️ Advanced Features
- 🐳 Docker Ready: Containerized application with health checks
- 📝 Structured Logging: JSON logging with configurable levels (DEBUG, INFO, WARN, ERROR)
- 🔄 Continuous & One-shot Modes: Run continuously or execute single sync cycles
- 📈 Performance Monitoring: Detailed transfer statistics and timing information
- 🔍 Content Matching: Intelligent filename-based matching between source and destination
🌍 Environment Variables
| Variable | Description | Example | Required |
|---|---|---|---|
SOURCE_PLEX_HOST |
Source Plex server hostname/IP | 192.168.1.10 |
✅ |
SOURCE_PLEX_PORT |
Source Plex server port | 32400 |
❌ |
SOURCE_PLEX_TOKEN |
Source Plex server API token | xxxxxxxxxxxx |
✅ |
SOURCE_PLEX_REQUIRES_HTTPS |
Use HTTPS for source server | true/false |
❌ |
DEST_PLEX_HOST |
Destination Plex server hostname/IP | 192.168.1.20 |
✅ |
DEST_PLEX_PORT |
Destination Plex server port | 32400 |
❌ |
DEST_PLEX_TOKEN |
Destination Plex server API token | xxxxxxxxxxxx |
✅ |
DEST_PLEX_REQUIRES_HTTPS |
Use HTTPS for destination server | true/false |
❌ |
| Variable | Description | Example | Required |
|---|---|---|---|
SSH_USER |
SSH username | mediauser |
✅ |
SSH_PASSWORD |
SSH password (for password auth) | secretpass |
❌* |
SSH_KEY_PATH |
SSH private key path (for key auth) | /keys/id_rsa |
❌* |
SSH_PORT |
SSH port | 22 |
❌ |
*Either password or key path is required. Password auth requires sshpass to be installed for both SCP and rsync transfers.
| Variable | Description | Example | Required |
|---|---|---|---|
SYNC_LABEL |
Plex label to identify content to sync | Sync2Secondary |
✅ |
SYNC_INTERVAL |
Minutes between sync cycles | 60 |
❌ |
LOG_LEVEL |
Logging level | INFO |
❌ |
DRY_RUN |
Test mode without changes | false |
❌ |
| Variable | Description | Example | Required |
|---|---|---|---|
SOURCE_REPLACE_FROM |
Source path prefix to strip for destination mapping | /data/Media |
❌ |
SOURCE_REPLACE_TO |
Container path for source media (leave empty for same-volume mounting) | /media/source |
❌ |
DEST_ROOT_DIR |
Destination server root directory | /mnt/data |
✅ |
🎛️ Advanced Configuration
| Variable | Description | Default |
|---|---|---|
WORKER_POOL_SIZE |
Number of concurrent workers | 4 |
PLEX_API_RATE_LIMIT |
Plex API requests per second | 10.0 |
TRANSFER_BUFFER_SIZE |
Transfer buffer size (KB) | 64 |
MAX_CONCURRENT_TRANSFERS |
Max simultaneous transfers | 3 |
| Variable | Description | Default |
|---|---|---|
ENABLE_COMPRESSION |
Enable transfer compression | true |
RESUME_TRANSFERS |
Resume interrupted transfers | true |
📝 Getting Your Plex Token
-
Via Plex Web App:
- Open Plex Web App
- Open browser developer tools (F12)
- Go to Network tab
- Refresh the page
- Look for requests to
/library/sections - Find the
X-Plex-Tokenheader value
-
Via Plex API:
curl -X POST 'https://plex.tv/api/v2/users/signin' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'user[login]=YOUR_EMAIL&user[password]=YOUR_PASSWORD'
🏷️ Adding Labels to Media
-
In Plex Web Interface:
- Navigate to your movie or TV show
- Click "Edit" (pencil icon)
- Go to "Tags" tab
- Add your sync label (e.g.,
Sync2Secondary) to "Labels" field - Click "Save Changes"
-
Bulk Labeling with Labelarr:
- Use Labelarr for bulk label management
- Set up rules to automatically apply labels based on criteria
🖥️ Command Line Usage
# Run a single sync cycle
docker run --rm -v $(pwd)/config:/config syncarr --oneshot
# Validate configuration
docker run --rm -v $(pwd)/config:/config syncarr --validate
# Show version information
docker run --rm syncarr --version
# Run with debug logging
docker run --rm -e LOG_LEVEL=DEBUG syncarr --oneshot📊 Monitoring & Logs
# Follow logs in real-time
docker-compose logs -f syncarr
# View last 100 lines
docker-compose logs --tail=100 syncarr
# Filter for errors only
docker-compose logs syncarr | grep '"level":"error"'# Check container health
docker-compose ps
# Manual health check
docker-compose exec syncarr ./syncarr --validate- DEBUG: Detailed operation logs, file-by-file progress
- INFO: High-level status updates, sync summaries
- WARN: Non-critical issues, skipped items
- ERROR: Critical errors, failed operations
📊 7-Phase Sync Process
- 🔍 Content Discovery: Scan source Plex server for labeled media
- 🧹 Cleanup: Remove orphaned files from destination (frees space and ensures Plex detects removals)
- 📂 File Transfer: Copy media files using high-performance transfers
- 🔄 Library Refresh: Update destination Plex library
- 🎯 Content Matching: Match source items to destination items by filename
- 📝 Metadata Sync: Synchronize comprehensive metadata between matched items
🧩 Components Overview
┌─────────────────────┐ ┌─────────────────────┐
│ Source Plex │ │ Destination Plex │
│ Server │ │ Server │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
│ SyncArr │
│ ┌─────────────────┐ │
└────┤ Sync Orchestrator├───┘
└─────────┬───────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────▼────┐ ┌───────▼────┐ ┌──────▼──────┐
│Content │ │File Transfer│ │Metadata │
│Discovery│ │ (rsync) │ │Synchronizer │
└─────────┘ └─────────────┘ └─────────────┘
Key Components:
- 🎯 Sync Orchestrator: Coordinates the entire synchronization process
- 🔍 Content Discovery: Finds labeled media using Plex API
- 📁 File Transfer: High-performance rsync with automatic directory creation
- 📝 Metadata Synchronizer: Comprehensive metadata and watched state sync
- 🔌 Plex Client: Direct Plex API interactions with custom implementation
- ⚙️ Configuration Manager: Environment-based configuration management
🏗️ Building from Source
# Clone the repository
git clone https://github.com/nullable-eth/syncarr.git
cd syncarr
# Build the application
go build -o syncarr ./cmd/syncarr
# Run tests
go test ./...
# Build Docker image
docker build -t syncarr:latest .
# Run with development settings
LOG_LEVEL=DEBUG DRY_RUN=true ./syncarr --oneshot📁 Project Structure
syncarr/
├── cmd/syncarr/ # Main application entry point
├── internal/
│ ├── config/ # Configuration management
│ ├── discovery/ # Content discovery and matching
│ ├── logger/ # Structured logging
│ ├── metadata/ # Metadata synchronization
│ ├── orchestrator/ # Main sync coordination
│ ├── plex/ # Plex API client wrapper
│ └── transfer/ # File transfer (rsync/scp)
├── pkg/types/ # Shared data types
├── docker/ # Docker configurations
├── scripts/ # Utility scripts
├── Dockerfile # Docker build configuration
└── README.md # This file
🤝 Contributing
We welcome contributions! Here's how to get started:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Run tests:
go test ./... - Build and test:
docker build -t syncarr:test . - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
Development Guidelines:
- Follow Go best practices and
gofmtformatting - Add tests for new functionality
- Update documentation for user-facing changes
- Use structured logging with appropriate levels
🔧 Common Issues
{"level":"error","msg":"Permission denied (publickey,password)"}Solutions:
- Verify SSH credentials are correct
- Ensure SSH user has access to destination paths
- Test SSH connection manually:
ssh user@destination-server - For password auth: Ensure
SSH_PASSWORDis set andsshpassis installed - For key auth: Ensure private key is mounted and
SSH_KEY_PATHis correct
{"level":"error","msg":"rsync: command not found"}Solutions:
- The Docker image includes rsync by default
- If building custom image, ensure rsync is installed
- Check container logs for rsync availability
{"level":"error","msg":"Failed to create destination directory"}Solutions:
- Verify SSH user has write permissions on destination server
- Check
DEST_ROOT_DIRpath exists and is accessible - Ensure sufficient disk space on destination
{"level":"error","msg":"Unauthorized: Invalid token"}Solutions:
- Regenerate Plex token following the guide above
- Verify token has access to required libraries
- Check Plex server is accessible from container
📊 Debug Mode
Enable detailed logging for troubleshooting:
environment:
LOG_LEVEL: "DEBUG"
DRY_RUN: "true" # Test without making changesDebug logs include:
- Individual file transfer progress
- SSH command execution details
- Plex API request/response details
- Metadata comparison results
- Directory creation attempts
This project is licensed under the MIT License - see the LICENSE file for details.
- Plex API: Direct integration with Plex Media Server API
- logrus: Structured logging framework
- Go SSH Libraries: Secure file transfer capabilities
- rsync: High-performance file synchronization
- Docker: Containerization and deployment