Automated, resilient backup utility for MikroTik RouterOS - parallel, encrypted binary & plaintext backups with tmpfs staging and tag/group targeting, pluggable storage (local/SFTP/S3/Nextcloud) with retention & mirroring, and an optional self-hosted web UI/API for scheduling, monitoring, and push-restore.
  • Python 77.9%
  • HTML 12.7%
  • CSS 4.3%
  • JavaScript 3.9%
  • Shell 1%
  • Other 0.2%
Find a file
JD 18592bcef0
Some checks failed
release / image (push) Successful in 42s
ci / battery (push) Failing after 1m18s
release: 0.10.0
2026-07-02 03:55:15 +08:00
.forgejo/workflows ci(release): drop unsupported twine --skip-existing; tolerate already-published version 2026-06-23 10:40:28 +08:00
config feat(web): System settings, export categories, clickable metrics, modal Close 2026-06-14 16:18:59 +08:00
deploy fix: set-password config dir, accurate macvlan-v6 + rate-limit docs, dockerignore build artifacts 2026-06-23 01:08:00 +08:00
doc docs: documentation overhaul — Docker-first README, architecture diagram, consolidated installs 2026-07-02 01:45:05 +08:00
img docs: documentation overhaul — Docker-first README, architecture diagram, consolidated installs 2026-07-02 01:45:05 +08:00
scripts ci: add scripts/ci-local.sh to run the full gate locally before pushing 2026-06-22 23:31:38 +08:00
src/rosbackup release: 0.10.0 2026-07-02 03:55:15 +08:00
ssh-keys initial commit 2025-01-02 14:38:41 +08:00
testlab feat(testlab): dual-NIC routers with a WAN uplink so online upgrades work 2026-06-15 16:16:09 +08:00
tests feat(web): status filters on ssh-keys/groups/tags (online/offline + member-status) 2026-07-02 03:53:08 +08:00
.dockerignore fix: set-password config dir, accurate macvlan-v6 + rate-limit docs, dockerignore build artifacts 2026-06-23 01:08:00 +08:00
.gitignore deploy: compose stack (bridge+IPv6) for rosbackup-web behind Caddy 2026-06-23 00:36:41 +08:00
.pre-commit-config.yaml build(rewrite): slice 1 — package scaffold, packaging, CI/test tooling 2026-06-10 02:07:11 +08:00
CHANGELOG.md release: 0.10.0 2026-07-02 03:55:15 +08:00
Dockerfile ci(release): OCI image labels + auto-publish Forgejo release from CHANGELOG on tag 2026-06-23 03:47:46 +08:00
LICENSE feat(web): scaled run chart, latency leaderboards, inventory latency; email update 2026-06-14 03:07:32 +08:00
pyproject.toml release: 0.10.0 2026-07-02 03:55:15 +08:00
README.md docs: documentation overhaul — Docker-first README, architecture diagram, consolidated installs 2026-07-02 01:45:05 +08:00
RELEASE-PROCESS.md docs: add RELEASE-PROCESS.md (tag-driven release runbook) 2026-07-02 03:30:01 +08:00
requirements-web.lock build: add .dockerignore and a hashed web-extra lock for the image 2026-06-23 00:11:33 +08:00
requirements.lock refactor: legacy entry-point shims + lean deps (slice 6-2) 2026-06-10 19:23:53 +08:00
requirements.txt refactor: legacy entry-point shims + lean deps (slice 6-2) 2026-06-10 19:23:53 +08:00
ROADMAP.md docs: reconcile ROADMAP — mark verified architecture items done, add Web Platform backlog 2026-07-02 02:28:45 +08:00

rosbackup-ng

rosbackup-ng

Next Generation RouterOS Device Fleet Management & Backup

CI License: MIT Python 3.9+ Code style: ruff Types: mypy

⚠️ Under active development — feature-incomplete until v1.0.
rosbackup-ng is usable today, but behavior, configuration, and APIs may change between releases until v1.0 arrives.


Automated, resilient backup utility for MikroTik RouterOS devices. It creates, downloads, and stores both binary (.backup) and plaintext (.rsc) backups — in parallel, with encryption, tmpfs staging, tag/group targeting, pluggable storage destinations, and connectivity built to survive flaky overlay links (e.g. ZeroTier). An optional self-hosted web platform adds a dashboard, HTTP API, schedules, and push-restore.

It runs as a lean CLI (rosbackup) or as a long-running web service (rosbackup-web). The web platform deploys as a Docker stack (see Deploy with Docker); pip, a single-file zipapp, and from-source installs live in doc/INSTALLATION-METHODS.md.

rosbackup-ng web dashboard

The optional self-hosted web platform — fleet dashboard with reachability, backup health, latency, and recent runs. See Web platform.

At a glance

rosbackup --compose-style renders a live, scale-aware view — a per-row list for small fleets, or an aggregate dashboard (progress bar, success/failure/running counts, throughput ETA, failures tail) at thousands of targets. See Compose-style output.

Highlights

  • Both backup formats — binary (.backup) + plaintext (.rsc), in parallel, with encryption and tmpfs staging to spare router flash.
  • Resilient connectivity — retry-within-a-window, TCP keepalives, and operation-level reconnect, built to survive flaky overlay links (e.g. ZeroTier).
  • Fleet at scale — tags/groups, scoped runs (--group/--tag), and a compose-style live view that scales from 4 routers to 5,000.
  • Pluggable storage — mirror to local / sftp / nextcloud (and s3 via an extra), with per-destination retention and group/tag routing.
  • Optional web platform — self-hosted UI + /api/v1: dashboard, schedules, push-restore, firmware upgrades, an encrypted Vault for keys/passwords, API tokens, and an audit log.
  • Secure by default — SSH key auth, self-contained host-key verification, encrypted backups, 0600 files, and log redaction.

See doc/FEATURES.md for the full (non-exhaustive) feature list.

How it fits together

One backup engine, two front-ends. The rosbackup CLI and the rosbackup-web service both drive the same console-free core (orchestrator.run_backup); the web adds the encrypted Vault, the scheduler, the UI, and the /api/v1 HTTP API on top.

        config dir · global.yaml · targets.yaml · web.db (vault, web only)
                                  │
                 ┌────────────────┴─────────────────┐
                 ▼                                   ▼
        rosbackup (CLI)                     rosbackup-web (service)
        standalone · no server              UI · /api/v1 · scheduler · Vault
        filesystem SSH key                  ◀── browser / API client (Bearer token)
                 │                                   │
                 │                                   │  inject_vault_keys():
                 │                                   │  key_secret → in-memory key
                 └────────────────┐   ┌───────────────┘
                                  ▼   ▼
        core engine · orchestrator.run_backup(config, targets) → BackupRunResult
        parallel SSH · tmpfs staging · retention
                                  │
                 ┌────────────────┴─────────────────┐
                 ▼                                   ▼
        RouterOS fleet (SSH / SCP)          storage: local · sftp · nextcloud · s3
  • Shared core — both front-ends load the config and call the same run_backup(config, targets); the web is a thin adapter, not a second implementation.
  • Vault is web-only — the web decrypts a target's ssh.key_secret into an in-memory key just before a run (inject_vault_keys); the standalone CLI authenticates with filesystem keys.
  • HTTP APIPOST /api/v1/runs triggers a run and GET /api/v1/runs/{id} polls results (Bearer-token auth), so backups can be driven programmatically.

Full internals are in doc/DESIGN_REFERENCE.md.

Documentation

Prerequisites

  • Docker Engine + Compose v2 — or Python 3.9+ for the other install methods
  • RouterOS devices with SSH access enabled
  • An SSH key pair for the backup user (see BOOTSTRAP)
  • RouterOS v7.7 or later for tmpfs staging (falls back to flash on older versions)

Deploy with Docker

The supported way to run rosbackup-ng is the Docker Compose stack under deploy/. It pulls a pre-built image from the registry (a local build is the fallback), runs rosbackup-web behind a Caddy TLS front door, and keeps your config + backups on the host. Prerequisite: Docker Engine + Compose v2.

Quickstart

git clone https://git.jdneer.com/jd/rosbackup-ng /opt/rosbackup-ng && cd /opt/rosbackup-ng/deploy
sudo ./deploy.sh up                 # first run scaffolds .env + ./config and pulls the image
$EDITOR config/global.yaml config/targets.yaml   # add your routers + SSH key (see CONFIG_REFERENCE)
sudo ./deploy.sh set-password       # set the web admin password (once)
sudo ./deploy.sh up                 # apply — idempotent, safe to re-run

Open https://<host>/ (self-signed cert by default) and log in. That's the whole setup.

TLS (optional)

sudo ./deploy.sh set-fqdn backup.example.com        # public hostname
sudo ./deploy.sh set-tls-desec <deSEC-token>        # automatic Let's Encrypt via DNS-01
# or bring your own cert:
sudo ./deploy.sh set-cert ./fullchain.pem ./key.pem

Day-to-day

sudo ./deploy.sh status     # health + container state
sudo ./deploy.sh logs       # follow logs
sudo ./deploy.sh update     # pull the latest image + restart
sudo ./deploy.sh down       # stop the stack

Network profiles (bridge/NAT66 vs macvlan native addressing), manual-cert import, and the full deploy.sh reference are in doc/DEPLOY.md and doc/NETWORKING.md.

Prefer not to use Docker? pip (from the package registry), a single-file zipapp, and from-source installs are in doc/INSTALLATION-METHODS.md.

Configuration

The CLI and the web platform read the same config dir (global.yaml + targets.yaml). A minimal global.yaml:

backup_path_parent: backups          # where backups are stored (required)
backup_password: change-me           # used when a target sets `encrypted: true` (optional)

ssh:
  user: rosbackup                    # default SSH username
  connect_retry:                     # resilient connect for flaky/overlay links (ZeroTier), on by default
    total_timeout: 180               # retry window, seconds

tmpfs:
  enabled: true                      # stage binary backups in RAM to spare router flash

A minimal targets.yaml:

targets:
  - name: ROUTER-1
    host: 192.168.88.1
    tags: [site-hq, core]                          # labels for groups / --tag (optional)
    ssh:
      private_key: ./ssh-keys/private/id_rosbackup

See doc/CONFIG_REFERENCE.md for the full reference — retention, encryption, per-target overrides, notifications, groups, storage routing, and web capability toggles.

Usage

# Back up all enabled targets
rosbackup

# A specific target only
rosbackup --target ROUTER-1

# A named group or a tag (selectors are mutually exclusive with --target)
rosbackup --group critical
rosbackup --tag site-hq

# Discover configured groups/tags with device counts
rosbackup --list

# Scale-aware compose-style output
rosbackup --compose-style

# Validate connectivity/access without writing anything
rosbackup --dry-run

Compose-style output

--compose-style (-x) renders a live view that scales from a handful of routers to thousands. Small fleets get a per-row view:

[+] Executing backup for 4 targets ...

    ✔ ROUTER1                  Finished            3.8s
    ✔ ROUTER2                  Finished            3.8s
    ⠹ ROUTER3                  Backing Up          2.1s
    ⠹ ROUTER4                  Connecting          0.6s

Summary:
    Total time: 3.9s
    Total size: 107.0KB
    Success: 4 | Failed: 0 | Total: 4

Large fleets automatically switch to an aggregate dashboard — a live progress bar, success/failure/running counts, throughput-based ETA, the currently-running window, and a failures tail:

[+] Backing up 5000 targets
    ██████████░░░░░░░░░░░░░░░░░░░░ 1640/5000  33%   ✔ 1628  ✘ 12  ⠹ 48 running   2m14s   eta 4m31s

Active:
    ⠹ router-1641             Backing Up          1.2s
    ⠼ router-1642             Getting Info        0.4s
    … and 46 more running

Recent failures:
    router-0044, router-0210

State updates are O(1) and rendering is O(visible rows), so the view stays responsive regardless of fleet size.

Other examples

rosbackup --no-parallel              # sequential
rosbackup --max-parallel 10          # cap concurrency
rosbackup --log-level DEBUG          # verbose
rosbackup --no-tmpfs                 # disable tmpfs staging for this run
rosbackup --tmpfs-size-mb 25         # override tmpfs size for this run
rosbackup --log-file backups.log     # also write a 0600 log file

Bootstrap (provisioning a router)

Create the backup user and install its key on a device (see doc/BOOTSTRAP.md):

# Generate a key pair for the backup user
ssh-keygen -t ed25519 -f ssh-keys/private/id_rosbackup

# Provision a router (prompts for the existing admin password)
rosbackup-bootstrap -H 192.168.88.1 -k ssh-keys/private/id_rosbackup.pub

# See exactly what it would do, without changing anything
rosbackup-bootstrap -H 192.168.88.1 -k ssh-keys/private/id_rosbackup.pub --dry-run

Web platform

rosbackup-web is a self-hosted UI + HTTP API that reads the same config directory as the CLI (dashboard pictured near the top). Deploy it with the Docker stack (doc/DEPLOY.md); to run it without Docker, see doc/INSTALLATION-METHODS.md. Once it's installed:

rosbackup-web -c /etc/rosbackup --set-password   # set the admin password once
rosbackup-web -c /etc/rosbackup                  # serve on 127.0.0.1:8474

It gives you a dashboard, targets inventory (table + cards), run-now with live progress, schedules, a backup browser with download + push-restore, firmware upgrades, an encrypted Vault for SSH keys and passwords, API tokens, and an audit log. Bind it to localhost and terminate TLS at a reverse proxy (sample Caddyfile + systemd unit under doc/examples/).

Full details — auth, the Vault, schedules, push-restore, capability toggles, the API, and the single-file build — are in doc/WEB_PLATFORM.md.

Command completion

Enable bash completion for the current shell session:

source scripts/rosbackup-ng-completion.bash

It provides option, file-path, and target-name completion (from targets.yaml) for the rosbackup and rosbackup-bootstrap commands.

Logging

Console logging is colored via rich. With --log-file, logs are also written to a file (created 0600) including backup status, connection details, retries, and errors. Format: MM-DD-YYYY HH:MM:SS [LEVEL] [target] message.

Development

pip install -e ".[dev,web]"
ruff check src tests && ruff format --check src tests
mypy src
bandit -q -r src
pytest -q

CI runs this same battery on every push via self-hosted Forgejo Actions (.forgejo/workflows/ci.yml), deliberately using no external/marketplace actions — every step is hand-written shell.

Before pushing, run the whole gate in one shot with a venv that matches CI's resolved dependencies — this catches the failures a pytest-only check misses (ruff/format/bandit, and runner-environment flakes):

scripts/ci-local.sh            # ruff -> format -> mypy -> bandit -> pytest -> shiv build + smoke
scripts/ci-local.sh --fresh    # rebuild the .civenv from scratch first

It runs every gate (not just the first failure) and exits non-zero if any fails. The .civenv it creates is gitignored and reused across runs.

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Run the checks above (they must all pass)
  4. Commit and open a Pull Request

License

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

Support

Open an issue or send a PR.