No description
  • Python 99.5%
  • Shell 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-15 08:57:13 +02:00
mktfarm Fix legacy RSA terminal access and serial Ctrl-D exit 2026-10-15 08:57:13 +02:00
tests Fix legacy RSA terminal access and serial Ctrl-D exit 2026-10-15 08:57:13 +02:00
.gitignore Manage Python and dependencies with uv and project virtualenv 2026-10-14 14:13:21 +02:00
.python-version Manage Python and dependencies with uv and project virtualenv 2026-10-14 14:13:21 +02:00
AGENTS.md Manage Python and dependencies with uv and project virtualenv 2026-10-14 14:13:21 +02:00
mkt Manage Python and dependencies with uv and project virtualenv 2026-10-14 14:13:21 +02:00
pyproject.toml Manage Python and dependencies with uv and project virtualenv 2026-10-14 14:13:21 +02:00
README.md Fix legacy RSA terminal access and serial Ctrl-D exit 2026-10-15 08:57:13 +02:00
uv.lock Manage Python and dependencies with uv and project virtualenv 2026-10-14 14:13:21 +02:00
VALIDATION.md Fix legacy RSA terminal access and serial Ctrl-D exit 2026-10-15 08:57:13 +02:00

MikroTik test farm

A standalone RouterOS CHR/QEMU lab in /root/mktfarm. Each named instance has its own disk, password, SSH port and captured mint state. The old /root/routeros-flagging lab is untouched. This farm starts with no instances.

Setup

Dependencies on this host are already installed. On a comparable Debian/Ubuntu host:

apt install qemu-system-x86 openssh-client

Install uv if needed, then create/sync the locked Python environment:

cd /root/mktfarm
uv sync --locked

pyproject.toml declares Python dependencies, uv.lock pins them, and .python-version selects Python 3.14. uv can download that interpreter if it is unavailable locally. .venv/ is ignored by Git and can be recreated with uv sync --locked. To intentionally update dependencies, run uv lock --upgrade, then uv sync --locked, test and commit the lockfile.

Run /root/mktfarm/mkt directly, or use the installed mktfarm command. The CLI uses uv run --locked with the project .venv and stores runtime data under state/, excluded from Git and restricted to the current user. RouterOS version downloads come from MikroTik's official CHR archive; checksums and URLs are recorded. The farm has no runtime dependency on the earlier lab.

Commands

mktfarm spawn ros6/6.49.7
mktfarm spawn ros7/7.19.6
mktfarm spawn custom/7.12.1 --port 2400 --password TestOnly-2026 --private
mktfarm list
mktfarm info ros6
mktfarm exec ros6 '/system resource print'
mktfarm terminal ros6
mktfarm terminal ros6 --serial
mktfarm stop ros6
mktfarm start ros6
mktfarm refresh ros6
mktfarm ssh ros6 off
mktfarm ssh ros6 on
mktfarm delete ros6

spawn NAME/VERSION [NAME/VERSION ...] downloads the exact v6/v7 version (including beta/rc releases), creates an independent disk, boots and provisions the admin password and SSH service, ensures a default gateway for remote replies, verifies the version and configured password, checks that empty and incorrect passwords are rejected, then captures a mint snapshot. Default credentials are admin / LabOnly-2026. Ports are allocated sequentially from 2300, skipping assigned or occupied ports. --port selects the first port; each subsequent router uses the next port in the range. Occupied ports fail for the affected router. Spawn options (--password, --private, --memory) apply to every router in the batch. --memory MB selects RAM (default 256 MB). Versions need to exist in MikroTik's archive; this command does not silently substitute a release. This backend uses CHR images for v6/v7 and does not support RouterOS v5. A v5 argument is rejected before the batch starts.

External SSH access is on by default: QEMU listens on 0.0.0.0:PORT. Connect from another machine with ssh -p PORT admin@192.168.5.46 while that address belongs to this host. Outbound NAT is enabled. ssh NAME off changes the listener to 127.0.0.1; local exec and terminals still work. ssh NAME on restores all-interface access. The setting persists through stop/start and refresh. External firewall/routing rules are outside this CLI.

Parallel spawn example:

mktfarm spawn router1/6.49.7 router2/7.19.6 router3/6.48.1 --jobs 2

Parallel provisioning overlaps downloads, guest boot and SSH setup. A small limit is useful because each guest also consumes host CPU and RAM, and simultaneous mint snapshots add disk activity. The default is two workers; increase it when the host has spare capacity. Downloads of the same version share a per-version lock, so one cached image is created safely. All name/port reservations happen before workers launch, under the farm lock. The farm lock remains held until the batch finishes, protecting against another CLI mutating those instances mid-spawn. On Ctrl+C, queued spawns are cancelled and active spawns finish safely; instance files/reservations remain available for inspection or deletion.

All instance commands except terminal accept multiple routers. list with no names lists the whole farm; supplied names filter the output. exec takes router names followed by one quoted command; ssh takes router names followed by on or off:

mktfarm spawn router1/6.49.7 router2/7.19.6
mktfarm list router1 router2
mktfarm info router1 router2
mktfarm stop router1 router2
mktfarm start router1 router2
mktfarm refresh router1 router2
mktfarm exec router1 router2 '/system resource print'
mktfarm ssh router1 router2 off
mktfarm ssh router1 router2 on
mktfarm delete router1 router2

Spawn reserves names and ports in the supplied order, then provisions up to two routers concurrently by default. Use --jobs N to adjust concurrency or --jobs 1 for sequential spawning. Other batch operations run sequentially in the supplied order. Spawn completion messages can appear in completion order. An individual router failure is reported by name, remaining routers are attempted, and the command exits with status 1 if any fail. Successful operations are not rolled back. Invalid argument syntax, duplicate router names and out-of-range explicit port ranges are rejected before operations begin (status 2). Terminal continues to accept exactly one router.

exec NAME [NAME ...] 'COMMAND' runs the same RouterOS command on each router and labels multi-router output. Quote the whole command to prevent the host shell from interpreting RouterOS syntax. terminal opens native OpenSSH and displays the password to enter. Its command-line options append legacy ssh-rsa host-key support for older RouterOS guests while keeping modern defaults preferred; this does not change global SSH configuration. terminal --serial opens the QEMU serial console; press Ctrl+D to send logout/EOF to RouterOS and close the local terminal, or Ctrl+] to detach without sending logout. Both shortcuts close the local terminal even when RouterOS is at its login prompt. A serial login uses admin and the instance password. Only one serial terminal should be attached at a time.

stop saves the current disk + RAM/device state and quits QEMU. start resumes that saved state and verifies SSH. This preserves experimental changes and avoids the disk-only persistence issue observed on some v6 versions. Stopping is a hypervisor suspend operation, not a RouterOS poweroff. If QEMU was terminated outside this CLI, start refuses to reuse a stale resume snapshot; use refresh to recover to mint.

refresh stops that instance, replaces its disk with the mint disk and restores mint RAM/device state. All changes since provisioning are discarded, including users, configuration, files and logs created during experiments. The configured SSH binding is preserved. Mint means the official installation plus lab password/SSH/gateway setup, not an untouched factory image. Guest clock and uptime resume from the captured snapshot. Snapshots require the same QEMU version; checksums are verified before refresh.

delete stops the instance and permanently removes its disks, snapshots, credentials and logs. The shared download cache remains. No confirmation prompt is required for an explicit delete/refresh command.

Storage and troubleshooting

  • state/images/VERSION/: independent original-image cache and provenance.
  • state/instances/NAME/instance.json: credentials, configured port, MAC, initial version and provisioning status.
  • disk.img: current writable disk; mint/: provisioning snapshot; resume/: latest stopped snapshot.
  • qemu.log, bootstrap.log, mint-resource.txt: startup and provisioning evidence.

list reports the original provisioned version; after an in-guest upgrade, use exec NAME '/system resource print' for the current version. Mint refresh always returns to the provisioned version. A failed spawn preserves its instance files for diagnosis; delete it before retrying the same name. Operations are serialized by a farm lock and refuse conflicting lifecycle changes. The CLI identifies disk owners using actual QEMU processes, not stale PID files.

Source changes are committed in Git. Runtime images and credentials are never committed.

Validation

uv run --locked python -m unittest discover -s tests -v

Lifecycle verification uses disposable real v6 and v7 guests, including configuration mutation/refresh, stop/start, password authentication, SSH binding changes and deletion. See VALIDATION.md for the recorded results.