- Python 60.2%
- Shell 36.4%
- CSS 2.5%
- Dockerfile 0.9%
New manifest.py builds an inventory (version -> arch -> packages+sizes, channel heads) from what's on disk; the sweep writes it atomically + human-readable on the final pass, alongside /routeros/index.schema.json (bundled copy of packagemini-schema.json, drift-tested). For automated consumers, starting with rosbackup-ng package discovery. Tests + doc/PACKAGE-INDEX.md. Naming: data = index.json (per the schema's own title), contract = packagemini-schema.json / index.schema.json — not 'schema.json' (which would conflate data with contract). |
||
|---|---|---|
| .forgejo/workflows | ||
| certs | ||
| config | ||
| doc | ||
| src | ||
| .gitignore | ||
| CHANGELOG.md | ||
| DISCLAIMER.md | ||
| docker-compose.yml | ||
| LICENSE | ||
| NOTICE | ||
| packagemini-schema.json | ||
| README.md | ||
| VERSION.md | ||
![]()
rosbackup-packagemini
Self-hosted RouterOS firmware mirror
⚠️ Under active development — feature-incomplete until v1.0.
rosbackup-packagemini is usable today, but behavior and configuration may change between releases until v1.0 arrives.
A self-hosted RouterOS firmware mirror: it periodically fetches .npk packages and bundle zips
from MikroTik's official servers, serves them over HTTP/HTTPS via Caddy, and generates a themed
web UI plus an Atom feed. Routers fetch firmware from your mirror instead of
download.mikrotik.com — just swap the hostname.
Standalone (no dependency on rosbackup-ng), rootless, and self-hardening, with a one-command Rocky Linux 9 installer that doubles as the manager.

The themed web UI — mirrored RouterOS versions grouped by channel (stable / long-term / testing / development), with release dates, per-file type chips, and a built-in legend. Point a router at it and just swap the hostname.
Features
- Mirrors RouterOS firmware — choose channels (stable / long-term / testing / development),
per-channel retention, architectures, and packages (
main-only/ named /all-extras). - Drop-in fetch paths —
/routeros/<version>/…, plus CHR images, netinstall (Windows + Linux), WinBox, and an Atom feed (/feed.xml). - Atomic downloads — files are never half-served; in-progress versions stay hidden until complete.
- Scales to large fleets — parallel upstream fetching, plus host + Caddy tuning auto-scaled to the box (connection backlogs, conntrack, socket buffers, and file-descriptor limits sized to RAM / CPU cores) to serve firmware to hundreds–thousands of routers concurrently.
- Caddy front door — Let's Encrypt TLS via DNS-01 (deSEC, works on isolated hosts), rate limiting, IP allow/deny, optional Basic Auth, security headers.
- In-place, version-safe updates —
deploy.sh updatefetches all release tags and lets you pick a version (default: latest), rebuilds, and refuses downgrades;--forcereinstalls the current version. - Monitoring — JSON access logs, an optional GoAccess board (on its own port), monthly reports.
- Rootless & hardened — containers run as
uid/gid 1000; the installer hardens the host (key-only SSH, fail2ban incl. a web-flood jail, automatic security updates, sysctl, firewall, tight file perms). Seedoc/SYSTEM-HARDENING.md.
Requirements
- A Rocky Linux 9 host (also RHEL / AlmaLinux / CentOS Stream 9). The installer sets up Docker.
- For HTTPS: a public FQDN and a deSEC API token (DNS-01).
- Disk: ~30–50 GB with the defaults — see disk sizing.
Install
Clone the repository onto the host and run src/deploy.sh as root. It installs Docker, hardens the
host, opens the firewall, generates config, and brings up the stack. Idempotent.
# on the server (as root):
dnf install -y git
git clone https://git.jdneer.com/jd/rosbackup-packagemini.git /opt/packagemini
cd /opt/packagemini
sudo ./src/deploy.sh # first run creates packagemini.yml from the example and stops
$EDITOR packagemini.yml # set public_base_url, channels, architectures, …
sudo ./src/deploy.sh # builds + starts the mirror
Cloning (rather than copying) also means ./src/deploy.sh status shows the exact commit, and
sudo ./src/deploy.sh update upgrades the mirror in place (pick a version; latest by default).
Install a specific version with --ref <tag|branch> (default: the latest release tag).
Everything in one go (mirror + GoAccess monitor + lazydocker TUI + sysadmin tools):
sudo ./src/deploy.sh --all
# à la carte: --with-monitor --with-lazydocker --with-tools --docker-only --no-harden --no-tune --ref <tag|branch>
--all (and a plain install) apply system hardening and performance tuning by default — opt out
per stage with --no-harden / --no-tune. See doc/SYSTEM-HARDENING.md and
doc/PERFORMANCE-TUNING.md.
The sync worker starts immediately and repeats on sync_interval (default 6 h); the web container
serves files as soon as the first sweep writes them. The run is non-interactive once configured
(preset any first-run answer via env for unattended installs — see ./src/deploy.sh --help).
Manage
src/deploy.sh is also the manager — run it from anywhere (it operates on the repo root):
sudo ./src/deploy.sh status # health, security, mirror config, content, sync
sudo ./src/deploy.sh start | stop | restart # compose lifecycle
sudo ./src/deploy.sh logs [service] # follow logs
sudo ./src/deploy.sh refresh # trigger a sync sweep now
sudo ./src/deploy.sh update [tag|branch] # upgrade in place (pick a version; default: latest)
sudo ./src/deploy.sh --force # rebuild/reinstall the current version
sudo ./src/deploy.sh harden # toggle security hardening measures (interactive)
sudo ./src/deploy.sh tune [--remove] # apply / remove the host performance profile (scaled to RAM/cores)
sudo ./src/deploy.sh set-fqdn <fqdn> # migrate to a new hostname (cert re-issues)
sudo ./src/deploy.sh set-timezone <zone> # host + release-date timezone
sudo ./src/deploy.sh set-cert # import/replace a manual TLS cert (renewals)
sudo ./src/deploy.sh set-mirror-auth on|off # HTTP basic auth on the mirror
sudo ./src/deploy.sh set-operator # footer + User-Agent operator name/email (public)
sudo ./src/deploy.sh open-goaccess # print the GoAccess board URL
sudo ./src/deploy.sh clear-config [data] # uninstall / reconfigure
Full workflow + every command: doc/MANAGE.md (or ./src/deploy.sh --help).
Point a router at the mirror
/tool fetch url="https://<mirror>/routeros/<ver>/routeros-<ver>.npk" mode=https
The listing at https://<mirror>/routeros/<ver>/ shows every file for that version.
Configure
Settings live in packagemini.yml (copy config/packagemini.example.yml); secrets go in .env
(config/env.example), managed by deploy.sh. Every option is annotated in the example file.
Backup/restore is just copying the host-bound data/, logs/, reports/ directories.
Documentation
- doc/MANAGE.md — day-to-day management workflow + every
deploy.shcommand. - doc/DESIGN.md — architecture, full configuration reference, package semantics, TLS, rate limiting, timezones, monitoring, disk sizing.
- doc/SYSTEM-HARDENING.md — host hardening + fail2ban/firewall ops.
- doc/PERFORMANCE-TUNING.md — kernel/Caddy tuning scaled to the host (
tune,--no-tune). - doc/REAL-TIME-MONITORING.md — the optional GoAccess board + live WebSocket.
- doc/PACKAGE-INDEX.md — machine-readable
/routeros/index.jsoninventory for automated consumers. - doc/MANUAL-X509-CERT.md — import your own TLS certificate (PEM).
- CHANGELOG.md · VERSION.md
Repository layout
docker-compose.yml # the stack (build contexts point at src/*)
src/ deploy.sh, sync/, web/, monitor/ # installer/manager + container build contexts
config/ packagemini.example.yml, env.example
doc/ DESIGN.md, MANAGE.md, SYSTEM-HARDENING.md, PERFORMANCE-TUNING.md, UPDATES.md, MANUAL-X509-CERT.md, TODO.md
Runtime files (packagemini.yml, .env, data/, logs/, reports/) live at the repo root and
are gitignored; the live .env stays there so docker-compose auto-loads it.
License
Apache-2.0 — see LICENSE, NOTICE, and DISCLAIMER.md.