Migrating from v0.4 to v0.6¶
Beetroot v0.6 is the stability-and-cleanup release on the road to v1.0:
it freezes the CLI and Python API after a round of intentional pre-1.0
breaking changes. It is the first hop since v0.4 that touches a
user-visible contract — v0.5 shipped no schema change and no breaking
changes (api_version stayed 3, v0.4 instances and registries worked
unchanged), so upgrading from v0.5 is the same as upgrading from v0.4 and
this guide covers both.
If you'd rather scan the changes than walk through them, the headline
list lives at the top of
CHANGELOG.md
under Breaking changes. This page expands each bullet into the exact
command or edit you run.
1. Schema bump: api_version: 3 → api_version: 4 (non-additive)¶
v0.6 raised SUPPORTED_API_VERSION to 4 (the current release is 6; see
CHANGELOG.md),
and unlike the additive 2 → 3 hop in v0.4, this one renames a field, so
it is not a silent auto-bump.
The top-level stealth: key is gone; its only populated field,
stealth.denylist, moved under magisk.denylist.
A beetroot.yaml that still contains a stealth: block is rejected at
load with a migration hint — it is not silently dropped. Edit it:
# Before (api_version 3)
api_version: 3
stealth:
denylist:
- com.example.target
# After (api_version 4)
api_version: 4
magisk:
denylist:
- com.example.target
YAMLs that merely omit api_version, or pin 1 / 2 / 3 without a
stealth: block, auto-bump on load to the current api_version (now 6;
the additive path), so a config that never used stealth.denylist needs no
edit. Persist the bump with beetroot apply <name>.
2. The env verb was removed¶
beetroot env (and env --all) is gone. Use the structured endpoints
that replaced it:
# Before
beetroot env alpha
beetroot env --all alpha
# After — machine-readable instance data as JSON
beetroot status alpha --json
For interactive use, beetroot shell <name> and beetroot frida <name>
cover what the human-readable env output was used for. See
status in the CLI reference.
3. Second Frida port renamed to frida_control¶
The second of Frida's two consecutive ports — its RPC/command channel —
is now spelled frida_control in beetroot.yaml, and the rendered
compose env var is FRIDA_PORT_CONTROL. The data port is unchanged.
Existing instances re-render the new env var on the next
beetroot apply <name>. If you parse the generated .env directly,
update your tooling to read FRIDA_PORT_CONTROL. The stride-of-10 port
layout itself is unchanged — see Port allocation.
4. restore --as → --name¶
beetroot restore now takes --name for the target instance name. The
old --as is kept as a hidden alias for one release and will be
removed, so update scripts now:
# Before
beetroot restore alpha.tar.zst --as alpha-fork
# After
beetroot restore alpha.tar.zst --name alpha-fork
5. DeviceBackend Protocol redesigned (programmatic users)¶
Third-party backends are now a first-class, stable contract. If you ship a custom backend, the signatures changed:
frida_clitakes aSequence(was positional*args).install_frida(version=None)— version is now an explicit keyword.shell(args=...)— command passed as a keywordSequence.from_metatakes aBackendConfigBase.- Capabilities are opt-in sub-protocols —
Lifecycle/ModuleInstaller/HealthCheckable/Snapshottable. A backend implements only what it supports rather than stubbing the whole surface.
See Adding a backend for the current ~30-LOC recipe against the redesigned Protocol.
6. Manager and registry API changes (programmatic users)¶
# Before
from beetroot import Manager, registry
instances = Manager.list()
registry.add(name, absolute_path=path, index=idx)
# After
from beetroot import Manager, registry
instances = Manager.list_instances() # renamed
all_backends = Manager.all() # new — resolved backends
backend = Manager.get(name) # now returns a resolved backend
index = registry.add_allocating(name, backend=...) # registry.add removed
Instance.destroy(yes=False) now raises instead of prompting — the
confirmation prompt moved to the CLI layer, so the OOP API never blocks on
stdin. Pass yes=True (or go through beetroot destroy, which prompts)
to tear an instance down programmatically.
7. Behaviour fixes you may notice¶
These aren't config edits, but they change observable behaviour:
- A sha256 mismatch on a downloaded frida-server or module zip no longer leaves a poisoned cache entry that re-fails forever; the bad file is discarded and re-fetched.
- Adopted adb devices' Frida ports are now counted in cross-backend
collision detection, and
statusreports an adb device's real allocated Frida endpoint (was hardcodedlocalhost:27042). up/down/restart --allskip non-container backends instead of aborting the whole batch.beetroot shell <name> -c '<cmd>'now passes the command through.ls --jsonno longer prints advisories to stdout.
8. Verify with beetroot ls + beetroot doctor¶
NAME KIND IDX ADB FRIDA STATUS PATH
alpha redroid 0 localhost:5555 localhost:27042 running /home/you/alpha
Confirms magisk.zygisk = 1, the GMS denylist enrolment, and Frida
reachability survived the upgrade. If doctor flags the denylist, double
-check that you moved stealth.denylist to magisk.denylist (§1) and ran
beetroot apply alpha.
Troubleshooting¶
ValidationError / migration hint mentioning stealth: at load.
§1 — your YAML still has a top-level stealth: block. Move
stealth.denylist under magisk.denylist and set api_version: 6.
No such command 'env'. §2 — the env verb was removed. Use
beetroot status <name> --json for machine-readable data.
Scripts reading FRIDA_PORT from the generated .env see nothing for
the control port. §3 — the control port env var is now
FRIDA_PORT_CONTROL. Re-run beetroot apply <name> to re-render .env.
AttributeError: module 'beetroot.registry' has no attribute 'add' /
Manager.list. §6 — registry.add → registry.add_allocating,
Manager.list → Manager.list_instances.