Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

RsyncGUI

A professional macOS GUI for rsync with real-time progress, AI-powered insights, launchd scheduling, multi-destination sync, desktop widgets, and a local API server.

Build macOS 14.0+ Swift 5.9 Apple Silicon License Version Tests


Features

Feature Description
100+ rsync flags Tabbed visual editor across 14 categories (Basic, Transfer, Preserve, Filters, Comparison, Bandwidth, Output, SSH, Ownership, Backup, Logging, Network, I/O, Checksum)
Real-time progress Animated gradient progress circle with speed, ETA, file count, and current file display
Multi-source / multi-destination Fan-out (1:N), fan-in (N:1), and full mesh (N:N) sync modes with parallel or sequential execution
launchd scheduling Native macOS scheduling (hourly, daily, weekly, monthly, custom cron) that runs even when the app is closed
SSH remote sync Public key authentication with Keychain credential storage, connection testing, and key path validation
iCloud Drive sync One-click iCloud destination setup with automatic .icloud placeholder exclusion
AI insights (10 features) Error diagnosis, change summary, anomaly detection, smart scheduling, storage prediction, exclusion suggestions, NLP job creation, health scoring, recovery assistant, sensitive file detection
Multi-model load balancing Spread AI work across every enabled, healthy model (local Ollama + MLX, frontier OpenRouter, optional Nova Gateway) with a least-busy policy and health-gated failover. Three independent toggles; Nova is never required
Describe-it-in-English rsync Type your intent in plain English and a balanced LLM proposes a concrete rsync command. It is shown for review and pre-fills the builder — it is never auto-executed
Desktop widget WidgetKit extension (Small / Medium / Large) showing health score, last sync, next sync, and recent activity
Menu bar integration Status bar icon with quick job access and window toggle
Pre/post sync scripts Run custom scripts with environment variables (JOB_NAME, JOB_STATUS, FILES_TRANSFERRED); only absolute paths accepted
Job dependencies Chain jobs with conditional execution and CryptoKit-based change detection to skip unchanged sources
Delta reporting Structured post-sync report of files added, modified, deleted, with byte-level statistics
Dry run mode Preview all changes before execution
Nova API server HTTP API on port 37424 (loopback only) for programmatic control

Architecture

graph TD
 subgraph UI["SwiftUI Frontend"]
 CV[ContentView] --> JL[JobListView]
 CV --> JD[JobDetailView]
 CV --> JE[JobEditorView]
 JE -->|"Browse / iCloud / type path"| SDP["SyncJob.setDestinationPath(id:path:)"]
 SDP -->|"member-wise Equatable change"| JE
 CV --> SP[SyncProgressView]
 CV --> AI[AIInsightsView]
 CV --> HT[JobHistoryTabView]
 CV --> SV[SettingsView]
 MB[MenuBarManager] --> CV
 end
 subgraph Services["Service Layer"]
 JM[JobManager] --> RE[RsyncExecutor]
 JM --> AES[AdvancedExecutionService]
 JM --> SM[ScheduleManager]
 RE -->|Process.arguments| RSYNC["/usr/bin/rsync"]
 RE -->|stdout parsing| PP[Progress Parser]
 AES -->|parallel / sequential| RE
 AES -->|change detection| CD[CryptoKit Checksums]
 SM -->|generate plist| LA["~/Library/LaunchAgents/"]
 AIS[AIInsightsService] --> ABM[AIBackendManager]
 ABM --> OLLAMA["Ollama / MLX / TinyLLM"]
 WDS[WidgetDataSync] -->|App Group| WK[WidgetKit Extension]
 end
 subgraph Data["Persistence"]
 JOBS["jobs.json"]
 HIST["ExecutionHistory"]
 DR["DeltaReport"]
 end
 subgraph API["Nova API Server :37424"]
 STATUS["GET /api/status"]
 GETJOBS["GET /api/jobs"]
 RUN["POST /api/jobs/:id/run"]
 DRYRUN["POST /api/jobs/:id/dryrun"]
 HISTORY["GET /api/history"]
 end
 UI --> Services
 JE -->|"Save: addJob / updateJob → saveJobs()"| JM
 JM --> JOBS
 RE --> HIST
 RE --> DR
 API --> JM
 PP --> SP
 HIST --> WDS
 AIS --> HIST
Loading

Sync Process Flow

sequenceDiagram
 participant User
 participant UI as SwiftUI
 participant JM as JobManager
 participant AES as AdvancedExecutionService
 participant RE as RsyncExecutor
 participant RS as /usr/bin/rsync
 participant AI as AIInsightsService
 participant WK as WidgetKit
 User->>UI: Click "Run Job"
 UI->>JM: executeJob(id)
 JM->>JM: Load SyncJob from jobs.json
 JM->>JM: Check dependencies (CryptoKit hash)
 alt Multi-destination (N:N)
 JM->>AES: executeFanOut(job, destinations)
 AES->>RE: spawn parallel/sequential tasks
 else Single destination
 JM->>RE: execute(job)
 end
 RE->>RE: Build rsync arguments (100+ flags)
 RE->>RE: Run pre-sync script (if configured)
 RE->>RS: Process.launch(arguments)
 loop stdout lines
 RS-->>RE: progress output (% complete, speed, file)
 RE-->>UI: Update SyncProgressView (circle, ETA, speed)
 end
 RS-->>RE: Exit code + final stats
 RE->>RE: Parse DeltaReport (added/modified/deleted)
 RE->>RE: Run post-sync script (if configured)
 RE->>JM: Save ExecutionHistory entry
 JM->>AI: Analyze results (anomalies, health score)
 AI-->>JM: AI insights (errors, suggestions)
 JM->>WK: Sync latest stats via App Group
 JM-->>UI: Job complete (success/failure)
Loading

Multi-Model Load Balancing & Describe-it-in-English rsync

RsyncGUI ships the shared multi-model LLM load balancer. AI work (including the natural-language rsync assistant) is spread across every enabled, healthy model using a least-busy policy — the single-user version of how Nova's gateway balances load. Three independent toggles compose the pool, and each backend is health-gated so an unreachable one is simply skipped:

  • All local — every discovered Ollama model (/api/tags) plus locally-installed MLX models.
  • All frontier — OpenRouter models (bring-your-own-key, stored in the macOS Keychain).
  • Nova Gatewayoptional OpenAI-compatible backend at http://127.0.0.1:18792 (health on /v1/models). A failed health check just marks it unavailable; everything else keeps working.

Nova is never a hard requirement. With zero Nova the feature works on local models and/or an OpenRouter key alone. There is no dependency on Nova, PostgreSQL, or the gateway.

Describe it in English

The Job Editor's Basic tab has a "Describe it in English (AI)" field. Type an intent — for example "mirror Photos to the NAS, skip video files, delete extras on the destination" — and the balanced LLM returns a concrete rsync command. It is surfaced for review and, on an explicit click, pre-fills the command builder.

Safety: rsync is destructive (--delete), so the generated command is never run automatically. A pure, network-free validator (parseRsyncSuggestion) extracts only a valid rsync invocation and rejects everything else — shell chaining (; && |), command substitution (` $()), redirection (> <), non-rsync programs (rm, sudo, cp), and any program-executing rsync flag (-e, --rsh, --rsync-path) via a strict flag allow-list. If no backend is enabled the feature disables itself with a clear reason rather than failing.

graph TD
 U["User intent (plain English)"] --> PB["RsyncPromptBuilder<br/>(pure, network-free)"]
 PB --> LB["LLMLoadBalancerService"]
 subgraph Pool["Enabled + health-gated pool"]
 OL["Ollama (local)"]
 MLX["MLX (local)"]
 OR["OpenRouter (frontier)"]
 NG["Nova Gateway (optional)"]
 end
 LB -->|"LoadBalancer.next()<br/>least-busy"| Pool
 Pool -->|"raw LLM text"| PV["parseRsyncSuggestion()<br/>strict validator / sanitizer"]
 PV -->|"rejected: injection / non-rsync"| X["Discarded, nothing shown"]
 PV -->|"clean rsync only"| RC["RsyncCommand"]
 RC --> RV["Review card (read-only)"]
 RV -->|"explicit Apply"| CB["Command builder pre-filled"]
 CB -.->|"user starts the job themselves"| RUN["rsync runs"]
Loading

Fixes in 1.7.4

Destination path editing (issue #4). Browsing to a destination and clicking Select, or typing a destination path by hand, could appear to do nothing — the Destination Path would not populate or would not be saved.

Root cause: SyncDestination overrode equality (==) to compare only its id. SwiftUI relies on Equatable for change detection, so editing the path of an existing destination (unchanged id) was seen as "no change" and the view never refreshed — the edit looked lost. Three earlier attempts patched the picker plumbing but left this id-only equality in place, which is why it kept regressing.

The fix:

  • Removed the custom == so Swift synthesizes correct member-wise equality — a path change is now a real change that SwiftUI observes.
  • Extracted the path-setting logic into a pure, unit-tested helper, SyncJob.setDestinationPath(id:path:), that the editor calls for Browse, iCloud, and manual entry. The behavior is now covered by a regression suite (DestinationEditingTests) spanning unit, integration, functional, security, performance, retry, and frame categories — including a test that locks in the equality fix so this cannot silently regress again.

A related destination-picker fix already shipped in 1.7.3, so updating from 1.6.0 resolves the reported behavior.


Installation

From DMG (recommended for most users)

  1. Download the latest .dmg from Releases.
  2. Open it and drag RsyncGUI into your Applications folder.
  3. Launch it from Applications. No sandbox — full file system access for unrestricted rsync operation.

See "RsyncGUI can't be opened because the developer cannot be verified"? That means you have a build that isn't yet Developer-ID-signed and notarized. To open it anyway:

  • macOS 14 and earlier: Control-click (right-click) the app → OpenOpen.
  • macOS 15 (Sequoia) / 26 and later: double-click it, dismiss the dialog, then open System Settings → Privacy & Security, scroll down, and click Open Anyway.
  • Or from Terminal: xattr -dr com.apple.quarantine "/Applications/RsyncGUI.app"

Notarized releases open with no prompt at all — maintainers, see RELEASE.md.

From Source

Requires Xcode 16 or later. RsyncGUI is pure Swift with no third-party package dependencies, so there is nothing extra to install:

git clone git@github.com:kochj23/RsyncGUI.git
cd RsyncGUI
open RsyncGUI.xcodeproj
# Build & run: Cmd+R

Requirements

Requirement Minimum
macOS 14.0 (Sonoma)
Architecture Universal (Apple Silicon + Intel)
rsync Bundled with macOS; Homebrew version also supported
AI features (optional) Ollama, MLX, TinyLLM, or any supported backend

Project Structure

RsyncGUI/
|-- RsyncGUI/
| |-- RsyncGUIApp.swift App entry point, window management
| |-- NovaAPIServer.swift HTTP API server (port 37424, loopback)
| |-- Info.plist Bundle configuration
| |-- RsyncGUI.entitlements Sandbox disabled, full disk access
| |-- Design/
| | +-- ModernDesign.swift Glassmorphic theme, colors, card styles
| |-- Models/
| | |-- SyncJob.swift Job model (sources, destinations, modes, flags)
| | |-- RsyncOptions.swift 100+ rsync flags across 14 categories
| | |-- ScheduleConfig.swift launchd plist generation (cron, interval, idle)
| | |-- ExecutionHistory.swift Run history with speed, duration, byte stats
| | |-- DeltaReport.swift Post-sync change report (add/modify/delete)
| | |-- ParallelismConfig.swift Fan-out/fan-in/mesh execution strategies
| | +-- ConnectionTest.swift SSH connection validation model
| |-- Views/
| | |-- ContentView.swift Root navigation (sidebar + detail)
| | |-- JobListView.swift Job list with search and status indicators
| | |-- JobDetailView.swift Single job overview (last run, next run, health)
| | |-- JobEditorView.swift 14-tab rsync flag editor
| | |-- SyncProgressView.swift Animated progress circle with live stats
| | |-- DeltaReportView.swift Post-sync file change browser
| | |-- JobHistoryTabView.swift Execution history timeline
| | |-- ExecutionHistoryView.swift Single execution detail
| | |-- AIInsightsView.swift AI analysis panel (10 insight types)
| | |-- SettingsView.swift App preferences, AI backend config
| | +-- TestProgressView.swift Progress parser debug view
| +-- Services/
| |-- JobManager.swift Job CRUD, persistence, dependency resolution
| |-- RsyncExecutor.swift Process spawning, stdout parsing, progress
| |-- AdvancedExecutionService.swift Multi-dest orchestration, change detection
| |-- ScheduleManager.swift launchd plist install/uninstall/list
| |-- AIInsightsService.swift AI prompt construction and response parsing
| |-- AIBackendManager.swift Backend discovery, health check, routing
| |-- AIBackendManager+Enhanced.swift Extended AI capabilities
| |-- AIBackendStatusMenu.swift AI backend status indicator
| |-- MenuBarManager.swift NSStatusItem menu bar integration
| +-- WidgetDataSync.swift App Group data sharing with widget
|
|-- RsyncGUI Widget/
| |-- RsyncGUIWidget.swift WidgetKit timeline provider (S/M/L)
| |-- WidgetData.swift Shared data models for widget
| |-- SharedDataManager.swift App Group read/write
| +-- Info.plist
|
|-- RsyncGUITests/ 379 tests across 15 files
| |-- RsyncOptionsTests.swift Flag generation, archive mode, sanitization
| |-- ProgressParsingTests.swift Speed/time/bytes parsing
| |-- SyncJobTests.swift Job model, sync modes, Codable
| |-- NovaAPITests.swift API routing, response shapes
| |-- FrameTests.swift App launch, view instantiation
| |-- PathValidationTests.swift Path security, traversal detection
| |-- SecurityTests.swift Credential exposure, sanitization
| |-- ScheduleConfigTests.swift Plist generation, XML injection
| |-- CommandInjectionTests.swift SSH/rsync-path sanitization
| |-- FunctionalFlowTests.swift End-to-end job flows
| |-- WidgetDataTests.swift Widget data encoding
| |-- DeltaReportTests.swift Change report parsing
| |-- ExecutionHistoryTests.swift History entries, Codable
| |-- IntegrationTests.swift Real rsync execution
| +-- DependencyCheckTests.swift Job dependency logic
|
+-- RsyncGUI.xcodeproj/

2 targets | 25 Swift source files | 379 tests | Zero external dependencies


Building

git clone https://github.com/kochj23/RsyncGUI.git
cd RsyncGUI
xcodebuild -project RsyncGUI.xcodeproj -scheme RsyncGUI -configuration Release build

Testing

xcodebuild -project RsyncGUI.xcodeproj -scheme RsyncGUI -destination 'platform=macOS' test

403 tests across 16 test classes:

Test Class Tests Category
RsyncOptionsTests 57 Unit -- argument generation for 100+ flags, archive mode, filter sanitization, Codable
ProgressParsingTests 38 Unit -- speed/time/bytes parsing, final stats extraction, to-check lines
SyncJobTests 29 Unit -- job init, sync modes, execution strategy, destination types, Codable
NovaAPITests 29 Functional -- API endpoint routing, response shapes, status payload
FrameTests 28 Frame -- app launch, view instantiation, widget data models
PathValidationTests 26 Security -- tilde expansion, iCloud validation, SMB/USB, traversal detection
SecurityTests 25 Security -- path traversal, credential exposure, filter sanitization, binary resolution
ScheduleConfigTests 24 Unit -- plist generation, RunAtLoad, idle config, XML injection prevention
CommandInjectionTests 23 Security -- SSH host/user validation, rsync-path sanitization, shell escaping
FunctionalFlowTests 18 Functional -- end-to-end job creation, edit, and execution flows
WidgetDataTests 18 Unit -- widget data encoding, App Group sync, timeline entries
DeltaReportTests 17 Unit -- report formatting, itemize parsing, mixed change detection
ExecutionHistoryTests 17 Unit -- history entries, transfer speed calculation, Codable
IntegrationTests 15 Integration -- rsync binary verification, local sync with temp dirs, dry run, delete, exclude
DependencyCheckTests 15 Unit -- satisfied/unsatisfied dependencies, missing jobs, parallel file splitting
DestinationEditingTests 25 Unit / Integration / Functional / Security / Performance / Retry / Frame -- destination path editing (issue #4) and member-wise Equatable regression lock

Nova API Server

Port 37424 (127.0.0.1 loopback only). No authentication required.

Method Path Description
GET /api/status App status, version, job count, uptime
GET /api/ping Health check
GET /api/jobs List all sync jobs
GET /api/jobs/:id Single job detail
POST /api/jobs/:id/run Execute a job
POST /api/jobs/:id/dryrun Dry-run a job
GET /api/history Recent execution history
GET /api/jobs/:id/history History for a specific job
curl -s http://127.0.0.1:37424/api/status | python3 -m json.tool
curl -X POST http://127.0.0.1:37424/api/jobs/<uuid>/run

License

MIT License -- Copyright (c) 2026 Jordan Koch

See LICENSE for the full text.


Written by Jordan Koch

About

Professional rsync GUI for macOS with real-time progress visualization — modern RsyncOSX alternative built with SwiftUI

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /