Skip to content

Syncarr syncs two plex servers by copy files directly to the server and copying metadata and watch status from one server to another.

License

Notifications You must be signed in to change notification settings

nullable-eth/syncarr

Repository files navigation

SyncArr 🎬📺🔃

GitHub Release Docker Image Go Version GitHub Actions

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.

🚀 Quick Start

Docker Compose Example

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!

✨ Features

🎯 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

📋 Configuration

🌍 Environment Variables

Plex Server Configuration

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

SSH Configuration

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.

Sync Configuration

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

Path Mapping

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

Performance Tuning

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

Transfer Options

Variable Description Default
ENABLE_COMPRESSION Enable transfer compression true
RESUME_TRANSFERS Resume interrupted transfers true

🚀 Usage

📝 Getting Your Plex Token
  1. 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-Token header value
  2. 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
  1. 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"
  2. 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

View 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"'

Health Check

# Check container health
docker-compose ps

# Manual health check
docker-compose exec syncarr ./syncarr --validate

Log Levels

  • 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

🏗️ Architecture

📊 7-Phase Sync Process
  1. 🔍 Content Discovery: Scan source Plex server for labeled media
  2. 🧹 Cleanup: Remove orphaned files from destination (frees space and ensures Plex detects removals)
  3. 📂 File Transfer: Copy media files using high-performance transfers
  4. 🔄 Library Refresh: Update destination Plex library
  5. 🎯 Content Matching: Match source items to destination items by filename
  6. 📝 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

🔧 Development

🏗️ 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:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes and add tests
  4. Run tests: go test ./...
  5. Build and test: docker build -t syncarr:test .
  6. Commit changes: git commit -m 'Add amazing feature'
  7. Push to branch: git push origin feature/amazing-feature
  8. Open a Pull Request

Development Guidelines:

  • Follow Go best practices and gofmt formatting
  • Add tests for new functionality
  • Update documentation for user-facing changes
  • Use structured logging with appropriate levels

🆘 Troubleshooting

🔧 Common Issues

SSH Authentication Failed

{"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_PASSWORD is set and sshpass is installed
  • For key auth: Ensure private key is mounted and SSH_KEY_PATH is correct

Rsync Not Found

{"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

Directory Creation Failed

{"level":"error","msg":"Failed to create destination directory"}

Solutions:

  • Verify SSH user has write permissions on destination server
  • Check DEST_ROOT_DIR path exists and is accessible
  • Ensure sufficient disk space on destination

Plex Token Invalid

{"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 changes

Debug logs include:

  • Individual file transfer progress
  • SSH command execution details
  • Plex API request/response details
  • Metadata comparison results
  • Directory creation attempts

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • 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

About

Syncarr syncs two plex servers by copy files directly to the server and copying metadata and watch status from one server to another.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 2

  •  
  •