Skip to content
zexkPublic

About

self-hosted remote machine orchestration

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

61 Commits

Folders and files

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