Skip to content

[r5n-64] hydra cli#16

Merged
ice-chillios merged 22 commits into
developfrom
r5n-64-hydra-cli
Apr 2, 2026
Merged

[r5n-64] hydra cli#16
ice-chillios merged 22 commits into
developfrom
r5n-64-hydra-cli

Conversation

@deralaxo

@deralaxo deralaxo commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the Hydra CLI — a tool for managing fleets of GitHub self-hosted runners from a single command. Hydra handles downloading, registering, starting, stopping, and removing runners with disk-optimized storage using hard links and symlinks, reducing disk usage from ~558 MB per runner to near-zero for additional runners.

Changes

Features

  • CLI scaffold — Entry point, base command, config types, constants following the same patterns as sisyphus
  • init command — Interactive and non-interactive setup with profile creation, URL validation, --force overwrite guard, and profile name prompt in interactive mode
  • create command — Provisions N runners with per-runner spinner output, "ensure total count" semantics (fills gaps, skips existing), and config tracking. Accepts [profile] [count] positionals
  • status command — Shows all runners across all profiles with color-coded status (running/registered/unknown), PID info, natural sort order, and compact single-block output
  • start / stop commands — Start/stop runners with status-aware filtering. Interactive mode shows multiselect with only actionable runners (stopped for start, running for stop)
  • remove command — Stops, deregisters, and cleans up runners. Accepts [profile] [ids...] positionals
  • profile list — Detailed profile view in boxed format showing URL, provider, OS, labels, runner counts, and default indicator
  • profile remove — Removes a profile with confirmation, automatically stops and deregisters all associated runners
  • profile default — Set the default profile used when no profile is specified
  • Named profileshydra init prompts for profile name interactively, or accepts it via --profile flag in direct mode. defaultProfile is set automatically on first init or when missing

CLI-Core Enhancement

  • Interactive mode error recoveryExit errors thrown during interactive subcommand execution now return to the main menu instead of terminating the CLI. Applies to all CLIs using @r5n/cli-core

GitHub Runner Provider

  • Download — Fetches latest runner tarball from GitHub releases, caches by version in shared directory
  • Hard-link optimizationbin/ directory (84 MB) is hard-linked per runner (0 bytes extra), externals/ (345 MB) is symlinked. 10 runners use ~430 MB instead of ~5.5 GB
  • Registration — Fetches short-lived registration tokens via gh CLI, runs config.sh --unattended quietly
  • Process management — Spawns run.sh with proc.unref() so CLI exits immediately, tracks PIDs in .pid files, kills process tree via pkill -f on stop
  • Removal — Stops processes before deregistering, fetches removal tokens from GitHub API, handles BOM in .runner JSON files

CLI Design

  • Positional profile argument — All commands use [profile] [ids...] positionals instead of --profile flag (e.g., hydra start default runner-1)
  • Implicit defaults — Omitting profile uses the config's defaultProfile, omitting IDs targets all runners
  • Unified profile resolutionresolveProfile() handles empty profiles check, default fallback, and existence validation in one place. Used by create, start, stop, remove, profile default, and profile remove
  • Interactive prompts — All commands fully functional in interactive mode with profile selection, runner multiselect, and count prompts
  • Shared utilitiesselectProfile() for interactive profile picker (auto-selects when only one), resolveProfile() for validated profile lookup, resolveRunnerIds() for direct mode ID resolution

Bug Fixes

  • Process lifecycleproc.unref() prevents CLI from hanging, pkill -f <absDir> kills entire process tree (bash -> run-helper -> Runner.Listener)
  • Runner removal stops processes — Runners are stopped before deregistration to prevent orphaned processes
  • Consistent error messages — All commands show "No profiles configured" when no profiles exist, instead of "Profile 'default' not found"
  • Config-aware default profile — Hints and fallbacks use config.defaultProfile instead of hardcoded DEFAULT_PROFILE constant
  • Symlink resolutionresolve() for absolute paths so symlinks work correctly across directories
  • BOM handling — GitHub runner writes .runner with UTF-8 BOM, stripBom() handles JSON parsing
  • Config.sh cwd — Must run with cwd set to runner dir, not via absolute path, because it resolves BASH_SOURCE
  • Idempotent start — Checks existing PID before spawning to prevent duplicate processes
  • CLI hints — Init and create hints correctly reference positional profile syntax

Testing

  • Full lifecycle tested on test-repo: init -> create runners -> start all -> status (verify running + PIDs) -> stop all -> status (verify registered) -> remove all
  • Interactive mode tested: All commands work from the interactive menu with proper profile selection, runner multiselect, and error recovery (returns to menu on failure)
  • Multi-profile tested: Multiple profiles with separate runner pools, correct grouping in status and profile list, default profile switching
  • Disk optimization verified: du confirms 0 bytes for hard-linked bin/ directories, link count = 2
  • Edge cases tested: create with existing runners (gap filling), remove single vs all, start already-running runners, stop already-stopped runners, invalid URLs, zero runner count, existing config overwrite, natural sort with 10+ runners, no profiles configured error consistency

@deralaxo
deralaxo requested a review from ice-chillios March 31, 2026 18:16
@linear

linear Bot commented Mar 31, 2026

Copy link
Copy Markdown
R5N-64 Hydra CLI

TBA

@deralaxo
deralaxo marked this pull request as ready for review April 2, 2026 01:03
@ice-chillios
ice-chillios merged commit b06781a into develop Apr 2, 2026
0 of 5 checks passed
@ice-chillios
ice-chillios deleted the r5n-64-hydra-cli branch April 2, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants