Zum Inhalt springen

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, captive-portal.
  • 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)

./taranac logs <service> for a service that has no container on this host says so instead of printing nothing — no <service> container on this host., followed by the containers this stack does have here, or ./taranac up -d when it has none — and exits 1.

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, remove-witness, synccluster-wizard.sh on the host
cluster renew-cert, rotate-cathe host’s etcd-agent container — on any member, the witness included
cluster remove-node, cluster detachcluster-remove.sh on the host
diagnosediagnose.sh — --ha re-execs diagnose-ha.sh
captive-portal join, update, reset-trust, uninstall, statusportal-join.sh, against the portal installed beside the wrapper
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 collector — that is how those hosts get their own join scripts, and it puts this wrapper on all three. 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. The exceptions on a witness are cluster renew-cert and cluster rotate-ca, which run in its own etcd-agent. ./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, except a Docker older than 28.0 or Podman, which it refuses with the command that updates or replaces it.
./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 three 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); --ipv6-own-address-ok, which lets a re-run on an existing installation go on although inventory entries that matched IPv6 devices through the Docker gateway stop matching them (IPv6); and -h / --help. Anything else is refused by name. Before it writes anything it checks the host — Docker 28.0 or newer, not Podman, IPv6 in the kernel, a dual-stack test network — and refuses with the fix; see IPv6 → What the host needs.

On a standalone server, re-running install.sh when .env already exists refreshes the images of the bundle’s version: it never regenerates secrets — it backfills any a newer bundle introduced, repairs file permissions, runs the inventory checks ./taranac update runs, pulls and restarts — recreating the stack’s Docker network when its address plan changed. On an HA cluster node or a witness it refuses, changes nothing and names ./taranac update. It also refuses to start a database it finds on the host without the .env that belongs to it, and a former cluster member’s database as a standalone one. A release upgrade (images and bundle files) is always ./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 collector, a standalone 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> (tokens are hex; --token=<secret> works too)--env-file <path> (default ./.env) · --print-auth (print the derived auth header and exit — a diagnostic for “invalid credentials”) · --leave (retire this witness after cluster remove-witness: refused while the host is still in the ring; stops the witness etcd, deletes its state and certificate, keeps .env as .env.left-<time>; --confirm NOT-A-MEMBER when no database node answers)
collector-join.sh--core <https url>, and the enrollment token at the prompt (it does not echo), from --enrollment-token-file <path|-> (- reads stdin) or from TARANAC_ENROLLMENT_TOKEN. --enrollment-token is refused--ca-file <path> | --trust-any · --collector-id <label> · --poll-interval <sec> · --with-nmap (build nmap into a local image from your own mirrors, for the scanner’s Deep method — see Scanner) · --status · --reset-trust · --update (with --check, --version <X> or --from <image>.tar) · --uninstall (with --force to discard a non-empty outbox)
portal-join.sh (usually run as ./taranac captive-portal join)--core <https url>, and the portal token at the prompt, from --token-file <path|-> or from TARANAC_PORTAL_TOKEN. --token is refused--ca-file <path> | --trust-any · --dir <dir> · --client-interface <if> · --uplink-interface <if> · --dhcp-range <first last> · --upstream-dns <ip> · --nat on|off · --client-isolation on|off · -y · --status · --update (with --check) · --reset-trust · --uninstall. See Standalone captive portal

A collector or portal token is not taken as an argument, where it would sit in the shell history and in ps.

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.

A standalone captive portal is addressed by name

Section titled “A standalone captive portal is addressed by name”

On a host that carries a standalone captive portal, the wrapper addresses it as captive-portal, wherever its directory is (./captive-portal beside the wrapper by default, or TARANAC_PORTAL_DIR):

CommandWhat it does
./taranac captive-portal join --core <url> [--trust-any]Install or re-install the portal on this host; it asks for the portal token (or reads --token-file <path|-> / TARANAC_PORTAL_TOKEN). The core prints this line ready to paste on the portal’s Deployment tab
./taranac captive-portal statusTrust, token, heartbeat and datapath, from local state only
./taranac captive-portal datapathThe rules, the allowed clients and the speed limits in force
./taranac captive-portal restart portalCheck the certificate in certs/ first, then restart; refuses — leaving the running portal untouched — if the certificate cannot be served
./taranac captive-portal update [--check]Move the portal to the version its core runs
./taranac captive-portal reset-trustAccept the core’s certificate again after a real replacement
./taranac captive-portal uninstallRemove the portal, and its datapath, from this host

Anything else — logs -f portal, ps — is passed to docker compose in the portal’s directory. See Standalone captive portal.

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).

If the username is held by more than one account (a local and a directory account, say), unlock, reset-password and create-admin refuse: they print every account holding the name (id, source, LDAP configuration), change nothing and exit non-zero. Remove or rename the extra accounts, then run the command again. See One username, one account.

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.

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, except remove-witness (any database node) and sync / detach (the node concerned). These rewrite host files and may recreate containers, so they run on the host, not in a container. The topology commands take the cluster’s single update lock in etcd, so one of them during another, or during a node’s update, is refused with who holds it.

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; takes the cluster’s config/cluster.yml whole (keeping its own as config/cluster.yml.<time>.bak) and writes only this node’s DB_HOSTS / ETCD_HOSTS / ETCD_INITIAL_CLUSTER; recreates nothing
remove-witness--address ADDR (the witness, by name or address) · --forget ADDR (let a removed witness that will never run --leave go) · --yes (needed without a terminal) — on any database node; refused on a 1-node cluster (→ ./ha-deconvert.sh), on 2 database nodes, or when the voters left would not hold a majority; restarts nothing
remove-node--node NAME or --address ADDR · --yes (needed without a terminal). Given the witness’s name or address it hands over to remove-witness
detach--confirm FORK-MY-DATA (required) · --confirm NOT-A-MEMBER (in addition, when no cluster member answers) · --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) · --to NAME (with --rehearse: the replica to hand over to; by default the one rehearsed longest ago, so repeated runs prove every replica) · --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 that is only a name, with no etcd or Patroni member behind it. A live member — the leader, an etcd member, a running Patroni member — is refused (and its button in the UI disabled): use remove-node
license-check— exit 0 when the HA license allows join tokens, 3 when it does not (for scripts)
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: every topology command publishes its change to config/cluster.yml before it makes it, and a node whose copy differs, or that has none, takes the cluster’s copy and keeps its own as config/cluster.yml.<time>.bak. The one thing you publish by hand is a skip: entry you wrote into this node’s file — it is not merged otherwise:

Terminal window
./taranac cluster manifest-publish < config/cluster.yml # push this node's hand-edited copy
./taranac cluster sync # take the cluster's copy on this node

See The cluster manifest.

The etcd certificates — these run in the host’s etcd-agent container, so they work on any member, the witness included. Without a running agent they refuse, change nothing, and print how to start it (./taranac up -d --no-deps etcd-agent) or update the host.

SubcommandOptions
renew-cert— renew this member’s etcd certificate now (members renew their own 45 days ahead anyway) · --all (run on any member: every member renews its own; one line per member with the old and new end date; exits non-zero unless every member renewed, naming each that could not) · --timeout N (for --all, default 120 s)
rotate-ca— replace the cluster’s etcd CA on every member, one at a time, nothing restarted · --yes (no [y/N] question; needed without a terminal) · --timeout N (seconds to wait for each member’s answer, default 90). Exit 0 done, 1 refused (nothing changed), 3 stopped part way — run it again, which first puts every member back on one CA

rotate-ca refuses before it changes anything unless every member answers and runs the same release. While it runs, update, add-node, add-witness, remove-node, remove-witness and renew-cert --all are refused; after a run that stopped, add-node and add-witness stay refused until a re-run completes it, and update warns. See etcd certificates and the etcd CA.

Going back to one server, and leftovers. ./ha-deconvert.sh on the last database node takes the cluster apart:

OptionEffect
--yesNo prompt
--confirm NO-OTHER-NODESNeeded only when neither the etcd ring nor the node roster can be read; otherwise the script refuses while either lists another database node, and prints the remove-node for each
--forceThe last survivor no longer looks like the primary; never skips the other-database-node check
--abandonUndo a cluster init, a conversion or a join that never finished (removes this node’s etcd container and volume, keeps .env.bak-<time>); refused on a member and once Patroni has run on this node’s data
--decommission-dead --node-name NAMETake a dead node out of the ring and the roster; refused for a node that still answers

Typed confirmation words. Commands that can lose data or split a cluster ask for a word — typed in a terminal, or for most of them given with --confirm:

WordAsked by
FORK-MY-DATAcluster detach
NOT-A-MEMBERcluster detach and witness-join.sh --leave, when no member of the cluster answers
NO-OTHER-NODESha-deconvert.sh, only when neither ring nor roster can be read
FAILOVERcluster switchover --force
OUT-OF-ORDERupdate --allow-out-of-order (--confirm OUT-OF-ORDER without a terminal)
DOWNGRADEupdate --version <older> on a cluster member (typed)
DELETE-ALL-DATA./taranac down -v --confirm=DELETE-ALL-DATA — standalone only; down -v is refused outright on a cluster node and on the witness

The other guards on a cluster — ./taranac up refusing a NODE_ADDRESS that is not this host’s, install.sh on a member, a hand-edited TARANAC_VERSION — are listed with their cures in Commands that refuse, and why.

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 --protocol snmp --device "core-sw-01" # why an SNMP poll comes back short
./taranac diagnose --ha # this node's view of the cluster

One command, four 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; one recorded SNMP poll of one device, which writes nothing to the database (see SNMP); 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 and tcpdump on the host (the virtual appliance ships it): run them as root, or from an account that can sudo without a password, which the script uses on its own. See Diagnostic bundles.

The packet-capturing runs collect a few things more:

  • Every run (tacacs, radius, nac) writes ipv6.txt: Docker’s version and whether its ip6tables is on, kernel IPv6, accept_ra, and for each published port of the daemon its IPv6 listener and Docker’s IPv6 DNAT. While IPv6 devices still arrive from the Docker gateway, it names the inventory entries that cover the gateway, as an [IPv6] finding in the summary. See IPv6.
  • --protocol radius and --protocol nac collect the radiusd supervisor’s traces: one per restart it caused (a crash, a crash loop, a rollback, a misroute), with the 24-hour count. The radiusd crashed family of alerts asks for exactly this command.
  • --protocol nac also shows EAP-TLS revocation on the node: the per-CA CRL and OCSP policy and the chain the daemon walks from each CA, the CRLs on disk compared with the database, the last day’s OCSP refusals by class, and a live probe of each OCSP responder. See Certificate revocation → Diagnosing.
OptionEffect
--protocol tacacs|radius|nac|ncm|snmpwhich 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)
--device NAMEfor snmp: the device’s name in Taranac (or its id), not an address — which address is polled is one of the things the report explains
--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 or report (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 stable one
--from <bundle>.tar.gzapply a local bundle tarball (air-gapped); the version is read from the bundle’s own VERSION file, never from the file name (a --version that disagrees is ignored, with a warning)
--checkreport only, change nothing (same as ./taranac version)
--repairwith --version <the version this node runs>: re-apply it — a new backup, the files swapped, the stack recreated
--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
--skip-image-verifyaccept pulled images whose digests differ from the release’s IMAGES.lock — a registry mirror that re-pushed them
--skip-space-checkpull even when the free-disk estimate says there is not enough room
--allow-out-of-ordercluster repair only: skip the cluster’s update order, never its one-at-a-time lock. Asks for the word OUT-OF-ORDER; without a terminal add --confirm OUT-OF-ORDER. See Running it again, repairs and downgrades
--ipv6-own-address-okgo on although inventory entries that matched IPv6 devices through the Docker gateway stop matching them; without it the update names them and stops while the inventory has no IPv6 entry. TARANAC_IPV6_OWN_ADDRESS_OK=1 does the same, also for an older updater that does not know the flag. See IPv6
--yes, -yanswer the confirmation prompt — nothing else

What update does, in order: checks the host (Docker 28.0 or newer, not Podman, IPv6 in the kernel, a dual-stack test network; accept_ra=2 where needed) and the device inventory (gateway-covering entries; TACACS+ addresses that logged in without an entry, listed and kept in .taranac-update.log), records the images the containers run now, checks free disk space, pulls the new release’s images while the running version is still pinned, compares each with the digest list the release ships (IMAGES.lock) and reads each image’s files against its layers, replacing a damaged one (Damaged images). Only then does it back up the current bundle files, swap in the new ones, leave your .env and config/ untouched (it only lists any new settings worth adding), bump the pinned version and restart, then remove 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. A refused host check, or a failure while pulling or verifying, leaves the node exactly as it was.

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 re-run on a node already on the version answers Already on <version> — … Nothing to do. only when the running containers agree too; otherwise it finishes the interrupted update. It makes no backup and recreates nothing — it only starts containers that are stopped. Use --version <v> --repair to re-apply on purpose.
  • A plain update never goes backwards. On a pre-release or a version newer than the latest stable release, it leaves the node as it is.
  • On a cluster run it on any member: it names the node to update next (every replica, then the leader, then the witness), refuses on the wrong machine before changing anything, and holds the cluster’s single update lock. Copy the printed commands — they carry --version <target>. See Upgrades under HA.
  • A witness updates only itself. ./taranac update on an HA witness goes last, after every database node has finished (it waits for them, and refuses naming one that has not), writes exactly one line — TARANAC_VERSION=<v> — into the witness .env, applies a changed docker-compose.witness.yml itself on a healthy etcd ring, and starts no Taranac container. A witness that ran --leave is reported as having left its cluster, with nothing to update.
  • A collector host is not moved from here either — 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.3.2.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 /root/taranac-backup-YYYYMMDDTHHMMSSZ.tar # a file on this host
./taranac restore taranac-backup-YYYYMMDDTHHMMSSZ.tar # already in the backups volume
./taranac restore <archive> --master-key-file /root/old.key # this install runs a different key
./taranac restore <archive> --yes # skip the phase-2 confirmation
ArgumentEffect
<archive>The archive’s path on this host — relative to where you run the command, or absolute — which is copied into the backups volume; or the file name of an archive already in that volume
--master-key-file <path|->Open the archive with the key in that file (nothing else in it), when this install does not already run the original one; - reads it from stdin. The key is written into .env
TARANAC_MASTER_KEYThe same, from the environment. The file wins if both are given
--master-keyRefused, with nothing changed: a key on the command line sits in the shell history and in ps
--yes, -ySkip the interactive confirmation before the destructive phase 2. Required without a terminal (piped, ssh without -t, or the key read from stdin): without it, restore refuses before it changes anything and prints the command with --yes
--Ends the options, for an archive name that starts with -
-h, --helpThe built-in help

The script refuses up front an install whose key is not read from .env (MASTER_KEY_SOURCE other than env) and, under HA, any node but the leader. Under HA it also never puts an older etcd CA from the archive over the cluster’s current one: that copy is set aside in config/etcd-ca.restored-<time>. Phase 1 verifies the 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.<UTC time>, and checks that the key now in .env opens the archive: if not, .env is put back and nothing else has changed. It restores the certificates and ./config, stops the api and loads the database from a one-off container (phase 2), clears the daemons’ master_key volume so their entrypoints adopt the restored key, recreates the services that read those secrets, waits for the api to be healthy, restarts everything, and removes the decrypted extraction. If the load fails, it tells you whether the data went in; re-running the same command is safe.

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 phases are also reachable individually inside the api image, which is what the script calls: python -m app.scripts.restore host <archive> [--master-key -] [--out DIR] (default /restore; - reads the key from stdin), python -m app.scripts.restore check-key <archive> (one key on stdin; exit 0 it opens the archive, 10 not a key, 11 another key) and python -m app.scripts.restore db <archive> [--yes] [--force], where --force overrides a version or schema mismatch. Phase 2 runs in a one-off container (./taranac run --rm --no-deps -T api …) with the api stopped, because the api will not start on the restored key until the dump is in.

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. It runs on the leader only, refuses an archive cut under other keys than the cluster runs now (before a rotate-key), and keeps the cluster’s live roster and published manifest. 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 class — clients.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 on a standalone server = 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 cluster remove-witnessRetire the arbiter once there are 3+ database nodes (Pro)
./taranac cluster renew-cert [--all]Renew this member’s etcd certificate now, or every member’s (any member, witness included)
./taranac cluster rotate-caReplace the cluster’s etcd CA on every member, nothing restarted
./taranac diagnose --protocol <p> --device <ip>One device’s traffic, logs, config and records in one archive
./taranac diagnose --protocol snmp --device <name>One recorded SNMP poll of one device, explained layer by layer
./taranac diagnose --haThis node’s view of the cluster — run it on every node and the witness
./taranac captive-portal status|datapath|restart|update|reset-trust|uninstallOperate the standalone captive portal installed on this host
./taranac versionShow installed version, check for a newer one
./taranac updateUpgrade images and bundle files to the latest (on a cluster: any member, it names the next)
./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 <path to archive>Guided 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 release (standalone)re-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 <path to archive> (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
SNMP polling fails or a forwarding table comes back empty./taranac diagnose --protocol snmp --device <name>
The master key was exposed, or you rotate on a schedule./taranac rotate-key --confirm LOCAL-ACCESS-VERIFIED