swactor/HOW_TO_TEST_DRIVE.md
Zachery Aaron Shores-Chmielewski a5bac57b0b feat: deploy CI pipeline with ci-relay and local-runner
Add ci-relay (VPS webhook receiver) and local-runner (Thinkpad CI
executor) crates that communicate over iroh. Includes .ci.yml smoke
test pipeline, simulation tests, and development docs.

Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski
2026-02-16 12:46:55 +07:00

10 KiB

How to Test Drive the Local CI Runner over LAN

This guide walks through running the local CI runner on the Thinkpad and triggering it from Forgejo on your main laptop. By the end, pushes to your Forgejo repos will automatically run CI pipelines on the Thinkpad.

Network Layout

┌──────────────────────┐         LAN         ┌──────────────────────┐
│   Main Laptop        │◄───────────────────►│   Thinkpad           │
│                      │                      │                      │
│   Forgejo instance   │   webhook POST ───► │   local-runner       │
│   (e.g. :3000)       │ ◄── status API ──── │   (e.g. :8787)       │
│                      │                      │   dashboard (:9090)  │
└──────────────────────┘                      └──────────────────────┘

Prerequisites

  • Main laptop: Forgejo running and accessible on LAN (e.g. http://192.168.1.100:3000)
  • Thinkpad: Rust toolchain installed, this repo cloned, network-reachable from the main laptop
  • Both machines on the same LAN (or routable to each other)

Find each machine's LAN IP:

# On each machine
ip addr show | grep 'inet ' | grep -v 127.0.0.1
# or
hostname -I

We'll use these example IPs throughout the guide:

  • Main laptop (Forgejo): 192.168.1.100
  • Thinkpad (CI runner): 192.168.1.200

Replace them with your actual IPs.


Step 1: Create a Forgejo API Token

On your main laptop, open Forgejo in a browser:

  1. Go to Settings > Applications (top-right user menu > Settings > Applications)
  2. Create a new token with at least these permissions:
    • repo: read/write (needed to post commit statuses)
  3. Copy the token (e.g. abc123def456)

Step 2: Write a .ci.yml for Your Repository

Create a .ci.yml file on the Thinkpad. This defines what pipelines and jobs to run.

Example for a Rust project:

pipelines:
  check:
    triggers:
      - event: push
        branches: ["*"]
        exclude: ["master"]
    jobs:
      fmt:
        run: cargo fmt -- --check
      clippy:
        run: cargo clippy -- -D warnings
      test:
        needs: [fmt, clippy]
        run: cargo test
        timeout: 300

  release:
    triggers:
      - event: push
        branches: ["master"]
    jobs:
      test:
        run: cargo test --all-features
        timeout: 600
      bench:
        needs: [test]
        run: cargo bench

Key points:

  • triggers[].event: one of push, tag, merge
  • triggers[].branches: glob patterns ("*" matches all, "feature-*" matches prefixed)
  • triggers[].exclude: branches to skip
  • jobs[].run: a single command string, or a list of commands (run sequentially)
  • jobs[].needs: list of jobs that must pass first (DAG dependencies)
  • jobs[].timeout: max seconds before the job is killed (default: 300)
  • jobs[].env: extra environment variables as key-value pairs

Save this file somewhere accessible on the Thinkpad, e.g. /home/user/ci/my-project.ci.yml.


Step 3: Build the Local Runner on the Thinkpad

SSH into the Thinkpad (or work directly on it):

# Clone the repo if not already present
git clone <repo-url> ~/swactor
cd ~/swactor

# Build the local-runner binary
cargo build --release -p local-runner

The binary will be at target/release/local-runner.


Step 4: Create a Working Directory

The runner clones your repo into a working directory for each pipeline. Create it:

mkdir -p ~/ci-work

Step 5: Start the Local Runner

./target/release/local-runner \
  --port 8787 \
  --forgejo-url http://192.168.1.100:3000 \
  --forgejo-token abc123def456 \
  --secret my-webhook-secret \
  --yaml /home/user/ci/my-project.ci.yml \
  --work-dir /home/user/ci-work \
  --repo-url http://192.168.1.100:3000/user/repo.git \
  --dashboard-port 9090
Flag Description
--port Port the webhook listener binds to (default: 8787)
--forgejo-url URL of your Forgejo instance on the main laptop
--forgejo-token API token created in Step 1
--secret Webhook secret (must match what you set in Forgejo, see Step 6)
--yaml Path to the .ci.yml file on disk
--work-dir Base directory for git clones (one subdirectory per pipeline)
--repo-url Git clone URL for the repository (HTTP or SSH)
--dashboard-port Optional: enables the web dashboard on this port

You should see:

Webhook listener on http://0.0.0.0:8787
Local CI runner started
  Webhook: http://0.0.0.0:8787
  YAML:    /home/user/ci/my-project.ci.yml
  Workdir: /home/user/ci-work
  Dashboard: http://0.0.0.0:9090

The runner is now listening for webhooks.


Step 6: Configure the Forgejo Webhook

On your main laptop, open Forgejo and go to the repo's settings:

  1. Navigate to Settings > Webhooks > Add Webhook > Forgejo
  2. Fill in:
    • Target URL: http://192.168.1.200:8787 (Thinkpad's LAN IP and runner port)
    • Secret: my-webhook-secret (must match --secret from Step 5)
    • Trigger On: Choose which events to send:
      • Push Events (for push triggers)
      • Create Events (for tag triggers)
      • Pull Request Events (for merge triggers)
    • Branch filter: leave blank to send all branches, or set a pattern
    • Active: checked
  3. Click Add Webhook

Test the webhook connection

After adding the webhook, Forgejo shows a Test Delivery button. Click it to send a test ping. Check the Thinkpad terminal for output. Forgejo also shows the response status — you should see 200 OK.


Step 7: Push and Watch

From your main laptop (or anywhere with push access):

cd ~/my-project
git checkout -b test-ci
echo "// test" >> src/main.rs
git add src/main.rs
git commit -m "test CI"
git push origin test-ci

On the Thinkpad terminal, you'll see the runner:

  1. Receive the webhook
  2. Match triggers in .ci.yml
  3. Queue the pipeline
  4. Clone the repo and checkout the commit SHA
  5. Execute jobs one at a time, respecting the DAG order
  6. Stream stdout/stderr in real time
  7. Report commit statuses back to Forgejo

Back in Forgejo, the commit will show status checks (pending, then success/failure) next to the SHA.


Step 8: View the Dashboard (Optional)

If you started with --dashboard-port 9090, open a browser on any LAN machine:

http://192.168.1.200:9090

This shows the swactor runtime dashboard with CI-specific panels: active pipelines, recent pipelines, job statuses, and actor system metrics.


Behavior Reference

One-at-a-Time Execution

Jobs run serially — only one job executes at any moment across all pipelines. This guarantees benchmark isolation with no resource contention.

Queue Supersede

If you push twice to the same branch quickly:

  • First push is already running: it finishes normally
  • Second push is queued: it runs after the first finishes
  • Third push arrives while second is still queued: the second is superseded (marked as error, skipped), and the third takes its place in the queue

Only queued pipelines get superseded — a running pipeline always runs to completion.

DAG Dependencies

Within a pipeline, jobs respect their needs dependencies. If test needs [fmt, clippy], then fmt runs first, then clippy, then test. If fmt fails, test is skipped.

CI Environment Variables

Every job command has these injected:

Variable Value
CI true
CI_COMMIT_SHA The commit being tested
CI_BRANCH The branch name
CI_PIPELINE_ID Numeric pipeline identifier
CI_JOB_NAME Name of the current job

Plus any env keys from the job definition in .ci.yml.

Commit Status Reporting

The runner posts status updates to the Forgejo API for each pipeline and each job:

  • pending when a pipeline/job is queued
  • success when all jobs pass
  • failure when a job fails
  • error when a pipeline is superseded

These appear as commit status checks in Forgejo's UI.


Troubleshooting

Webhook not reaching the Thinkpad

  • Verify the Thinkpad's firewall allows inbound on the webhook port:
    # On Thinkpad
    sudo ufw allow 8787/tcp   # if using ufw
    # or
    sudo iptables -A INPUT -p tcp --dport 8787 -j ACCEPT
    
  • Confirm connectivity from main laptop:
    curl -v http://192.168.1.200:8787
    # Should get "method not allowed" (405) — that means the server is reachable
    

Signature mismatch (401)

  • The --secret flag on the runner must exactly match the Secret field in Forgejo's webhook config
  • If you don't want signature verification, set both to empty strings (omit --secret and leave Secret blank in Forgejo)

Git clone fails

  • Make sure --repo-url is reachable from the Thinkpad:
    # On Thinkpad
    git ls-remote http://192.168.1.100:3000/user/repo.git
    
  • If the repo is private, use an authenticated URL:
    http://user:password@192.168.1.100:3000/user/repo.git
    
    Or use SSH:
    git@192.168.1.100:user/repo.git
    

Status updates not appearing in Forgejo

  • Verify the API token has repo write permissions
  • Check the Thinkpad terminal for StatusReporter: failed to post status errors
  • Test the token manually:
    curl -H "Authorization: token abc123def456" \
         http://192.168.1.100:3000/api/v1/user
    

Jobs failing unexpectedly

  • Check that the Thinkpad has the necessary toolchain (cargo, rustup, etc.)
  • The working directory for each pipeline is {work-dir}/pipeline-{id}/ — you can inspect it
  • Job stdout/stderr is streamed to the runner's terminal output

Quick-Start Cheat Sheet

# === Thinkpad ===
cd ~/swactor
cargo build --release -p local-runner
mkdir -p ~/ci-work

./target/release/local-runner \
  --port 8787 \
  --forgejo-url http://LAPTOP_IP:3000 \
  --forgejo-token YOUR_TOKEN \
  --secret YOUR_SECRET \
  --yaml /path/to/.ci.yml \
  --work-dir ~/ci-work \
  --repo-url http://LAPTOP_IP:3000/user/repo.git \
  --dashboard-port 9090

# === Main Laptop (Forgejo) ===
# Repo > Settings > Webhooks > Add Webhook:
#   URL:    http://THINKPAD_IP:8787
#   Secret: YOUR_SECRET
#   Events: Push, Create, Pull Request

# === Test it ===
git push origin my-branch   # triggers CI on the Thinkpad