Bashcamp is open source. The scenario library is the community asset — the platform is the vehicle. If you've studied with Bashcamp, you know what a good scenario feels like. Build one.
Scenarios are the primary contribution path. A scenario is three files in a directory. No platform knowledge required — just bash and Linux.
Base image improvements — additions to docker/base-ubuntu/ or
docker/base-rocky/ that make the environment more realistic. Open an issue
before making changes here so we can discuss scope.
Bug fixes and platform improvements — open an issue first for anything that
touches api/, proxy/, or deploy/.
Documentation — always welcome. Same PR process.
A scenario lives at scenarios/<your-scenario-id>/ and contains exactly three files:
scenarios/privilege-escalation-01/
├── meta.json — machine-readable metadata
├── provision.sh — bash script that creates the broken environment
└── README.md — student-facing instructions and hints
Copy scenarios/_template/ to get started:
cp -r scenarios/_template scenarios/your-scenario-id{
"id": "your-scenario-id",
"title": "Short, descriptive title",
"distro": "ubuntu-22.04",
"difficulty": "beginner",
"duration_minutes": 20,
"objectives": ["3.1", "3.3"],
"description": "One sentence: what is broken and what does the student need to fix."
}distro — required. ubuntu-22.04 or rocky-9. Pick one. Your scenario is
written for one distro family. If you want to cover both, submit a sister scenario
and link them via distro_pair.
difficulty — beginner, intermediate, or advanced. Beginner means a
student who has read the study guide but hasn't practiced can solve it in under 30
minutes. Advanced means a working sysadmin would still have to think.
objectives — CompTIA Linux+ (XK0-006) objective codes. Look them up in the
exam objectives document. Map your scenario to what it actually tests.
distro_pair (optional) — the id of the sister scenario on the other distro
family. If you've written the same scenario for both Ubuntu and Rocky, link them:
"distro_pair": "privilege-escalation-01-rocky"This script runs as root inside the container at session start. It must create the broken state that defines the scenario.
#!/bin/bash
set -euo pipefail
# Create the student user for this scenario
useradd -m -s /bin/bash kgarcia
echo "kgarcia:linux+practice" | chpasswd
usermod -aG sudo kgarcia
# Create the broken condition
echo "kgarcia ALL=(ALL:ALL) ALL BADSYNTAX" >> /etc/sudoersRules — CI will enforce these after Milestone 8. Until then, run these checks locally:
- Must start with
#!/bin/bashandset -euo pipefail - No
apt,apt-get,dnf, oryum. Package installation at session start violates the 30-second startup budget. All packages must already be in the base image. If your scenario needs a tool that isn't there, open an issue — we'll discuss adding it to the base image or creating a plugin. - Must exit 0 on success
- Must be idempotent — safe to run twice without breaking anything
- Must complete in under 30 seconds
- Must pass shellcheck with no warnings
This is what the student reads. Write it like a ticket or a helpdesk call, not a textbook exercise.
Good:
Ticket #1047 — kgarcia can't sudo
User kgarcia called at 9:14am. They're getting "sudo: parse error in /etc/sudoers" and can't run any administrative commands. The server is otherwise healthy. Fix the sudoers file and restore kgarcia's sudo access.
Hint:
visudovalidates the file before saving. If sudo is already broken, there's another way in.
Bad:
Exercise: The sudoers file has a syntax error on line 28. Use visudo to fix it. The correct syntax is:
kgarcia ALL=(ALL:ALL) ALL
Don't include the answer or the exact fix. The point is for the student to figure it out. Hints are good; solutions are not.
- Fork https://github.com/b-mackenzie-alexander/bashcamp.cloud
- Create a branch:
feat/your-scenario-id - Copy
scenarios/_template/and fill in all three files - Test locally (see below)
- Open a pull request to
develop
The planned CI will automatically validate your meta.json, lint your
provision.sh, check for forbidden package installation commands, run the
provision script against the base image, and confirm it exits 0 in under 30
seconds. Until those workflows land, run the local checks below and fix any
failures before requesting review.
You need Docker installed.
Build the base image:
docker build docker/base-ubuntu/ -t bashcamp/ubuntu-22.04-base
# or
docker build docker/base-rocky/ -t bashcamp/rocky-9-baseRun your provision.sh:
SCENARIO=your-scenario-id
DISTRO=ubuntu-22.04 # or rocky-9
docker run -d --name test-scenario \
--tmpfs /run --tmpfs /run/lock \
--cgroupns=host \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
--cap-drop ALL \
--cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add FOWNER \
--cap-add KILL --cap-add SETUID --cap-add SETGID --cap-add SYS_ADMIN \
--cap-add AUDIT_WRITE \
--security-opt no-new-privileges:false \
bashcamp/${DISTRO}-base
docker cp scenarios/${SCENARIO}/provision.sh test-scenario:/tmp/provision.sh
docker exec test-scenario bash /tmp/provision.sh
echo "Exit code: $?"
# Verify the broken state is set up correctly:
docker exec -it test-scenario /bin/bash
# Clean up:
docker rm -f test-scenarioThe base images already define systemd as their entrypoint. Do not append
/sbin/init to the local docker run command; Docker will pass it as an argument
to systemd and the container can exit immediately. The cgroup bind must be rw
to match the API runtime path and allow systemd to manage its cgroup slice on
cgroup v2 hosts.
Validate your meta.json:
node scripts/validate-meta.js scenarios/your-scenario-id/meta.jsonShellcheck your provision.sh:
shellcheck scenarios/your-scenario-id/provision.shThe Linux+ exam tests both Debian and Red Hat families. A scenario written for
Ubuntu covers apt, /etc/default/grub, the sudo group, and AppArmor. The same
scenario for Rocky Linux covers dnf, grub2-mkconfig, the wheel group, and
SELinux. These differences are exactly what the exam tests.
If you submit a scenario for one distro and want to cover the other:
- Create a second directory:
scenarios/your-scenario-id-rocky/ - Write a new
provision.shfor Rocky (different commands, same broken condition) - Link them via
distro_pairin bothmeta.jsonfiles
The platform will offer students the option to switch distros within the same scenario category. Both must be independently working and independently tested.
Be constructive. Scenarios should be educational, not humiliating. Treat other contributors the way you'd want to be treated when you were learning.
Security issues: see SECURITY.md — do not open public issues for vulnerabilities.
Project: https://github.com/b-mackenzie-alexander/bashcamp.cloud