Skip to content

About

No description, website, or topics provided.

Resources

Stars

15 stars

Watchers

0 watching

Forks

Repository files navigation

LiveCTF Bot Game System

Data Archive

Data from the qualifiers including database, API snapshot, replays, bots and handouts can be found in this GCS bucket: https://storage.googleapis.com/livectf26-archive

Crates

Crate Description
emulator Emulator for the custom architecture
driver Runs bots and the game engine
coordinator Retrieves game data and bots, runs driver, reports results
api Web API for teams and admins
game-engine Game logic and simulation
game Game visualizer

Dev setup

sudo apt-get update && sudo apt-get install -y \
  libwayland-dev libasound2-dev libudev-dev pkg-config \
  libx11-dev libxkbcommon-x11-0 libssl-dev

cargo install 'sea-orm-cli@^2.0.0-rc'

sudo -i -u postgres psql <<'SQL'
CREATE DATABASE livectf;
CREATE USER livectf WITH ENCRYPTED PASSWORD 'livectf';
GRANT ALL PRIVILEGES ON DATABASE livectf TO livectf;
\c livectf
GRANT ALL ON SCHEMA public TO livectf;
SQL

export DATABASE_URL=postgres://livectf:livectf@localhost/livectf
sea-orm-cli migrate
sea-orm-cli generate entity \
  --output-dir entities/src/entities \
  --database-url "$DATABASE_URL"

Deployment Runbook

Operational guide for setting up and running the bot-game infrastructure. For the architectural rationale and the GCP alternative design see docs/cicd.md.

Prerequisites

Tool Version Install
Terraform ≥ 1.6 https://developer.hashicorp.com/terraform/install
Ansible ≥ 2.15 pip install ansible
make any system package

Docker is only needed locally if you want to run cargo make docker-build-* inside bot-game/ to test image builds. CI handles all production builds. The Hetzner CLI is not required — Terraform manages all resources.

All make commands below run from the repo root.


One-time setup (new environment from scratch)

1. Create Hetzner project and credentials

  1. Log in to console.hetzner.cloud and create a new project
  2. Generate an API token (Read & Write) — Project → Security → API tokens
  3. Create an Object Storage access key — Project → Security → S3 credentials — note the access key and secret key
  4. Generate an SSH key pair if you don't have one:
    ssh-keygen -t ed25519 -C "bot-game-organizer"

2. Bootstrap Terraform state bucket

This creates the Hetzner Object Storage bucket that holds all future Terraform state. Run once.

cp infrastructure-bootstrap/terraform.tfvars.example infrastructure-bootstrap/terraform.tfvars
# fill in storage_access_key and storage_secret_key
make infra-bootstrap

Note the outputs:

state_bucket_name = "bot-game-tfstate"
s3_endpoint       = "https://nbg1.your-objectstorage.com"

3. Fill in infrastructure secrets

cp infrastructure/terraform.tfvars.example infrastructure/terraform.tfvars
# fill in hcloud_token and ssh_public_keys

Server types and other per-environment settings are hardcoded in infrastructure/main.tf.

4. Provision servers

cp infrastructure/backend.hcl.example infrastructure/backend.hcl
# fill in bucket, endpoint, access_key, secret_key

make infra-init
make infra-apply          # both environments at once
# or scope to one:
make infra-apply-staging
make infra-apply-prod

Note the IP addresses from the Terraform output:

staging_api_ip       = "1.2.3.4"
staging_runner_ip    = "5.6.7.8"
production_api_ip    = "9.10.11.12"
production_runner_ip = "13.14.15.16"

5. Update Ansible inventory

Edit ansible/inventory.yml and fill in the IPs from step 4:

api-staging:
  ansible_host: 1.2.3.4
runner-staging:
  ansible_host: 5.6.7.8
api-prod:
  ansible_host: 9.10.11.12
runner-prod:
  ansible_host: 13.14.15.16

Alternatively, regenerate it from the Terraform template:

terraform -chdir=infrastructure output -raw ansible_inventory > ansible/inventory.yml

6. Create a GitHub PAT for image pulls

Servers pull images from ghcr.io at deploy time. Create a PAT with read:packages scope: GitHub → Settings → Developer settings → Personal access tokens

7. Fill in Ansible secrets

The secrets files are gitignored. Fill them in for each environment:

ansible/group_vars/all/secrets.yml (shared across all hosts):

ghcr_token: "ghp_YOUR_PAT"

ansible/group_vars/staging/secrets.yml:

db_password: "CHANGEME"
paseto_secret: "000...000"   # 32-byte hex string
admin_password: "CHANGEME"
storage_access_key_id: "YOUR_KEY"
storage_secret_access_key: "YOUR_SECRET"
coordinator_token: "CHANGEME"

Do the same for ansible/group_vars/production/secrets.yml.

Non-sensitive settings (bucket names, endpoints, registry path) are in ansible/group_vars/staging/vars.yml and ansible/group_vars/production/vars.yml — update those if your values differ.

Then run:

make ansible-setup-staging
make ansible-setup-prod

8. Configure GitHub repository secrets and variables

Go to GitHub → Repository → Settings → Secrets and variables → Actions.

Secrets (repository-level):

Secret name Value
HETZNER_SSH_KEY Contents of your private SSH key (~/.ssh/id_ed25519)

9. Create GitHub environments

Go to GitHub → Repository → Settings → Environments and create two environments:

staging — no protection rules needed.

Variable Value
API_HOST Public IP of staging API server
RUNNER_HOST Public IP of staging runner server

production — add at least one required reviewer to gate the prod deploy in release.yml.

Variable Value
API_HOST Public IP of production API server
RUNNER_HOST Public IP of production runner server

10. Upload the world file

The world.bin file is not generated by CI. Copy it to both runner servers:

scp /path/to/world.bin root@STAGING_RUNNER_HOST:/opt/bot-game/runner/world.bin
scp /path/to/world.bin root@PROD_RUNNER_HOST:/opt/bot-game/runner/world.bin

Day-to-day operations

Access services

All ports except SSH are firewalled. Use SSH port forwarding to reach the API.

1. Build the CLI

cd bot-game/cli
cargo build --release
# binary: bot-game/cli/target/release/livectf

Add it to your PATH or refer to it by path in the commands below.

2. Configure the CLI

Copy the template and point it at the forwarded port:

cp bot-game/cli/cli/Config.toml.tpl bot-game/cli/Config.toml
# api_url = "http://localhost:3000"
# token = ""   ← fill in after login

The CLI looks for Config.toml in the current directory. Run all CLI commands from bot-game/cli/ or pass --config /path/to/Config.toml.

3. Open the SSH tunnel

In a dedicated terminal (keep it open):

ssh -L 3000:localhost:3000 root@API_HOST

4. Authenticate

livectf auth login --username admin --password <admin_password>
# prints: {"token":"v4.local..."}

Copy the token value into Config.toml:

token = "v4.local..."

Verify it works:

livectf auth me
# {"Admin": null}  or  {"GameManager": null}

5. Common operations

Teams:

livectf teams list
livectf teams create --name "Team Rocket"
livectf teams get <team-id>

Bots:

livectf teams upload-bot <team-id> path/to/bot.bin
livectf teams bots <team-id>

Games:

# Create a game for a specific phase with a random seed
livectf games create --seed $(uuidgen) --phase phase1

# Add a bot as a participant
livectf games add-participant <game-id> --bot-id <bot-id>

# Start the game (coordinator picks it up)
livectf games start <game-id>

# Check status
livectf games get <game-id>

# Record the result (one --score per participant)
livectf games finish <game-id> --score <bot-id>:<points> --score <bot-id>:<points>

Leaderboard:

livectf leaderboard

Impersonate a team (admin support flow):

When an organizer needs to see the player UI exactly as a particular team sees it — to debug a bot listing, check a download link, reproduce a player report — mint a short-lived session token for that team and use it in the frontend.

# Mint a 24-hour session token for the team. Requires admin credentials in
# Config.toml (see step 4 above).
livectf auth impersonate <team-id>
# prints: {"status":"ok","token":"v4.local..."}

Then paste the v4.local.… value into the frontend's Login screen (textarea). The frontend recognises the paseto prefix and validates the token directly via /api/auth/me — no further exchange — so the session is indistinguishable from a normal team login. livectf auth me will report {"Team":{"team_id":"…"}} when run with that token.

Deploy to staging

Automatic — push to main. build.yaml runs first; if it passes, staging.yml builds images and deploys.

To check deployment status:

ssh root@STAGING_API_HOST systemctl status bot-game-api
ssh root@STAGING_RUNNER_HOST systemctl status 'bot-game-runner@*'

Deploy to production

  1. On GitHub, create a Release targeting the commit you want to ship
  2. release.yml re-tags the images built for that commit and deploys to production

Open the API for competition participants

When the competition goes live:

make infra-open-firewall

To close it again after the event:

make infra-close-firewall

No server restart needed — Hetzner applies firewall changes immediately.

Update server configuration

Re-run the relevant Ansible playbook. It is idempotent.

make ansible-setup-staging   # api + runner
make ansible-setup-prod

Update the world file

scp /path/to/new-world.bin root@PROD_RUNNER_HOST:/opt/bot-game/runner/world.bin
ssh root@PROD_RUNNER_HOST systemctl restart 'bot-game-runner@*'

View logs

ssh root@STAGING_API_HOST journalctl -u bot-game-api -f
ssh root@STAGING_RUNNER_HOST journalctl -u 'bot-game-runner@*' -f

Troubleshooting

Containers not starting after deploy:

ssh root@HOST systemctl status bot-game-api
ssh root@HOST journalctl -u bot-game-api -n 50

Image pull fails on server: Check the ghcr.io token is valid and has read:packages scope:

ssh root@HOST docker pull ghcr.io/OWNER/REPO/api:staging

If it fails, update ansible/group_vars/all/secrets.yml with a fresh token and re-run make ansible-setup-*.

Terraform state locked:

terraform -chdir=infrastructure force-unlock LOCK_ID

Runner nsjail errors:

ssh root@RUNNER_HOST docker run --cap-add SYS_ADMIN --cap-add SYS_PTRACE debian:bookworm-slim echo ok

If this fails the host kernel may not support user namespaces — check /proc/sys/kernel/unprivileged_userns_clone.

About

No description, website, or topics provided.

Resources

Stars

15 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages