Skip to content
Join the waitlistWaitlist

One SSH command

Install Guard on a Linux server with one SSH command that checks the installer's signature first, then sets it up with your rules. The key is typed on the server, never here.

One command from your computer: it connects to your server, installs Guard after checking its signature, and walks you through the setup there. Your key is typed into the server, with hidden input, and never into a command line, a URL or this page.

stored only in this browser · ·

Your settings from this site

The default rules. Set your own on the playground, or paste a rules code.

  • 5× max
  • 2.0% at stop
  • no stop: Guard sets one
  • liq. ≥ 10%
  • size ≤ 200%
  • open ≤ 6%
  • daily 6%
  • drawdown 25%
  • 5/5 markets
  • paper mode
Start Guard in
Paper: real prices, no orders, no key. Mainnet is never preset from a web page: you type it on the server, and the account again.

Where Guard runs

Next to your bot. If your bot runs on a server, Guard runs on the same server.

Ubuntu 24.04 or Debian 12 is best: the key is stored encrypted with systemd-creds. No port is opened; Guard listens on 127.0.0.1:8547.

Secrets: typed on the server, never here

This page never asks for a key, and no command it makes contains one. Shell history, screen sharing and this browser would all keep it.

API wallet private key

For testnet and mainnet. Create an API wallet in the Hyperliquid app: it can trade, it cannot withdraw. Guard asks for its key with hidden input on the server and stores it encrypted for the service. API wallets

Alert webhook, optional

A webhook URL is a bearer secret too. Add it on the server, in Guard's config, after the setup. Configuration

Public values: safe to enter here, checked in this browser

your server · what the setup asks
$ ssh -t you@your-server "curl -fsSL https://zunderlabs.com/i | sh -s -- --rules zr1_eyJ2IjoxLCJt…✓ installer verified: Sigstore signature of the v1.0.0 checksums (zunderlabs/zunder-guard)✓ zunder-guard 1.0.0 installed to /usr/local/bin · service user zunder-guard Your rules from zunderlabs.com  max leverage 5× · loss at stop 2% · no stop: Guard sets one 2% away  liquidation ≥ 10% · size ≤ 200% · open risk ≤ 6%  daily loss stop 6% · drawdown halt 25% · markets: allKeep these rules? [Y/edit] › YHyperliquid account address › 0x8c41…a90fMode [paper/testnet/mainnet] › paper✓ paper mode: real prices, no orders, no key needed ✓ Guard is running (systemd: zunder-guard) · 127.0.0.1:8547 · paperClient key for your bot (shown once): zc_7Hq2…Lm9xNext: point your bot at http://127.0.0.1:8547

Run this from your computer

One command with your rules. It connects to your server, installs Guard after checking its signature, and asks you the rest there.

terminal
ssh -t you@your-server "curl -fsSL https://zunderlabs.com/i | sh -s -- --rules zr1_eyJ2IjoxLCJt…siKiJdfQ"

Hover or focus a part of the command to see what it does.

Prefer to read first? curl -fsSLO https://zunderlabs.com/i && less i, then sh i --rules …. Or verify by hand: Verify a release.

Check it, then point your bot at it

On the machine where Guard runs:

curl -fsS http://127.0.0.1:8547/healthz# 200 and "ok": Guard is upzunder-guard status# mode, rules, risk state, the last decisions
  1. The loader (zunderlabs.com/i, 37 lines of sh) downloads install.sh, SHA256SUMS and the Sigstore bundle of that release. It verifies the bundle (signed by Guard’s release workflow on GitHub, for exactly that tag) and checks install.sh against the signed checksums. A mismatch stops it before anything runs. Without cosign on the server it fetches a pinned cosign and checks its SHA-256 first.
  2. The installer checks again: the signature, then the release archive for the server’s architecture (x86-64 or ARM64) against the signed checksums, and only then extracts it.
  3. It creates a system user zunder-guard with no login shell and a state directory readable only by that user, and installs the binary.
  4. The setup is Guard’s own (zunder-guard init --interactive): it shows your rules in plain words and asks to keep or edit them, then the account address and the mode. Paper is the default. Mainnet must be typed in full, followed by the account again.
  5. For testnet or mainnet it asks for the API wallet key with hidden input. The key is read, never echoed, never put on a command line or into the environment, checked with zunder-guard key check, and stored with systemd-creds: encrypted, and decrypted only for the service when it starts. On systemd older than 250 it falls back to a file readable only by Guard’s user, and says so.
  6. It installs the hardened systemd unit, bound to 127.0.0.1:8547, starts it, and prints the client key for your bot (once).

It opens no port. Running the same command again reconfigures Guard; it asks before replacing its configuration, and the journal is kept.

Skip the loader and run the same checks yourself, on the server (needs cosign):

Terminal window
V=v1.0.0; R=https://github.com/zunderlabs/zunder-guard/releases/download/$V
curl -fsSLO "$R/install.sh" -O "$R/SHA256SUMS" -O "$R/SHA256SUMS.sigstore.json"
cosign verify-blob --bundle SHA256SUMS.sigstore.json \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity "https://github.com/zunderlabs/zunder-guard/.github/workflows/release.yml@refs/tags/$V" \
SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS && sh install.sh --rules zr1_…

Verified OK means the checksums were signed by Guard’s release workflow on GitHub, for that tag. Then read install.sh; it is short on purpose. More: Verify a release.

Cloud-init, CI or ssh without -t give the installer no terminal, so it refuses, unless you pass --non-interactive with every value: --rules, --network, and for testnet or mainnet --account and --key-file (a file the installer reads, never the key itself on the command line).

This page as plain Markdown, for people and LLMs: /docs/deploy/ssh.md