Skip to content

CLI & scripts

Taranac is administered almost entirely from its web UI. The command line exists for the jobs the UI cannot do for itself: bringing the stack up, recovering an admin who is locked out of the UI, building and operating an HA cluster, collecting diagnostics, rotating the master key, updating to a new release, and disaster recovery. All of it runs from the deployment bundle directory on the host (the folder created when you unpacked the release and ran ./install.sh), through one thin wrapper named taranac.

You do not need to know Docker to operate Taranac, but it helps to know that the wrapper is just a shortcut around docker compose: anything you can do with docker compose you can do by typing it after ./taranac.

The ./taranac wrapper routes its convenience subcommands to its own handlers and passes everything else straight through to docker compose.

./taranac has two kinds of commands:

  • Convenience subcommands it interprets itself — unlock, reset-password, create-admin, installation-id, cluster, diagnose, restore, rotate-key, reset, version, update.
  • Everything else is passed straight through to docker compose, so ./taranac ps, ./taranac logs, ./taranac up -d, ./taranac down, ./taranac restart <service> all behave exactly like the compose commands of the same name.
Terminal window
./taranac ps # service status
./taranac logs -f api # follow one service's logs
./taranac restart edge # restart the edge (e.g. to apply a new TLS cert)
./taranac up -d # start the stack (detached)
./taranac down # stop everything (data in volumes is preserved)

Most of them are a docker compose exec into the api container. Several are not: recovery, key rotation, updates, diagnostics and cluster building have to rewrite host files, capture packets on the host’s interfaces or recreate containers — none of which a container can do for itself — so they live in their own scripts next to the wrapper.

SubcommandWhere it runs
unlock, reset-password, create-admin, installation-id, resetinside the api container
cluster status, verify, connectivity, switchover, join-token, register, revoke, decommission, manifest-*inside the api container (app.scripts.cluster)
cluster init, add-witness, add-node, synccluster-wizard.sh on the host
cluster remove-node, cluster detachcluster-remove.sh on the host
diagnosediagnose.sh--ha re-execs diagnose-ha.sh
restoretaranac-restore.sh
rotate-keyrotate-key.sh
version, updatetaranac-update.sh

If one of those scripts is missing from the bundle the wrapper says so and tells you to run ./taranac update once — it never falls through to something else.

The same bundle is unpacked on a full Taranac node, on an HA witness and on a standalone NCM collector — that is how those hosts get their own join scripts, and it puts this wrapper on all three. Since 1.2.7 the wrapper resolves which kind of host it is on first and drives that host’s compose file, so ./taranac ps on a witness shows the arbiter’s etcd rather than a product stack that was never meant to run there.

On a witness or a collector, the commands that need the database or the API — cluster …, unlock, reset-password, create-admin, installation-id, reset — are refused with the reason and a list of what can be done on that host. ./taranac diagnose --ha is deliberately allowed on the witness: an HA fault is usually a disagreement between nodes, and support needs the arbiter’s side of it. On a witness, down / stop / kill also print what stopping the arbiter costs you in votes.

These three scripts run once per host, in order, and are the only thing you run before the platform is up. After that, day-to-day work is the wrapper above.

ScriptRun asWhat it does
sudo bash bootstrap.shrootInstalls Docker Engine + the compose plugin and adds your user to the docker group. Idempotent — skips anything already present.
./install.shyour userFirst run: asks for your domain and admin details, generates every secret, writes .env, pulls images, starts the stack, and prints the admin login.
./taranac restart edgeyour userApplies a trusted TLS certificate after you drop tls.crt + tls.key into config/tls/.

install.sh takes exactly two options: --no-start, which writes the configuration but does not pull or start anything (used to prepare a node that will join an HA cluster, since a joining node must have an empty data volume), and -h / --help. Anything else is refused by name.

install.sh is also the upgrade path for the images: re-running it when .env already exists never regenerates secrets — it just pulls and restarts. For a full release upgrade (images and bundle files) use ./taranac update, described below.

Satellite hosts have their own join script

Section titled “Satellite hosts have their own join script”

A host that is not a full Taranac node — an HA witness, a standalone NCM collector, a remote captive portal — is brought online by one command that enrolls it against the core. Each of them takes -h / --help.

ScriptEnroll withAlso takes
witness-join.sh--broker <host>:9443 --token <secret> --pin sha256:<fp>--env-file <path> (default ./.env) · --print-auth (print the derived auth header and exit — a diagnostic for “invalid credentials”)
collector-join.sh--core <https url> --enrollment-token <secret>--ca-file <path> | --trust-any · --collector-id <label> · --poll-interval <sec> · --status · --reset-trust · --update (with --check, --version <X> or --from <image>.tar) · --uninstall (with --force to discard a non-empty outbox)
portal-join.sh--core <https url> --token <portal-token>--ca-file <path> | --trust-any · --dir <dir> · --status · --reset-trust · --uninstall

A witness pins the join channel by a fingerprint the primary prints on its console — not trust-on-first-use — and the normal way to get all three arguments is to let ./taranac cluster add-witness print the command for you. A collector is moved between versions with ./collector-join.sh --update, never with ./taranac update: a collector has to match the version its core runs, which is not the newest release while you upgrade hosts one at a time.

The reason an operator reaches for the CLI most often is being locked out of the web UI. Taranac locks an account after a number of failed logins (MAX_LOGIN_ATTEMPTS, 5 by default) and the lock does not auto-expire — common on an internet-facing host once someone starts guessing passwords. Since the lock blocks the UI, recovery has to happen from the server console.

Each of these runs the corresponding tool inside the api container.

Terminal window
./taranac unlock # list currently locked accounts
./taranac unlock admin # unlock one account by username
./taranac unlock --all # unlock every locked account

Pass either a username or --all, not both.

Terminal window
./taranac reset-password admin # reset, print a new random password
./taranac reset-password admin --password 'S3cret!' # set an exact password
./taranac reset-password admin --no-force-change # don't force a change at next login

By default the account is required to change the password at the next login.

When no admin login works at all, create-admin is a create-or-repair tool: it creates the admin if missing, or repairs/re-enables and re-passwords an existing one.

Terminal window
./taranac create-admin # use INITIAL_ADMIN_* from .env, print password
./taranac create-admin opsadmin --email ops@example.com
./taranac create-admin admin --password 'My$tr0ngPass'

If --password is omitted a strong password is generated and printed; if the username is omitted it falls back to INITIAL_ADMIN_USERNAME (or admin).

Two subcommands support the Pro edition. installation-id only reads. cluster spans everything from a status read to building a cluster and evicting a node from it, so each destructive verb carries its own guard — the safe ones (status, verify without --rehearse, connectivity, manifest-fetch, manifest-info) can be run at any time. Both are refused on a witness or a collector host.

Terminal window
./taranac installation-id # print this install's immutable installation ID

The installation ID is the value a Pro license binds to. Send it to TaranacLabs out-of-band to have a signed .lic issued, then upload that file under Settings → Licensing. The same ID is shown on the Licensing page in the UI. See Licensing & editions for the full flow.

Since 1.2.3 cluster is the whole HA interface: it builds the cluster as well as operating it. You never hand-edit .env on each machine, copy TLS material over scp or carry the master key by hand — and you never drive ha-convert.sh / ha-join.sh yourself, though they are still in the bundle because the wizard calls them.

./taranac cluster with no arguments prints one help text covering both families, and every subcommand takes its own --help. This page is the syntax; the procedure, the ordering rules and what each command proves are on High availability.

Terminal window
./taranac cluster init --witness 10.0.0.3=wit-01 --node 10.0.0.2=node-02
./taranac cluster add-witness --address 10.0.0.3
./taranac cluster add-node --address 10.0.0.2
./taranac cluster status

Building the cluster — run on the host holding the primary role. These rewrite this host’s .env and recreate containers, so they run on the host, not in a container.

SubcommandOptions
init--cluster NAME · --address ADDR · --name NAME (default <cluster>-node-1) · --witness ADDR[=NAME] · --node ADDR[=NAME] (repeatable) · --pg-allow-cidr LIST (default: the /24 of each address) · --force (recreate config/cluster.yml, discarding its skip: section) · --yes
add-witness--address ADDR (required) · --name NAME · --port N (9443) · --timeout N (1800 s) · --wait-seconds N (120) · --reinstall · --yes
add-node--address ADDR (required) · --name NAME · --port N (9443) · --timeout N (1800 s) · --ttl-minutes N (60) · --wait-seconds N (120) · --reinstall · --yes
sync--yes — run on the node the preflight reported stale; reconciles config/cluster.yml and then DB_HOSTS / ETCD_HOSTS
remove-node--node NAME or --address ADDR · --yes
detach--confirm FORK-MY-DATA (required) · --yes — turns this node back into a standalone install

A node’s name is chosen at init or not at all: it is written into the etcd ring, the certificates and every node’s .env, so there is no rename. Omit =NAME and one is derived (<cluster>-node-N).

Operating the cluster — these run inside the api container, where the database session, RBAC and the audit log are.

SubcommandOptions
status— nodes, roles, replication lag and health
verify--rehearse (actually hand leadership over and back) · --even-if-serving (rehearse on a cluster already carrying traffic)
connectivity--format text|tsv (default text) — the N×N matrix of every node’s proven login to every other node’s Postgres. Exits non-zero unless every pair is proven by a fresh login
switchover--to NAME (optional for a planned handover, required with --force) · --force · --confirm FAILOVER (required with --force — a forced promotion discards whatever the promoted node has not replayed)
join-token--name N (required) · --address A (required) · --ttl-minutes N (60)
register--token SECRET (required) — redeem a join token on the joining node
revoke--id ID (required) — revokes a pending join token, not a node
decommission--id ID or --name NAME (resolved among active nodes) — soft-deletes a roster row
manifest-publishreads config/cluster.yml on stdin and publishes it cluster-wide
manifest-fetchprints the published manifest on stdout; exit 5 if none has been published
manifest-infoeval-able MP_* metadata — when it was published, how big, its sha256

The manifest subcommands are normally driven by the wizard. They are first-class commands because they are the two cures the wizard prints when a node’s copy of config/cluster.yml and the cluster’s copy disagree — it names both and never picks one for you:

Terminal window
./taranac cluster manifest-publish < config/cluster.yml # this node's copy is right
./taranac cluster sync # the cluster's copy is right
Terminal window
./taranac diagnose --protocol tacacs --device 10.0.0.5 # one device's traffic, decoded
./taranac diagnose --protocol ncm --config "SW1 - show runn" # why a config collection fails
./taranac diagnose --ha # this node's view of the cluster

One command, three collectors: a packet capture around one device with the daemon log, Taranac’s own records and the generated config; a session recorder for one tracked configuration; and a cluster-state snapshot to run on every node, the witness included. None of them writes a secret into what it produces — only 12-hex key fingerprints. The packet-capturing collectors need root: run them as root, or from an account that can sudo without a password, which the script uses on its own. See Diagnostic bundles.

OptionEffect
--protocol tacacs|radius|nac|ncmwhich collector to run
--hathe cluster-state snapshot instead. Takes no --protocol and no --device, and is handled before any other argument is parsed
--device IPthe device’s address as Taranac sees it (tacacs / radius / nac)
--config NAMEthe tracked configuration to diagnose (ncm), by name or id
--duration SECcapture for a fixed time instead of waiting for Enter
--out DIRwhere to write the archive (default ./diagnostics)
--max-size MBstop the capture past this size (default 100)
--full-transcriptncm only: keep recording past the first command. The report then contains the device configuration — share with care
-h, --helpthe built-in help

--ha accepts its own two options instead: --out DIR (same default) and --log-lines N (default 400).

./taranac update is a true release upgrade: it refreshes both the container images and the bundle files (the wrapper itself, docker-compose.yml, install.sh, docs, .env.example). A plain docker compose pull only updates images and silently leaves the bundle scripts stale — update closes that gap.

Terminal window
./taranac version # show installed version + check for a newer one
./taranac update # update to the latest published version
./taranac update --check # report only; change nothing
OptionEffect
--version <X>update to a specific version rather than the latest
--from <bundle>.tar.gzapply a local bundle tarball (air-gapped); the version is derived from the filename if --version is not given
--checkreport only, change nothing (same as ./taranac version)
--no-restartswap the files and pin the version, but skip the image pull and restart. Apply later with ./taranac up -d
--keep-imageskeep the superseded images instead of reclaiming their disk
--allow-out-of-ordercontinue past the cluster-readiness refusal (see Upgrading a cluster)
--yes, -yskip the confirmation prompt

What update does, in order: backs up the current bundle files, swaps in the new ones, leaves your .env and config/ untouched (it only lists any new settings worth adding), bumps the pinned version, pulls images and restarts, then removes the superseded images — keeping the installed version and the one before it, and only ever under this install’s own IMAGE_PREFIX. Database migrations run automatically when the api container starts.

Two things worth knowing before you run it:

  • It never updates one half. If the bundle tarball cannot be fetched, the update aborts with nothing changed rather than pulling images over a stale framework — a release may need the new docker-compose.yml as much as the new images. A failed checksum aborts the same way.
  • A witness is not a Taranac node. Since 1.2.7, ./taranac update on an HA witness refreshes the bundle’s files (witness-join.sh, the witness compose file, diagnose.sh, the HA runbook) and stops: it neither reads nor edits the witness .env, and it starts nothing. A collector host behaves the same way — move a collector with ./collector-join.sh --update, which asks its core what version to be.

Air-gapped hosts. A failed online check is a notice, not an error — normal operation never touches the network. To update offline, copy the bundle tarball onto the host and point update at it:

Terminal window
./taranac update --from taranac-bundle-1.2.8.tar.gz

If a bundle is so old it has no update command yet, copy taranac and taranac-update.sh from a newer bundle once; from then on it is self-maintaining.

./taranac reset truncates every table, re-applies the default seeds (settings, RBAC roles, AAA templates and profiles, the default policy), regenerates the daemon configs, and recreates the admin from the INITIAL_ADMIN_* values in .env. It is meant for demo and lab stands, and it is guarded so you cannot fire it by accident.

Terminal window
./taranac reset # dry run — print the plan, change nothing
./taranac reset --confirm RESET # actually perform the reset
FlagEffect
--confirm RESETRequired to actually wipe. Without it, reset only prints the plan.
--no-backupSkip the automatic pre-reset backup and its abort-on-failure guard.
--force-prodOverride the environment guard so reset runs outside demo/development. Dangerous.

Two guards must both pass:

  1. Environment — only APP_ENV=demo or development may reset. On anything else (production) it refuses unless you add --force-prod. To reset a production-flagged stand deliberately, set APP_ENV=demo in .env, run ./taranac up -d to recreate the api container, then reset.
  2. Typed confirmation — nothing happens without --confirm RESET.

By default a backup is taken before the wipe and the reset aborts if that backup fails, so you are never left without a safety net. --no-backup removes both.

./taranac rotate-key replaces the key that encrypts every stored secret — device and group shared secrets, enable passwords, the internal CA’s private keys, NCM credentials, TOTP secrets, the LDAP bind password, the license. It runs on a stopped system, in one database transaction, so “half the data on the old key” cannot happen, and it keeps the previous key under config/retired-keys/ so archives taken before the rotation stay restorable.

Terminal window
./taranac rotate-key --confirm LOCAL-ACCESS-VERIFIED
./taranac rotate-key --confirm LOCAL-ACCESS-VERIFIED --new-key-file /path/to/key
FlagEffect
--confirm LOCAL-ACCESS-VERIFIEDRequired. Typed in full, and nothing happens without it.
--new-key-file <path>Use a key you supply instead of a generated one.
--current-key-file <path>Read the current key from a file instead of the interactive prompt.
--no-backupSkip the backup taken before the rotation (not recommended).
-h, --helpThe built-in help.

You are asked for the current key even when it is already in .env. That is not a formality: it proves you still hold the key that opens every archive taken so far.

Restore is not in the UI — it erases the current configuration — so it runs from the CLI, off the API, and verifies your master key before it touches anything. One command drives both phases:

Terminal window
./taranac restore taranac-backup-YYYYMMDDTHHMMSSZ.tar
./taranac restore <archive>.tar --master-key <KEY> # this install runs a different key
./taranac restore <archive>.tar --yes # skip the phase-2 confirmation
ArgumentEffect
<archive>.tarThe archive filename, as it sits in ./backups — the api container reads it from that same mounted volume.
--master-key <KEY>Open the archive with an explicit key, when this install does not already run the original one.
--yes, -ySkip the interactive confirmation before the destructive phase 2.
-h, --helpThe built-in help.

Phase 1 verifies the original master key and extracts the archive’s host files and certificates. The script then overlays the five values that must survive a rebuild — MASTER_KEY, SECRET_KEY, TARANAC_MFA_API_KEY, INTERNAL_API_KEY, REPORTS_RENDERER_API_KEY — onto this host’s .env, keeping the previous file as .env.pre-restore; clears the daemons’ master_key volume so their entrypoints adopt the restored key; restores the certificates and ./config; recreates the services that read those secrets; and only then runs phase 2, which replaces the configuration with the archive’s. Everything finishes with a full restart so each service re-reads what was restored.

./backups/restore holds decrypted secrets — delete it when you are done. Two things the archive never carries: MFA enrollments (taranac-mfa has its own database and its own crypto) and the Active Directory machine account (Samba rotates its password weekly, so a restored one would be a broken trust — re-join the domain from System → Authentication if MS-CHAPv2 is used).

The two phases are also reachable individually inside the api container, which is what the script calls: python -m app.scripts.restore host <archive> [--master-key KEY] [--out DIR] (default /restore) and python -m app.scripts.restore db <archive> [--yes] [--force], where --force overrides a version or schema mismatch.

For a non-standard layout, point the script at the right pair explicitly rather than passing a positional: it reads TARANAC_ENV_FILE and TARANAC_COMPOSE_FILE, defaulting to .env and docker-compose.yml.

Under HA the script detects the cluster and merges the Patroni overlay by itself; recovery runs on the leader, and the other nodes rejoin afterwards. See Backup & upgrades for the full runbook.

In-container watchers (background, not commands)

Section titled “In-container watchers (background, not commands)”

You will not run these yourself, but it helps to know they exist when reading logs. The TACACS+, RADIUS and NAC containers each run small watcher scripts that keep the running daemon in sync with the configuration the web UI produces. The admin UI generates a config file, drops a signal file next to it, and a watcher picks up the change:

Watcher (in each daemon container)Role
reload_watcher.shWaits on a .reload_signal file; on TACACS+ sends SIGHUP to reload; on RADIUS/NAC validates with radiusd -XC first, then decides between a SIGHUP and an in-place restart.
restart_class.sh (RADIUS/NAC)Defines the restart classclients.conf, radiusd.conf, sites-enabled/aaa, dictionary — and hashes it. FreeRADIUS applies those only on a full restart; a SIGHUP would silently no-op them. When the hash changes the watcher restarts the supervised radiusd in place (no container churn); otherwise it hot-reloads.
validate_watcher.shRuns a dry-run validation of a pending config and writes the result back so the UI can show whether a change is valid before applying it.
health_watcher.shReports daemon health used by the container healthcheck.
log_trimmer.sh / trim_log.pyTrim daemon log files to keep them bounded.
dictionary_index.py (RADIUS/NAC)Indexes the dictionaries FreeRADIUS actually ships, so the settings UI can tell you which attribute names are already taken.

The effect for an operator: when you save an AAA, NAC or device change in the UI, the relevant daemon reloads on its own within seconds — there is no command to run and no restart. If a reload ever doesn’t take, ./taranac restart <service> (for example radius or nac) forces a clean restart.

CommandPurpose
sudo bash bootstrap.shInstall Docker + compose, add user to docker group (once, as root)
./install.shFirst install (generate secrets, start stack); re-run = image refresh
./taranac psService status
./taranac logs -f <service>Follow a service’s logs
./taranac up -d / ./taranac downStart / stop the stack
./taranac restart <service>Restart one service (e.g. edge after a new cert)
./taranac unlock [<user>|--all]List or unlock locked admin accounts
./taranac reset-password <user>Reset a forgotten admin password
./taranac create-admin [<user>]Create or repair an admin login
./taranac installation-idPrint the installation ID to obtain a Pro license
./taranac clusterPrint the HA help — both the building and the operating family (Pro)
./taranac cluster init|add-witness|add-nodeBuild an HA cluster (Pro)
./taranac cluster status|verify|connectivityCluster health, survivability and the login matrix (Pro)
./taranac cluster switchover [--to <name>]Hand leadership over on purpose (Pro)
./taranac cluster remove-node --node <name>Evict a node; detach --confirm FORK-MY-DATA for this one (Pro)
./taranac diagnose --protocol <p> --device <ip>One device’s traffic, logs, config and records in one archive
./taranac diagnose --haThis node’s view of the cluster — run it on every node and the witness
./taranac versionShow installed version, check for a newer one
./taranac updateUpgrade images and bundle files to the latest
./taranac rotate-key --confirm LOCAL-ACCESS-VERIFIEDRotate the master key on a stopped system, in one transaction
./taranac reset --confirm RESETFactory reset (demo/dev only)
./taranac restore <archive>.tarGuided disaster recovery from a backup
SituationUse
Routine admin locked out after failed logins./taranac unlock <user>
Admin forgot their password./taranac reset-password <user>
No working admin at all (e.g. RBAC mistake)./taranac create-admin
Pull a newer image of the same releasere-run ./install.sh
Move to a new product version./taranac update
Wipe a demo stand back to factory state./taranac reset --confirm RESET
Recover a real platform after data loss./taranac restore <archive>.tar (never reset)
Device administration or 802.1X stopped working./taranac diagnose --protocol <p> --device <ip>
The cluster disagrees with itself./taranac diagnose --ha on every node, witness included
The master key was exposed, or you rotate on a schedule./taranac rotate-key --confirm LOCAL-ACCESS-VERIFIED