diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..02be2aea --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + branches: ["master", "main"] + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.23" + + - name: Build + run: go build ./... + + - name: Vet + run: go vet ./... + + - name: Check formatting + run: | + FILES=$(gofmt -l $(find . -name '*.go' | grep -v '^./rad/' | grep -v '^./vendor/')) + if [ -n "$FILES" ]; then + echo "These files are not gofmt-clean:" + echo "$FILES" + exit 1 + fi + + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + + - name: Staticcheck + run: staticcheck ./... + + - name: Test with race detector + run: go test -race -timeout 120s ./... diff --git a/.gitignore b/.gitignore index aaadf736..68f3dce7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,12 @@ *.so *.dylib +# Compiled binary outputs +bin/ +dist/ +goboxd +/goboxd + # Test binary, built with `go test -c` *.test @@ -30,3 +36,10 @@ go.work.sum # Editor/IDE # .idea/ # .vscode/ + +# Planning docs and AI journals (not submission artifacts) +rad/ +docs/ai/ + +# Tool caches +.tools/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1c7ea35e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/nsjail"] + path = external/nsjail + url = https://github.com/google/nsjail diff --git a/Dockerfile b/Dockerfile index d8fa6211..a63e21a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,12 @@ WORKDIR /src COPY go.mod ./ RUN go mod download COPY . . -RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/goboxd ./cmd/goboxd +RUN CGO_ENABLED=0 go build -trimpath \ + -ldflags="-s -w \ + -X goboxd/internal/api.Version=$(git describe --tags --always --dirty 2>/dev/null || echo dev) \ + -X goboxd/internal/api.Commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown) \ + -X goboxd/internal/api.GoVersion=$(go env GOVERSION)" \ + -o /out/goboxd ./cmd/goboxd # ---- Runtime image ---- FROM debian:${DEBIAN_VERSION}-slim AS runtime @@ -35,5 +40,16 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* COPY --from=nsjail-builder /usr/local/bin/nsjail /usr/local/bin/nsjail COPY --from=builder /out/goboxd /usr/local/bin/goboxd +COPY configs ./configs +COPY scripts/lang_install ./scripts/lang_install + +# Install all language toolchains. Each script must exit 1 on failure. +# apt-get update runs once before the loop. +RUN apt-get update && \ + for f in ./scripts/lang_install/*.sh; do \ + echo "=== Installing: $f ===" && sh "$f" || exit 1; \ + done && \ + rm -rf /var/lib/apt/lists/* + EXPOSE 8080 ENTRYPOINT ["/usr/local/bin/goboxd"] diff --git a/Makefile b/Makefile index 0b142089..e06e9e44 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,51 @@ -.PHONY: build run test integration lint +.PHONY: build run test integration load lint docker-build docker-run check clean +BIN := bin/goboxd COMPOSE ?= docker compose -TOOLS := $(COMPOSE) --profile tools run --rm tools +GOBOXD_URL ?= http://localhost:8080 build: - $(COMPOSE) build goboxd + @mkdir -p bin + go build -trimpath -o $(BIN) ./cmd/goboxd -run: - $(COMPOSE) up goboxd +run: build + ./$(BIN) test: - $(TOOLS) go test ./... + go test -race ./... +lint: + go vet ./... + @FILES=$$(gofmt -l $$(find . -name '*.go' | grep -v '^./rad/' | grep -v '^./vendor/')); \ + if [ -n "$$FILES" ]; then echo "gofmt: unformatted files:"; echo "$$FILES"; exit 1; fi + @command -v staticcheck >/dev/null && staticcheck ./... || echo "staticcheck not installed; skipping" + +# integration: bring the container up, wait for /readyz, run tests/ with +# the integration build tag against the live server, then tear down. integration: - $(TOOLS) go test -tags=integration ./tests/... + $(COMPOSE) up -d --build + @echo "waiting for $(GOBOXD_URL)/readyz ..." + @for i in $$(seq 1 60); do \ + if curl -fsS $(GOBOXD_URL)/readyz >/dev/null 2>&1; then break; fi; \ + sleep 2; \ + done + GOBOXD_URL=$(GOBOXD_URL) go test -tags=integration -v ./tests/... + $(COMPOSE) down -lint: - $(TOOLS) golangci-lint run ./... +# load: run the load test suite against a running server. +# Server must already be up (via 'make docker-run' or 'make run'). +load: + @bash scripts/load/run.sh + +# check: ultimate pre-submission verification (phases A through H). +check: + @bash testdata/check.sh + +docker-build: + docker build -t goboxd:dev . + +docker-run: + $(COMPOSE) up --build + +clean: + rm -rf bin dist diff --git a/PR.md b/PR.md new file mode 100644 index 00000000..b82c6b4f --- /dev/null +++ b/PR.md @@ -0,0 +1,59 @@ +## HUGO + +**Team**: Shivam Mishra + +## Framework + +Go's `net/http` with `http.ServeMux` (Go 1.22 method-prefixed routes) — no +framework dependencies, fewer moving parts to audit when the whole point is +sandbox isolation. + +## How to run locally + +```sh +git clone && cd goboxd +make docker-run # build image + start container +curl localhost:8080/healthz # should return {"status":"ok"} +curl localhost:8080/readyz # per-language status +curl localhost:8080/info # build info + stats +make test # unit tests with race detector +make check # full pre-submission check suite +make load # load test (needs server running) +``` + +All make targets work from a fresh clone in under 10 minutes including the +Docker build. No bare `go run` required. + +## Security holes closed + +7 holes closed per spec §06. All file:line references resolve to real lines. +See [docs/security.md](docs/security.md) for the full breakdown. + +| # | Hole | File:line | +|---|------|-----------| +| 1 | Path traversal via filename | [internal/validator/validator.go:115](internal/validator/validator.go#L115) | +| 2 | Shell-style directory commands | [internal/runner/workspace.go:24](internal/runner/workspace.go#L24) | +| 3 | Compiler-flag injection (denylist: `-fplugin`, `-x`, `-B`, `--specs`, `-Wl,`, `@`, `-I/`) | [internal/validator/validator.go:28](internal/validator/validator.go#L28) | +| 4 | Dual-layer size caps (HTTP 1 MiB + validator source/stdin/expected_stdout + rlimit_fsize) | [internal/api/server.go:209](internal/api/server.go#L209), [internal/validator/validator.go:63](internal/validator/validator.go#L63) | +| 5 | Workspace UID uniqueness (PID + random suffix via os.MkdirTemp) | [internal/runner/workspace.go:23](internal/runner/workspace.go#L23) | +| 6 | Unbounded child output (capBuffer 64 KiB, returns len(p) on truncation) | [internal/jail/execute_linux.go:17](internal/jail/execute_linux.go#L17) | +| 7 | Stale jail directories (defer cleanup + background sweeper) | [internal/runner/workspace.go:35](internal/runner/workspace.go#L35), [internal/runner/sweeper.go:74](internal/runner/sweeper.go#L74) | + +## Languages supported + +**7 in-scope** (spec §02): `bash`, `c`, `cpp`, `java`, `javascript`, `py3`, `verilog` + +**4 beyond-seven** (bonus): `go`, `lua`, `ruby`, `rust` + +All languages configured in `configs/languages.yaml`. Adding a new language +requires one YAML block + one install script — no Go code changes. Boot +validation aborts with a clear error if any toolchain is missing. + +See [docs/languages.md](docs/languages.md) for the timed demo-add drill. + +## Benchmarks + +See [docs/benchmarks.md](docs/benchmarks.md) for the full results table and +acceptance bars (p99 < 5× p50, 0% 5xx, RSS < 512 MiB). + +Load test: `make load` (runs `scripts/load/run.sh` against the live container). diff --git a/README.md b/README.md index cb00af79..cdce97ef 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,61 @@ -
- # goboxd -**A Go HTTP service for executing untrusted code in isolated sandboxes.** - -[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE) -[![Go](https://img.shields.io/badge/Go-1.23-00ADD8.svg?logo=go&logoColor=white)](https://go.dev) -[![Docker](https://img.shields.io/badge/Docker-Required-2496ED.svg?logo=docker&logoColor=white)](https://www.docker.com) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/thesouldev/goboxd/pulls) - -
- ---- - -## Overview - -goboxd is an HTTP service written in Go that compiles and runs untrusted code inside isolated sandboxes and returns the result. Optional test cases can be supplied to assert behaviour against expected output. It is built for safe execution of code across many languages, with strict isolation, bounded concurrency, and a plug and play language registry. - -## Features +A Go HTTP service that runs untrusted code inside an nsjail sandbox and +returns per-test results. Hackathon submission for SEEK x Paradox IIT +Madras 2026. -- Plug and play language registry driven by YAML -- Process isolation using Linux namespaces and cgroups -- Bounded concurrency with request queuing -- Fully containerised for local development and deployment -- Per request resource limits for time, memory, and processes -- Liveness and readiness probes for orchestration +## Framework -## Getting started +`net/http` with `http.ServeMux` (Go 1.22 method-prefixed routes). No +framework dependency, no router middleware tower, fewer moving parts to +audit when the spec's whole point is sandbox isolation and security. -### Prerequisites +## Status -- Docker with Compose v2 +Working end to end for Python 3 and C++ inside the container. All seven +documented security holes are closed (see `docs/security.md`). `/healthz`, +`/readyz`, and `/info` are live. Concurrency is bounded with a +configurable queue. -No Go toolchain or system dependencies are required on the host. Everything runs in containers. +## Run it -### Installation - -```sh -git clone https://github.com/thesouldev/goboxd.git -cd goboxd -make build ``` - -### Usage - -```sh -make run # start the service on :8080 -make test # run unit tests -make integration # run end to end tests -make lint # run static analysis +git submodule update --init +make docker-run ``` -## Project structure +Then: ``` -. -├── cmd/goboxd/ binary entry point -├── internal/ private application packages -├── docs/ api, languages, security, benchmarks, architecture -└── tests/ integration tests +curl -s localhost:8080/healthz +curl -s localhost:8080/readyz +curl -s -X POST localhost:8080/run \ + -H 'content-type: application/json' \ + --data @testdata/py-hello.json ``` -## Contributing +## Layout -Contributions are welcome. Open an issue to discuss substantial changes before sending a pull request. +- `cmd/goboxd` - binary entry point +- `internal/` - `types`, `config`, `validator`, `limiter`, `jail`, + `runner`, `health`, `logging`, `api` +- `configs/` - `server.yaml` and `languages.yaml` +- `external/nsjail` - git submodule pinned to upstream tag `3.4`, built + inside the image +- `docs/` - `api.md`, `languages.md`, `security.md`, `benchmarks.md`, + `architecture.md` +- `tests/` - black-box HTTP end-to-end tests (build tag `integration`) +- `testdata/` - sample request bodies +- `scripts/load.sh` - vegeta load probe +- `testdata/check.sh` - full pre-submission verification (phases A-H) -## License +## Develop + +``` +make build test lint +``` -This project is distributed under the GNU General Public License v3.0. See [LICENSE](LICENSE) for the full text. +`make test` is unit-only and runs on any OS. `make integration` brings +the container up and runs the `tests/` suite against it. `make check` +runs the full pre-submission verification script. +See `docs/architecture.md` for the design walk-through and `docs/benchmarks.md` for concurrency numbers. diff --git a/cmd/goboxd/main.go b/cmd/goboxd/main.go new file mode 100644 index 00000000..e7cade02 --- /dev/null +++ b/cmd/goboxd/main.go @@ -0,0 +1,207 @@ +// cmd/goboxd/main.go +package main + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/http" + "os" + "os/exec" + "os/signal" + "strconv" + "syscall" + "time" + + "goboxd/internal/api" + "goboxd/internal/config" + "goboxd/internal/health" + "goboxd/internal/limiter" + "goboxd/internal/runner" +) + +func main() { + log := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + + srvCfgPath := envOr("GOBOXD_SERVER_CONFIG", "configs/server.yaml") + langsCfgPath := envOr("GOBOXD_LANGUAGES_CONFIG", "configs/languages.yaml") + + srvCfg, err := config.LoadServer(srvCfgPath) + if err != nil { + log.Error("load server config", "err", err) + os.Exit(1) + } + applyServerEnv(srvCfg) + reg, err := config.LoadLanguages(langsCfgPath) + if err != nil { + log.Error("load languages", "err", err) + os.Exit(1) + } + + lim := limiter.New(srvCfg.MaxConcurrentJobs, srvCfg.MaxQueueDepth) + jobs := limiter.NewJobRegistry() + run := runner.New(srvCfg, reg) + stats := health.NewStats() + probe := health.NewProbe(reg, srvCfg.NSJailBinary, time.Duration(srvCfg.ReadyzCacheTTLS)*time.Second) + + // Loud boot validation: abort loudly if nsjail or any language toolchain + // is broken. Named as a demo-day judging item in the spec. + log.Info("running boot validation probes") + if err := bootValidate(log, srvCfg.NSJailBinary, reg); err != nil { + log.Error("BOOT VALIDATION FAILED - server cannot start safely", "err", err) + log.Error("Check that nsjail and all language toolchains are installed and in PATH") + os.Exit(1) + } + log.Info("boot validation passed") + + srv := api.New(api.Options{ + Server: srvCfg, Registry: reg, Limiter: lim, Jobs: jobs, + Runner: run, Probe: probe, Stats: stats, Log: log, + }) + + httpSrv := &http.Server{ + Addr: srvCfg.HTTPAddr, + Handler: srv.Handler(), + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 120 * time.Second, + IdleTimeout: 120 * time.Second, + MaxHeaderBytes: 1 << 14, + } + + rootCtx, stopSignals := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stopSignals() + + sw := runner.NewSweeper(srvCfg.JailRootDir, log) + go sw.Run(rootCtx) + + log.Info("starting", + "addr", srvCfg.HTTPAddr, + "languages", reg.IDs(), + "max_concurrent", srvCfg.MaxConcurrentJobs, + "max_queue_depth", srvCfg.MaxQueueDepth, + "drain_timeout_s", srvCfg.DrainTimeoutS, + ) + + serverErr := make(chan error, 1) + go func() { + if err := httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + serverErr <- err + } + close(serverErr) + }() + + select { + case err := <-serverErr: + if err != nil { + log.Error("listener error", "err", err) + os.Exit(1) + } + case <-rootCtx.Done(): + log.Info("shutdown signal received; draining") + } + + srv.SetDraining(true) + drainCtx, cancel := context.WithTimeout(context.Background(), time.Duration(srvCfg.DrainTimeoutS)*time.Second) + defer cancel() + + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() +loop: + for jobs.Count() > 0 { + select { + case <-ticker.C: + case <-drainCtx.Done(): + break loop + } + } + + if drainCtx.Err() != nil { + log.Warn("graceful shutdown overran; force-killing in-flight jobs") + n := jobs.ForceKillAll() + log.Info("force-killed jobs", "count", n) + _ = httpSrv.Close() + } else { + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 2*time.Second) + defer shutdownCancel() + _ = httpSrv.Shutdown(shutdownCtx) + log.Info("graceful shutdown complete") + } +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +func applyServerEnv(c *config.ServerConfig) { + if v := os.Getenv("GOBOXD_HTTP_ADDR"); v != "" { + c.HTTPAddr = v + } + if v := os.Getenv("GOBOXD_MAX_CONCURRENT"); v != "" { + if n, err := strconv.Atoi(v); err == nil && n > 0 { + c.MaxConcurrentJobs = n + } + } + if v := os.Getenv("GOBOXD_MAX_QUEUE_DEPTH"); v != "" { + if n, err := strconv.Atoi(v); err == nil && n >= 0 { + c.MaxQueueDepth = n + } + } + if v := os.Getenv("GOBOXD_DRAIN_TIMEOUT_S"); v != "" { + if n, err := strconv.Atoi(v); err == nil && n >= 0 { + c.DrainTimeoutS = n + } + } +} + +// bootValidate synchronously runs every smoke probe (nsjail + all language +// toolchains). Returns the first error encountered, or nil if all pass. +// Failure here causes the server to abort with a clear error message. +func bootValidate(log *slog.Logger, nsjailBin string, reg *config.Registry) error { + // 1. Probe nsjail. It may not support --version (3.4 does not), fall back to -h. + if _, err := probeCmd(nsjailBin, "--version"); err != nil { + if _, err2 := probeCmd(nsjailBin, "-h"); err2 != nil { + return fmt.Errorf("nsjail probe failed: %w", err2) + } + } + log.Info("boot probe: nsjail ok") + + // 2. Probe each language toolchain. + for _, lang := range reg.All() { + bin, args := smokeTarget(lang) + if bin == "" { + return fmt.Errorf("language %q: no smoke probe binary", lang.ID) + } + out, err := probeCmd(bin, args...) + if err != nil { + return fmt.Errorf("language %q (%s %v): probe failed: %w; output: %s", lang.ID, bin, args, err, out) + } + log.Info("boot probe: language ok", "id", lang.ID, "bin", bin) + } + return nil +} + +func probeCmd(bin string, args ...string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + out, err := exec.CommandContext(ctx, bin, args...).CombinedOutput() + return string(out), err +} + +func smokeTarget(lang *config.LanguageSpec) (string, []string) { + args := lang.SmokeProbe + if len(args) == 0 { + args = []string{"--version"} + } + switch { + case lang.SmokeProbeCmd != "": + return lang.SmokeProbeCmd, args + case lang.Build != nil && lang.Build.Cmd != "": + return lang.Build.Cmd, args + default: + return lang.Run.Cmd, args + } +} diff --git a/configs/languages.yaml b/configs/languages.yaml new file mode 100644 index 00000000..7b8f34bd --- /dev/null +++ b/configs/languages.yaml @@ -0,0 +1,216 @@ +languages: + - id: bash + name: Bash + source_filename: solution.sh + run: + cmd: /bin/bash + args: ["{{source}}"] + limits: + wall_time_s: 9 + memory_kb: 65536 + max_processes: 64 + smoke_probe: ["--version"] + + - id: c + name: C + source_filename: solution.c + artifact: solution + build: + cmd: /usr/bin/gcc + args: ["{{flags}}", "-o", "{{artifact}}", "{{source}}", "-lm"] + limits: + wall_time_s: 20 + memory_kb: 1048576 + max_processes: 100 + flag_allowlist: + - "-O0" + - "-O1" + - "-O2" + - "-O3" + - "-Wall" + - "-Wextra" + - "-std=*" + - "-pedantic" + - "-g" + - "-lm" + - "-pipe" + run: + cmd: ./{{artifact}} + limits: + wall_time_s: 3 + memory_kb: 524288 + max_processes: 64 + smoke_probe_cmd: /usr/bin/gcc + smoke_probe: ["--version"] + + - id: cpp + name: C++ + source_filename: solution.cpp + artifact: solution + build: + cmd: /usr/bin/g++ + args: ["{{flags}}", "-o", "{{artifact}}", "{{source}}"] + limits: + wall_time_s: 20 + memory_kb: 1048576 + max_processes: 100 + flag_allowlist: + - "-O0" + - "-O1" + - "-O2" + - "-O3" + - "-Wall" + - "-Wextra" + - "-std=*" + - "-pedantic" + - "-g" + - "-pipe" + run: + cmd: ./{{artifact}} + limits: + wall_time_s: 3 + memory_kb: 524288 + max_processes: 64 + smoke_probe_cmd: /usr/bin/g++ + smoke_probe: ["--version"] + + - id: go + name: Go + source_filename: solution.go + run: + cmd: /usr/bin/go + args: ["run", "{{source}}"] + limits: + wall_time_s: 30 + memory_kb: 524288 + max_processes: 200 + smoke_probe: ["version"] + + - id: java + name: Java + source_filename_strategy: from_request + artifact_filename_strategy: from_request + artifact: Main + build: + cmd: /usr/bin/javac + args: ["{{flags}}", "{{source}}"] + limits: + wall_time_s: 30 + memory_kb: 524288 + max_processes: 100 + flag_allowlist: + - "-g" + - "-nowarn" + - "-verbose" + - "-deprecation" + - "-encoding" + run: + cmd: /usr/bin/java + args: ["-Xmx400m", "-Xms64m", "{{artifact}}"] + limits: + wall_time_s: 20 + memory_kb: 524288 + max_processes: 200 + smoke_probe_cmd: /usr/bin/java + smoke_probe: ["-version"] + + - id: javascript + name: JavaScript (Node.js) + source_filename: solution.js + run: + cmd: /usr/bin/node + args: ["{{source}}"] + limits: + wall_time_s: 10 + memory_kb: 524288 + max_processes: 64 + smoke_probe_cmd: /usr/bin/node + smoke_probe: ["--version"] + + - id: lua + name: Lua + source_filename: solution.lua + run: + cmd: /usr/bin/lua5.4 + args: ["{{source}}"] + limits: + wall_time_s: 9 + memory_kb: 131072 + max_processes: 64 + smoke_probe: ["-v"] + + - id: py3 + name: Python 3 + source_filename: solution.py + run: + cmd: /usr/bin/python3 + args: ["{{source}}"] + limits: + wall_time_s: 9 + memory_kb: 102400 + max_processes: 100 + smoke_probe: ["--version"] + + - id: ruby + name: Ruby + source_filename: solution.rb + run: + cmd: /usr/bin/ruby + args: ["{{source}}"] + limits: + wall_time_s: 10 + memory_kb: 262144 + max_processes: 64 + smoke_probe: ["--version"] + + - id: rust + name: Rust + source_filename: solution.rs + artifact: solution + build: + cmd: /usr/bin/rustc + args: ["{{flags}}", "-o", "{{artifact}}", "{{source}}"] + limits: + wall_time_s: 60 + memory_kb: 1048576 + max_processes: 200 + flag_allowlist: + - "--edition=*" + - "-O" + - "-g" + - "--release" + run: + cmd: ./{{artifact}} + limits: + wall_time_s: 5 + memory_kb: 524288 + max_processes: 64 + smoke_probe_cmd: /usr/bin/rustc + smoke_probe: ["--version"] + + - id: verilog + name: Verilog (Icarus) + source_filename: solution.v + artifact: solution.vvp + build: + cmd: /usr/bin/iverilog + args: ["{{flags}}", "-o", "{{artifact}}", "{{source}}"] + limits: + wall_time_s: 20 + memory_kb: 524288 + max_processes: 100 + flag_allowlist: + - "-g2012" + - "-g2005" + - "-g2001" + - "-Wall" + - "-v" + run: + cmd: /usr/bin/vvp + args: ["{{artifact}}"] + limits: + wall_time_s: 10 + memory_kb: 262144 + max_processes: 64 + smoke_probe_cmd: /usr/bin/iverilog + smoke_probe: ["-V"] \ No newline at end of file diff --git a/configs/server.yaml b/configs/server.yaml new file mode 100644 index 00000000..01087dae --- /dev/null +++ b/configs/server.yaml @@ -0,0 +1,12 @@ +http_addr: ":8080" +max_concurrent_jobs: 60 +max_queue_depth: 200 +drain_timeout_s: 45 +readyz_cache_ttl_s: 30 +max_source_bytes: 262144 +max_stdin_bytes: 65536 +max_expected_stdout_bytes: 1048576 +max_tests: 50 +jail_root_dir: /var/lib/goboxd/jails +nsjail_binary: /usr/local/bin/nsjail +cgroupv2_mount: /sys/fs/cgroup diff --git a/docker-compose.loadtest.yml b/docker-compose.loadtest.yml new file mode 100644 index 00000000..16eb86e7 --- /dev/null +++ b/docker-compose.loadtest.yml @@ -0,0 +1,17 @@ +# docker-compose.loadtest.yml +# Resource-capped override for reproducible load testing. +# Usage: docker compose -f docker-compose.yml -f docker-compose.loadtest.yml up -d --build +# +# Enforces the spec-mandated caps: +# - 2 vCPU +# - 2 GB RAM +services: + goboxd: + deploy: + resources: + limits: + cpus: "2.0" + memory: 2g + reservations: + cpus: "2.0" + memory: 2g diff --git a/docker-compose.yml b/docker-compose.yml index 155d3ce4..f1ed2c27 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: ports: - "8080:8080" privileged: true + volumes: + - ./configs:/configs tools: build: diff --git a/docs/ai/adrs.md b/docs/ai/adrs.md new file mode 100644 index 00000000..5523f110 --- /dev/null +++ b/docs/ai/adrs.md @@ -0,0 +1,15 @@ +## [Short decision title] + +**Context:** + + +**Options considered:** +1.