Skip to content

Repository files navigation

RunRun Logo

RunRun

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.

Features

  • 🚀 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 Bulma (all CSS, JS, and icons embedded in the single binary)
  • 🔄 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

Screenshots

Tasks Dashboard

The landing page after login. Aggregate counters (total, running, success, failed, idle, executions) sit above a search box with status and tag filters, and every configured task gets a card showing its description, tags, last run time, duration, and last status — with View and Run actions.

Tasks Dashboard

Task Detail

Per-task view with the task description, tags, a Run Task button, and the full execution history: execution ID, status, start time, duration, and a link to the logs of each run.

Task Detail

Execution Logs

Live log viewer for a single execution, streamed over WebSocket. Logs can be searched, filtered by level, cleared, copied, or downloaded, with a line counter and auto-scroll toggle. Each line is timestamped and shows step boundaries, commands, exit codes, and per-step durations.

Execution Logs

Tech Stack

  • Backend: Go 1.25+ with Chi router
  • Templating: Templ for type-safe HTML templates
  • Styling: Bulma 0.9.4 (CSS-only) + Alpine.js 3 (CSP build) — vendored and embedded; no Node, no CDN
  • Authentication: JWT tokens with bcrypt password hashing
  • Real-time: WebSocket for live log streaming
  • Testing: Testify for comprehensive test coverage; assistant-driven browser validation via the playwright-cli skill (see specs/001-replace-tailwind-bulma/quickstart.md)

Installation

Prerequisites

  • Go 1.25 or higher
  • Task for build orchestration (go install github.com/go-task/task/v3/cmd/task@latest)
  • Git
  • No Node.js, no npm, no npx. All CSS, JS, and icons are vendored and embedded at build time.

From Source

# Clone the repository
git clone https://github.com/sgaunet/runrun.git
cd runrun

# (Optional) Pre-build the templ tool — it's a version-pinned `tool` dependency
# in go.mod (Go 1.24+), so `go tool templ` fetches/builds it automatically anyway
task install-tools

# Build everything (templates, static-asset sync + audit, binary)
task build-all

# The binary will be in the current directory
./runrun version

Using Go Install

go install github.com/sgaunet/runrun/cmd/runrun@latest

Quick Start

1. Create Configuration File

Create 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!'"

2. Generate Password Hash

./runrun hash-password yourpassword

Copy the output hash to your config file.

3. Start the Server

./runrun server --config config.yaml --port 8080

4. Access the Web Interface

Open your browser and navigate to:

http://localhost:8080

Login with your configured username and password.

Configuration

Server Settings

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

Authentication

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.

Task Configuration

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"

Task Fields

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

Development

Building from Source

# (Optional) Pre-build the templ tool — it's a version-pinned `tool` dependency
# in go.mod (Go 1.24+), so `go tool templ` fetches/builds it automatically anyway
task install-tools

# Generate template code from .templ files (runs `go tool templ generate`)
task generate

# Sync the dev static tree into the embed tree
task sync-assets

# Verify required assets are present and no legacy artifacts remain
task audit-assets

# Build the application (runs generate + sync-assets + audit-assets implicitly)
task build

# Run all tests
task test

See specs/001-replace-tailwind-bulma/quickstart.md for the full build/run/validate workflow and the assistant-driven browser validation procedure.

Project Structure

runrun/
├── cmd/runrun/          # CLI application entry point
├── configs/             # Example configuration files
├── internal/
│   ├── assets/         # //go:embed static asset filesystem (mirror of server/static)
│   ├── auth/           # Authentication & authorization
│   ├── config/         # Configuration management
│   ├── csrf/           # CSRF protection
│   ├── executor/       # Task execution engine
│   ├── middleware/     # HTTP middleware (incl. per-request CSP nonce)
│   ├── ratelimit/      # Rate limiting
│   ├── server/
│   │   └── static/     # Source of truth for embedded assets (Bulma + Alpine + app.css + sprite)
│   ├── templates/      # Templ templates
│   └── websocket/      # WebSocket hub & client management
└── Taskfile.yml        # Task runner commands (sync-assets, audit-assets, build, …)

Testing

# 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 ./...

API Documentation

REST Endpoints

Authentication

POST /login

{
  "username": "admin",
  "password": "yourpassword"
}

Response:

{
  "message": "Login successful"
}

Sets session cookie for authentication.

POST /logout

Clears session and invalidates token.

Task Execution

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
  }
}

Logs

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.

Health Checks

GET /health

Overall health check.

GET /health/ready

Readiness probe for Kubernetes/container orchestration.

GET /health/live

Liveness probe.

WebSocket Protocol

Connection

Connect to WebSocket endpoint with authentication:

ws://localhost:8080/logs/ws/{executionID}

Authentication via:

  • Session cookie (from web login)
  • Authorization header: Bearer {jwt-token}

Message Types

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"
}

Security

Authentication & Authorization

  • 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 Protection

  • CSRF tokens required for all state-changing operations (POST, PUT, DELETE)
  • Tokens can be provided via:
    • X-CSRF-Token header
    • csrf_token form field
  • Token validation uses constant-time comparison

Rate Limiting

  • Configurable rate limiting on login endpoint (default: 5 attempts per 15 minutes)
  • IP-based visitor tracking
  • Automatic cleanup of old visitor entries

WebSocket Security

  • Manual authentication in WebSocket handler (outside middleware chain)
  • Origin validation prevents Cross-Site WebSocket Hijacking (CSWSH)
  • Supports both session cookie and Authorization Bearer token

HTTP Security Headers

  • 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

Deployment

Docker

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:latest

Kubernetes

Example 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-config

Systemd Service

Create /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.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable runrun
sudo systemctl start runrun

Troubleshooting

Common Issues

"JWT secret must be at least 32 characters"

Solution: Generate a strong secret:

openssl rand -base64 32

Add to your config file.

WebSocket connection fails

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

Task execution hangs

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_tasks in config

"Queue is full" error

Solution: Increase max_concurrent_tasks in server configuration:

server:
  max_concurrent_tasks: 10  # Increase from default 5

Template changes not reflected

Solution: Regenerate template code:

task generate
task build

CSS or JS changes not appearing

Solution: Re-sync the embed tree and rebuild:

task sync-assets
task build

All static assets are embedded in the binary, so a rebuild is required for any change under internal/server/static/ to take effect.

Debug Mode

Enable debug logging:

server:
  log_level: "debug"

FAQ

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/execute

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Write tests for new functionality
  4. Ensure tests pass: task test
  5. Format code: go fmt ./...
  6. Commit changes: git commit -m 'Add amazing feature'
  7. Push to branch: git push origin feature/amazing-feature
  8. Open a Pull Request

Development Setup

# Clone your fork
git clone https://github.com/yourusername/runrun.git
cd runrun

# (Optional) Install tools — templ is a version-pinned go.mod `tool` dependency,
# so this step only pre-builds it; not required before running other tasks
task install-tools

# Run tests with coverage
go test ./... -cover

# Build and test locally
task build
./runrun server --config configs/example.yaml

Code Style

  • Follow standard Go conventions
  • Use gofmt for formatting
  • Write tests for new features
  • Update documentation for API changes

License

[Include your license information here]

Support

Acknowledgments

About

Web-based task execution platform with real-time monitoring. Execute shell commands and multi-step tasks through a modern web interface with live log streaming via WebSocket.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages