Flashduty Runner
English | 中文
Flashduty Runner is a lightweight, secure agent that runs in your environment to execute commands and access resources on behalf of Flashduty AI SRE platform.
How It Works
┌──────────────────┐ WebSocket (TLS) ┌────────────────────┐
│ Flashduty AI │ ◄─────────────────────────► │ Flashduty Runner │
│ SRE Platform │ │ (Your Server) │
└──────────────────┘ └────────────────────┘
│
▼
┌────────────────────┐
│ • Execute Commands │
│ • Read/Write Files │
│ • MCP Tool Calls │
└────────────────────┘
The runner establishes a persistent WebSocket connection to Flashduty cloud, receives task requests, executes them locally, and returns results.
Security
All code is open source - you can audit every line of code to verify exactly what the runner does.
Multi-layer Security Design
| Layer |
Protection |
| Transport |
TLS-encrypted WebSocket, token authentication |
| Command Execution |
Shell parsing to prevent injection attacks (e.g., cmd1; cmd2) |
| Permission Control |
Configurable glob-based command whitelist/blacklist |
| File System |
Operations sandboxed to workspace root, symlink escape protection |
Permission Configuration
The runner uses glob pattern matching for command permissions. You have full control over what commands can be executed.
Option 1: Strict Mode (Recommended for shared environments)
Only allow specific commands explicitly:
permission:
bash:
"*": "deny" # Deny all by default
"kubectl get *": "allow"
"kubectl describe *": "allow"
"kubectl logs *": "allow"
"cat *": "allow"
"ls *": "allow"
Option 2: Trust Mode (For dedicated/isolated environments)
If the runner is deployed in an isolated environment dedicated to AI operations, you can choose to trust the AI model's judgment:
permission:
bash:
"*": "allow" # Trust AI model
"rm -rf /": "deny" # Block catastrophic commands if desired
This mode is suitable when:
- The runner runs in an isolated VM/container with limited blast radius
- You trust the AI model's capabilities and want maximum flexibility
- Quick incident response is more important than restrictive permissions
Option 3: Read-Only Mode (For monitoring only)
permission:
bash:
"*": "deny"
"cat *": "allow"
"head *": "allow"
"tail *": "allow"
"ls *": "allow"
"grep *": "allow"
"ps *": "allow"
"df *": "allow"
"free *": "allow"
Quick Start
One-line Install / Update (Linux + macOS)
# Install or update (prompts for token if not already set)
curl -fsSL https://raw.githubusercontent.com/flashcatcloud/flashduty-runner/main/install.sh | sudo bash
# Non-interactive (pass token on the sudo line)
curl -fsSL https://raw.githubusercontent.com/flashcatcloud/flashduty-runner/main/install.sh | sudo TOKEN=wnt_xxx bash
# Pin a specific version
curl -fsSL https://raw.githubusercontent.com/flashcatcloud/flashduty-runner/main/install.sh | sudo VERSION=v0.0.5 bash
# Uninstall (keeps /etc/flashduty-runner/ config)
curl -fsSL https://raw.githubusercontent.com/flashcatcloud/flashduty-runner/main/install.sh | sudo bash -s -- --uninstall
# Uninstall and wipe everything (binary, config, workspace, service user)
curl -fsSL https://raw.githubusercontent.com/flashcatcloud/flashduty-runner/main/install.sh | sudo bash -s -- --purge
On Linux with systemd the script also creates a flashduty service user, writes /etc/flashduty-runner/env, installs a hardened unit, and runs systemctl enable --now. On macOS and non-systemd Linux it installs the binary only. Run with --help for all flags.
Runners upgrade themselves regardless of how they were installed. Flashduty pushes new releases over the runner's existing control channel; the runner verifies the download, atomically swaps its binary, and restarts into the new version — automatically rolling back if the new build can't reconnect within a probation window. When the runner's own directory is writable (the installer's /var/lib/flashduty-runner/bin layout, or any user-writable location), the swap happens in place. When it is not — e.g. a manual install in root-owned /usr/local/bin run as a regular user — the upgrade lands at the canonical state-home path (<runner home>/bin/flashduty-runner, default ~/.flashduty/bin/) instead, and on every later start the original binary hands off to the newest canonical version automatically. One consequence of that fallback: the file in /usr/local/bin keeps its installed timestamp/version on disk, while flashduty-runner version and the running service always reflect the current version.
The auto-update download URL is chosen by the backend, not remembered from how the runner was installed (the install-time MIRROR_URL is never persisted). A self-hosted or air-gapped deployment that installs via a private mirror must therefore also point the backend's install_script_url at that same mirror, or backend-pushed upgrades will resolve from the public GitHub release host.
Manual Binary Installation
# Linux (amd64)
curl -LO https://github.com/flashcatcloud/flashduty-runner/releases/latest/download/flashduty-runner_Linux_x86_64.tar.gz
tar -xzf flashduty-runner_Linux_x86_64.tar.gz
sudo mv flashduty-runner /usr/local/bin/
# Linux (arm64)
curl -LO https://github.com/flashcatcloud/flashduty-runner/releases/latest/download/flashduty-runner_Linux_arm64.tar.gz
tar -xzf flashduty-runner_Linux_arm64.tar.gz
sudo mv flashduty-runner /usr/local/bin/
# macOS (Apple Silicon)
curl -LO https://github.com/flashcatcloud/flashduty-runner/releases/latest/download/flashduty-runner_Darwin_arm64.tar.gz
tar -xzf flashduty-runner_Darwin_arm64.tar.gz
sudo mv flashduty-runner /usr/local/bin/
# macOS (Intel)
curl -LO https://github.com/flashcatcloud/flashduty-runner/releases/latest/download/flashduty-runner_Darwin_x86_64.tar.gz
tar -xzf flashduty-runner_Darwin_x86_64.tar.gz
sudo mv flashduty-runner /usr/local/bin/
Auto-upgrade works here too. A root-owned binary in /usr/local/bin cannot replace itself in place, so the first backend-pushed upgrade is installed to the runner's writable state home (~/.flashduty/bin/flashduty-runner by default) and the process restarts into it; from then on, starting /usr/local/bin/flashduty-runner transparently hands off to the newest self-updated version. To pin the installed version instead, run with --disable-auto-update.
Docker Installation
docker run -d \
--name flashduty-runner \
-e FLASHDUTY_RUNNER_TOKEN=ent_xxx \
-v /var/flashduty/workspace:/workspace \
registry.flashcat.cloud/public/flashduty-runner:latest
# With custom endpoint
docker run -d \
--name flashduty-runner \
-e FLASHDUTY_RUNNER_TOKEN=ent_xxx \
-e FLASHDUTY_RUNNER_URL=wss://custom.example.com/safari/environment/ws \
-v /var/flashduty/workspace:/workspace \
registry.flashcat.cloud/public/flashduty-runner:latest
Running
# Basic usage (token required)
flashduty-runner run --token ent_xxx
# Specify workspace directory
flashduty-runner run --token ent_xxx --workspace ~/projects
# Specify custom WebSocket endpoint
flashduty-runner run --token ent_xxx --url wss://custom.example.com/safari/environment/ws
# Check version
flashduty-runner version
Systemd Service (Linux)
Create /etc/systemd/system/flashduty-runner.service:
[Unit]
Description=Flashduty Runner
After=network.target
[Service]
Type=simple
User=flashduty
EnvironmentFile=/etc/flashduty-runner/env
ExecStart=/usr/local/bin/flashduty-runner run
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Create /etc/flashduty-runner/env:
FLASHDUTY_RUNNER_TOKEN=ent_xxx
# FLASHDUTY_RUNNER_URL=wss://custom.example.com/safari/environment/ws
# FLASHDUTY_RUNNER_HOME=/var/flashduty
sudo mkdir -p /etc/flashduty-runner
sudo vim /etc/flashduty-runner/env # add your token
sudo systemctl daemon-reload
sudo systemctl enable --now flashduty-runner
Skills
Skills are materialized lazily. When the agent calls the skill tool, the cloud sends sync_skill with (skill_name, checksum, zip_data?):
- Empty
zip_data = probe. The runner returns {cached: true, path} if <home>/skills/<name>/.checksum already matches the requested checksum and SKILL.md is present; otherwise {cached: false} and the cloud will retry with the bundle attached.
- Non-empty
zip_data = install. The runner wipes <home>/skills/<name>/, unzips, and writes .checksum. Each skill name has exactly one slot — no version retention. Builtin skills go through the same flow with their bundle sourced from the cloud's embedded FS instead of S3.
Inspect installed skills with ls ~/.flashduty/skills/.
Configuration Reference
Configuration is via command-line flags or environment variables (flags take precedence).
| Flag |
Env Variable |
Required |
Default |
Description |
--token |
FLASHDUTY_RUNNER_TOKEN |
Yes |
- |
Authentication token |
--url |
FLASHDUTY_RUNNER_URL |
No |
wss://api.flashcat.cloud/safari/environment/ws |
WebSocket endpoint |
--workspace |
FLASHDUTY_RUNNER_HOME |
No |
~/.flashduty |
Runner home directory. Skills land at <home>/skills/<name>/. FLASHDUTY_RUNNER_WORKSPACE is accepted as a deprecated alias for back-compat. |
--log-level |
FLASHDUTY_RUNNER_LOG_LEVEL |
No |
info |
Log level: debug, info, warn, error |
Troubleshooting
Connection Issues
| Symptom |
Cause |
Solution |
failed to connect |
Network issue |
Check firewall allows outbound port 443 |
authentication failed |
Invalid token |
Verify token in Flashduty console |
| Runner not showing online |
Connection dropped |
Check logs, verify token matches account |
# Test connectivity
curl -v https://api.flashcat.cloud/health
# Check runner logs
journalctl -u flashduty-runner -f
Permission Issues
| Symptom |
Cause |
Solution |
command denied |
Command not in whitelist |
Add pattern to permission.bash |
path escapes workspace |
Path traversal blocked |
Use paths within workspace_root |
Permission Pattern Rules:
- Patterns are matched in order, last match wins
* matches any characters
- Empty config defaults to deny all
Debug Mode
Enable debug logging to see detailed permission decisions:
flashduty-runner run --token ent_xxx --log-level debug
# Or via environment variable
export FLASHDUTY_RUNNER_LOG_LEVEL=debug
Contributing
We welcome contributions! Please see CONTRIBUTING.md.
License
Apache License 2.0 - see LICENSE.
Made with ❤️ by Flashcat