brun

package module
v0.0.20 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 30, 2025 License: MIT Imports: 25 Imported by: 0

README

BRun

BRun Logo

Trigger → Run

Do you find tools like GitHub Actions or Ansible useful, but would like a simple way to do similar things natively? BRun is a native Linux automation tool that connects triggers (boot, cron, file changes, git commits) to actions (run scripts, send emails, log events, reboot). Build CI/CD pipelines, automate system tasks, or test embedded devices—all with a single binary and no dependencies.

Features/goals:

  • simple!!!
  • fast!!!
  • 📦 no dependencies - install a single statically linked binary and go for it ...
  • 🛠️ built-in units for common tasks like boot, scripts, cron, email, git, file watching
  • 🔗 units can be chained into pipelines
  • 💻 first priority is to run native
  • 🚫 does not require containers (but may support them in the future)
  • 📄 simple YAML config format
  • 🔒 built-in secrets management with SOPS encryption

Things you might do with BRun

  • Reboot cycle test for embedded systems.
  • Nightly Yocto builds on your powerful workstation.
  • Run admin tasks like backups.
  • Monitor the /etc directory a server for changes.
  • Implemented a watchdog that reboots the system under certain conditions.
  • Run build/test/deploy pipelines.
  • Notify someone when CPU usage is too high or disk space too low.

Table of Contents

Example Configuration

Here's an example showing how various units are specified and interact (see also more examples and our own dogfood):

config:
  state_location: /var/lib/brun/state.yaml

units:
  # Start trigger - fires every time brun runs
  - start:
      name: on-start
      on_success:
        - build

  # Boot trigger - fires once per boot cycle
  - boot:
      name: on-boot
      on_success:
        - build
        - test
      always:
        - log-boot

  # Run unit - executes shell commands/scripts
  - run:
      name: build
      directory: /home/user/project
      script: |
        echo "Building project..."
        go build -o brun ./cmd/brun
        echo "Build complete"
      on_success:
        - test
      on_failure:
        - log-build-error

  # Run unit - run tests
  - run:
      name: test
      script: |
        echo "Running tests..."
        go test -v
      on_success:
        - log-success
      on_failure:
        - log-test-error

  # Log unit - write to log files
  - log:
      name: log-boot
      file: /var/log/brun/boot.log

  - log:
      name: log-success
      file: /var/log/brun/success.log

  - log:
      name: log-build-error
      file: /var/log/brun/build-errors.log

  - log:
      name: log-test-error
      file: /var/log/brun/test-errors.log

  # Count unit - track how many times units trigger
  - count:
      name: build-counter

  # Cron trigger - runs every 5 minutes (useful in daemon mode)
  - cron:
      name: periodic-check
      schedule: "*/5 * * * *"
      on_success:
        - test

  # File trigger - monitors source files for changes (daemon mode)
  - file:
      name: watch-files
      pattern: "**/*.go"
      on_success:
        - build
        - test

  # Git trigger - monitors repository for changes
  - git:
      name: watch-repo
      repository: /home/user/project
      branch: main
      poll: 2m
      on_success:
        - build

  # Email unit - send notifications
  - email:
      name: email-failure
      to:
        - admin@example.com
      from: brun@example.com
      subject: "Build/Test Failure"
      smtp_host: smtp.gmail.com
      smtp_port: 587
      smtp_user: brun@example.com
      smtp_password: your-app-password
      smtp_use_tls: true
      include_output: true

  # Reboot unit - reboot the system (for reboot cycle testing)
  - reboot:
      name: reboot-system
      delay: 5

Install

To install, download the latest release binary.

Example Install on Linux:

Copy and paste the following into your terminal:

export VER=0.0.9
export ARCH=$(uname -m)
# Convert aarch64 to arm64 to match release binary names
[ "$ARCH" = "aarch64" ] && ARCH="arm64"
export BINARY=brun-v${VER}-Linux-${ARCH}
wget https://github.com/cbrake/brun/releases/download/v${VER}/${BINARY}
# Install to ~/.local/bin for user, /usr/local/bin for root
if [ "$(id -u)" -eq 0 ]; then
  mkdir -p /usr/local/bin
  install -m 755 ${BINARY} /usr/local/bin/brun
else
  mkdir -p ~/.local/bin
  install -m 755 ${BINARY} ~/.local/bin/brun
fi
rm ${BINARY}
Auto Start with Systemd

If you would like to install a systemd unit to run BRun automatically, then run:

brun install (run BRun once then exit)

or

brun install -daemon (run in daemon mode)

If brun install is run as root, it installs a systemd service that runs as root, otherwise as the user who runs the install.

If a config file does not exist, one is created.

SSH Authentication for Git Units:

If you're using Git units with SSH repositories, the generated user service file automatically includes SSH agent support. The service file includes:

Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket

The %t specifier expands to your user runtime directory (typically /run/user/$UID). This setting is harmless if you don't use SSH - Git will simply use other authentication methods (HTTPS, deploy keys, etc.).

If your SSH agent uses a different socket path, edit ~/.config/systemd/user/brun.service and reload:

systemctl --user daemon-reload
systemctl --user restart brun.service

Alternative SSH Authentication Methods:

  • Deploy Keys: Use repository-specific SSH keys that don't require an agent
  • HTTPS with Credentials: Use HTTPS URLs with credential storage instead of SSH
  • System Services: For root services, configure a dedicated service user with its own SSH key
Updating

After initial installation, the brun update command can be used to update to the latest release.

Usage

Usage: brun COMMAND [OPTIONS]

Commands:
  run <config-file>       Run brun with the given config file
  install                 Install brun as a systemd service
  update                  Updates BRun to the latest version
  version                 Display version information

Run Options:
  -daemon                 Run in daemon mode (continuous monitoring)
  -unit <name>            Run a single unit (triggers disabled, useful for debugging)
  -trigger <name>         Trigger a unit and execute its on_success triggers

Install Options:
  -daemon                 Install service in daemon mode (continuous monitoring)

Examples:
  brun run config.yaml
  brun run config.yaml -daemon
  brun run config.yaml -unit my-build
  brun install
  brun install -daemon

One-time run:

By default, BRun runs once, checks all trigger conditions, executes any units whose conditions are met, and then exits. This is suitable for:

  • Running from external cron
  • Manual invocation
  • Testing configurations
brun run config.yaml

Daemon mode:

BRun supports a daemon mode that continuously monitors trigger conditions and executes units when triggered. In this mode, triggers are checked every 10 seconds. This is suitable for:

  • System service deployment
  • Continuous monitoring with cron triggers
  • Long-running background processes
brun run config.yaml -daemon

Circular Dependency Protection

BRun protects against circular dependencies when units trigger each other. For example, if Unit A triggers Unit B, and Unit B triggers Unit A, this could cause an infinite loop.

How it works:

  • The orchestrator tracks the current execution path (call stack) as units trigger each other
  • Before executing a unit, the orchestrator checks if it's already in the current call stack
  • If a unit is already in the call stack, it is skipped to prevent circular dependencies
  • Units can be triggered multiple times in the same execution as long as they're not in a circular loop

This approach allows:

  • Flexible trigger chains: The same unit (like an email or log unit) can be triggered multiple times from different branches in a single execution
  • Circular dependency protection: Units cannot trigger themselves directly or indirectly through other units in the same execution path

Example - Circular dependency prevented:

units:
  - start:
      name: start-trigger
      on_success:
        - task-a

  - run:
      name: task-a
      script: echo "Task A"
      on_success:
        - task-b

  - run:
      name: task-b
      script: echo "Task B"
      on_success:
        - task-a # This would create a circular dependency

In this example:

  • start-trigger triggers task-a
  • task-a triggers task-b
  • task-b attempts to trigger task-a, but it's already in the call stack
  • The circular trigger is prevented, and the log shows: "Unit 'task-a' already in call stack, skipping to prevent circular dependency"

Example - Multiple triggers allowed:

units:
  - start:
      name: start-trigger
      on_success:
        - build-frontend
        - build-backend

  - run:
      name: build-frontend
      script: npm run build
      always:
        - notify-team

  - run:
      name: build-backend
      script: go build ./...
      always:
        - notify-team

  - email:
      name: notify-team
      to:
        - team@example.com
      # ... email config ...

In this example:

  • Both build-frontend and build-backend trigger notify-team
  • The notify-team email unit runs twice (once from each build)
  • This is allowed because notify-team is not in a circular dependency

Logging

By default, logging is sent to STDOUT, and each unit logs:

  • when it triggers or runs
  • any errors

Additional log units can log specific events.

State

BRun uses a single common state file (YAML format) where all units store state between runs. This unified approach simplifies state management and makes it easy to:

  • Track all unit state in one location
  • Back up and restore state atomically
  • Clear all state with a single file deletion
  • Inspect and debug state using standard YAML tools

The state file location must be set in the BRun config file.

State Data:

Units store different types of state information in the YAML file:

  • Boot trigger: Last boot time (RFC3339 timestamp) and boot count
  • Cron trigger: Last execution time (RFC3339 timestamp)
  • Count unit: Trigger counts per triggering unit
  • File trigger: File hashes for change detection
  • Git trigger: Last processed commit hash

State File Format:

The state file uses YAML format for consistency with the configuration file. Each unit stores its state under a key corresponding to its name or type.

The state file is automatically created with appropriate permissions (0644) when BRun runs for the first time.

Secrets Management

BRun supports encrypting configuration files with SOPS (Secrets OPerationS), allowing you to safely store passwords, API keys, and other sensitive data directly in your config files.

Benefits:

  • Keep secrets encrypted at rest in version control
  • Transparent decryption at runtime - no UI changes needed
  • Support for multiple key providers (age, PGP, AWS KMS, GCP KMS, Azure Key Vault)
  • Backward compatible - plaintext configs still work

Quick Start:

  1. Install SOPS and age

  2. Generate an encryption key:

age-keygen -o ~/.config/sops/age/keys.txt
# Save the public key (age1...) shown in output
  1. Encrypt your config file:
sops --encrypt --age <your-public-key> --in-place config.yaml
  1. Run BRun normally:
brun run config.yaml  # Automatically decrypts

Your secrets are now encrypted in the config file but decrypted transparently when BRun runs. The file structure remains visible (unit names, triggers, etc.), only sensitive values are encrypted.

Selective Field Encryption:

You can configure SOPS to encrypt only sensitive fields (like passwords and API keys) while keeping the rest of your config readable. Create a .sops.yaml file in your repository root:

creation_rules:
  - path_regex: \.yaml$
    encrypted_regex: "^(.*password.*|.*secret.*|.*key.*|.*token.*|smtp_user)$"
    age: your-public-key-here

This will encrypt only fields matching the patterns (password, secret, key, token, etc.) while leaving structural fields like name, script, and directory in plaintext for easy review in version control.

See .sops.yaml for a complete example configuration.

File Format

YAML is used for the BRun config file and is similar to config files used in GitLab CI/CD, Drone, Ansible, etc.

The configuration is composed of chainable units. Each unit can trigger additional units. This allows us to start/sequence operations and create workflow pipelines.

Config

The BRun file consists of a required config section with the following fields:

config:
  state_location: /var/lib/brun/state.yaml

Fields:

  • state_location (required): Path to the state file where units store their state between runs.
    • Defaults to /var/lib/brun/state.yaml for root installs
    • Defaults to ~/.config/brun/state.yaml for user installs

The config file also contains a units section as described below.

Variables

(NOTE: Variables are in planning phase and have not been implemented yet.)

Variables can be defined in a vars block and referenced in any string field using Go Templates. Variables are expanded when a unit is run so that variable updates are processed.

Syntax:

Variables are accessed using Go template syntax with double curly braces:

vars:
  project_name: myapp
  build_dir: /home/user/builds
  version: 1.0.0

units:
  - run:
      name: build
      directory: { { .build_dir } }
      script: |
        echo "Building {{ .project_name }} version {{ .version }}"
        go build -o {{ .project_name }}

Features:

  • Basic variables: Access with {{ .variable_name }}
  • Nested variables: Use dot notation {{ .config.path }}
  • Pipelines: Chain operations {{ .name | upper | quote }}
  • Conditionals: {{ if eq .env "prod" }}production{{ else }}development{{ end }}
  • Loops: {{ range .items }}{{ . }}{{ end }}
  • Functions: Built-in functions like eq, ne, lt, gt, and, or, not

Example with nested variables:

Variables can be nested using maps to organize related configuration:

vars:
  database:
    host: localhost
    port: 5432
    name: myapp_db
  server:
    host: 0.0.0.0
    port: 8080

units:
  - run:
      name: start-server
      script: |
        echo "Starting server on {{ .server.host }}:{{ .server.port }}"
        echo "Connecting to database {{ .database.name }} at {{ .database.host }}:{{ .database.port }}"
        ./start-server

Example with conditionals:

vars:
  environment: production
  enable_tests: true

units:
  - run:
      name: deploy
      script: |
        echo "Deploying to {{ .environment }}"
        {{ if eq .environment "production" }}
        ./deploy-prod.sh
        {{ else }}
        ./deploy-dev.sh
        {{ end }}

  - run:
      name: test
      {{ if .enable_tests }}
      script: go test -v ./...
      {{ else }}
      script: echo "Tests disabled"
      {{ end }}

Environment variables:

Environment variables can be accessed using the env function (if available):

units:
  - run:
      name: build
      script: |
        echo "User: {{ env "USER" }}"
        echo "Home: {{ env "HOME" }}"

Whitespace control:

Use - to trim whitespace before or after template actions:

script: |
  {{- if .debug }}
  echo "Debug mode enabled"
  {{- end }}

See the Go template documentation for full syntax reference.

Units

BRun supports the following unit types:

Common Unit Fields

All units share the following common fields:

  • name (required): A unique identifier for the unit. This name is used to reference the unit when triggering it from other units.
  • on_success (optional): An array of unit names to trigger when this unit completes successfully.
  • on_failure (optional): An array of unit names to trigger when this unit fails.
  • always (optional): An array of unit names to trigger regardless of whether this unit succeeds or fails. These units run after success/failure triggers.

Trigger unit behavior:

When a trigger unit (boot, cron, file, git, start) is triggered by another unit via on_success, on_failure, or always, the trigger unit's condition is still checked before execution. For example, if a cron unit triggers a git unit, the git unit will only execute if there are actual git updates detected. This prevents unnecessary operations and ensures triggers only fire when their conditions are truly met.

Boot Unit

The boot unit triggers if this is the first time the program has been run since the system booted. The boot unit stores the last boot time in the common state file.

Behavior:

The boot trigger detects boot events by:

  1. Reading /proc/uptime to calculate the system boot time
  2. Comparing this with a stored boot time from the previous run (saved in the common state file)
  3. Triggering when the boot times differ by more than 10 seconds

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - boot:
      name: boot-trigger
      on_success:
        - build-unit
        - test-unit

When the boot trigger fires successfully, it will trigger the units listed in on_success (in this example, build-unit and test-unit).

The boot time is automatically stored in the common state file under the unit's name.

Count Unit

The Count unit creates an entry in the state file for every unit that triggers this unit and counts how many times it has been triggered. This is useful for tracking how often specific events (like errors) occur or how many times particular units execute. The count quickly tells you something happened, and then the logfiles can be examined to understand why.

Behavior:

  • Tracks separate counts for each unit that triggers it
  • Stores counts in the state file under the count unit's name
  • Each triggering unit has its own counter
  • Counts persist across runs

State File Format:

The count unit stores data in the state file like this:

count-runs:
  start-trigger: 5

count-builds:
  build: 3

count-failures:
  build: 1

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - start:
      name: start-trigger
      on_success:
        - build
      always:
        - count-runs

  - run:
      name: build
      script: |
        go build -o brun ./cmd/brun
      on_success:
        - count-builds
      on_failure:
        - count-failures

  - count:
      name: count-runs

  - count:
      name: count-builds

  - count:
      name: count-failures
Cron Unit

The Cron unit is a trigger that fires based on a cron schedule. It uses the standard cron format to define when the trigger should activate. In daemon mode, the trigger is checked every 10 seconds. The robfig/cron package is used for schedule parsing.

Fields:

  • schedule (required): Cron schedule in standard format (minute hour day month weekday)

Behavior:

  • Triggers based on the cron schedule
  • Stores last execution time in the state file
  • Works in both one-time and daemon modes
  • In one-time mode: triggers if schedule indicates it should have run since last execution
  • In daemon mode: continuously monitors and triggers at scheduled times

Cron Schedule Format:

Standard 5-field cron format:

* * * * *
│ │ │ │ │
│ │ │ │ └─── Day of week (0-6, Sunday=0)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)

Examples:

  • * * * * * - Every minute
  • */5 * * * * - Every 5 minutes
  • 0 2 * * * - Daily at 2:00 AM
  • 30 14 * * 1-5 - Weekdays at 2:30 PM
  • 0 0 1 * * - First day of every month at midnight

State File Format:

The cron unit stores the last execution time:

daily-backup:
  last_execution: "2025-10-03T02:30:00-04:00"

health-check:
  last_execution: "2025-10-03T18:00:00-04:00"

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  # Cron trigger - runs every day at 2:30 AM
  - cron:
      name: daily-backup
      schedule: "30 2 * * *"
      on_success:
        - backup-unit

  # Cron trigger - runs every 5 minutes
  - cron:
      name: health-check
      schedule: "*/5 * * * *"
      on_success:
        - check-services

  - run:
      name: backup-unit
      script: |
        echo "Running daily backup..."
        # backup commands here

  - run:
      name: check-services
      script: |
        echo "Checking services..."
        # health check commands here
Email Unit

The Email unit sends email notifications with optional output from triggering units. This is useful for alerting on build failures, test results, or other important events. Supports both plain SMTP and STARTTLS encryption.

Fields:

  • to (required): Array of email addresses to send to
  • from (required): Sender email address
  • subject_prefix (optional): Email subject line prefix. ': :<success|fail>' is appended after prefix and is always included.
  • smtp_host (required): SMTP server hostname
  • smtp_port (optional): SMTP server port. Defaults to 587 (submission port)
  • smtp_user (optional): SMTP username for authentication
  • smtp_password (optional): SMTP password for authentication
  • smtp_use_tls (optional): Enable STARTTLS encryption. Defaults to true
  • include_output (optional): Include captured output from triggering unit. Defaults to true
  • limit_lines (optional): limit number email lines emailed to number specified.

Behavior:

  • Sends plain text emails using SMTP
  • Can include output from the unit that triggered it (useful for log/error reporting)
  • Supports SMTP authentication
  • STARTTLS encryption enabled by default
  • Works with common email providers (Gmail, SendGrid, Mailgun, etc.)

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - boot:
      name: boot-trigger
      on_success:
        - build

  - run:
      name: build
      script: |
        go build -o brun ./cmd/brun
        go test -v
      on_failure:
        - email-failure

  - email:
      name: email-failure
      to:
        - admin@example.com
        - alerts@example.com
      from: brun@example.com
      subject_prefix: "Build Alert"
      smtp_host: smtp.gmail.com
      smtp_port: 587
      smtp_user: brun@example.com
      smtp_password: your-app-password
      smtp_use_tls: true
      include_output: true

This will send emails with subjects like:

  • Build Alert: build:success (on success)
  • Build Alert: build:fail (on failure)

Gmail example:

For Gmail, you need to use an app-specific password:

- email:
    name: notify-admin
    to:
      - you@gmail.com
    from: your-app@gmail.com
    subject_prefix: "CI/CD"
    smtp_host: smtp.gmail.com
    smtp_port: 587
    smtp_user: your-app@gmail.com
    smtp_password: your-16-char-app-password
    smtp_use_tls: true
Email Receive Unit (TODO)

This can receive emails to trigger units.

File Unit

The File unit monitors files and triggers when they are changed. Files can be specified using glob patterns with support for ** recursive matching. New or removed files are detected as changes.

Fields:

  • pattern (required): Glob pattern to match files (supports ** for recursive matching)

Behavior:

  • Monitors files matching the glob pattern
  • Triggers when file content changes (detected via SHA256 hash)
  • Triggers when files are added or removed
  • Stores file hashes in the state file
  • Triggers on first run (initial file state)
  • Ignores directories (only monitors regular files)
  • Works in both one-time and daemon modes

Pattern Syntax:

The file unit supports advanced glob patterns including:

  • * - matches any sequence of non-separator characters
  • ? - matches any single non-separator character
  • [abc] - matches any character in the set
  • [a-z] - matches any character in the range
  • ** - matches zero or more directories recursively

Pattern Examples:

  • **/*.go - all Go files recursively
  • src/**/*.ts - all TypeScript files under src/
  • config/*.yaml - config files non-recursively
  • **/*.{html,css,js} - multiple filetypes

State File Format:

The file unit stores a hash of all monitored files:

watch-source:
  files_state: "file1.go:a1b2c3...|file2.go:d4e5f6..."

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  # File trigger - monitors Go source files
  - file:
      name: watch-source
      pattern: "**/*.go"
      on_success:
        - build
        - test

  - run:
      name: build
      script: |
        echo "Building..."
        go build -o app ./cmd/app

  - run:
      name: test
      script: |
        echo "Running tests..."
        go test -v ./...

Daemon mode example:

When running in daemon mode, the file trigger continuously monitors files and automatically triggers builds/tests when changes are detected:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - file:
      name: auto-build
      pattern: "**/*.go"
      on_success:
        - build
        - test
      always:
        - email-notify

  - run:
      name: build
      script: |
        go build -o app ./cmd/app

  - run:
      name: test
      script: |
        go test -v ./...

  - email:
      name: email-notify
      to:
        - team@example.com
      from: ci@example.com
      subject_prefix: "Build Status"
      smtp_host: smtp.example.com
      smtp_port: 587
      smtp_user: ci@example.com
      smtp_password: secret

Run with: brun run config.yaml -daemon

This creates a continuous integration system that automatically builds and tests your code whenever source files are modified.

Git Unit

The Git unit is a trigger that fires when changes are detected in a Git repository. It monitors the repository's HEAD commit and triggers when new commits are detected. This is useful for automatically running builds, tests, or deployments when code changes.

If the repository field points to a local Git workspace (vs a Repo URL), the workspace and submodules are updated to the latest on the specified branch.

Fields:

  • repository (required): Path to the Git repository to monitor
  • branch (required): Branch to monitor
  • reset (optional): optionally reset the workspace to the state of the repo HEAD (git reset --hard)
  • poll (optional): polling interval for checking repository updates (e.g., 2m, 30s, 1h). When set, the git unit actively polls for updates at the specified interval. When omitted, the unit operates in passive mode: it will NOT check during orchestrator polling, but WILL check when explicitly triggered by another unit (e.g., via on_success). This enables event-driven workflows where git checks happen on-demand without continuous polling overhead.
  • debug (optional): when true, logs detailed git operation messages (fetch, reset, submodule updates). Defaults to false.

SSH Authentication:

When using SSH-based Git repositories with systemd, the service requires access to your SSH agent. See the Auto tart with systemd section for configuration details on setting the SSH_AUTH_SOCK environment variable.

Behavior:

  • Monitors the HEAD commit hash of the specified Git repository
  • Triggers when the commit hash changes (new commits detected)
  • Stores the last seen commit hash in the state file
  • Triggers on first run (initial repository state)
  • Uses go-git library (no git CLI tool required)
  • Works in both one-time and daemon modes

State File Format:

The git unit stores the last seen commit hash:

watch-repo:
  last_commit_hash: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"

Configuration example:

When running in daemon mode, the git trigger continuously monitors the repository and automatically triggers builds/tests when new commits are detected:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - git:
      name: auto-build
      repository: /home/user/project
      branch: main
      poll: 2m # Check for updates every 2 minutes
      debug: false # Suppress verbose git operation logs
      on_success:
        - build

  - run:
      name: build
      directory: /home/user/project
      script: |
        go build -o app ./cmd/app
        go test -v ./...
      always:
        - email

  - email:
      name: email
      to:
        - team@example.com
      from: ci@example.com
      subject_prefix: "Build Success"
      smtp_host: smtp.example.com
      smtp_port: 587
      smtp_user: ci@example.com
      smtp_password: secret

This creates a continuous integration system that automatically builds and tests your code whenever changes are pushed to the repository.

Passive mode example (event-driven):

For efficient resource usage, you can configure git units without polling and trigger them explicitly:

config:
  state_location: /var/lib/brun/state.yaml

units:
  # Check for git updates once per hour
  - cron:
      name: hourly-check
      schedule: "0 * * * *"
      on_success:
        - check-repo

  # Git unit in passive mode (no poll field)
  - git:
      name: check-repo
      repository: /home/user/project
      branch: main
      # No poll field - only checks when triggered by cron
      on_success:
        - build

  - run:
      name: build
      directory: /home/user/project
      script: |
        go build -o app ./cmd/app
        go test -v ./...

This approach checks for git updates only when the cron triggers it, reducing system overhead while maintaining automated builds.

Log Unit

The Log unit writes log entries to a file. This is useful for recording events, errors, or other information during pipeline execution. The logfile is created if it doesn't exist, and entries are appended with timestamps.

Fields:

  • file (required): Path to the logfile where entries will be written

Behavior:

  • Creates the logfile and parent directories if they don't exist
  • Appends log entries with timestamps
  • File permissions are set to 0644
  • Directory permissions are set to 0755

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - start:
      name: start-trigger
      on_success:
        - build
      always:
        - log-run

  - run:
      name: build
      script: |
        go build -o brun ./cmd/brun
      on_failure:
        - log-error

  - log:
      name: log-run
      file: /var/log/brun/pipeline.log

  - log:
      name: log-error
      file: /var/log/brun/errors.log
Ntfy Unit

The ntfy unit allows notifications be sent out using the ntfy.sh service.

Fields:

  • topic (required): Ntfy topic to post to
  • server (optional): Ntfy server URL. Defaults to https://ntfy.sh
  • title_prefix (optional): Notification title prefix. ': :<success|fail>' is appended after prefix and is always included
  • priority (optional): Notification priority (min, low, default, high, urgent)
  • tags (optional): Comma-separated tags/emojis for the notification
  • include_output (optional): Include captured output from triggering unit. Defaults to true
  • limit_lines (optional): Limit number of output lines included in notification

Behavior:

  • Sends notifications via HTTP POST to ntfy.sh (or self-hosted server)
  • Can include output from the unit that triggered it (useful for log/error reporting)
  • Title automatically includes triggering unit name and success/fail status

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - boot:
      name: boot-trigger
      on_success:
        - build

  - run:
      name: build
      script: |
        go build -o brun ./cmd/brun
        go test -v
      on_failure:
        - notify-failure
      on_success:
        - notify-success

  - ntfy:
      name: notify-failure
      topic: my-build-alerts
      title_prefix: Build Failed
      priority: high
      tags: warning,skull
      include_output: true
      limit_lines: 50

  - ntfy:
      name: notify-success
      topic: my-build-alerts
      title_prefix: Build Succeeded
      priority: default
      tags: white_check_mark
      include_output: false
Reboot Unit

The reboot unit logs and reboots the system. This is typically used in reboot cycle testing where the boot trigger can count boot cycles and trigger test sequences.

Fields:

  • delay (optional): Number of seconds to wait before executing reboot (default: 0 for immediate reboot)

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - reboot:
      name: reboot-system
      delay: 5 # optional delay in seconds before reboot (default: 0)
Run Unit

The Run unit executes arbitrary shell commands or scripts. This is the primary execution unit for running builds, tests, or any other commands. The exit code determines success or failure, which then triggers the appropriate units.

Multiple Run units can be defined in a configuration file to create build and test pipelines.

Fields:

  • script (required): Shell commands to execute. Can be a single command or a multiline script
  • directory (optional): Working directory where the script will be executed. Defaults to the directory where BRun was invoked
  • timeout (optional): Time out duration for the task to complete (e.g., 30s, 5m, 1h, 1h30m). If no timeout is specified, it runs until completion. If the task times out, an error message is logged.
  • shell (optional): specify shell to use when running command (bash, etc.). By default, 'sh' is used.
  • use_pty (optional): when set to true, wraps the command with script to provide a pseudo-TTY. This is useful for tools like BitBake that require a TTY environment. Default is false.

Behavior:

  • The script is executed using the system shell
  • Exit code 0 is considered success and triggers on_success units
  • Nonzero exit codes are considered failures and trigger on_failure units
  • Both STDOUT and STDERR are logged

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - boot:
      name: boot-trigger
      on_success:
        - build

  - run:
      name: build
      directory: /home/user/project
      script: |
        go build -o brun ./cmd/brun
        go test -v
      on_success:
        - deploy
      on_failure:
        - notify-failure

  - run:
      name: deploy
      script: |
        ./deploy.sh

  - run:
      name: bitbake-build
      shell: bash
      use_pty: true
      script: |
        source oe-init-build-env
        bitbake core-image-minimal
      timeout: 2h
Start Unit

The Start trigger always fires when BRun runs. This can be used to trigger other units every time the program executes, regardless of boot state or other conditions.

Behavior:

  • Always triggers on every BRun
  • Does not maintain any state
  • Useful for unconditional execution pipelines

Configuration example:

config:
  state_location: /var/lib/brun/state.yaml

units:
  - start:
      name: start-trigger
      on_success:
        - build-unit
        - test-unit

Program Lifecycle

BRun traps kill signals and waits for all triggers to complete before exiting.

Status

This project is in the exploratory phase as we explore various concepts. The syntax of the BRun file may change as we learn how to better do this.

If you are using BRun, please like this repo and subscribe to release updates. If there are features you would like, open an issue. This provides motivation to keep the project going.

Feedback/contributions welcome! Please discuss before implementing anything major.

See issues and ideas for future direction.

I have no idea if this works on Windows - feel free to try and report status.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Install

func Install(daemonMode bool) error

Install installs brun as a systemd service If run as root, installs system-wide service Otherwise, installs user service daemonMode determines whether the service runs in daemon mode (continuous) or oneshot mode

func Update added in v0.0.5

func Update(currentVersion string) error

Update checks for and downloads the latest version of brun

Types

type BootConfig

type BootConfig struct {
	UnitConfig `yaml:",inline"`
}

BootConfig represents the configuration for a boot trigger

type BootDetector

type BootDetector struct {
	// contains filtered or unexported fields
}

BootDetector detects system boot time and tracks whether this is the first run since boot

func NewBootDetector

func NewBootDetector(stateFile string) *BootDetector

NewBootDetector creates a new boot detector with the given state file path

func (*BootDetector) GetBootTime

func (bd *BootDetector) GetBootTime() (time.Time, error)

GetBootTime returns the system boot time by reading /proc/uptime

func (*BootDetector) IsFirstRunSinceBoot

func (bd *BootDetector) IsFirstRunSinceBoot() (bool, error)

IsFirstRunSinceBoot checks if this is the first run since system boot It compares the current boot time with the stored boot time from the last run

type BootTrigger

type BootTrigger struct {
	// contains filtered or unexported fields
}

BootTrigger is a trigger unit that fires on the first run after system boot

func NewBootTrigger

func NewBootTrigger(name string, state *State, onSuccess, onFailure, always []string) *BootTrigger

NewBootTrigger creates a new boot trigger unit

func (*BootTrigger) Always

func (s *BootTrigger) Always() []string

Always returns the list of units to trigger regardless of success/failure

func (*BootTrigger) Check

func (s *BootTrigger) Check(ctx context.Context, mode CheckMode) (bool, error)

Check returns true if this is the first run since system boot

func (*BootTrigger) Name

func (s *BootTrigger) Name() string

Name returns the name of the unit

func (*BootTrigger) OnFailure

func (s *BootTrigger) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*BootTrigger) OnSuccess

func (s *BootTrigger) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*BootTrigger) Run

func (s *BootTrigger) Run(ctx context.Context) error

Run executes the trigger unit Note: Check() has already been called by the orchestrator before Run() is invoked

func (*BootTrigger) Type

func (s *BootTrigger) Type() string

Type returns the unit type

type CheckMode added in v0.0.17

type CheckMode int

CheckMode indicates how a trigger unit's Check method is being called

const (
	// CheckModePolling indicates Check is called during orchestrator's periodic polling cycle
	CheckModePolling CheckMode = iota

	// CheckModeManual indicates Check is called because another unit triggered this one
	CheckModeManual
)

func (CheckMode) String added in v0.0.17

func (m CheckMode) String() string

String returns a human-readable string for the CheckMode

type Config

type Config struct {
	ConfigBlock ConfigBlock         `yaml:"config"`
	Units       []UnitConfigWrapper `yaml:"units"`
}

Config represents the SimplCI configuration file

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig loads a configuration file from the given path. If the file is encrypted with SOPS, it will be automatically decrypted.

func (*Config) CreateUnits

func (c *Config) CreateUnits() ([]Unit, error)

CreateUnits creates unit instances from the configuration

type ConfigBlock

type ConfigBlock struct {
	StateLocation string `yaml:"state_location"`
}

ConfigBlock represents the config section of the configuration file

type CountConfig

type CountConfig struct {
	UnitConfig `yaml:",inline"`
}

CountConfig represents the configuration for a Count unit

type CountUnit

type CountUnit struct {
	// contains filtered or unexported fields
}

CountUnit tracks how many times it has been triggered by each unit

func NewCountUnit

func NewCountUnit(name string, state *State, onSuccess, onFailure, always []string) *CountUnit

NewCountUnit creates a new Count unit

func (*CountUnit) Always

func (c *CountUnit) Always() []string

Always returns the list of units to always trigger

func (*CountUnit) Name

func (c *CountUnit) Name() string

Name returns the unit name

func (*CountUnit) OnFailure

func (c *CountUnit) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*CountUnit) OnSuccess

func (c *CountUnit) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*CountUnit) Run

func (c *CountUnit) Run(ctx context.Context) error

Run executes the count unit

func (*CountUnit) SetTriggeringUnit

func (c *CountUnit) SetTriggeringUnit(unitName string)

SetTriggeringUnit sets the name of the unit that triggered this count

func (*CountUnit) Type

func (c *CountUnit) Type() string

Type returns the unit type

type CronConfig

type CronConfig struct {
	UnitConfig `yaml:",inline"`
	Schedule   string `yaml:"schedule"`
}

CronConfig represents the configuration for a cron trigger

type CronTrigger

type CronTrigger struct {
	// contains filtered or unexported fields
}

CronTrigger is a trigger unit that fires based on a cron schedule

func NewCronTrigger

func NewCronTrigger(name, schedule string, state *State, onSuccess, onFailure, always []string) *CronTrigger

NewCronTrigger creates a new cron trigger unit

func (*CronTrigger) Always

func (c *CronTrigger) Always() []string

Always returns the list of units to trigger regardless of success/failure

func (*CronTrigger) Check

func (c *CronTrigger) Check(ctx context.Context, mode CheckMode) (bool, error)

Check returns true if the cron schedule has triggered since the last execution

func (*CronTrigger) Name

func (c *CronTrigger) Name() string

Name returns the name of the unit

func (*CronTrigger) OnFailure

func (c *CronTrigger) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*CronTrigger) OnSuccess

func (c *CronTrigger) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*CronTrigger) Run

func (c *CronTrigger) Run(ctx context.Context) error

Run executes the trigger unit Note: Check() has already been called by the orchestrator before Run() is invoked

func (*CronTrigger) Type

func (c *CronTrigger) Type() string

Type returns the unit type

type EmailConfig

type EmailConfig struct {
	UnitConfig    `yaml:",inline"`
	To            []string `yaml:"to"`
	From          string   `yaml:"from"`
	SubjectPrefix string   `yaml:"subject_prefix,omitempty"`
	SMTPHost      string   `yaml:"smtp_host"`
	SMTPPort      int      `yaml:"smtp_port,omitempty"`
	SMTPUser      string   `yaml:"smtp_user,omitempty"`
	SMTPPassword  string   `yaml:"smtp_password,omitempty"`
	SMTPUseTLS    *bool    `yaml:"smtp_use_tls,omitempty"`
	IncludeOutput *bool    `yaml:"include_output,omitempty"`
	LimitLines    int      `yaml:"limit_lines,omitempty"`
}

EmailConfig represents the configuration for an Email unit

type EmailUnit

type EmailUnit struct {
	// contains filtered or unexported fields
}

EmailUnit sends email notifications

func NewEmailUnit

func NewEmailUnit(name string, to []string, from, subjectPrefix, smtpHost string, smtpPort int,
	smtpUser, smtpPassword string, smtpUseTLS, includeOutput bool, limitLines int,
	onSuccess, onFailure, always []string) *EmailUnit

NewEmailUnit creates a new Email unit

func (*EmailUnit) Always

func (e *EmailUnit) Always() []string

Always returns the list of units to always trigger

func (*EmailUnit) Name

func (e *EmailUnit) Name() string

Name returns the unit name

func (*EmailUnit) OnFailure

func (e *EmailUnit) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*EmailUnit) OnSuccess

func (e *EmailUnit) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*EmailUnit) Run

func (e *EmailUnit) Run(ctx context.Context) error

Run executes the email unit

func (*EmailUnit) SetOutput

func (e *EmailUnit) SetOutput(output string)

SetOutput sets the output data from the triggering unit

func (*EmailUnit) SetTriggerError

func (e *EmailUnit) SetTriggerError(err error)

SetTriggerError sets the error from the triggering unit

func (*EmailUnit) SetTriggeringUnit

func (e *EmailUnit) SetTriggeringUnit(unitName string)

SetTriggeringUnit sets the name of the unit that triggered this email

func (*EmailUnit) Type

func (e *EmailUnit) Type() string

Type returns the unit type

type FileConfig

type FileConfig struct {
	UnitConfig `yaml:",inline"`
	Pattern    string `yaml:"pattern"`
}

FileConfig represents the configuration for a file trigger

type FileTrigger

type FileTrigger struct {
	// contains filtered or unexported fields
}

FileTrigger is a trigger unit that fires when files matching a pattern change

func NewFileTrigger

func NewFileTrigger(name, pattern string, state *State, onSuccess, onFailure, always []string) *FileTrigger

NewFileTrigger creates a new file trigger unit

func (*FileTrigger) Always

func (f *FileTrigger) Always() []string

Always returns the list of units to trigger regardless of success/failure

func (*FileTrigger) Check

func (f *FileTrigger) Check(ctx context.Context, mode CheckMode) (bool, error)

Check returns true if files matching the pattern have changed

func (*FileTrigger) Name

func (f *FileTrigger) Name() string

Name returns the name of the unit

func (*FileTrigger) OnFailure

func (f *FileTrigger) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*FileTrigger) OnSuccess

func (f *FileTrigger) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*FileTrigger) Run

func (f *FileTrigger) Run(ctx context.Context) error

Run executes the trigger unit Note: Check() has already been called by the orchestrator before Run() is invoked

func (*FileTrigger) Type

func (f *FileTrigger) Type() string

Type returns the unit type

type GitConfig

type GitConfig struct {
	UnitConfig `yaml:",inline"`
	Repository string `yaml:"repository"`
	Branch     string `yaml:"branch"`
	Reset      bool   `yaml:"reset"`
	Poll       string `yaml:"poll"`
	Debug      bool   `yaml:"debug"`
}

GitConfig represents the configuration for a git trigger

type GitHubRelease added in v0.0.5

type GitHubRelease struct {
	TagName string `json:"tag_name"`
	Name    string `json:"name"`
}

GitHubRelease represents the GitHub API release response

type GitTrigger

type GitTrigger struct {
	// contains filtered or unexported fields
}

GitTrigger is a trigger unit that fires when git repository changes are detected

func NewGitTrigger

func NewGitTrigger(name, repository, branch string, reset bool, pollInterval time.Duration, debug bool, state *State, onSuccess, onFailure, always []string) *GitTrigger

NewGitTrigger creates a new git trigger unit

func (*GitTrigger) Always

func (g *GitTrigger) Always() []string

Always returns the list of units to trigger regardless of success/failure

func (*GitTrigger) Check

func (g *GitTrigger) Check(ctx context.Context, mode CheckMode) (bool, error)

Check returns true if the git repository has new commits since last check

func (*GitTrigger) Name

func (g *GitTrigger) Name() string

Name returns the name of the unit

func (*GitTrigger) OnFailure

func (g *GitTrigger) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*GitTrigger) OnSuccess

func (g *GitTrigger) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*GitTrigger) Run

func (g *GitTrigger) Run(ctx context.Context) error

Run executes the trigger unit Note: Check() has already been called by the orchestrator before Run() is invoked

func (*GitTrigger) Type

func (g *GitTrigger) Type() string

Type returns the unit type

type LogConfig

type LogConfig struct {
	UnitConfig `yaml:",inline"`
	File       string `yaml:"file"`
}

LogConfig represents the configuration for a Log unit

type LogUnit

type LogUnit struct {
	// contains filtered or unexported fields
}

LogUnit writes log messages to a file

func NewLogUnit

func NewLogUnit(name, file string, onSuccess, onFailure, always []string) *LogUnit

NewLogUnit creates a new Log unit

func (*LogUnit) Always

func (l *LogUnit) Always() []string

Always returns the list of units to always trigger

func (*LogUnit) Name

func (l *LogUnit) Name() string

Name returns the unit name

func (*LogUnit) OnFailure

func (l *LogUnit) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*LogUnit) OnSuccess

func (l *LogUnit) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*LogUnit) Run

func (l *LogUnit) Run(ctx context.Context) error

Run executes the log unit

func (*LogUnit) SetOutput

func (l *LogUnit) SetOutput(output string)

SetOutput sets the output data from the triggering unit

func (*LogUnit) SetTriggeringUnit

func (l *LogUnit) SetTriggeringUnit(unitName string)

SetTriggeringUnit sets the name of the unit that triggered this log

func (*LogUnit) Type

func (l *LogUnit) Type() string

Type returns the unit type

type NtfyConfig added in v0.0.20

type NtfyConfig struct {
	UnitConfig    `yaml:",inline"`
	Topic         string `yaml:"topic"`
	Server        string `yaml:"server,omitempty"`
	TitlePrefix   string `yaml:"title_prefix,omitempty"`
	Priority      string `yaml:"priority,omitempty"`
	Tags          string `yaml:"tags,omitempty"`
	IncludeOutput *bool  `yaml:"include_output,omitempty"`
	LimitLines    int    `yaml:"limit_lines,omitempty"`
}

NtfyConfig represents the configuration for an Ntfy unit

type NtfyUnit added in v0.0.20

type NtfyUnit struct {
	// contains filtered or unexported fields
}

NtfyUnit sends notifications via ntfy.sh

func NewNtfyUnit added in v0.0.20

func NewNtfyUnit(name, topic, server, titlePrefix, priority, tags string,
	includeOutput bool, limitLines int,
	onSuccess, onFailure, always []string) *NtfyUnit

NewNtfyUnit creates a new Ntfy unit

func (*NtfyUnit) Always added in v0.0.20

func (n *NtfyUnit) Always() []string

Always returns the list of units to always trigger

func (*NtfyUnit) Name added in v0.0.20

func (n *NtfyUnit) Name() string

Name returns the unit name

func (*NtfyUnit) OnFailure added in v0.0.20

func (n *NtfyUnit) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*NtfyUnit) OnSuccess added in v0.0.20

func (n *NtfyUnit) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*NtfyUnit) Run added in v0.0.20

func (n *NtfyUnit) Run(ctx context.Context) error

Run executes the ntfy unit

func (*NtfyUnit) SetOutput added in v0.0.20

func (n *NtfyUnit) SetOutput(output string)

SetOutput sets the output data from the triggering unit

func (*NtfyUnit) SetTriggerError added in v0.0.20

func (n *NtfyUnit) SetTriggerError(err error)

SetTriggerError sets the error from the triggering unit

func (*NtfyUnit) SetTriggeringUnit added in v0.0.20

func (n *NtfyUnit) SetTriggeringUnit(unitName string)

SetTriggeringUnit sets the name of the unit that triggered this notification

func (*NtfyUnit) Type added in v0.0.20

func (n *NtfyUnit) Type() string

Type returns the unit type

type Orchestrator

type Orchestrator struct {
	// contains filtered or unexported fields
}

Orchestrator manages unit execution and triggering

func NewOrchestrator

func NewOrchestrator(units []Unit) *Orchestrator

NewOrchestrator creates a new orchestrator with the given units

func (*Orchestrator) GetActiveUnit added in v0.0.9

func (o *Orchestrator) GetActiveUnit() string

GetActiveUnit returns the name of the currently executing unit, or empty string if none

func (*Orchestrator) GetResults

func (o *Orchestrator) GetResults() map[string]*UnitResult

GetResults returns all execution results

func (*Orchestrator) Run

func (o *Orchestrator) Run() error

Run executes the orchestrator (for use with oklog/run)

func (*Orchestrator) RunDaemon

func (o *Orchestrator) RunDaemon(ctx context.Context) error

RunDaemon executes in daemon mode, continuously checking triggers

func (*Orchestrator) RunOnce added in v0.0.9

func (o *Orchestrator) RunOnce(ctx context.Context) error

RunOnce executes all units once with the given context This method is useful for testing and one-time execution

func (*Orchestrator) RunSingleUnit

func (o *Orchestrator) RunSingleUnit(ctx context.Context, unitName string, runTriggers bool) error

RunSingleUnit executes a single unit by name If runTriggers is true, the unit runs and all its triggers are executed If runTriggers is false, the unit runs in isolation without executing its triggers

func (*Orchestrator) SetDaemonMode added in v0.0.9

func (o *Orchestrator) SetDaemonMode(daemon bool)

SetDaemonMode configures whether the orchestrator should run in daemon mode

func (*Orchestrator) Stop added in v0.0.9

func (o *Orchestrator) Stop(error)

Stop stops the orchestrator (for use with oklog/run)

type RebootConfig

type RebootConfig struct {
	UnitConfig `yaml:",inline"`
	Delay      int `yaml:"delay,omitempty"` // delay in seconds before reboot
}

RebootConfig represents the configuration for a reboot unit

type RebootUnit

type RebootUnit struct {
	// contains filtered or unexported fields
}

RebootUnit is a unit that logs and reboots the system

func NewRebootUnit

func NewRebootUnit(name string, delay int, onSuccess, onFailure, always []string) *RebootUnit

NewRebootUnit creates a new reboot unit

func (*RebootUnit) Always

func (r *RebootUnit) Always() []string

Always returns the list of units to trigger regardless of success/failure

func (*RebootUnit) Name

func (r *RebootUnit) Name() string

Name returns the name of the unit

func (*RebootUnit) OnFailure

func (r *RebootUnit) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*RebootUnit) OnSuccess

func (r *RebootUnit) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*RebootUnit) Run

func (r *RebootUnit) Run(ctx context.Context) error

Run executes the reboot unit

func (*RebootUnit) Type

func (r *RebootUnit) Type() string

Type returns the unit type

type RunConfig

type RunConfig struct {
	UnitConfig `yaml:",inline"`
	Script     string `yaml:"script"`
	Directory  string `yaml:"directory,omitempty"`
	Timeout    string `yaml:"timeout,omitempty"`
	Shell      string `yaml:"shell,omitempty"`
	UsePTY     bool   `yaml:"use_pty,omitempty"`
}

RunConfig represents the configuration for a Run unit

type RunUnit

type RunUnit struct {
	// contains filtered or unexported fields
}

RunUnit executes shell scripts/commands

func NewRunUnit

func NewRunUnit(name, script, directory string, timeout time.Duration, shell string, usePTY bool, onSuccess, onFailure, always []string) *RunUnit

NewRunUnit creates a new Run unit

func (*RunUnit) Always

func (r *RunUnit) Always() []string

Always returns the list of units to always trigger

func (*RunUnit) Name

func (r *RunUnit) Name() string

Name returns the unit name

func (*RunUnit) OnFailure

func (r *RunUnit) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*RunUnit) OnSuccess

func (r *RunUnit) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*RunUnit) Run

func (r *RunUnit) Run(ctx context.Context) error

Run executes the shell script

func (*RunUnit) Type

func (r *RunUnit) Type() string

Type returns the unit type

type StartConfig

type StartConfig struct {
	UnitConfig `yaml:",inline"`
}

StartConfig represents the configuration for a Start trigger

type StartTrigger

type StartTrigger struct {
	// contains filtered or unexported fields
}

StartTrigger is a trigger that always fires when brun starts

func NewStartTrigger

func NewStartTrigger(name string, onSuccess, onFailure, always []string) *StartTrigger

NewStartTrigger creates a new Start trigger

func (*StartTrigger) Always

func (s *StartTrigger) Always() []string

Always returns the list of units to always trigger

func (*StartTrigger) Check

func (s *StartTrigger) Check(ctx context.Context, mode CheckMode) (bool, error)

Check always returns true since this trigger fires on every run

func (*StartTrigger) Name

func (s *StartTrigger) Name() string

Name returns the trigger name

func (*StartTrigger) OnFailure

func (s *StartTrigger) OnFailure() []string

OnFailure returns the list of units to trigger on failure

func (*StartTrigger) OnSuccess

func (s *StartTrigger) OnSuccess() []string

OnSuccess returns the list of units to trigger on success

func (*StartTrigger) Run

func (s *StartTrigger) Run(ctx context.Context) error

Run executes when the trigger fires

func (*StartTrigger) Type

func (s *StartTrigger) Type() string

Type returns the trigger type

type State

type State struct {
	// contains filtered or unexported fields
}

State represents the common state file for all units

func NewState

func NewState(filePath string) *State

NewState creates a new state manager with the given file path filePath must not be empty

func (*State) Get

func (s *State) Get(unitName, key string) (any, bool)

Get retrieves a value from state for the given unit name and key

func (*State) GetString

func (s *State) GetString(unitName, key string) (string, bool)

GetString retrieves a string value from state

func (*State) Load

func (s *State) Load() error

Load reads the state file from disk

func (*State) Save

func (s *State) Save() error

Save writes the state file to disk

func (*State) Set

func (s *State) Set(unitName, key string, value any) error

Set stores a value in state for the given unit name and key and automatically saves

func (*State) SetString

func (s *State) SetString(unitName, key, value string) error

SetString stores a string value in state and automatically saves

type TriggerUnit

type TriggerUnit interface {
	Unit

	// Check returns true if the trigger condition is met
	// mode indicates whether this is a polling check or a manual trigger from another unit
	Check(ctx context.Context, mode CheckMode) (bool, error)

	// OnSuccess returns the names of units to trigger on success
	OnSuccess() []string

	// OnFailure returns the names of units to trigger on failure
	OnFailure() []string

	// Always returns the names of units to trigger regardless of success/failure
	Always() []string
}

TriggerUnit represents a unit that watches for conditions and triggers other units

type Unit

type Unit interface {
	// Name returns the name of the unit
	Name() string

	// Run executes the unit with the given context
	Run(ctx context.Context) error

	// Type returns the type of unit (e.g., "trigger", "task")
	Type() string
}

Unit represents a unit of work in the CI system

type UnitConfig

type UnitConfig struct {
	Name      string   `yaml:"name"`
	Type      string   `yaml:"type"`
	OnSuccess []string `yaml:"on_success,omitempty"`
	OnFailure []string `yaml:"on_failure,omitempty"`
	Always    []string `yaml:"always,omitempty"`
}

UnitConfig represents the base configuration for all units

type UnitConfigWrapper

type UnitConfigWrapper struct {
	Boot   *BootConfig   `yaml:"boot,omitempty"`
	Count  *CountConfig  `yaml:"count,omitempty"`
	Cron   *CronConfig   `yaml:"cron,omitempty"`
	Email  *EmailConfig  `yaml:"email,omitempty"`
	File   *FileConfig   `yaml:"file,omitempty"`
	Git    *GitConfig    `yaml:"git,omitempty"`
	Log    *LogConfig    `yaml:"log,omitempty"`
	Ntfy   *NtfyConfig   `yaml:"ntfy,omitempty"`
	Reboot *RebootConfig `yaml:"reboot,omitempty"`
	Run    *RunConfig    `yaml:"run,omitempty"`
	Start  *StartConfig  `yaml:"start,omitempty"`
}

UnitConfigWrapper wraps different unit configuration types

type UnitResult

type UnitResult struct {
	Unit   Unit
	Error  error
	Output string // Captured stdout/stderr
}

UnitResult represents the result of a unit execution

Directories

Path Synopsis
cmd
brun command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL