Skip to content

SGribanov/RunnerMonitor

Repository files navigation

RunnerMonitor

Русская версия

RunnerMonitor is a lightweight terminal UI and command-line tool for monitoring and controlling CI runners. It was built for a workstation that has multiple GitHub Actions runners across Windows and WSL/Linux, with a planned move to a dedicated runner host on the local network.

The tool combines local runner lifecycle state with GitHub runner state, queue information, repository ownership, safe lifecycle commands, and read-only visibility for GitHub-hosted jobs and remote self-hosted runners.

What It Does

  • Discovers GitHub Actions runner folders from configured Windows, WSL, and Linux runner roots.
  • Shows the project/repository each runner belongs to.
  • Merges local service/process state with GitHub status from gh api.
  • Shows queued and in-progress GitHub-hosted workflow jobs as read-only rows.
  • Shows self-hosted runners registered in monitored GitHub repositories even when their runner folders are not local to this machine.
  • Displays busy state, queued workflow count, and stale queued workflow count.
  • Auto-refreshes TUI state every 5 seconds while keeping the previous table visible during refresh.
  • Checks GitHub Releases once on TUI startup and shows a concise update notice when a newer version is available.
  • Throttles repeated GitHub status lookups during TUI auto-refresh to reduce process and API overhead.
  • Starts, stops, restarts, clears, removes, and reprovisions selected runners.
  • Keeps destructive operations guarded by dry-runs, busy-runner checks, and explicit confirmation.
  • Provides project-scoped commands that Codex or an operator can run before a push or CI wait.
  • Supports saved SSH remote-runner host profiles for the future dedicated runner machine.
  • Keeps OneDev support as a future provider-oriented direction.

Stack

  • Go 1.26+
  • Charmbracelet Bubble Tea for the TUI
  • Charmbracelet Bubbles for table and text input widgets
  • Charmbracelet Lip Gloss for terminal styling
  • GitHub CLI (gh) for GitHub Actions runner and workflow data
  • Windows PowerShell for Windows service discovery/control
  • WSL and Linux systemd for Linux runner discovery/control
  • SSH for remote runner-host access

Requirements

  • Go 1.26+

  • GitHub CLI authenticated with access to monitored repositories:

    gh auth status
  • Windows PowerShell for local Windows service discovery.

  • WSL for current-workstation Linux runner discovery.

  • git for current-project repository detection.

Quick Start

Download the latest ready-to-run Windows package from GitHub Releases:

RunnerMonitor-v0.6.0-windows-x64.zip

Extract the ZIP and start the TUI:

powershell -NoProfile -ExecutionPolicy Bypass -File .\runner-monitor.ps1

Or build locally from source:

Build the executable and create the app-local config:

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build.ps1
.\runner-monitor.ps1 --show-config

Open the TUI:

.\runner-monitor.ps1

Print inventory once:

.\runner-monitor.ps1 --once

Audit runner cleanup candidates:

.\runner-monitor.ps1 --audit

Start runners for the current GitHub project:

.\runner-monitor.ps1 --start-current

Configuration

RunnerMonitor reads host-specific settings from runner-monitor.json next to the executable. For the default local build, the config path is:

C:\Repos\RunnerMonitor\bin\runner-monitor.json

Create or inspect it:

.\runner-monitor.ps1 --init-config
.\runner-monitor.ps1 --show-config

--show-config masks wslSudoPassword as <set> or <empty> and never prints the actual value.

Default config:

{
  "projectsRoot": "C:\\Repos",
  "windowsRunnerRoots": [
    "C:\\Runners"
  ],
  "wslRunnerRoots": [
    "/home/gsv777/Runners"
  ],
  "linuxRunnerRoots": [
    "/opt/Runners",
    "/srv/Runners"
  ],
  "githubHostedRepos": [],
  "tuiRefreshIntervalSeconds": 5,
  "wslSudoPassword": ""
}

Settings fields:

Field Purpose
projectsRoot Root directory used by --project, for example C:\Repos.
windowsRunnerRoots Windows runner root folders to scan and treat as safe for runner-folder deletion.
wslRunnerRoots WSL runner root folders to scan.
linuxRunnerRoots Linux runner root folders used on a native Linux runner host.
githubHostedRepos Extra owner/repo values or GitHub URLs whose GitHub-hosted workflow jobs should be monitored even when no local runner exists.
tuiRefreshIntervalSeconds TUI auto-refresh interval in seconds. Defaults to 5 when omitted or invalid.
wslSudoPassword Direct WSL sudo password value for fallback service control. Keep it only in the app-local config.

For tests or special launch contexts, override the config path:

$env:RUNNER_MONITOR_CONFIG = "D:\Temp\runner-monitor.json"

Do not commit a real runner-monitor.json or sudo password.

TUI Usage

Inside the TUI:

Runner information auto-refreshes every 5 seconds by default. Change tuiRefreshIntervalSeconds in runner-monitor.json to use a different interval. The refresh command starts an immediate refresh; when existing data is available, the table stays visible until the new data arrives. Automatic refreshes reuse recent GitHub status briefly to avoid shelling out to gh for every repository every 5 seconds; manual refresh still fetches fresh GitHub state.

GitHub-hosted Actions jobs appear as github host rows with hosted local state. RunnerMonitor discovers hosted jobs for repositories already represented by local runners and for repositories listed in githubHostedRepos. These rows are read-only: lifecycle, cleanup, remove, delete, and reprovision commands are intentionally skipped because GitHub-hosted runners are ephemeral GitHub infrastructure, not local machines.

Self-hosted runners returned by GitHub for monitored repositories also appear when no matching local runner folder is discovered. These partner or otherwise remote rows use github as host, remote as local state, and (not local) as path. They expose GitHub status, busy state, labels, OS, version, and queue counts. Local lifecycle, cleanup, logs, and reprovision commands are skipped because RunnerMonitor cannot control another machine's runner folder or service. remove [N] confirm and delete [N] confirm can unregister these remote-only self-hosted runner registrations through GitHub API after explicit confirmation; no local folder or service action is attempted.

On startup, the TUI performs one best-effort GitHub Releases check through gh. If the internet is unavailable or the check fails, RunnerMonitor starts normally and does not show an error. If a newer release exists, a short update notice appears above the command input.

Press h or ?, or type help, to open the in-app help panel. It gives a short command reference without leaving the TUI. Esc closes help first; when help is closed, Esc exits the TUI.

Command Description
h, ?, help Show or hide the in-app help panel.
refresh Refresh local and GitHub runner state immediately.
Arrow keys Select a runner row.
start [N] Start runner N, or the selected runner if N is omitted.
stop [N] Stop runner N, or the selected runner if N is omitted.
start all Start every controllable runner in the current inventory.
stop all Stop every controllable runner in the current inventory.
restart [N] Restart runner N, or the selected runner if N is omitted.
force-stop [N] Stop even when GitHub reports the runner as busy. Use carefully.
force-restart [N] Restart even when GitHub reports the runner as busy. Use carefully.
clear [N] Safely clear idle runner work files for runner N or selected row.
clear idle Clear all idle runners. Busy runners are skipped.
auto-clear on Run safe idle cleanup after refresh.
auto-clear off Disable refresh-triggered auto cleanup.
remove [N] Dry-run GitHub runner unregistration for runner N or selected row.
remove [N] confirm Execute runner unregistration after confirmation.
delete [N] confirm Unregister and delete a safe runner folder; for remote-only GitHub rows, unregister only.
logs [N] Open runner logs for runner N or selected row.
connect remote NAME Open the saved remote RunnerMonitor TUI over SSH.
q, quit, exit, Esc, Ctrl+C Exit the TUI.

The table is resize-aware. On narrow terminals, low-priority columns are hidden before the main project, runner, status, busy, and queue columns are allowed to drift. In the TUI table, the Busy column uses plain true/false text so status columns stay aligned and readable.

After lifecycle commands such as start, stop, start all, stop all, and restart, the TUI starts an immediate status refresh so the table catches up with the service and GitHub state without waiting for the next auto-refresh tick. The command input accepts d as the first typed character, so delete N confirm can be typed normally.

CLI Command Reference

Inventory and audit:

.\runner-monitor.ps1 --once
.\runner-monitor.ps1 --audit

Project-scoped lifecycle:

.\runner-monitor.ps1 --start-repo SGribanov/DeltaG
.\runner-monitor.ps1 --stop-repo SGribanov/DeltaG
.\runner-monitor.ps1 --restart-repo SGribanov/DeltaG

Single-runner lifecycle:

.\runner-monitor.ps1 --start-runner ideabox-runner --repo SGribanov/IdeaBox
.\runner-monitor.ps1 --stop-runner ideabox-runner --repo SGribanov/IdeaBox
.\runner-monitor.ps1 --restart-runner ideabox-runner --repo SGribanov/IdeaBox

Current Git project lifecycle:

.\runner-monitor.ps1 --start-current
.\runner-monitor.ps1 --stop-current
.\runner-monitor.ps1 --restart-current

start for service-managed runners is a readiness check, not just a service request. For systemd-backed runners, including WSL runners, RunnerMonitor enables the unit, starts it, waits for the local service to become active, and then waits until GitHub reports the runner as online.

stop for service-managed runners waits for the local service to stop and then waits until GitHub reports the runner as offline. When a Windows service runner is controlled from a non-elevated TUI, RunnerMonitor opens an elevated PowerShell helper through UAC; after approval, the helper performs the stop and the TUI keeps refreshing until the row catches up.

Safe cleanup:

.\runner-monitor.ps1 --clear-repo SGribanov/DeltaG
.\runner-monitor.ps1 --clear-current
.\runner-monitor.ps1 --clear-idle
.\runner-monitor.ps1 --clear-runner ideabox-runner

Autostart policy:

.\runner-monitor.ps1 --disable-autostart

Config:

.\runner-monitor.ps1 --init-config
.\runner-monitor.ps1 --init-config --overwrite-config
.\runner-monitor.ps1 --show-config

Remote host profiles:

.\runner-monitor.ps1 --configure-remote runnerbox
.\runner-monitor.ps1 --connect-remote runnerbox

Runner removal is dry-run by default:

.\runner-monitor.ps1 --remove-runner ideabox-runner --repo SGribanov/IdeaBox
.\runner-monitor.ps1 --remove-runner ideabox-runner --repo SGribanov/IdeaBox --confirm
.\runner-monitor.ps1 --remove-runner ideabox-runner --repo SGribanov/IdeaBox --confirm --delete-folder

Runner reprovisioning configures an existing prepared runner distribution folder. The --project value is a project folder name under configured projectsRoot.

.\runner-monitor.ps1 --add-runner runner-monitor-win --project RunnerMonitor --runner-folder C:\Runners\SGribanov-RunnerMonitor\runner-monitor-win --labels "self-hosted,Windows,X64"
.\runner-monitor.ps1 --add-runner runner-monitor-win --project RunnerMonitor --runner-folder C:\Runners\SGribanov-RunnerMonitor\runner-monitor-win --labels "self-hosted,Windows,X64" --confirm --replace

Install and start the runner service after configuration:

.\runner-monitor.ps1 --add-runner runner-monitor-win --project RunnerMonitor --runner-folder C:\Runners\SGribanov-RunnerMonitor\runner-monitor-win --labels "self-hosted,Windows,X64" --confirm --replace --service

Remote Runner Host

When runners move to a dedicated machine on the network, run RunnerMonitor on that machine and connect to it over SSH.

Configure or update a remote host profile:

.\runner-monitor.ps1 --configure-remote runnerbox

The prompt asks for:

  • remote name;
  • SSH host or alias;
  • host OS: windows or linux;
  • remote RunnerMonitor path;
  • default remote project path.

Open the saved remote TUI:

.\runner-monitor.ps1 --connect-remote runnerbox

Equivalent Windows SSH command:

ssh -t runnerbox "powershell -NoProfile -ExecutionPolicy Bypass -File C:/Repos/RunnerMonitor/runner-monitor.ps1"

Start runners for the current remote project before Codex pushes or waits for CI:

ssh runnerbox "cd C:/Repos/DeltaG; powershell -NoProfile -ExecutionPolicy Bypass -File C:/Repos/RunnerMonitor/runner-monitor.ps1 --start-current"

Linux remote host example:

ssh -t runnerbox "cd /opt/RunnerMonitor && ./runner-monitor"
ssh runnerbox "cd /srv/DeltaG && /opt/RunnerMonitor/runner-monitor --start-current"

Saved remote profiles are stored separately from the app-local runner settings under the current user's config directory as RunnerMonitor\remote-hosts.json.

Runner Folder Layout

The preferred current layout is:

C:\Runners\<owner>-<repo>\<runner-name>
/home/gsv777/Runners/<owner>-<repo>/<runner-name>

For a future dedicated Linux host, use a stable shared root such as:

/opt/Runners/<owner>-<repo>/<runner-name>
/srv/Runners/<owner>-<repo>/<runner-name>

Runner folder migration is tracked separately and should be performed runner-by-runner. Do not move or delete busy runners without explicit approval.

Safety Model

  • Busy runners are protected by default.
  • Removal and reprovisioning are dry-run by default.
  • Folder deletion requires --delete-folder and is limited to configured safe runner roots.
  • Folder deletion rejects configured root folders themselves and normalized path traversal.
  • Cleanup removes safe generated content such as _work contents and runner installer archives, while preserving runner registration and binaries.
  • --show-config never prints the real WSL sudo password.
  • Local app config files and secrets must not be committed.
  • GitHub runner registration/remove token output is redacted from command errors; token passing is kept out of parent process arguments where the runner config scripts allow it.

Build And Test

Run tests:

go test ./...

Build:

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build.ps1

Generate icon assets:

uv run --with pillow python .\scripts\generate-icon-assets.py
go run github.com/akavel/rsrc@v0.10.2 -arch amd64 -ico .\assets\runner-monitor-hourglass.ico -o .\cmd\runner-monitor\runner-monitor_windows_amd64.syso

Repository Layout

cmd/runner-monitor/     Application entry point
internal/app/           Discovery, GitHub integration, lifecycle, TUI, cleanup
scripts/                Build, migration, cleanup, and automation helpers
assets/                 Hourglass icon assets
reports/                Runner audit reports
research/               Long-lived project insights
tasks/                  Per-issue implementation plans and status files

Contributing

See CONTRIBUTING.md. Contributions should preserve the safety model: no unguarded destructive runner operations, no committed secrets, and no silent changes to runner registrations. Please also follow the Code of Conduct.

Security

See SECURITY.md. Do not open public issues with secrets, runner tokens, sudo passwords, or private hostnames.

License

RunnerMonitor is licensed under the MIT License.

About

Lightweight TUI and CLI for monitoring and controlling self-hosted CI runners

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors