kevin-cazal/deploy_challenges

GitHub: kevin-cazal/deploy_challenges

Stars: 0 | Forks: 0

# deploy_challenges Deploy [ctfcli](https://github.com/CTFd/ctfcli)-compatible challenge repositories to a CTFd instance. ## Docker image Published to [GitHub Container Registry](https://github.com/kevin-cazal/deploy_challenges/pkgs/container/deploy_challenges): ghcr.io/kevin-cazal/deploy_challenges:latest Tags: `latest` (default branch), commit SHA, and `v*` semver on git tags. ### Deploy from a git repo (CTFd on the host) export GPG_PASSPHRASE='…' # required if challenges use private/flag.txt.gpg docker run --rm \ -e GPG_PASSPHRASE \ --add-host=host.docker.internal:host-gateway \ ghcr.io/kevin-cazal/deploy_challenges:latest \ https://github.com/Manta-Epitech-Academy/osint_investigator_ctf_challenges.git \ --subdir challenges \ --url http://host.docker.internal:9042/ctfd/default \ --token ctfd_YOUR_ADMIN_TOKEN On macOS/Windows Docker Desktop, `host.docker.internal` is usually available without `--add-host`. ### Deploy from a private git repo (SSH) The image includes `openssh-client` and GitHub host keys. Mount your SSH key (or agent). Git clone uses `ssh -F /dev/null` by default so a host-mounted `~/.ssh/config` with wrong ownership inside Docker does not block the clone. export GPG_PASSPHRASE='…' docker run --rm \ -e GPG_PASSPHRASE \ -v "$HOME/.ssh:/root/.ssh:ro" \ ghcr.io/kevin-cazal/deploy_challenges:latest \ git@github.com:kevin-cazal/shell-1-challenges.git \ --url https://YOUR_CTFD_HOST/ctfd/default \ --token ctfd_YOUR_ADMIN_TOKEN \ --force With **ssh-agent** instead of mounting keys: docker run --rm \ -e GPG_PASSPHRASE \ -v "$SSH_AUTH_SOCK:/ssh-agent" \ -e SSH_AUTH_SOCK=/ssh-agent \ ghcr.io/kevin-cazal/deploy_challenges:latest \ git@github.com:kevin-cazal/shell-1-challenges.git \ --url https://YOUR_CTFD_HOST/ctfd/default \ --token ctfd_YOUR_ADMIN_TOKEN \ --force HTTPS private repos work too — embed a GitHub PAT in the URL: `https://x-access-token:TOKEN@github.com/owner/repo.git` ### Local challenge directory docker run --rm \ -e GPG_PASSPHRASE \ -v "$PWD/challenges:/challenges:ro" \ --add-host=host.docker.internal:host-gateway \ ghcr.io/kevin-cazal/deploy_challenges:latest \ /challenges --no-clone \ --url http://host.docker.internal:9042/ctfd/default \ --token ctfd_YOUR_ADMIN_TOKEN ### Options | Flag | Description | |------|-------------| | `--url` | CTFd API base URL (required) | | `--token` | Admin API token (required) | | `--subdir` | Subdirectory inside the source repo | | `--no-clone` | Source is a local path | | `--force` | Re-sync existing challenges | | `--wait` | Seconds to wait for CTFd (default 60) | | `--no-sync-flags` | Skip GPG/API flag sync | | `GPG_PASSPHRASE` | Decrypt `private/flag.txt.gpg` or `flag.yml.gpg` | | `SHELL1_FLAG_SECRET` | CTFd env for `type: dynamic` flags (plugin) | ### CTFd home page If the challenge root contains **`index.html`** (HTML fragment), deploy updates the CTFd page with route **`index`** (create or patch). Omit the file to leave the instance home page unchanged. ### Images in challenge descriptions Markdown like `![alt](https://raw.githubusercontent.com/kevin-cazal/deploy_challenges/main/screenshot.png)` is rewritten to CTFd file URLs (`/files/…`) after install, using attachments listed in `files:`. Keep images next to `challenge.yml` and list them under `files:` (see [CHALLENGE_REPOSITORY.md](CHALLENGE_REPOSITORY.md)). Flag sync supports **`private/flag.yml`** (`static`, `regex`, `dynamic`, `custom`) and legacy **`private/flag.txt`**. See [CHALLENGE_REPOSITORY.md](CHALLENGE_REPOSITORY.md). ## Build locally docker build -t deploy-challenges . docker run --rm deploy-challenges --help ## Run without Docker python3 -m venv .venv .venv/bin/pip install -r requirements.txt export GPG_PASSPHRASE='…' .venv/bin/python deploy_challenges.py … --url … --token … Requires **git**, **gpg**, and **openssh-client** (for `git@…` URLs) on the host for clone and encrypted flags. ## License See repository defaults.