-
v1.0.0
Stablereleased this
2026-09-18 14:02:01 +00:00 | 0 commits to master since this releaseFirst stable release. The configuration format and the
/routeros/index.jsonschema are now covered by
Semantic Versioning: breaking changes only arrive with a new major version.Fixed
- Truncated downloads were stored and served as complete files. When upstream dropped a connection
mid-transfer the body simply ended early — no error — and the short file was renamed into place.
MikroTik's CDN cuts long-running connections, so on a slow link this hit most large files: main
routeros-*.npkpackages, CHR images, netinstall archives, WinBox, and theall_packagesbundles. A
truncated bundle failed to unpack on every sweep (bundle <ver>/<arch> failed: File is not a zip file),
so that arch never completed and stayed hidden — e.g.arm/arm64and theirzerotierpackage
missing from a version — while being re-downloaded every sweep. Downloads are now verified against
Content-Lengthand resumed with HTTPRangefor as long as each request adds bytes (giving up
after 3 consecutive requests without progress); nothing incomplete is ever written to the mirror.
404s (package not built for an arch) are still not retried. refreshleft the sync worker down after the next reboot. It sent SIGHUP withdocker compose kill,
and Docker marks a container as manually stopped ondocker killwith any signal; the
unless-stoppedrestart policy then skippedsyncat the next daemon start — e.g. the automatic
reboot after security updates — whilewebandgoaccesscame back.refreshnow signals the worker
from inside the container. If you usedrefreshon an earlier version, runsudo ./src/deploy.sh start
once (or checkstatus) after the next reboot.
Added
- Integrity pass —
deploy.sh verify(python -m packagemini --verify). Compares every mirrored
file's size with upstream'sContent-Length; a mismatch removes the file and reopens its arch
(.complete/.size/ "download all" zip) so the next sweep re-fetches it. Files whose upstream
length can't be determined are left alone. The worker runs the pass once automatically on first
start per mirror root (.verifiedmarker), so mirrors filled by earlier releases repair themselves
on upgrade. Upgrade note: expect affected arches to disappear from the listings and
index.jsonuntil they have been re-fetched — on a slow link that can take hours.
Changed
- Terminology: "rootless" → "non-root containers" throughout the docs, README badge, compose
comments anddeploy.sh status. The containers run asuid:gid 1000, but the Docker daemon runs as
root with no user-namespace remapping, which is not what "rootless Docker" means. No behaviour change;
DESIGN.md now states the actual boundary. - README: removed the "under active development — feature-incomplete until v1.0" notice.
- Default branch renamed
main→master. The CI trigger,deploy.sh's no-tags fallback and the
docs follow. Installs that track the branch tip must switch once:sudo ./src/deploy.sh update master
(installs on release tags — the default — are unaffected).
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
0 downloads
- Truncated downloads were stored and served as complete files. When upstream dropped a connection
-
v0.13.0
Stablereleased this
2026-07-03 12:55:22 +00:00 | 6 commits to master since this releaseAdded
- Machine-readable package index. Every sweep now writes
/routeros/index.json— a human-readable
(2-space-indented), atomically-written inventory of every RouterOS.npkon the mirror (version → arch →
packages with sizes, plus channel heads) for automated consumers, starting with rosbackup-ng package
discovery. It lists only what's actually on disk (retention-pruned versions drop out); additive fields
won't bump the top-levelschemaversion. The JSON Schema is served alongside at
/routeros/index.schema.json(canonical:packagemini-schema.json, drift-tested). Both are served
behind the mirror's optional basic-auth and revalidated (not immutably cached). See
doc/PACKAGE-INDEX.md.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Machine-readable package index. Every sweep now writes
-
v0.12.0
Stablereleased this
2026-07-03 12:29:48 +00:00 | 7 commits to master since this releaseSecurity hardening sweep — a 4-agent audit (deploy.sh, Python worker, infra/web) plus a docs↔code
security-claims cross-check. Fixes below, ranked by severity.Security
- CRITICAL — root RCE via
set-fqdn. An unvalidated FQDN (set-fqdn <arg>/PM_FQDN) was spliced
into ased -iscript; a|or newline could trigger GNU sed'secommand → arbitrary command
execution as root (and YAML injection intopackagemini.yml). FQDNs are now validated against a
hostname charset (_check_fqdn) before they reachsed/ the config writer. - HIGH — GoAccess board exposure. The board's
:7443listener — and its unauthenticated/ws
WebSocket — inherited none of the mirror's IPallow/denygate, rate-limit, or headers, so an
allowlist didn't cover the board and basic-auth brute-force was unthrottled. The board block now applies
the same allow/deny (covering/ws), a rate-limit, and headers; the raw log archive under
/srv/report/archiveis hidden from browsing. - HIGH — path traversal.
version/arch/pintokens reachedmkdirbefore validation. A new
safe_token()guard gates every token before it builds a filesystem path or URL — at discovery
(sources.py), at config load (architectures/pinrejected), and defensively beforemkdir. - MEDIUM — Caddyfile injection.
public_base_url,tls_*, usernames,*_hash_env, and the server
timeouts are validated at config load (reject braces/tabs/newlines; env-name + username charsets),
matching the existingrate/ CIDR validation. - MEDIUM — secret & permission exposure. Passwords are hashed via stdin (no longer visible in
ps/ argv);.envand the imported TLS key are written under a private umask (no world-readable
window); the public-facingwebcontainer getsno-new-privileges(matching sync/goaccess). - MEDIUM — resource DoS. Response reads are capped (8 MiB text / 4 GiB artifact) so a misbehaving
upstream can't exhaust worker memory; the main npk is size-checked after download (verify_npkwired
into the sweep) and re-fetched if it fails. - LOW / defense-in-depth. git refs rejected if they start with
-or contain metacharacters; strict
CSP on the (fully self-contained) mirror site; the feed usesquoteattrfor XML attributes; the
SSH key-only guard now requires a non-emptyauthorized_keys(avoids lockout); marker dotfiles
(.complete/.size/.absent) hidden from the file server.
Docs
- Corrected security claims that over-promised:
sources.archive_backfillis now marked not yet
implemented (it was a silent no-op stub), and the false "checks NPK magic bytes" claim is replaced
with the actual behavior (TLS-verified fetch + a size sanity check; no cryptographic verification).
SYSTEM-HARDENING documents the7443/tcpfirewall opening and the board's public-listener caveat. - New doc/REAL-TIME-MONITORING.md — the GoAccess board + live-update
WebSocket architecture, with a dashboard screenshot.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- CRITICAL — root RCE via
-
v0.11.3
Stablereleased this
2026-07-03 10:12:23 +00:00 | 8 commits to master since this releaseFixed
- Per-channel version count could drift above
keep_last.seen.json[channel]accumulated targeted
versions as an ever-growing union and was never capped, so churny pre-release channels (testing = rc,
development = beta) reported e.g. 11 versions understatus → Content → by channeleven with
keep_last: 10— andseen.jsongrew unbounded. It's now capped to the newestkeep_lastper channel
after the union (which still guards against a sweep that momentarily under-discovers). On-disk retention
was already correct; this was a reporting/growth drift.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
2 downloads
- Per-channel version count could drift above
-
v0.11.2
Stablereleased this
2026-07-03 09:44:41 +00:00 | 9 commits to master since this releaseFixed
statusswappiness hint no longer repeats the value (read "0 0 = …"); now "lower = keep hot firmware
in RAM cache, avoid swapping out".
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
v0.11.1
Stablereleased this
2026-07-03 09:42:38 +00:00 | 10 commits to master since this releaseChanged
statusPerformance section: each value now carries a short plain-language explanation (what the
connection backlog / conntrack / congestion-control / swappiness number actually means) so the section
is self-explanatory instead of opaque numbers.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
v0.11.0
Stablereleased this
2026-07-03 09:35:29 +00:00 | 11 commits to master since this releaseCodebase audit sweep (5 parallel agents: Python source, tests,
deploy.sh, docs, infra) — fixes below.Added
- Continuous CI — a
testworkflow runs the suite on every push tomain, not only on tag releases
(regressions between versions were previously unseen until the next tag). webwaits on asynchealthcheck (Caddyfile present) viadepends_on: condition: service_healthy,
so it no longer crash-loops against a missing config on first boot.- More tests — config validation (rate / sync_interval / packages), MikroTik URL-builder token guards,
HTML-escaping of operator/changelog fields, and asset cache-busting; plus an autouse fixture that resets
site.pyglobals between tests (removes order-dependent flakiness).
Fixed
- config:
web.rate_limit.rateis now format-validated at load — a typo like600/mused to pass
silently and render an invalid Caddyfile (written before downloads, so it could wedge first boot). - sweep: a version pinned into several channels is downloaded once — it was submitted to the pool
per-channel, causing redundant CDN fetches and a race on the shared.partfile. - deploy.sh: operator name/email and GoAccess/basic-auth usernames are escaped/validated before
being written intopackagemini.yml(a"///|could corrupt it);cmd_statusnowexports
LC_ALL(was an inertlocal) and restoresset -e; the three divergent "is GoAccess enabled" greps
are unified into one_goaccess_enabledhelper; the dnf-automaticsed/mkdirwrites are guarded;
set-certwarns whenopensslis missing; deadhf_perms_stateremoved; thethenkeyword-variable
renamed. - docs: corrected the
reboot_commandbehaviour in UPDATES.md (packagemini always overwrites it,
not "kept if present"); DESIGN.md GoAccess row (own port +portkey); "five → six" hardening measures
in SYSTEM-HARDENING.md; marked shipped TODO items (footer toggle, reports-over-web) done; fixed stale
/goaccess/…URLs in the compose file,env.example, and docs; documentedPM_GOACCESS_PORT.
Changed
- Docstrings added to
run_once,render_caddyfile,classify,same_bytes;Configdataclass type
hints aligned (tuple[str, ...]/dict[str, ChannelCfg]);.coveragegit-ignored.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Continuous CI — a
-
released this
2026-07-03 06:56:43 +00:00 | 12 commits to master since this releaseChanged
--help: moved--forcefrom MANAGE to INSTALL so each section is stylistically uniform (INSTALL =
--flags, MANAGE = bare-word commands).--forceis a reinstall/rebuild of the current version, so it
belongs with the install flags; behaviour is unchanged (deploy.sh --forcestill works).
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
released this
2026-07-03 06:45:05 +00:00 | 13 commits to master since this releaseFixed
- Swept the last
harden↔performance crossed wires after the 0.10.1 split (two-agent audit of the
script + all docs): thestatusdefault-profile hint, the comment baked into the generated
98-packagemini-performance.conf, thetunecode comments, andconfig/packagemini.example.ymlnow
all point attune/tune --remove(neverharden). Marked the now-shipped performance-tuning item
done in TODO, and addedPERFORMANCE-TUNING.md/MANAGE.mdto the README + DESIGN doc indexes.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Swept the last
-
released this
2026-07-03 06:38:46 +00:00 | 14 commits to master since this releaseFixed
statusPerformance line pointed athardento toggle tuning — now points attune --remove(harden
no longer touches performance).
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download