- Go 75%
- JavaScript 10.3%
- Just 6.2%
- CSS 5.2%
- Dockerfile 2%
- Other 1.3%
|
All checks were successful
Build Docker Image / build (push) Successful in 1m13s
|
||
|---|---|---|
| .forgejo/workflows | ||
| config | ||
| deploy | ||
| example-jobs | ||
| rclone | ||
| runner | ||
| scheduler | ||
| ui | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| main.go | ||
| README.md | ||
| renovate.json | ||
rclone-manager
Scheduling and monitoring for rclone sync jobs. Define jobs as YAML files and rclone-manager will run them on a schedule, showing live status either as a terminal UI or as a web dashboard.
Requirements
- Go 1.21+
rcloneinstalled and available on$PATH
Usage
rclone-manager --jobs <path-to-jobs-dir> [--poll <interval>] [--ui tui|web]
| Flag | Default | Description |
|---|---|---|
--jobs |
(required) | Path to directory containing job YAML files |
--poll |
5m |
How often to check for due jobs |
--rclone-config |
$RCLONE_CONFIG |
Path to rclone.conf. Empty leaves rclone to its own default discovery (~/.config/rclone/rclone.conf, RCLONE_CONFIG_<REMOTE>_* env vars, ...) |
--ui |
tui (or $RCLONE_MANAGER_UI) |
Interface to run: tui (terminal) or web (HTTP) |
--web-addr |
:8080 (or $RCLONE_MANAGER_WEB_ADDR) |
Listen address for the web interface |
Interfaces
Both modes run the same scheduler and runner; they only differ in how you watch them.
--ui tui (default) renders a live dashboard in the terminal. Press q to quit.
--ui web serves the same information over HTTP and needs no TTY, which makes it the right choice under Kubernetes, Docker without -it, or anywhere you would rather point a browser than attach to a process.
rclone-manager --jobs ./my-jobs --ui web --web-addr :8080
| Endpoint | Description |
|---|---|
GET / |
Dashboard: current job with live progress, upcoming schedule, run history |
GET /api/state |
The same data as JSON — handy for scripting or testing |
POST /api/jobs/{name}/run |
Run a job immediately, ignoring its schedule. 404 if unknown, 409 if already running |
GET /healthz, GET /readyz |
Liveness and readiness. Both are cheap and never touch rclone, so a long sync will not fail a probe |
The page polls /api/state once a second (backing off to every 15s while the tab is hidden). All assets are embedded in the binary — nothing is fetched from a CDN.
Job format
Each .yaml / .yml file in the jobs directory defines one sync job:
name: my-backup
source: /path/to/source
destination: /path/to/destination
operation: sync # sync, copy, or bisync
schedule: 1h # Go duration (e.g. 30m, 6h) or cron expression (e.g. "0 2 * * *")
flags: # optional rclone flags
checksum: true
Fields
| Field | Required | Description |
|---|---|---|
name |
yes | Human-readable job name |
source |
yes | Source path or rclone remote |
destination |
yes | Destination path or rclone remote |
operation |
yes | sync, copy, or bisync |
schedule |
yes | Interval (Go duration) or 5-field cron expression |
flags |
no | Extra flags passed to rclone |
Docker
The image never contains an rclone.conf — supply it at run time:
docker build -t rclone-manager .
docker run --rm -it \
-v ~/.config/rclone/rclone.conf:/config/rclone.conf:ro \
-v ./my-jobs:/jobs:ro \
rclone-manager
Without a TTY, run the web interface instead:
docker run --rm -p 8080:8080 \
-v ~/.config/rclone/rclone.conf:/config/rclone.conf:ro \
-v ./my-jobs:/jobs:ro \
rclone-manager --jobs /jobs --ui web
The image sets RCLONE_CONFIG=/config/rclone.conf, so mounting the file there
is all that is needed. Point it elsewhere by overriding that env var or passing
--rclone-config <path>; pass --rclone-config "" to define remotes purely
through RCLONE_CONFIG_<REMOTE>_* env vars instead. If the configured path does
not exist, startup fails with an explicit error rather than silently running
with no remotes.
Read-only config mounts
Kubernetes secrets and :ro bind mounts are not writable, but rclone rewrites
its config file whenever it refreshes an OAuth token. When the config is not
writable, rclone-manager copies it to a private 0600 file under $TMPDIR at
startup and points rclone at the copy. Refreshed tokens therefore last for the
lifetime of the process and are re-derived from the refresh token after a
restart. $TMPDIR must be writable — with a read-only root filesystem, mount an
emptyDir at /tmp.
Kubernetes
deploy/kubernetes.yaml is a working example: the config arrives as a Secret
mounted at /config/rclone.conf, and jobs as a ConfigMap at /jobs.
kubectl create secret generic rclone-config \
--from-file=rclone.conf=$HOME/.config/rclone/rclone.conf
kubectl apply -f deploy/kubernetes.yaml
The example runs --ui web, so no TTY is needed: the container exposes port
8080, both probes point at it, and a Service fronts it. Open the dashboard
with:
kubectl port-forward svc/rclone-manager 8080:80
then browse to http://localhost:8080. To run the TUI under Kubernetes
instead, drop the --ui web args and set tty: true and stdin: true on the
container, then attach with kubectl attach -it deploy/rclone-manager
(detach with Ctrl-P Ctrl-Q).
Building
go build -o rclone-manager .
Local development
A justfile wraps the common tasks. Everything runs through Docker, so no local
Go toolchain is needed — only just, docker, curl and jq.
just copy # build the image, start the web interface, run the example copy job
example-jobs/ holds two jobs that copy between directories inside the
container, so they exercise the scheduler, runner and rclone daemon end to end
without needing a configured remote. just seeds the source tree under
.local/data/ (git-ignored), which is also where you can inspect what was
copied.
| Recipe | Description |
|---|---|
just build |
Build the Docker image |
just run |
Build and start the web interface on http://localhost:8080 |
just copy |
run, then trigger the example copy job and print its result |
just trigger <job> |
Trigger any job by name and wait for it to finish |
just state |
Print /api/state as JSON |
just logs |
Follow the container logs |
just tui |
Run the terminal UI against the same example jobs |
just stop / just clean |
Stop the container / also delete the seeded data |