Skip to content
kastell-ops logo

Kastell Architecture

kastell-ops

Kastell CLI patterns, architecture, anti-patterns, and decision trees. Use automatically when working in Kastell codebase or when asked about Kastell server infrastructure, security audit, hardening, lock, provision, or provider management.

kastelldev/kastell0installs60stars

SKILL.md

Full skill instructions

Kastell Architecture

Kastell is a CLI toolkit for provisioning, securing, and managing self-hosted servers. TypeScript, ESM, strict mode. 31 CLI commands, 13 MCP tools, 4 cloud providers (hetzner, digitalocean, vultr, linode), 2 platform adapters (coolify, dokploy).

Live Context

Version: !node -e "import('fs').then(f=>console.log(JSON.parse(f.readFileSync('package.json','utf8')).version)).catch(()=>console.log('unknown'))" Registered servers: !kastell list 2>/​dev/​null || echo "No servers registered"

Architecture File Map

src/
  commands/     # 31 thin CLI wrappers (parse args + delegate only)
  core/         # Business logic (ALL computation here)
  providers/    # Cloud API: hetzner, digitalocean, vultr, linode
  adapters/     # Platform abstraction: coolify, dokploy
    interface.ts  # PlatformAdapter contract
    factory.ts    # getAdapter(), detectPlatform(), resolvePlatform()
  mcp/
    server.ts     # 13 tool registrations
    tools/        # Handler files (Zod schema + handler per tool)
  utils/        # ssh, config, cloudInit, modeGuard, migration
  types/        # ServerMode, ServerRecord, Platform
  constants.ts  # PROVIDER_REGISTRY (single source of truth)
  index.ts      # CLI entry point

Layer Rules

LayerPathResponsibilityRule
Commandssrc/​commands/Parse CLI args, call core, display outputZERO business logic
Coresrc/​core/All business logic, orchestrationNo UI/​chalk/​ora imports
Providerssrc/​providers/Cloud API calls per providerImplements cloud CRUD
Adapterssrc/​adapters/Platform-specific ops (Coolify/​Dokploy)Via PlatformAdapter interface
MCPsrc/​mcp/MCP server + tool handlersDelegates to core
Utilssrc/​utils/SSH, config, modeGuard, errorMapperShared infrastructure

Adapter Contract

Access adapters via getAdapter(platform) from src/​adapters/​factory.ts. Never import CoolifyAdapter or DokployAdapter directly in commands.

interface PlatformAdapter {
  readonly name: string;              // "coolify" | "dokploy"
  readonly port: number;              // 8000 (Coolify) | 3000 (Dokploy)
  readonly defaultLogService: string; // matches platform name
  readonly platformPorts: readonly number[]; // ports protected from firewall removal
  getCloudInit(serverName: string): string;
  healthCheck(ip: string, domain?: string): Promise<HealthResult>;
  createBackup(ip: string, serverName: string, provider: string): Promise<PlatformBackupResult>;
  getStatus(ip: string): Promise<PlatformStatusResult>;
  update(ip: string): Promise<UpdateResult>;
  restoreBackup?(ip, backupPath, manifest): Promise<PlatformRestoreResult>; // optional
}

Factory exports: getAdapter(platform), detectPlatform(ip), resolvePlatform(server)

Provider Registry

ProviderEnv KeyDisplay Name
hetznerHETZNER_TOKENHetzner Cloud
digitaloceanDIGITALOCEAN_TOKENDigitalOcean
vultrVULTR_TOKENVultr
linodeLINODE_TOKENLinode (Akamai)

PROVIDER_REGISTRY in src/​constants.ts is the single source of truth for providers.

What Do I Want to Add?

New CLI command

  1. src/​commands/<name>.ts — thin wrapper (parse + delegate, no logic)
  2. src/​core/<name>.ts — all business logic here
  3. src/​index.ts — register with program
  4. src/​__tests__/ — test core, not command

New audit check

  1. src/​core/​audit/<category>/ — add to existing category
  2. Update check catalog in src/​core/​audit/​catalog.ts
  3. No new command file needed — runs through kastell audit

New provider

  1. src/​providers/<name>.ts — implements base.ts contract
  2. src/​constants.ts — add to PROVIDER_REGISTRY
  3. No adapter changes — providers handle cloud API only

New MCP tool

  1. src/​mcp/​tools/​server<Name>.ts — Zod schema + handler
  2. src/​mcp/​server.ts — import + registerTool()
  3. Annotations: readOnlyHint / destructiveHint / idempotentHint

Key Conventions

  • ESM project ("type": "module") — import, not require
  • KASTELL_SAFE_MODE + isSafeMode() = destructive operation guard
  • assertValidIp() before every SSH operation
  • sanitizedEnv for subprocess calls
  • sanitizeResponseData() whitelist approach for API error responses
  • Config dir: user home kastell/ directory (auto-migrated from legacy name)
  • PROVIDER_REGISTRY = single source of truth for providers
  • withProviderErrorHandling HOF for consistent provider error handling
  • describe.each with jest.resetAllMocks() (not clearAllMocks())

Scripts (Deterministic)

Run without LLM — deterministic analysis scripts:

# Parse audit JSON into 5 security domain summaries
kastell audit --server myserver --json > /​tmp/​audit.json
scripts/​parse_audit.sh /​tmp/​audit.json

# Generate fleet-wide server score table
kastell fleet --json > /​tmp/​fleet.json
scripts/​fleet_report.sh /​tmp/​fleet.json

# Compare audit check count vs test coverage
scripts/​check_coverage.sh

Reference Files