One UI for every cron, watcher, and sync across your servers.
A self-hosted control panel for your cron jobs ⏰, folder watchers 👀 and rsync/rclone backups 🔁 — with live logs, safety guards, run history, and nothing hidden from you.
🇫🇷 Version française du README
Stop scattering your automation across crontabs, background inotifywait loops, and one-off rsync scripts on every box. SyncBridge pulls all of it into one web UI: pick a trigger (a schedule, a folder change, or on-demand) and an action (an rsync/rclone sync, or any shell command/script) — and watch it run. Think cronmaster + an rsync web UI + an inotify manager, and now multi-server, in a single ~9 MB Go binary. No database, no build step, zero runtime dependencies: pull the image and go. 🐳
The dashboard — every job with its trigger and live status, an instance switcher for other servers, and expandable per-job run history.
System import — SyncBridge sees your host's crontabs, systemd units and inotify watchers, and lets you import, reversibly disable, or permanently delete them (with a recoverable trash).
Composing a job visually — pick a type, a backend, a trigger. No script to write.
If your homelab looks like mine — a CRON/ folder here, an inotify/ watcher there, a dozen rsync one-liners you half-remember, spread over two or three machines — SyncBridge rounds all of it up. The rule of the house: total visibility. Nothing runs that you can't see. Every execution streams stdout/stderr to a live dashboard, keeps a run history, and dangerous operations are refused before they can hurt you.
- ⏰ Job scheduler — standard 5-field cron to run any script or command (
docker system prune, stack updates, DB exports, diagnostics…). - 👀 File watcher — watch a folder, filter by pattern (
*.mp4), fire an action. NFS-safe: hybrid inotify + polling, because inotify alone never sees remote NFS writes, plus a write-completion check so half-copied files aren't synced. ▶️ On-demand runner — trigger utility scripts manually, async, with streamed logs and a kill button.- 🔁 rsync / rclone sync — mirror / accumulate / move, checksum or time compare, bandwidth limit, exclusions, rotating safety trash, and a faithful system backup mode (ACL/xattr/hardlinks via
--fake-super, restorable identically). - 🔎 System monitor & control — scans host crontabs, systemd units (
.service/.timer/.path) and strayinotifywaitprocesses, so nothing on your box escapes you. Import splits them into your triggers vs system triggers (OS/packages), and lets you reversibly disable/re-enable a host trigger (cron lines commented with#SB-OFF#, systemd toggled viasystemctl) or permanently delete one — batched, gated behind typingdelete, and backed up to a recoverable trash. Needs a read-write host mount (seecompose.example.system.yaml). - 🕸️ Multi-instance control — run SyncBridge on several boxes and drive them all from one UI (like Dockge agents). Add a remote by URL + login; the top-right switcher then pilots its jobs, its system import and its live logs through a built-in reverse proxy, with an online/offline badge per instance. You can't mount another server's system volumes across the network — so you talk to its SyncBridge instead.
- 🕒 Run history — each job keeps its last runs (status, duration, first error line), persisted across reboots and shown inline.
- 🔔 Failure notifications — point SyncBridge at an ntfy topic or any webhook; failed runs (optionally successes too) send a notification. One field, no extra container.
- 🔐 Login & hardening — a themed web login (account created on first launch, password hashed with bcrypt), brute-force lockout with backoff, expiring sessions, and at-rest encryption (AES-GCM) of stored remote passwords using an auto-generated key in
/config. Since the UI can run scripts on your box, never expose it without auth — keep it on your LAN / behind Tailscale or a reverse proxy. - 🌍 Bilingual UI (EN default / FR toggle) with keyboard navigation, focus outlines and aria labels for accessibility.
SyncBridge refuses to shoot you in the foot:
- 💥 Empty/unmounted source guard — the classic rsync disaster: a mirror whose NFS source got unmounted looks empty, and
--deletewipes the destination. SyncBridge aborts the run instead. - 🔒 Anti-overlap lock — a job never stacks on top of itself (fast watchers, cron overrunning its own interval).
- ⏱️ Timeout — a hung job is killed after N seconds (whole process group), because a job that hangs forever is worse than one that fails.
- 🚧 Delete guardrails — mirror refuses
source == destand dest-inside-source; optional max-deletions abort. - ✅ Write-completion check — a watcher waits for the folder to go quiet (stable size) before firing.
- 🗑️ Recoverable trash — deleting a host trigger backs it up first; restore it from the Trash view.
mkdir syncbridge && cd syncbridge
curl -O https://raw.githubusercontent.com/GodsQuantum/SyncBridge/main/compose.example.yaml
curl -O https://raw.githubusercontent.com/GodsQuantum/SyncBridge/main/.env.example
cp .env.example .env
# adjust the volumes in compose.example.yaml to your paths, then:
docker compose -f compose.example.yaml up -dOpen http://<server-ip>:8788 🌐. On first launch the UI asks you to create an account (or set SYNCBRIDGE_USER/SYNCBRIDGE_PASSWORD to skip that). Everything lives in ./data (/config): jobs, account, run history, notification settings, the encryption key and any remote-instance credentials — back up that folder and you've backed up everything. The image is multi-arch (amd64 + arm64) on ghcr.io/godsquantum/syncbridge:latest.
Two compose variants ship: compose.example.yaml (read-only — SyncBridge sees your host triggers but never touches them) and compose.example.system.yaml (mounts host cron read-write so SyncBridge can disable/delete/write host cron). Start with the read-only one.
- Create the config folder and grab the two files above (
compose.example.yaml,.env.example). - Write your
.env— sensible defaults ship in.env.example; the only thing most people change isTZ. - Pick your volumes (bind mounts). The only required one is
./data:/config. Map the host folders you sync between, the scripts you want to trigger (:rois enough), and — if you want the System Monitor — the/host/...mounts. Every line is documented inline incompose.example.yaml, including the optional Docker socket (fordockerjobs) and thershared/mntmount (for nested NFS). docker compose up -d, open the UI, create your first job. Use dry-run on syncs before trusting them. 🧪
| Trigger | Fires when | Example |
|---|---|---|
| 🖐️ Manual | you click Run | YT.sh <url> |
| ⏰ Cron | a schedule | 0 4 * * * → docker system prune -f |
| 👀 Watch | a folder changes | a .mp4 lands → run an rsync, or an ffmpeg script |
Every job picks where it runs:
- 🟢 SyncBridge backend (default) — SyncBridge runs the job itself: live logs, kill button, clean shutdown, anti-overlap lock. Simple and fully visible. Stops if the container stops.
- 🟣 System backend — SyncBridge writes a real host cron entry (
/etc/cron.d, withflock+PATH) or a systemd.path/.serviceunit, so the job keeps running even if Docker/SyncBridge is down. Invariant: the artifact is removed before any rewrite and cleaned up on delete or backend switch — never a hidden duplicate. Writing host cron needs a read-write mount; the systemd variant needs a privileged container (seecompose.example.yaml).
Install SyncBridge on each machine, then from one instance add the others by URL + their login (the top-right + button). That instance proxies every API call to the selected remote, so its jobs, system import, trash and live logs all work as if local — you drive another box's crons without mounting its disks, and a 🟢/🔴 badge shows which instances are up. Requirement: the piloting instance must be able to reach the remote over the LAN (e.g. http://192.168.1.50:8788). Remote passwords are encrypted at rest. Keep it inside your LAN / Tailscale; the proxy is admin-only but there's no TLS between instances yet.
Each job keeps its recent runs (status, duration, first error line) in /config/history.json — click the clock icon on a job to expand them. Under Settings (⚙), set a notification URL: SyncBridge sends a text POST with a Title header on failures (and optionally successes), which works natively with ntfy and most webhooks. A Test button confirms it end-to-end.
All optional — sensible defaults ship in .env.example. Nothing here changes the volumes you mount.
| Variable | Default | What it does |
|---|---|---|
TZ |
UTC |
Container timezone (drives cron times). |
SB_DATA |
/config |
Where jobs, history, settings and keys live. |
SB_UID / SB_GID |
1000 |
Owner of files SyncBridge writes (config stays 1000 even when the container runs as root). |
SB_PORT |
8787 |
Internal port. Mapped via ports: in compose; override only to run several instances bare-metal. |
SB_IMPORT_PATHS |
/import/scripts:/import/crontab |
Colon-separated folders scanned for rsync/rclone commands to import. |
SB_SYS_CRON_PATHS / SB_SYS_SYSTEMD_PATHS |
host cron / systemd | Where the System Monitor reads host triggers. |
SB_SECRET |
(auto) | Encryption key for stored remote passwords. If unset, a random key is generated in /config/secret.key — no compose change needed. |
SB_LOCK_COMMANDS |
(off) | Set to 1 to forbid creating command/script jobs on this instance (locks it to sync-only). |
SYNCBRIDGE_USER / SYNCBRIDGE_PASSWORD |
(unset) | Pin the account via env instead of the first-run register screen. |
GET/POST /api/jobs list / create
PUT/DELETE /api/jobs/{id} edit / delete
POST /api/jobs/{id}/run run (?dry=1 to simulate a sync)
GET /api/jobs/{id}/stream live logs (SSE)
GET /api/jobs/{id}/history recent runs
POST /api/jobs/{id}/kill stop the running job
GET /api/system/scan host triggers detected (read-only)
GET /api/import/scan rsync/rclone commands found in your scripts/crontab
POST /api/system/toggle reversibly disable/re-enable a host trigger
POST /api/system/delete permanently delete host triggers (batch, confirm="delete")
GET /api/system/trash recoverable deleted triggers · /api/system/restore
GET/POST /api/remotes list / add a remote instance · DELETE /api/remotes/{id}
ANY /api/remote/{id}/... proxy to a piloted remote instance
GET/POST /api/settings notification settings (POST test=true to send a test)
POST /api/auth/register first-run account creation
POST /api/auth/login log in · /api/auth/logout log out
go build -o syncbridge . # static binary
go test ./... # unit tests
docker build -t syncbridge .One Go package, a handful of files: main.go (jobs, scheduler, HTTP), sysmon.go (host scan), system_backend.go (host cron/systemd writer), sys_toggle.go (disable/delete/trash), remote.go (multi-instance proxy), secrets.go (at-rest encryption), notify.go (settings + notifications), history.go (run history), auth.go (login), and web/. Easy to fork. 🍴
USB auto-ingest triggers · optional TLS between piloted instances · per-job history charts.
self-hosted cron manager · cronmaster alternative · rsync web UI · rclone GUI · inotify watcher manager · folder-watch automation · multi-server orchestration · homelab control panel · docker cron scheduler · centralize Linux scripts · systemd timer dashboard · self-hosted task scheduler.
MIT — see LICENSE. Contributions welcome. ❤️