# 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: ```bash # 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: ```yaml 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): ```bash # Clone the repo if not already present git clone ~/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: ```bash mkdir -p ~/ci-work ``` --- ## Step 5: Start the Local Runner ```bash ./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): ```bash 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: ```bash # 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: ```bash 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: ```bash # 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: ```bash 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 ```bash # === 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 ```