Skip to main content

Day-to-Day Operations

This page covers the routine tasks a mentor performs during an active season: starting and stopping the app, managing who can sign in, and keeping an eye on what the system is doing.

Running ops commands

The maintenance commands in this page (allowlist, users, audit-prune, backup, restore) run inside the control container via the coderunner CLI:

docker compose exec control coderunner <subcommand> <args>

Use docker compose run --rm control <subcommand> <args> instead for a one-off command while the control plane is stopped (e.g. restore). On the Google Cloud VM the compose project lives in /opt/coderunner and needs sudo (cd /opt/coderunner && sudo docker compose exec -T control …). On a from-source host checkout with Bun you can instead use the bun run <name> aliases shown in package.json. The examples below use the bun run short form; substitute the docker compose exec control coderunner <subcommand> form for a containerized deployment.

Starting and stopping​

Both the local and Google Cloud deployments run the control plane as a docker compose service. From the compose directory (the repo root locally, or /opt/coderunner on the VM):

docker compose up -d # start (runs DB migrations first, then serves)
docker compose stop # stop the control plane
docker compose restart control
docker compose ps # status — control should be "healthy"
docker compose logs -f control

Students connect to http://<host-ip>:4000/ (or https://<your-domain>/ behind Caddy on the VM). Student workspace containers keep running after the control plane stops and are reconciled automatically when it starts again. restart: unless-stopped brings the stack back after a host reboot.

Stopping workspace containers​

Containers are not stopped when the control plane stops. To stop all running student workspace containers:

# Stop and remove only stopped/exited managed containers
bun run docker:cleanup

# Force-stop all currently running managed containers
docker stop $(docker ps -q --filter label=frc-sim.managed=true)

You can also stop a single workspace's container via the admin API (see Admin API break-glass below).

Between sessions​

You do not need to stop containers manually between class sessions. The idle sweep stops containers automatically after students have been inactive for the configured timeout. The default is 30 minutes (IDLE_STOP_MINUTES); the cloud VM deployment uses 10 minutes. Containers are reconciled on the next session without any student-visible data loss.


Managing who can sign in​

Access is controlled by an allowlist of email addresses and domains. Sign-in is via OAuth (GitHub and/or Google), but only emails that match the allowlist are permitted through. An empty allowlist blocks everyone.

Viewing the allowlist​

bun run allowlist:list

Adding an entry​

Pass an individual email address or a whole domain. The script auto-detects which kind you mean based on whether the value contains an @:

# Allow a specific person
bun run allowlist:add coach@frcteam.org

# Allow everyone at a domain (useful for team Google Workspace accounts)
bun run allowlist:add frcteam.org

Removing an entry​

bun run allowlist:remove old-member@frcteam.org
bun run allowlist:remove frcteam.org

Changes take effect immediately; the running control plane watches data/allowlist.json and picks up edits without a restart.


Managing user roles​

The first admin is best bootstrapped without any exec step: set CODERUNNER_ADMIN_EMAIL (comma-separated) in .env before the first startup and those accounts are allowlisted and granted the admin role on first sign-in (an existing account is promoted at the next startup). See OAuth Credentials. The commands here are for ongoing role changes after that.

After a coach or mentor signs in for the first time they are a regular user. Promote them to admin so they can access the admin panel and use the admin API:

# List all users (shows ID, name, email, role, workspace slug)
bun run users:list

# Grant admin role (the user must already exist in the DB)
bun run users:promote coach@frcteam.org

# Remove admin role
bun run users:demote coach@frcteam.org

Admins can also promote and demote users from the admin panel in the browser. The system prevents demoting the last remaining admin.


Running a classroom session​

For a meeting, open Admin → Classrooms, choose how long the session will run, and click Start classroom. Share the code or join link with students. They can join with their name, without being added to the allowlist.

Ask students to choose Leave before handing a computer to someone else. This signs them out and frees a container slot. When the meeting is over, click End now. The classroom also ends when its time runs out; either way, its guest accounts and projects are deleted.

A classroom can have up to 60 guests. The container limit applies to running workspaces; see Capacity when planning for simultaneous use.

Container concurrency cap​

The system limits how many workspace containers can run simultaneously to prevent the host from being overloaded when many students sign in at once. The default cap is 10 (MAX_ACTIVE_CONTAINERS).

When a student tries to open their workspace and the cap has been reached, they see a toast: "Server at capacity. Your coach has been notified. Please try again in a few minutes." Students with already-running containers are unaffected.

Checking and adjusting the cap at runtime​

You can read and change the cap without restarting the control plane:

# Read current effective cap
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:4000/admin/config/max-active-containers

# Set cap to 15
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-X POST -H "Content-Type: application/json" \
-d '{"value": 15}' \
http://localhost:4000/admin/config/max-active-containers

The runtime override is stored in the database and takes precedence over the environment variable until changed again. The admin panel also shows the current cap and active container count with an inline editor.


Audit log​

All admin actions (promoting users, stopping containers, modifying the allowlist, changing the concurrency cap) are recorded in the audit log.

What is logged​

ActionTrigger
user.promotePromoting a user to admin
user.demoteDemoting an admin
user.deleteDeleting a user and their workspace
container.stopStopping a workspace's containers
container.restart-codeRestarting a workspace's code container
workspace.backupCreating an operator workspace backup
workspace.restoreRestoring a workspace from a backup
allowlist.addAdding an email/domain to the allowlist
allowlist.removeRemoving an email/domain from the allowlist
config.max-active-containersChanging the container concurrency cap
classroom.createStarting a classroom
classroom.endEnding a classroom early ("End now")
classroom.cleanupDeleting an ended or expired classroom's guests (system)
classroom.guest_joinA guest joining or rejoining a classroom

Each entry records the timestamp, the acting user (ID and email), the action, the target (kind and ID), and optional metadata.

Viewing the audit log​

The admin panel has an "Audit Log" tab with filters by actor, action, and time range.

Via the API:

# Latest entries
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:4000/admin/audit-log

# Filter by action prefix
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://localhost:4000/admin/audit-log?action=user&limit=50"

# Filter by actor email (substring match)
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://localhost:4000/admin/audit-log?actor=coach"

# Paginate using the smallest id from the previous page as cursor
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"http://localhost:4000/admin/audit-log?before=42&limit=25"

Pruning old entries​

Audit entries accumulate indefinitely. Prune them periodically:

# Remove all entries before a date
bun run audit:prune -- --before 2026-01-01

# Preview without deleting
bun run audit:prune -- --before 2026-01-01 --dry-run

Running this monthly is a reasonable cadence for a classroom deployment.


Admin API break-glass​

If ADMIN_TOKEN is set in your environment, you can call admin endpoints directly with a bearer token. This is useful for scripting and for bootstrapping before the first admin user has signed in.

# Overall system status: workspaces, container states, active builds
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:4000/admin/status | jq .

# Stop one workspace's containers
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-X POST \
http://localhost:4000/admin/workspaces/<workspaceId>/stop-containers

# Restart one workspace's code container
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-X POST \
http://localhost:4000/admin/workspaces/<workspaceId>/restart-code

If ADMIN_TOKEN is not set, admin endpoints require a signed-in admin session cookie. The token is optional and intended as a break-glass mechanism, not as the primary admin interface.