Skip to content

Latest commit

 

History

61 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zctl

Run commands on remote machines over WebSocket. Agents connect out to a central backend; the CLI dispatches from the other end. Managed machines need no inbound firewall rules and no key distribution.

demo

Quick start

git clone https://github.com/zexk/zctl
cd zctl
docker compose up --build -d

A docker-agent registers automatically once the stack is up. Build the CLI:

pnpm --filter @zctl/cli build
OP_TOKEN=$(node scripts/gen-token.js)
zctl login --url http://localhost:3000 --token $OP_TOKEN
zctl machines
HOSTNAME                 STATUS    OS         ARCH       LAST SEEN
──────────────────────────────────────────────────────────────────────
docker-agent             online    linux      aarch64    3s ago
zctl exec docker-agent uptime
10:42:01 up 2 min, 0 users, load average: 0.00, 0.00, 0.00
zctl logs docker-agent
COMMAND                          STATUS     EXIT   CREATED
────────────────────────────────────────────────────────────────────────
uptime                           completed  0      5/26/2025, 10:42:01 AM

The core also serves a small operator dashboard:

http://localhost:3000/dashboard

Paste the same operator token into the dashboard to view machines, command history, and run commands.

Features

  • Machine registration with persistent WebSocket connectivity
  • JWT auth: separate operator and agent roles
  • Remote command execution via sh -c, output stored per execution
  • Online/offline status derived from heartbeat timestamps
  • Execution history per machine
  • Browser dashboard backed by the same operator API endpoints

Architecture

CLI --> Core API/WS Server <-- Agents (Go)
                |
            PostgreSQL
Component Stack Role
Core TypeScript, Fastify, Drizzle, PostgreSQL HTTP API, WebSocket gateway, JWT auth
Agent Go, gorilla/websocket Outbound WS connection, command execution, heartbeats
CLI TypeScript, Commander.js Operator CLI

NixOS installation

Add the flake input and import the module:

inputs.zctl.url = "github:zexk/zctl";
imports = [ inputs.zctl.nixosModules.zctl ];

Control plane (one host):

services.zctl.core = {
  enable = true;
  environmentFile = "/run/secrets/zctl-env"; # JWT_SECRET=<32+ chars>; CORS_ORIGIN=https://zctl.example.com (defaults to *)
  openFirewall = true;
};

PostgreSQL is provisioned automatically (database.createLocally = true by default): a local database and role are created, and the service connects via Unix socket peer auth, no password required. To use an external database instead:

services.zctl.core.database.createLocally = false;
services.zctl.core.database.url = "postgres://user:pass@host/zctl";

Agents (any managed host):

services.zctl.agents.default = {
  enable = true;
  coreUrl = "https://zctl.example.com";
};

Multiple agents per host (e.g. reaching different control planes) are supported via additional attrset keys; each becomes a separate systemd unit (zctl-agent-<name>).

CLI (operator machines):

programs.zctl.enable = true;

Then authenticate:

zctl login --url https://zctl.example.com --token <operator-jwt>

The flake also exposes packages.<system>.{zctl-agent,zctl-core,zctl-cli} and overlays.default for use outside NixOS.

Development

nix develop              # enter devshell (Nix flake)
pnpm install             # install JS dependencies
docker compose up -d     # start PostgreSQL
pnpm --filter @zctl/core db:migrate
pnpm --filter @zctl/core dev
pnpm typecheck && pnpm test
cd agents/go-agent && go build ./...

Repository structure

apps/
├── core/              backend API and WebSocket server
└── cli/               operator CLI

agents/
└── go-agent/          Go agent

packages/
├── protocol/          shared message types
├── config/            env schema
└── shared/            utilities

About

self-hosted remote machine orchestration

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages