Install the server
Run zidane-server from Docker, a release binary, npm, the zidane command or a checkout, then create the first token.
Every way below runs the same server.
| Way | Good for | Command |
|---|---|---|
| Docker image | Servers, Compose, Coolify | docker run ghcr.io/tahul/zidane-server:8.1.0 |
| Release binary | A VPS with systemd, no container | zidane-server start |
| npm | A machine with Node, no install step | npx zidane-server start |
The zidane command | A machine that already has the terminal app | zidane server start |
| From source | Working on zidane | bun run server start |
Run it#
docker run -d --name zidane --restart unless-stopped \
-v zidane-data:/data -p 127.0.0.1:4280:4280 \
--memory 4g --pids-limit 2048 --security-opt no-new-privileges \
ghcr.io/tahul/zidane-server:8.1.0The image is public: no docker login. Images exist for linux/amd64 and linux/arm64. The agents run commands in the container: --memory and --pids-limit cap what they take from the host (raise them for heavy builds), and no-new-privileges keeps a setuid binary from gaining anything.
| Tag | Tracks |
|---|---|
8.1.0 | That release. It never moves (use it) |
8.1 | Its patch releases |
8 | Its minor and patch releases |
latest | The newest release |
The release workflow publishes a pre-release under its own version only (8.2.0-beta.1), never under 8.2, 8 or latest.
The image sets ZIDANE_SERVER_BEHIND_PROXY=1, so a direct WebSocket connection to 127.0.0.1:4280 gets a 403. Put a TLS proxy in front (Compose with Caddy). Token commands and health checks run through docker exec and the unix socket, so they need no proxy.
curl -fsSL https://zidane.si/install | sudo sh -s -- --server --prefix /usr/local
zidane-server start # 127.0.0.1:4280The install script downloads the archive for your machine from https://zidane.si/dl, checks its SHA-256 against the release manifest (and the manifest's signature when minisign is installed), and installs zidane-server to /usr/local/bin with its web app in /usr/local/share/zidane-server. Without sudo and --prefix, it installs to ~/.local/bin. --version <v> installs the release before the latest.
By hand, the archives are at https://zidane.si/dl/v<version>/, and the download page lists them with their checksums:
V=8.1.0 T=linux-x64 # darwin-arm64, darwin-x64, linux-x64, linux-arm64
curl -fLO "https://zidane.si/dl/v$V/zidane-server-v$V-$T.tar.gz"
tar -xzf "zidane-server-v$V-$T.tar.gz"
sudo install -m 0755 "zidane-server-v$V-$T/zidane-server" /usr/local/bin/Check the archive first: verify a download. The server keeps the latest release and the one before it.
The binary is one self-contained executable. The linux archives also hold zidane-server.service, a systemd unit (VPS with systemd). Linux builds need glibc (Debian, Ubuntu, Fedora). They do not run on Alpine (musl). macOS builds are signed with an Apple Developer ID and notarized.
npx zidane-server start # or: npm install -g zidane-serverThe zidane-server package is a small launcher. On first run it downloads the binary for your machine from https://zidane.si/dl, checks the release signature and the SHA-256, and keeps it in a cache for the next runs.
npm install -g zidane-tui # or: bun add -g zidane-tui
zidane server startThe zidane command includes the server. zidane server <command> is the same as zidane-server <command>.
bun install
bun run server start # the server, from src/server/main.ts
bun run server:build # or the binary: server/dist/zidane-server
bun run server:docker # or the image: zidane-server:devbun run server:smoke and bun run server:smoke:image test the binary and the image. bun run server:release:package writes the release archives and SHA256SUMS to server/dist/release/. In a checkout, server/bin/zidane-server.mjs runs the built binary, or the terminal app's zidane server when there is none.
Versions#
Pin an exact release (8.1.0), and upgrade the server and its clients together. A client and the server must share a protocol version. Each side speaks a range of them, so a client one release away usually still attaches, but a release can move the range past older clients (a patch release once changed the version, v7.0.10). On a moving tag, a plain redeploy can leave every client refused until it is upgraded too. The upgrade steps back up and wait for running work first.
What it prints at start#
One line on stderr, then a hint while no token exists:
✓ zidane server listening on ws://127.0.0.1:4280 and /home/me/.config/zidane/server.sock (data /home/me/.config/zidane, pid 4242)
Next: create a client token: zidane server token create --name <device> (there is none yet. This works while the server runs.)Without --data-dir, the server uses zidane's usual folders (where they are). A deployment passes --data-dir <dir> to keep everything under <dir>/.zidane.
The server listens on loopback by default and refuses any other interface without TLS or a proxy in front (Deploy).
Create the first token#
Create one token per device. The server prints it once on stdout and keeps only its hash.
zidane-server token create --name laptop # binary
docker exec zidane zidane-server token create --name laptop # imageKeep it in a file only you can read (chmod 600). If you have no shell before the first deploy, use a bootstrap token. For scopes, rotation and revoking, see Tokens.
To run session commands on other computers, add each one as a machine. A join code, a one-time code from the server, adds it (servers and machines).
Check it runs#
On the server's machine:
zidane-server health # over the unix socket
zidane-server health --http # GET /healthz on the server's portBoth exit 0 when the server is healthy (output).
Connect a window#
Clients send a token over ws:// only to 127.0.0.1 and localhost. To test from another machine before TLS is set up, open an SSH tunnel:
ssh -L 4280:127.0.0.1:4280 box
zidane --host ws://127.0.0.1:4280 --token-file box.tokenTo save the server in an app or connect a browser, see Clients.
Web app#
The server can serve zidane's web app at / on its own port: a chat in the browser, with no other host to run. It is off by default. Turn it on with --web (or ZIDANE_SERVER_WEB=1, or "web": true in server.json), then log a browser in with a pairing code:
zidane-server start --web
zidane-server pair # prints a code like 7KQM-3XFD, valid 10 minutesOpen the server's address (https://box.example.com/, or http://127.0.0.1:4280/ through the SSH tunnel above) and type the code. The browser gets its own device token, browser: <name> in zidane-server token list. A chat code (the default) lets it chat and answer tool approvals, --scope admin gives it everything (pairing rules).
Where the app comes from:
| Way | App |
|---|---|
| Docker image | Bundled, in /usr/local/share/zidane-server/web-dist |
| Release binary | web-dist/ in the archive. zidane server setup installs it next to the binary, and auto-update replaces both together. By hand, keep it next to the binary, or install it as /usr/local/share/zidane-server/web-dist beside /usr/local/bin/zidane-server: sudo mkdir -p /usr/local/share/zidane-server && sudo cp -R "zidane-server-v$V-$T/web-dist" /usr/local/share/zidane-server/ |
The zidane command | Not bundled: pass --web-root <dir> |
| From source | bun run server:web builds web/ into server/web-dist/ |
--web-root <dir> (ZIDANE_SERVER_WEB_ROOT, webRoot in server.json) serves another build: a directory with an index.html. With --web on and no app found, the server refuses to start and says why. --no-web turns it off over the variable and the file.
The app connects back over the same origin (wss://<host>/), so it needs no entry in ZIDANE_SERVER_ALLOWED_ORIGINS. Every page carries a strict Content-Security-Policy (default-src 'self', frame-ancestors 'none'), X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer. Hashed files under /assets/ are cached for a year, index.html never.