Skip to content

Setup as code

A setup file describes a team: its vaults and their settings, its roles, its rules, what the team records, its members, and the hosts in each vault. ZestSSH compares the file with the team, lists every change it would make in words, and applies them when you say so. You can write the file yourself, or save a team as one and change it.

A team’s data is end-to-end encrypted, so ZestSSH Cloud cannot apply a file: it cannot read the team’s data. A file is always applied by a member, in the ZestSSH app, with that member’s own keys. The command line is the same app, started from a terminal, acting as the account signed in on that computer. Every change it makes is an ordinary change by that member, as if they had made it on the Team page, and the team’s record decides what they may change.

  • On the Team page: open Team, then Setup file. It is there for owners and admins, on computers and on phones.
  • From a terminal: on Windows, macOS and Linux, start ZestSSH with admin. See From a terminal. On a computer, the Setup file page has a From a terminal section with the three commands, each with a Copy command button.
  1. Open Team, then Setup file.
  2. Under Save this team as a file, choose Save as a file, and choose where.

The file holds the team as your account can read it: “Its vaults and their settings, the roles, the rules, the members and the hosts, as this account can read them. No password, key or other secret is in the file: a host names its key by the key’s name.”

What could not go into the file is listed under Not in the file:

  • Vaults your account cannot open, and everything in them.
  • Names that stand for more than one vault, role, or host of one vault, since a file names each thing by its name.
  • Names a file cannot carry: a character that does not show, or a name too long.
  • Hosts that are not reached at an address, such as a serial line or a local shell.
  • Roles whose names this device cannot read, and roles named like the team’s own Owner and Admin.
  • Parts left unsaid because they name something the file does not hold, such as a role’s access to a vault you cannot open. Applying the file leaves those parts as they are.
  • Members whose address this device does not know.

A saved file, checked against the team it came from, changes nothing. Take the orchard line out and it describes any team, so you can use it as a template for another.

  1. Open Team, then Setup file.
  2. Under Check a file, choose Choose a file and pick the .json file.
  3. Read what it would do:
    • This is not a setup file ZestSSH can read, with every problem and where it is, such as crates[0].hosts[1].port: a number from 1 to 65535 goes here.
    • “This team is what the file says already. There is nothing to change.”
    • The changes, one by one, such as “Add the vault Payments” or “Invite sam@example.com as Operators”.
    • What stands in the way, such as 2 things stand in the way: what stops the file. “Nothing of this file is applied while something stands in its way. Change the file, or apply it from an account that may do all of it.”
    • Good to know: what the file leaves alone, and what to do next.
  4. Choose Apply 5 changes (the count is the plan’s), then confirm Apply this file?. Copy details beside it copies the whole plan as text, with its times as the file writes them.
  5. If ZestSSH asks you to sign in again, do so. Changes to who is in the team or what they may do need a sign-in from the last ten minutes.

While it applies, the page says which step it is at, such as “Applying 2 of 5 · Add the vault Payments”. Check again reads the same file again, and Choose another picks a different one.

The changes are made one after another, as you would make them by hand. If one fails, it stops there and says what was done: “It stopped. Made before it: 3. Not tried: 2. Nothing was undone: check the file again to go on.” Applying the same file again goes on from there, since every step is only what still differs.

If the team changed after you checked the file, nothing is applied: “The team changed since this was checked. Look at the changes again, then apply.” Hosts it wrote that have not reached ZestSSH Cloud yet go with the next sync.

On a computer, start the ZestSSH program with admin and a command:

zestssh admin export|plan|apply FILE [--orchard NAME] [--out FILE] [--wait SECONDS]
Command or optionWhat it does
export FILEWrites the team to FILE, and lists what was not in it.
plan FILESays what FILE would change, and changes nothing.
apply FILEMakes the changes FILE asks for.
--orchard NAMEWhich team, when this device holds more than one. Without it, ZestSSH takes the team the file names, else the only one on this device.
--out FILEAlso writes everything it said to a file.
--wait SECONDSHow long to wait for ZestSSH to be unlocked and signed in: 120 unless you say, at most 86400.
help, --help, -hShows how the command is used.

The program is zestssh.exe on Windows and zestssh on Linux, in the folder ZestSSH is installed in. On a Mac it is inside ZestSSH.app, in Contents/MacOS.

What happens:

  1. A command that is not understood is answered with how the command is used, before any window opens.
  2. Otherwise ZestSSH starts as it always does. If the app is locked, it asks to be unlocked; if nobody is signed in, sign in. It waits for that as long as --wait says: “Waiting for ZestSSH to be unlocked and signed in (—wait 120).”
  3. It runs the one command, writes what it did to the terminal (and to --out), and closes.

It ends with a code a script can read:

CodeMeaning
0All is as the file says, or was made so. For export: the file was written.
1It could not run: a command not understood, a file that could not be read or written, no team to work on, or the team could not be read.
2plan found changes to make, and nothing in their way.
3Something stands in the way: nothing of the file is applied.
4apply stopped at a change. What was made before it stays.

A terminal cannot ask you to sign in again. When the plan holds changes that need a recent sign-in, the command says so first: “ZestSSH Cloud takes those only after a sign-in from the last ten minutes.” If apply stops for that reason: “Sign in again in ZestSSH on this computer, then run this again.”

Adding vaults, inviting people, and adding or changing groups and hosts need no recent sign-in. Changing a vault’s settings, roles, their order, the rules, what the team records, and members do. So, with nobody signing in again, a computer can plan, and can apply the first kind of change.

A setup file is JSON, and starts with "zestssh_setup": 1. An example, for a team called Example Ops:

{
"zestssh_setup": 1,
"orchard": "Example Ops",
"rules": {
"require_2fa": true,
"email_domains": ["example.com"]
},
"crates": [
{
"name": "Production",
"allow": ["sftp"],
"session_logs": true,
"hosts": [
{
"name": "web-01",
"address": "web-01.example.com",
"key": "Deploy key",
"group": "Web",
"tags": ["web"]
},
{
"name": "db-01",
"address": "192.168.10.31",
"port": 2222,
"key": "Deploy key"
}
]
},
{
"name": "Payments",
"watched": true,
"allow": []
}
],
"roles": [
{
"name": "Operators",
"can": ["invite"],
"crates": { "Production": "use", "Payments": "connect" }
}
],
"members": [
{ "email": "priya@example.com", "role": "Operators" },
{ "email": "sam@example.com", "role": "Operators", "until": "2027-01-31T17:00:00Z" }
]
}

The file format keeps the names orchard (the team’s name) and crates (the list of vaults, one entry per vault, and in a role or member, the access to each vault), as the app reads and writes them.

What a file names is made so. What it leaves out is left as it is: a field you leave out of a host, a rule you do not name, a member who is not in the list. Every part is optional except the first line, and each entry of a list needs its name (a member, its email).

WhereFields
Topzestssh_setup (1), orchard (the team’s name), rules, records, crates (the vaults), roles, members. A file that names a team in orchard is applied to no other.
rulesrequire_2fa, email_domains, require_app_lock, max_lock_minutes (0 to 1440), no_export, offline_hours (0 to 8760), reauth_days (0 to 365), reauth_hours (0 to 8760), require_sso, trusted_devices
recordsWhat the team records: history, activity, devices, ips, each true or false
cratesOne entry per vault: name, owners_only, allow (any of sftp, forward, agent), ask_again, session_logs, watched, unwatched_away, offline_hours (0 to 8760), hosts
hosts (in a vault)name, address, port (22 unless you say), protocol (ssh unless you say, or mosh, et, telnet), key (a key’s name, or null for none), accounts (more keys, by name), group (a name, or null), tags
roles (highest first)name, can (any of invite, remove, change_roles, create_crates, the last one being the power to add vaults), crates (vault name to access)
membersemail, role (owner, admin or a role’s name), crates (their extra vaults, vault name to access), until (a time such as 2027-01-31T17:00:00Z, with Z or an offset, or null for no end)

reauth_days and reauth_hours are one rule, Sign in again. A file that names either one sets both: the one it leaves out becomes 0. Where both are more than 0, members are held to the shorter of the two. 0 in both turns the rule off. A team saved as a file writes reauth_hours only when the team uses hours.

Access to a vault is a level, connect, use, edit or manage, with +agent for agent forwarding ("use+agent"), or a list of switches: connect, reveal, add, edit, delete, manage, sftp, forward, agent. A list without connect is refused, since nothing else works without it.

A role’s crates, and a member’s crates, are all of it: a vault left out is one the role does not open, and a member’s empty crates ({}) takes every extra vault away.

A file can list up to 200 vaults, 30 roles, 2,000 members, and 2,000 hosts in a vault. Vault and role names are up to 50 characters, host, key and group names up to 255.

  • Everything goes by its name. A vault, a role, a host and a key by name, a member by address. Letter case and spaces at the ends do not count.
  • A new name is a new thing. Rename a host in the file and it is a new host; the old one stays, and shows as not in the file.
  • A host’s key is found by its name: in the host’s own vault first, else the only key of that name in the team. A group that is not there yet is added.

ZestSSH reads the whole file first and lists every problem it finds, each with its place, such as members[1].until. Among them:

  • No secrets. A field named password, passphrase, private_key, secret, token, key_data or pin is refused: “a setup file never carries a password, a key or another secret.”
  • No unknown field, so that a misspelled one is not skipped, and no field twice in one place.
  • No hidden characters in names, and no characters that turn the reading direction around, so that two names cannot look the same and be different.
  • Owner and Admin are the team’s own roles. A role of yours needs another name.
  • A watched vault allows the terminal only: watched with anything in allow is refused.

Then the plan tries every change on a copy of the team’s record, so the record’s own rules decide what this account may do, before anything is sent.

  • It never takes anything away. Members, invitations, vaults, roles and hosts the file does not name stay as they are, and the plan lists them under Good to know. Removing someone is done by a person, on the Members page.
  • It never carries a secret, and never makes a key. A host names a key that already exists: “Add the key in the app first: a file never carries one.”
  • It does not change the account that applies it. “Nobody changes their own role, extra vaults or end date by a file.”
  • It does not make an owner or admin before the account applying it has compared their keys: “Compare their keys with them first, on the Members page, then apply the file.”
  • It does not let anyone join. It invites people. They accept, and someone compares their keys and confirms them: “priya@example.com accepted. Compare their keys with them and confirm, on the Members page.”
  • It cannot do more than the account applying it. Only owners set the rules, roles, what the team records, session logs and watched sessions, and nobody gives more access than they have themselves: “This would give more than this account has itself.”
  • It cannot make an existing vault owners-only. A vault is for owners only from when it is added, or never.
  • It cannot see vaults the account cannot open. They are not compared. A vault the file adds is refused while one of those might be it.
  • It invites 30 people an hour at most, as ZestSSH Cloud takes from one account. Apply the file again later for the rest.

There is no special account for automation. A computer that runs the command uses an ordinary ZestSSH account, and that account is a member like any other. To give a machine its own:

  1. Make a ZestSSH account for it, such as setup@example.com.
  2. Invite that address to the team with the role it needs. Changes only owners can make need it to be an owner.
  3. On the computer that will run the command, sign in to ZestSSH as that account, and accept the invitation there. The command needs that account’s keys on that computer, and waits for them as long as --wait says.
  4. Someone in the team compares its keys and confirms it on the Members page, as for a person.

From then on it is compared like a person:

  • It holds a seat, like every member. See Seats and billing.
  • Its changes are signed with its own keys. Every member’s device checks them against the team’s record, as it checks anyone’s, and they show as that account’s changes.
  • It trusts others only as its device does. It hands a vault’s key only to people whose current keys its computer holds. When someone’s keys changed and the change waits to be looked at, the plan says “Compare their keys with them first, on the Members page.” For it to make someone an owner or admin, the service account itself must have compared that person’s keys, on its computer.
  • It needs a recent sign-in for most changes, like anyone. On an unattended computer, plan, and apply for vaults, invitations and hosts. Changes to roles, rules and members wait for someone to sign in again on that computer within ten minutes of applying.
  • It is unlocked like anyone’s app. With the app lock on, ZestSSH asks to be unlocked each time the command starts it, and waits as long as --wait says. An unattended computer needs someone to unlock it, or the app lock off where the team’s rules allow that.