Runme Guide
Runme lets you execute the code blocks in our Markdown documentation — both interactively in VS Code and non-interactively via the CLI or GitHub Actions. This ensures that tutorials, runbooks, and setup guides stay validated and never go stale.
Why We Use Runme
- Living documentation — code blocks in Markdown are the source of truth and are tested in CI
- One workflow — the same document works in VS Code notebooks, the terminal, and GitHub Actions
- No duplication — we don't maintain separate scripts alongside docs; the docs are the scripts
Installation
VS Code Extension
Install the Runme extension from the VS Code marketplace. Once installed, .md files open as interactive notebooks where each fenced code block becomes a runnable cell.
CLI
Recommended (Homebrew - macOS):
brew install runme
runme --version
Linux (Debian/Ubuntu):
# Download and install .deb package
RUNME_VERSION="3.16.5"
wget https://downloads.runme.dev/runme/${RUNME_VERSION}/runme_linux_x86_64.deb
sudo dpkg -i runme_linux_x86_64.deb
runme --version
Alternative (manual binary installation):
# Download the binary directly
RUNME_VERSION="3.16.5"
wget https://downloads.runme.dev/runme/${RUNME_VERSION}/runme_linux_x86_64.tar.gz
tar -xzf runme_linux_x86_64.tar.gz
sudo mv runme /usr/local/bin/
runme --version
How Code Blocks Are Annotated
We add runme attributes directly in the Markdown fenced code block header. The most common attributes:
| Attribute | Default | Purpose |
|---|---|---|
name |
auto-generated | Identifies the cell for runme run <name> |
interactive |
true |
false = capture output inline (no TTY needed) |
background |
false |
true = run as a background process |
excludeFromRunAll |
false |
true = skip when running all cells |
Syntax Example
```bash {"name":"greet","interactive":"false"}
echo "Hello from phine.af"
```
Non-executable blocks (diagrams, sample output, config snippets) use text or json as the language identifier — runme will not try to execute them.
Running Documentation Locally
Run a Single Named Cell
runme ls --filename docs/getting-started/06-qos-http-tutorial.md
To run a specific cell by name:
runme run deploy-stack --filename docs/getting-started/06-qos-http-tutorial.md
Run All Cells
Runs every cell except those marked excludeFromRunAll:
runme run --all --skip-prompts --filename docs/getting-started/06-qos-http-tutorial.md
Run in VS Code
- Open the
.mdfile — it automatically renders as a notebook - Click Run All in the toolbar, or run individual cells with the play button
- Use the Configure gear icon on a cell to inspect or change its attributes
Writing Runme-Compatible Documentation
Follow these conventions when adding or updating Markdown documentation in this project.
1. Name Every Executable Cell
Give each code block a descriptive, kebab-case name. This is the identifier used by the CLI and CI:
```bash {"name":"build-phine.af-core","interactive":"false"}
docker compose build af_core
```
2. Mark Non-Interactive Commands
Commands that produce output and require no user input should set interactive to false. This lets runme capture the output and is required for CI:
```bash {"name":"check-status","interactive":"false"}
docker ps --format "table {{.Names}}\t{{.Status}}"
```
3. Handle Wait Scenarios with Retry Loops
Do not use bare sleep for waiting on services. Use retry loops with a bounded number of attempts so the script self-terminates on failure:
```bash {"name":"wait-for-service","interactive":"false"}
retries=30
for i in $(seq 1 $retries); do
if curl -sf http://localhost:8080/health; then
echo "Service is ready"
break
fi
echo "Waiting... ($i/$retries)"
sleep 2
done
curl -sf http://localhost:8080/health
```
The final command outside the loop acts as an assertion — if the service never became ready, it will fail the cell.
4. Exclude Optional / Interactive Cells
Cells that are interactive (e.g., tshark capture), optional (e.g., baseline comparison), or destructive (e.g., --rmi all) should be excluded from automated runs:
```bash {"name":"start-tshark","background":"true","excludeFromRunAll":"true","interactive":"true"}
sudo tshark -i demo-oai ...
```
5. Use Background for Long-Running Processes
Servers or watchers that should run in the background:
```bash {"name":"start-server","background":"true"}
docker compose up -d
```
6. Use text for Non-Executable Blocks
Diagrams, example output, and configuration snippets should use the text language identifier to prevent runme from treating them as executable:
```text
┌──────────┐ ┌──────────┐
│ Client │────▶│ Server │
└──────────┘ └──────────┘
```
7. Hide Complex CI Scripts in Collapsible Sections
When you need both a simple command for users and a complex validation script for CI, use this pattern:
- Show a simple, user-friendly command with
excludeFromRunAll:"true"andinteractive:"true" - Hide the CI validation script in a
<details>collapsible section with a propernameattribute
Example:
Run the iperf3 client to measure bandwidth:
```bash {"excludeFromRunAll":"true","interactive":"true"}
docker exec oai-ext-dn iperf3 -c 10.60.0.1 -p 5070 -t 10
```
Observe the throughput in the output. With QoS enforcement active, you should see the bitrate capped around 5-10 Mbps.
<details>
<summary><b>Automated Validation Script (for CI/Testing)</b></summary>
The following script automatically validates that bandwidth is being rate-limited. This runs in CI but can also be executed manually:
```bash {"name":"run-iperf3-client","interactive":"false"}
IPERF_OUTPUT=$(docker exec oai-ext-dn iperf3 -c 10.60.0.1 -p 5070 -t 10 --json)
BITRATE_BPS=$(echo "$IPERF_OUTPUT" | jq -r '.end.sum_received.bits_per_second')
BITRATE_MBPS=$(echo "scale=2; $BITRATE_BPS / 1000000" | bc)
if (( $(echo "$BITRATE_MBPS < 3 || $BITRATE_MBPS > 12" | bc -l) )); then
echo "FAIL: Bitrate ${BITRATE_MBPS} Mbps outside expected range"
exit 1
fi
echo "PASS: Bitrate ${BITRATE_MBPS} Mbps within expected range"
```
</details>
Benefits:
- User experience: Users see clean, readable commands they can copy/paste
- CI automation: Named cell inside
<details>is still discoverable and executable by runme - Documentation clarity: Complex parsing logic doesn't clutter the tutorial narrative
- Flexibility: Users can expand the collapsible if they want to understand the validation logic
When to use this pattern:
- Scripts with multiple error handling branches
- Complex assertions that are important for CI but distracting in tutorials
CI Integration
We use the Runme CLI directly in GitHub Actions to execute named cells from tutorial documents. Each named cell becomes a workflow step.
Installing Runme in CI
First, install the Runme CLI in your workflow:
- name: Install Runme CLI
run: |
RUNME_VERSION="3.16.5"
wget https://downloads.runme.dev/runme/${RUNME_VERSION}/runme_linux_x86_64.deb
sudo dpkg -i runme_linux_x86_64.deb
runme --version
Workflow Structure
Execute named cells using runme run:
- name: Run tutorial step
run: runme run deploy-stack --filename docs/getting-started/06-qos-http-tutorial.md
Multiple steps can be sequenced:
- name: Deploy
run: runme run deploy-stack --filename docs/getting-started/06-qos-http-tutorial.md
- name: Verify
run: runme run verify-ue-ip --filename docs/getting-started/06-qos-http-tutorial.md
- name: Cleanup
if: always()
run: runme run cleanup --filename docs/getting-started/06-qos-http-tutorial.md
continue-on-error: true
Best Practices for CI
- Always use
--filename: Explicitly specify the markdown file path - Use
if: always(): For cleanup steps that should run even if previous steps fail - Add
continue-on-error: true: For optional cleanup steps that shouldn't fail the workflow - Sequential execution: Run cells in order; runme respects the document's working directory from frontmatter
Existing CI Workflows
| Workflow | File | What It Validates |
|---|---|---|
| Tutorials Validation | .github/workflows/test-tutorials.yml |
All runme-enabled getting-started tutorials |
| Integration Tests | .github/workflows/integration-tests.yml |
AF Core integration test suite |
| Build | .github/workflows/build.yml |
Compilation and unit tests |
Runme-Enabled Documentation
| Document | Description |
|---|---|
| QoS Enforcement Tutorial (HTTP) | Full end-to-end 5G QoS tutorial with iperf3 verification |
| QoS Enforcement Tutorial (gRPC) | Same tutorial over the gRPC northbound interface |
| Demo QoD Adapter Tutorial | Automated adapter-driven QoD session lifecycle |