Thank you for your interest in contributing to AstroCTFb! We welcome contributions from CTF organizers, Go developers, and security enthusiasts. Whether you're fixing bugs, adding new features, or improving documentation - your help makes AstroCTFb better.
Be respectful and constructive in all interactions. We're here to build something useful together.
- Go 1.26+ - backend is written in Go (see
backend/go.mod) - Docker & Docker Compose - for running PostgreSQL, Redis, and storage locally
- Make - build automation (
backend/Makefile) - Node.js / npx - required for OpenAPI spec bundling (
make openapi)
golangci-lint- linteroapi-codegen- OpenAPI code generationsqlc- SQL query code generationwire- dependency injection code generationmockery- mock generationgovulncheck- vulnerability scanning
cd backend && make install-tools# 1. Fork on GitHub, then:
git clone https://github.com/YOUR_USERNAME/AstroCTFb.git
cd AstroCTFb
# 2. Add upstream
git remote add upstream https://github.com/TakuyaYagam1/AstroCTFb.git# Copy env and configure (use .env.local.example for Docker stack with Vault)
cp .env.local.example .env.local
# Edit .env.local - set DB password, JWT secrets, etc.
# Start infrastructure (PostgreSQL, Redis, SeaweedFS, monitoring)
make -C backend compose-infra
# Run backend
cd backend && make runOr start the full stack (infra + backend):
make -C backend compose-fullAstroCTFb/
├── backend/ # Go backend
│ ├── cmd/app/ # Application entry point
│ ├── cmd/cleanup/ # Cleanup binary (cron jobs)
│ ├── config/ # Config loading from env / Vault
│ ├── internal/
│ │ ├── app/ # App wiring and server startup
│ │ ├── apperr/ # Domain error sentinels (ErrNotFound, etc.)
│ │ ├── cache/ # In-memory TTL cache (scoreboard, etc.)
│ │ ├── controller/
│ │ │ ├── restapi/
│ │ │ │ ├── errmap/ # Domain error -> HTTP status mapping
│ │ │ │ ├── middleware/ # Auth, rate-limit, competition guard, etc.
│ │ │ │ └── v1/ # REST API handlers (OpenAPI-generated interfaces)
│ │ │ └── websocket/v1/ # WebSocket handler
│ │ ├── domain/ # Domain entities and types
│ │ ├── loginlockout/ # Redis-backed failed-login lockout
│ │ ├── openapi/ # OpenAPI spec and generated code
│ │ ├── repo/
│ │ │ ├── persistent/ # PostgreSQL implementations (sqlc queries)
│ │ │ └── webapi/ # External HTTP clients (OAuth providers)
│ │ ├── scoring/ # Dynamic scoring engine (decay formulas)
│ │ ├── seed/ # Default data seeding (admin, settings)
│ │ ├── storage/ # File storage (local, S3/SeaweedFS)
│ │ ├── usecase/ # Business logic
│ │ │ ├── cacheutil/ # Shared cache invalidation helpers
│ │ │ ├── computil/ # Cached/Fresh competition resolver
│ │ │ ├── guard/ # Submission eligibility, challenge visibility checks
│ │ │ ├── avatar/ # Avatar upload, resize, WebP encode
│ │ │ ├── backup/
│ │ │ ├── challenge/
│ │ │ ├── competition/
│ │ │ ├── email/
│ │ │ ├── notification/
│ │ │ ├── page/
│ │ │ ├── settings/
│ │ │ ├── team/
│ │ │ └── user/
│ │ ├── websocket/ # WebSocket broadcaster and events
│ │ └── wire/ # Wire DI providers
│ ├── migrations/ # SQL migrations (goose, fixed 3-file set)
│ ├── queries/ # sqlc SQL query files
│ ├── pkg/ # Shared packages (crypto, httperr, mailer, slug, sse, testutil, validator, vault)
│ └── codegen/ # Code generation configs (sqlc, oapi-codegen, mockery, wire)
├── deployment/docker/ # Docker Compose files and configs
├── monitoring/ # Prometheus, Grafana, Loki, Alertmanager
└── docs/ # Architecture, deployment, monitoring docs
git checkout -b feature/your-feature-name # new feature
git checkout -b fix/scoreboard-freeze # bug fix
git checkout -b docs/update-contributing # documentation
git checkout -b refactor/challenge-usecase # refactoringfeat: add dynamic scoring for brackets
fix: prevent double hint unlock on concurrent requests
docs: document METRICS_ALLOWED_IPS env variable
refactor: extract team lock pattern to helper
test: add race condition tests for solve submission
chore: bump golangci-lint to v2
Submit PRs against main. Use the PR template. CI must pass before merge.
AstroCTFb follows Clean Architecture:
domain/- domain entities and types, no external dependenciesusecase/- business logic; depends only ondomainand repository interfacescontroller/- HTTP layer; calls usecases, never touches repo directlyrepo/persistent/- database implementations; implement repository interfaces defined in usecases
Error handling (two-layer system):
internal/apperr/- domain-level sentinel errors (ErrNotFound,ErrConflict, etc.)internal/controller/restapi/errmap/- mapsapperrsentinels to HTTP status codes- Handlers use
v1/helperto write error responses; they never importapperrdirectly
Rules:
- Usecases depend on interfaces, not concrete types (interfaces defined on consumer side)
- All multi-step DB operations must be wrapped in
tm.Run(ctx, ...)(transaction manager) - Use
singleflightfor cache-miss deduplication on hot read paths - Use
errgroupfor parallel independent operations - Always propagate
context.Contextas the first argument - Wrap errors with context:
fmt.Errorf("Layer - Method - step: %w", err)
- Edit
backend/internal/openapi/openapi.yml(split spec - routes go inopenapi/routes/) - Run
make openapi- regenerates types, server interface, and client - Implement the handler in
internal/controller/restapi/v1/ - Add usecase method and wire it up
- Register the route in
internal/controller/restapi/v1/router.go - Add/update tests
- Add the method to the appropriate interface in
internal/usecase/contract.go - Implement in
internal/usecase/<domain>/ - Regenerate mocks:
make mockery(ormake generate-mocks-<domain>) - Add unit tests with table-driven patterns
- Update Wire providers in
internal/wire/providers.goif new deps are needed, then runmake wire
The project uses a fixed 3-file migration set (no incremental numbered migrations):
| File | Purpose |
|---|---|
000001_init.sql |
Full schema: tables, constraints, indexes (no CONCURRENTLY) |
000002_concurrent_indexes.sql |
Indexes with CREATE INDEX CONCURRENTLY (-- +goose NO TRANSACTION) |
000003_seed.sql |
Default data (competition, settings, configs) |
- Add DDL changes to
000001_init.sql, concurrent indexes to000002, seed data to000003 - Update sqlc queries in
backend/queries/ - Run
make sqlcto regenerate query code - Update domain types in
internal/domain/if needed - Mention schema changes in your PR description
- Add to
backend/config/config.go(parse inNew()) - Add to
.env.exampleand.env.local.examplewith a comment - Document in docs/ENVIRONMENT.md
- Document in PR description
AstroCTFb has three test layers - run all before opening a PR:
cd backend
# Unit tests (fast, no Docker)
make test-unit
# Integration tests (requires running PostgreSQL + Redis)
make test-integration
# E2E tests (requires full stack)
make test-e2e
# With race detection (recommended for concurrency-sensitive code)
make test-unit-race
make test-integration-raceRequirements for new code:
- Unit tests for all usecase methods (table-driven, parallel where safe)
- Integration tests for new repository methods
- Race detection must pass (
-raceflag) - Run
make test-coverage-unitto check coverage
cd backend
# Lint (golangci-lint)
make lint
# Format
make fmt
# Vulnerability scan
make deps-audit
# Tidy modules after dependency changes
make mod-tidyAll of the above run in CI on every push and PR. Fix lint errors locally before pushing.
GitHub Actions runs on every push and PR:
| Job | Command |
|---|---|
golangci-lint |
make lint |
yamllint |
for all YAML files |
hadolint |
for backend/Dockerfile |
dotenv-linter |
for .env* files |
| Unit tests | make test-unit |
| Integration tests | make test-integration |
| E2E tests | make test-e2e |
Reproduce CI locally with the exact same commands before submitting.
- Formatting:
gofmt/go fmt- no exceptions - Error handling: always wrap with context:
fmt.Errorf("UserUseCase - GetByID: %w", err) - Error sentinels: use
internal/apperr/for domain errors; mapping to HTTP is ininternal/controller/restapi/errmap/ - Naming: exported -
CamelCase; unexported -camelCase; acronyms -ID,URL,JWT(notId,Url,Jwt) - Comments: only for non-trivial logic (transactions, concurrency, crypto, deadlock prevention); skip CRUD, transformers, handlers
- Function length: aim for < 50 lines; extract helpers for clarity
- No global state: all dependencies injected via constructors
- Transactions: multi-step operations always in
tm.Run(ctx, func(ctx context.Context) error { ... })
- Open an Issue for bugs or feature requests
- Check existing issues before opening duplicates
- For design questions or larger contributions, open a Discussion first
By contributing to AstroCTFb, you agree that your contributions will be licensed under the same license as the project (LICENSE).