RunRun is a web-based task execution platform written in Go that allows scheduling, executing, and monitoring shell command tasks through a modern web interface with real-time streaming updates.
- 🚀 Task Execution - Execute shell commands and multi-step tasks with configurable timeouts
- 📊 Real-time Monitoring - WebSocket-based live log streaming with automatic updates
- 🔐 Secure Authentication - JWT-based authentication with bcrypt password hashing
- 🛡️ CSRF Protection - Built-in CSRF token validation for state-changing operations
- ⏱️ Rate Limiting - Configurable rate limiting to prevent abuse
- 📝 Task History - Complete execution history with detailed logs and status tracking
- 🎨 Modern UI - Clean, responsive web interface built with Templ and Tailwind CSS
- 🔄 Concurrent Execution - Worker pool-based concurrent task processing
- 📁 Log Management - Automatic log file creation and management with tail support
- 🔍 Health Checks - Built-in health and readiness endpoints for monitoring
- Backend: Go 1.25+ with Chi router
- Templating: Templ for type-safe HTML templates
- Styling: Tailwind CSS with custom configuration
- Authentication: JWT tokens with bcrypt password hashing
- Real-time: WebSocket for live log streaming
- Testing: Testify for comprehensive test coverage
- Go 1.25 or higher
- Node.js (for Tailwind CSS build)
- Git
# Clone the repository
git clone https://github.com/sgaunet/runrun.git
cd runrun
# Install development tools (templ CLI)
task install-tools
# Build everything (templates, CSS, and binary)
task build-all
# The binary will be in the current directory
./runrun versiongo install github.com/sgaunet/runrun/cmd/runrun@latestCreate a configuration file config.yaml:
server:
port: 8080
log_level: "info"
max_concurrent_tasks: 5
session_timeout: 24h
log_directory: "./logs"
shutdown_timeout: 5m
auth:
jwt_secret: "your-secret-key-at-least-32-characters-long"
users:
- username: "admin"
password: "$2a$10$YourBcryptHashedPasswordHere"
tasks:
- name: "hello-world"
description: "Simple hello world task"
tags: ["demo"]
timeout: 1m
steps:
- name: "Echo Hello"
command: "echo 'Hello, World!'"./runrun hash-password yourpasswordCopy the output hash to your config file.
./runrun server --config config.yaml --port 8080Open your browser and navigate to:
http://localhost:8080
Login with your configured username and password.
| Option | Description | Default |
|---|---|---|
port |
HTTP server port | 8080 |
log_level |
Logging level (debug, info, warn, error) | info |
max_concurrent_tasks |
Maximum concurrent task executions | 5 |
session_timeout |
JWT session timeout duration | 24h |
log_directory |
Directory for task execution logs | ./logs |
shutdown_timeout |
Graceful shutdown timeout | 5m |
auth:
jwt_secret: "minimum-32-characters-secret-key"
users:
- username: "admin"
password: "$2a$10$..." # BCrypt hash
- username: "user"
password: "$2a$10$..."Important: JWT secret must be at least 32 characters long for security.
tasks:
- name: "backup-database"
description: "Backup PostgreSQL database"
tags: ["database", "backup"]
timeout: 30m
working_directory: "/var/backups"
environment:
DB_HOST: "localhost"
DB_NAME: "myapp"
steps:
- name: "Create backup directory"
command: "mkdir -p /var/backups/$(date +%Y%m%d)"
- name: "Run pg_dump"
command: "pg_dump -h $DB_HOST $DB_NAME > backup.sql"
- name: "Compress backup"
command: "gzip backup.sql"| Field | Required | Description |
|---|---|---|
name |
Yes | Unique task identifier |
description |
No | Human-readable description |
tags |
No | Array of tags for categorization |
timeout |
No | Maximum execution time (default: 5m) |
working_directory |
No | Working directory for command execution |
environment |
No | Environment variables (key-value pairs) |
steps |
Yes | Array of commands to execute sequentially |
# Install development dependencies
task install-tools
# Generate template code from .templ files
task generate
# Build Tailwind CSS
task build-css
# Build the application
task build
# Run all tests
task test
# Run with hot reload
task watchrunrun/
├── cmd/runrun/ # CLI application entry point
├── configs/ # Example configuration files
├── internal/
│ ├── auth/ # Authentication & authorization
│ ├── config/ # Configuration management
│ ├── csrf/ # CSRF protection
│ ├── executor/ # Task execution engine
│ ├── middleware/ # HTTP middleware
│ ├── ratelimit/ # Rate limiting
│ ├── server/ # HTTP server & handlers
│ ├── templates/ # Templ templates
│ └── websocket/ # WebSocket hub & client management
├── Taskfile.yml # Task runner commands
└── tailwind.config.js # Tailwind CSS configuration
# Run all tests
go test ./...
# Run tests with coverage
go test ./... -cover
# Run specific package tests
go test ./internal/auth/...
# Run tests verbosely
go test -v ./...POST /login
{
"username": "admin",
"password": "yourpassword"
}Response:
{
"message": "Login successful"
}Sets session cookie for authentication.
POST /logout
Clears session and invalidates token.
POST /tasks/:name/execute
Requires authentication. CSRF token required.
Response:
{
"execution_id": "abc12345-6789-...",
"message": "Task queued for execution"
}GET /api/status
Get status of all tasks and system statistics.
Response:
{
"tasks": [
{
"name": "hello-world",
"description": "Simple hello world task",
"tags": ["demo"],
"status": "success",
"last_run": "2025-01-15T10:30:00Z",
"duration": 5.0
}
],
"stats": {
"total": 10,
"running": 1,
"success": 7,
"failed": 2,
"queued": 0
}
}GET /logs/:executionID
View execution logs in browser.
GET /logs/:executionID/download
Download execution logs as file.
GET /logs/:executionID/poll?lines=N
Poll for log entries. Optional lines parameter returns last N lines.
GET /health
Overall health check.
GET /health/ready
Readiness probe for Kubernetes/container orchestration.
GET /health/live
Liveness probe.
Connect to WebSocket endpoint with authentication:
ws://localhost:8080/logs/ws/{executionID}
Authentication via:
- Session cookie (from web login)
- Authorization header:
Bearer {jwt-token}
Client → Server:
Subscribe to execution:
{
"type": "subscribe",
"execution_id": "abc12345-..."
}Unsubscribe:
{
"type": "unsubscribe",
"execution_id": "abc12345-..."
}Ping:
{
"type": "pong"
}Server → Client:
Log message:
{
"type": "log",
"execution_id": "abc12345-...",
"data": {
"line": "Step completed successfully",
"timestamp": "2025-01-15T10:30:05Z",
"level": "info"
},
"timestamp": "2025-01-15T10:30:05Z"
}Subscription confirmed:
{
"type": "subscribed",
"execution_id": "abc12345-...",
"timestamp": "2025-01-15T10:30:00Z"
}Error:
{
"type": "error",
"error": "Error message",
"timestamp": "2025-01-15T10:30:00Z"
}Ping (heartbeat):
{
"type": "ping",
"timestamp": "2025-01-15T10:30:00Z"
}- All passwords are hashed using bcrypt (cost factor 10)
- JWT tokens for session management with configurable timeout
- Two-tier token validation: JWT signature + session store (enables revocation)
- Session cookies are HTTP-only and use SameSite=Strict
- CSRF tokens required for all state-changing operations (POST, PUT, DELETE)
- Tokens can be provided via:
X-CSRF-Tokenheadercsrf_tokenform field
- Token validation uses constant-time comparison
- Configurable rate limiting on login endpoint (default: 5 attempts per 15 minutes)
- IP-based visitor tracking
- Automatic cleanup of old visitor entries
- Manual authentication in WebSocket handler (outside middleware chain)
- Origin validation prevents Cross-Site WebSocket Hijacking (CSWSH)
- Supports both session cookie and Authorization Bearer token
- Content-Security-Policy (CSP)
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- X-XSS-Protection
- Referrer-Policy
- Permissions-Policy
- Strict-Transport-Security (HSTS) for HTTPS
Create a Dockerfile:
FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o runrun cmd/runrun/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/runrun .
COPY configs/production.yaml ./config.yaml
EXPOSE 8080
CMD ["./runrun", "server", "--config", "config.yaml"]Build and run:
docker build -t runrun:latest .
docker run -p 8080:8080 -v $(pwd)/config.yaml:/root/config.yaml runrun:latestExample deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: runrun
spec:
replicas: 2
selector:
matchLabels:
app: runrun
template:
metadata:
labels:
app: runrun
spec:
containers:
- name: runrun
image: runrun:latest
ports:
- containerPort: 8080
env:
- name: SERVER_PORT
value: "8080"
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: config
mountPath: /config
volumes:
- name: config
configMap:
name: runrun-configCreate /etc/systemd/system/runrun.service:
[Unit]
Description=RunRun Task Execution Platform
After=network.target
[Service]
Type=simple
User=runrun
Group=runrun
WorkingDirectory=/opt/runrun
ExecStart=/opt/runrun/runrun server --config /etc/runrun/config.yaml
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable runrun
sudo systemctl start runrunSolution: Generate a strong secret:
openssl rand -base64 32Add to your config file.
Causes:
- Origin validation blocking connection
- Missing or invalid authentication token
- Execution ID doesn't exist
Solution:
- Check browser console for errors
- Verify authentication (login first)
- Ensure execution ID is correct
Causes:
- Task timeout not configured
- Command waiting for input
- Queue is full
Solution:
- Set appropriate timeout in task configuration
- Ensure commands don't require interactive input
- Increase
max_concurrent_tasksin config
Solution: Increase max_concurrent_tasks in server configuration:
server:
max_concurrent_tasks: 10 # Increase from default 5Solution: Regenerate template code:
task generate
task buildSolution: Rebuild Tailwind CSS:
task build-cssEnable debug logging:
server:
log_level: "debug"Q: Can I run tasks in parallel?
A: Yes, RunRun uses a worker pool. The number of concurrent tasks is controlled by max_concurrent_tasks in the configuration.
Q: How do I add new tasks?
A: Add them to your config.yaml file in the tasks section and restart the server.
Q: Can I trigger tasks via API?
A: Yes, use the POST /tasks/:name/execute endpoint with proper authentication.
Q: How long are logs kept?
A: Logs are stored permanently in the log_directory. Implement your own rotation/cleanup if needed.
Q: Can I use environment variables in commands?
A: Yes, define them in the task's environment section or use system environment variables.
Q: Is it safe to expose RunRun to the internet?
A: RunRun has security features (auth, CSRF, rate limiting), but additional hardening is recommended:
- Use HTTPS (reverse proxy with SSL/TLS)
- Implement network-level access controls
- Regular security updates
- Strong passwords and secrets
Q: How do I backup my configuration?
A: Simply backup your config.yaml file. All task definitions and user credentials are stored there.
Q: Can I integrate with CI/CD pipelines?
A: Yes, use the REST API to trigger tasks. Example with curl:
# Login and get session cookie
curl -c cookies.txt -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"yourpass"}'
# Execute task
curl -b cookies.txt -X POST http://localhost:8080/tasks/my-task/executeContributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Write tests for new functionality
- Ensure tests pass:
task test - Format code:
go fmt ./... - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
# Clone your fork
git clone https://github.com/yourusername/runrun.git
cd runrun
# Install tools
task install-tools
# Run tests with coverage
go test ./... -cover
# Build and test locally
task build
./runrun server --config configs/example.yaml- Follow standard Go conventions
- Use
gofmtfor formatting - Write tests for new features
- Update documentation for API changes
[Include your license information here]
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Built with Chi router
- Templates with Templ
- Styled with Tailwind CSS
- WebSocket support via Gorilla WebSocket