An interactive shell to browse and operate one or more FortiManagers over
their JSON-RPC API: ls ADOMs/devices/objects, cd around, and cp
objects between ADOMs or even between two different FortiManagers — with
tab completion and readline-style keybindings.
The virtual file system is derived from FortiManager's own swagger specs
(fmgshell/fmg_jsonrpc_api/swagger/), so the available paths reflect the
real API surface rather than a hand-maintained list. ADOM/device/object
instances are always fetched live from the connected FortiManager(s).
uv venv .venv --python 3.14
uv pip install --python .venv/bin/python -e ..venv/bin/fmgshell(or source .venv/bin/activate first, then just fmgshell)
Check the installed version:
fmgshell --versionfmgshell can be logged into any number of FortiManagers at once — that's
what lets cp transfer objects between them, not just between ADOMs of the
same one. Every path starts with a connection name, e.g.
/fmg1/dvmdb/adom/root; you can browse the structure (ls, cd) without
being logged in at all, but reaching real data (ADOM names, objects, ...)
requires logging into that path's connection first.
Each login picks a connection name: by default the next unused fmgN
(fmg1, fmg2, ...), or anything you like via login ... as <name>.
While only one connection is configured, it's the implicit default
everywhere: a path that doesn't start with a connection name is assumed to
mean it (so single-FortiManager use never needs to spell it out), and
logout/status default their connection argument to it too. Once a
second connection is added, that default disappears and every command/path
must name a connection explicitly.
fmgshell:/> ls # (empty until you log in)
fmgshell:/> login -i <fmg1-ip-or-fqdn> -u <username> [-p <password>] [--port 443]
# named "fmg1" automatically
fmgshell:/> login -i <fmg2-ip-or-fqdn> -u <username>
# named "fmg2" automatically
fmgshell:/> login -i <fmg3-ip-or-fqdn> -u <username> as core
# named "core" explicitly
If -p is omitted, you'll be prompted for the password without echo.
Navigate and operate:
fmgshell:/> cd /dvmdb/adom # same as /fmg1/dvmdb/adom
fmgshell:/fmg1/dvmdb/adom> ls # real ADOM names from fmg1
fmgshell:/fmg1/dvmdb/adom> cd /pm/config/adom/<adom1>/obj/firewall/address
fmgshell:...address> ls # real address objects in adom1
fmgshell:...address> cat <name> # show the object's full JSON
fmgshell:...address> cp <name> <adom2>/<name>
# same FMG, same name, different ADOM
fmgshell:...address> cp <name> <adom2>/<newname>
# copy + rename in one step
fmgshell:...address> cp -v <name> fmg2/<adom>/<name>
# copy to the OTHER FortiManager
fmgshell:/> status # system status of the only connection; `status fmg2` if there's more than one
fmgshell:/> debug enable # print raw JSON-RPC requests/responses (all connections)
fmgshell:/> debug disable
fmgshell:/> debug # (or `debug status`) show whether debug is on, per connection
fmgshell:/> version # fmgshell version + FortiManager ADOM version in use
fmgshell:/> logout # logs out of the only connection; `logout fmg2` if there's more than one
fmgshell:/> quit
cp's destination is never the full structural path — just the identity
segments the source's own path is parametrized on, in the same order,
followed by the target name. The leading <fmg>/ may be omitted, which
keeps the copy on the source's own connection; give it explicitly (fmg1
or fmg2) to copy across FortiManagers. For ADOM-scoped objects the shape
is [<fmg>/]<adom>/<name>; for device/vdom-scoped objects (e.g.
/pm/config/device/{device}/vdom/{vdom}/...) it's
[<fmg>/]<device>/<vdom>/<name>:
fmgshell:...address> cp addr1 adom2/addr1 # same FMG, different ADOM
fmgshell:...address> cp addr1 adom2/addr1-copy # copy + rename, same FMG
fmgshell:...address> cp addr1 fmg2/adom2/addr1 # different FortiManager entirely
fmgshell:...authentication-rule> cp rule1 fgt2/root/rule1
# device/vdom-scoped object
Tab completion on the destination suggests fmg1/fmg2, then valid
ADOM/device/vdom names for each identity slot (queried live from whichever
connection you typed), and existing object names for the final one. Because
tab-completion can't reliably guess whether an omitted first part means
"connection" or "ADOM", completion always spells the connection out; typing
the command by hand still accepts the shorter form.
ls -l (or -al) shows a created/last-modified column, and by whom, when
FortiManager exposes that for the object type. For ADOMs/devices it comes
straight from dvmdb; for pm/config objects (addresses, policies, ...) it
requires the "extra info"/"no loadsub" get options (see this
writeup),
which ls -l sends automatically — plain ls/cd don't, to keep
navigation fast:
fmgshell:/> cd /fmg1/dvmdb/adom
fmgshell:/fmg1/dvmdb/adom> ls -al
adom1 created: 2024-03-09 17:00:00, os version: 7.2, workspace mode: workflow
global created: 2023-07-22 06:26:40, os version: 6.2, workspace mode: disabled
root created: 2023-11-14 23:13:20, os version: 7.4, workspace mode: normal
fmgshell:/> cd /fmg1/pm/config/adom/root/obj/firewall/address
fmgshell:...address> ls -al
addr1 created: 2023-04-13 19:51:15, modified: 2023-04-18 12:25:48, modified by: admin, version: 3
addr2 -
Tab completion works at every step (FMG connections, ADOMs, devices, object
names, sub-resources). The shell is built on cmd2/prompt_toolkit, so
standard emacs/readline keybindings work: Ctrl-A/Ctrl-E (line
start/end), Ctrl-R (reverse history search), up/down arrows for history,
etc. Command history persists across sessions in .fmgshell_history.
- Hardcoded to FortiManager version 74's swagger files; other versions aren't wired in yet.
- Object "identity field" detection uses a priority list (
name,policyid,seq, ...) that covers the common tables but isn't exhaustive. cponly stripsoidbefore re-adding the object; some object types may need more cleanup (e.g. UUID handling).cponly supports objects identified by a name (the common case); FMG's rare singleton config objects (no identity segment of their own, e.g. a per-ADOM "system dns" settings block) aren't copyable yet.- The
ls -al"workspace mode" label (disabled/normal/workflow) is a best-effort mapping of the rawworkspace_modeinteger — FortiManager's swagger spec doesn't document that enum. Unrecognized values show asmode <n>. - Not yet tested against a live FortiManager.