MikroTik Backup System
Version 0.0.1 is the initial Rust rewrite of the previous Python/TypeScript application. It is a single self-hosted service with an embedded web UI, SQLite storage, RouterOS/SwOS backup workflows, scheduling, retention, notifications, and local OpenAPI documentation.
Quick start
Docker Compose
docker compose build app
docker compose up -d
The default published address is 127.0.0.1:5581. Persistent application data is stored in the data volume.
The image uses rust:1.99.1-alpine3.24 for the build stage and alpine:3.24 for runtime. The service runs as UID/GID 10001 with a read-only root filesystem.
Native build
Requires Rust 1.99.1 or compatible toolchain and the native dependencies needed by the crates in Cargo.lock.
cargo build --release --locked
./target/release/mikrotik-backup serve
Or use the launcher:
./start.sh
First account
Registration is disabled by default. Set DEFAULT_ADMIN_PASSWORD before first startup, or let the application create the initial administrator password file under DATA_DIR/secrets/initial-admin-password.
New installations and new accounts default to English. Imported accounts preserve their saved language preference. The UI also supports Polish, Spanish, and Norwegian.
Main configuration
| Variable | Default | Purpose |
|---|---|---|
DATA_DIR |
./storage |
Database, backups, secrets, SSH known hosts |
DATABASE_PATH |
<DATA_DIR>/routeros_backup_next.db |
SQLite database path |
LISTEN_ADDR |
127.0.0.1:5581 |
HTTP listener |
API_PREFIX |
/api |
API base path |
TIMEZONE |
Europe/Warsaw |
Scheduler timezone |
SECRET_KEY |
generated/persisted when absent | JWT signing secret |
ALLOW_REGISTRATION |
false |
Allow account registration |
DEFAULT_ADMIN_USERNAME |
admin |
Initial administrator name |
DEFAULT_ADMIN_PASSWORD |
empty | Initial administrator password |
SSH_HOST_KEY_POLICY |
accept-new |
accept-new or strict |
SSH_TIMEOUT_SECONDS |
30 |
Device SSH timeout |
MAX_PARALLEL_BACKUPS |
4 |
Concurrent backup limit |
CORS_ORIGINS |
[] |
Explicit allowed HTTP(S) origins |
Run configuration validation without starting the server:
./target/release/mikrotik-backup check-config
Data layout
DATA_DIR/
├── routeros_backup_next.db
├── backups/
│ ├── exports/<device-id>/
│ └── binary/<device-id>/
├── secrets/
└── ssh/
RouterOS text exports are stored under backups/exports; binary RouterOS backups and SwOS .swb files are stored under backups/binary.
Legacy Python/TypeScript import
The importer is explicit and never runs during normal startup. Stop the old application first and import into a new Rust destination.
./import-python.sh /srv/mikrotik-backup-old
Docker Compose example:
docker compose run --rm \
-v /srv/mikrotik-backup-old:/legacy:ro \
app import-python --source /legacy
The importer copies accounts, devices, settings, logs, backup metadata, and every referenced backup file. It validates existing backup checksums when present and refuses to publish an incomplete destination. See docs/migration.md.
RouterOS and SwOS
RouterOS uses SSH-based export/backup operations. SwOS uses its web interface and downloads /backup.swb, with Digest authentication first and Basic as fallback. Connection testing distinguishes general web reachability from backup-endpoint availability.
SwOS details and troubleshooting are in docs/switchos.md.
Configuration comparison
.rsc files support:
- side-by-side and unified diff;
- changed-lines-only mode and search;
- paged browser rendering to limit DOM size;
- semantic RouterOS analysis that groups changes by configuration section and identifies added, removed, and modified commands/fields;
- masking of sensitive values in semantic summaries.
Binary backups are not diffed.
Embedded web assets and caching
build.rs combines the UI sources listed in web/assets.list. Each compiled CSS/JavaScript bundle receives a content hash in its filename, for example:
/assets/app.<hash>.js
/assets/app.<hash>.css
/assets/docs.<hash>.js
/assets/docs.<hash>.css
The HTML shell is generated with those exact filenames. Hashed assets use:
Cache-Control: public, max-age=31536000, immutable
HTML and API responses remain non-cacheable by default. /favicon.ico intentionally returns 204 No Content.
API documentation
Interactive local documentation:
/docs
OpenAPI JSON:
/openapi.json
Swagger UI is embedded locally and does not require a CDN.
Validation
Frontend unit tests:
node web/tests/core.test.cjs
JavaScript syntax:
node --check web/js/i18n.js
node --check web/js/core.js
node --check web/js/views.js
node --check web/js/app.js
Rust checks when a toolchain is available:
cargo fmt --check
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
The browser smoke tests under web/tests/ require Playwright.
Security and dependencies
The service uses request size/time limits, JWT validation, password hashing, SSH host-key verification, secret redaction, restricted filesystem permissions, and a non-root container runtime. Dependency audit notes and the local libssh2-sys patch provenance are summarized in docs/security.md.
Third-party license notices are in docs/THIRD_PARTY_NOTICES.txt.