Contributing¶
Contributions to Bilrost are welcome. This guide covers the development workflow from fork to merged PR.
Development Setup¶
Prerequisites¶
You need macOS with Homebrew installed. The bootstrap script installs all other dependencies automatically, but for development you should also have:
# Core dependencies (installed by bootstrap.sh if missing)
brew install lima ansible jq gitleaks
# Development tools
brew install shellcheck yamllint
pip install yamllint # Also available via pip
Clone and Bootstrap¶
# Fork the repo on GitHub, then clone your fork
git clone https://github.com/<your-username>/openclaw-sandbox.git
cd openclaw-sandbox
# Bootstrap a VM for testing
./bootstrap.sh --openclaw ~/Projects/openclaw --secrets ~/.openclaw-secrets.env
Workflow¶
1. Fork the Repository¶
Fork Peleke/openclaw-sandbox on GitHub and clone your fork.
2. Create a Feature Branch¶
git checkout -b feat/my-feature main
Use conventional branch naming:
| Prefix | Purpose |
|---|---|
feat/ |
New features |
fix/ |
Bug fixes |
docs/ |
Documentation changes |
test/ |
Test additions or changes |
refactor/ |
Code refactoring |
3. Make Changes¶
See the Code Style section below for conventions.
4. Run Tests¶
Run quick tests for fast iteration (no VM required):
# All quick tests
./tests/overlay/run-all.sh --quick && \
./tests/sandbox/run-all.sh --quick && \
./tests/gh-cli/run-all.sh --quick && \
./tests/obsidian/run-all.sh --quick && \
./tests/cadence/run-all.sh --quick
Tip
If your change only affects one role, run just that role's tests during development. Run the full suite before opening a PR.
Before submitting, run ShellCheck locally:
shellcheck scripts/*.sh bootstrap.sh tests/**/*.sh
And lint the YAML:
yamllint -d relaxed ansible/
5. Open a Pull Request¶
Push your branch and open a PR against main:
git push origin feat/my-feature
CI will run automatically on your PR:
- YAML lint --
yamllint -d relaxedon all Ansible files - Ansible validation --
yaml.safe_load()on all.ymlfiles - ShellCheck -- lints
scripts/,bootstrap.sh, and test files
Note
CI does not run VM deployment tests. Run those locally before submitting: ./tests/<role>/run-all.sh (full mode, requires a running VM).
Code Style¶
Ansible YAML¶
- Use 2-space indentation.
- Always provide
defaults/main.ymlfor role variables. - Use
no_log: trueon any task that handles secrets. - Use
creates:orwhen:conditions for idempotent tasks. - Put Jinja2 expressions in double quotes:
"{{ variable }}".
Ansible YAML Gotcha
Standard YAML parsers (Python's yaml.safe_load()) will fail on Ansible files containing Jinja2 {{ }} expressions outside of quoted strings. CI uses yaml.safe_load(), so always quote your Jinja2 expressions.
Shell Scripts¶
- All scripts must pass ShellCheck.
- Use
set -euo pipefailat the top of every script. - Use
#!/usr/bin/env bashas the shebang. - Quote all variable expansions:
"$variable", not$variable. - Use
[[ ]]for conditionals, not[ ]. - Include a usage comment block at the top of each script.
File Naming Conventions¶
| Type | Pattern | Example |
|---|---|---|
| Ansible role | ansible/roles/<name>/ |
ansible/roles/overlay/ |
| Role defaults | defaults/main.yml |
|
| Role tasks | tasks/main.yml |
|
| Role handlers | handlers/main.yml |
|
| Role templates | templates/<name>.j2 |
templates/workspace.mount.j2 |
| Test runner | tests/<role>/run-all.sh |
|
| Ansible test | tests/<role>/test-<role>-ansible.sh |
|
| VM test | tests/<role>/test-<role>-role.sh |
|
| Scripts | scripts/<name>.sh |
scripts/sync-gate.sh |
Commit Messages¶
Use conventional commit format:
<type>(<scope>): <description>
[optional body]
Types: feat, fix, docs, test, refactor, chore.
Examples:
feat(overlay): add audit watcher for overlay writes
fix(telegram): replace open access with pairing-based security
docs: update README for v0.4.0
test(gh-cli): add APT repo verification checks
Adding a New Ansible Role¶
When adding a new role:
- Create the role directory structure under
ansible/roles/<name>/. - Add the role to
ansible/playbook.yml. - Wire any new flags into
bootstrap.sh. - Create the test suite (see Testing > Adding Tests).
- Update the README with the new feature.
Secrets Pipeline¶
If your role requires a new secret, there are 5 insertion points in the secrets handling:
ansible/roles/secrets/defaults/main.yml-- default variable- Regex extraction block #1 in secrets tasks (extract from file)
- Regex extraction block #2 in secrets tasks (extract from file)
has_directcondition (check for direct injection via-e)has_anycondition + status output
Plus the template that writes /etc/openclaw/secrets.env.
APT Repository Pattern¶
If your role installs software from an APT repository:
- Download the GPG key to
/etc/apt/keyrings/<name>.gpg(usecreates:for idempotency). - Add the source to
/etc/apt/sources.list.d/<name>.list. - Run
apt-get updatethenapt-get install.
This follows the same pattern used by the Docker and GitHub CLI roles.
Questions?¶
Open an issue on GitHub if you have questions about contributing.