Skip to content
Apurba94Public

About

Online judge like Codeforces/LightOJ: sandboxed multi-language judge, ICPC contests, Elo ratings, 40 original problems and a 26k-problem archive from Codeforces, AtCoder, UVa and CSES

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

CodeArena Online Judge

A complete online judge in the spirit of Codeforces, LightOJ, SPOJ and UVa: a judged problemset, ICPC-style contests with live standings, Elo-style ratings, a sandboxed multi-language judge, and a multi-judge archive that indexes ~26,000 problems from Codeforces, AtCoder, UVa and CSES.

  • Backend: Node.js (Express 5) + SQLite (built-in node:sqlite, WAL mode) — no native modules.
  • Judge: worker pool + a Python sandbox supervisor (Linux rlimits / per-worker uids, Windows Job Objects).
  • Frontend: dependency-free SPA (ES modules), CodeMirror editor, KaTeX math, light/dark themes, mobile layout.

Features

Area What you get
Problemset 40 original problems (statements with TeX, samples, full generated test data), tags, difficulty, acceptance, solved/attempted marks, filters
Judge C, C++17/20, Java, Python 3, PyPy 3, JavaScript, Go, Rust — auto-detected per judge node. Verdicts AC / WA / TLE / MLE / RE / CE / OLE, per-test time & memory, compile logs, checkers tokens, tokens-ci, lines, exact, float:EPS
Submitting In-browser editor with per-problem drafts, file upload, custom test run, live verdict updates, online IDE page
Contests Scheduling, registration, countdowns, problems hidden until start and locked out of the problemset until the end, ICPC scoring with penalty, first-solve highlights, standings freeze, per-contest submissions
Ratings Codeforces-style rating algorithm, rank titles and colours, rating history graph, rollback
Profiles Rating graph, activity heatmap, verdict/language/tag breakdowns, solved list, settings, password change
Archive ~26k problems from Codeforces (11.4k, with ratings & tags), AtCoder (9.6k, with estimated difficulty), UVa (5k), CSES (400); search, tag/difficulty/category filters, random pick, personal solved/to-do tracking, Codeforces progress sync, custom JSON import for any other judge
Admin Problem editor with markdown preview, test manager (upload .in/.out pairs, samples, view, delete), generate expected outputs from a reference solution, rejudge, contest editor, rating apply/rollback, user roles & bans, archive sync, announcements, judge node dashboard
Security scrypt passwords, DB-backed httpOnly sessions, CSRF header guard, CSP, rate limits, input validation, hidden-test checker messages, source visibility rules

Quick start (development)

Requirements: Node.js ≥ 22.13, Python ≥ 3.8 (the sandbox supervisor), plus whichever compilers you want to offer (g++, gcc, javac, pypy3, go, rustc are detected automatically; Python and Node work out of the box).

npm install
npm run seed      # admin account + 40 problems with generated tests + demo contests (~10 s)
npm run import    # optional: fetch the multi-judge archive (~26k problems, ~15 s)
npm start         # http://localhost:3000

The admin password generated by the seeder is in data/ADMIN_CREDENTIALS.txt — change it on the Settings page. npm run demo optionally fills the finished warm-up contest with judged submissions from demo_* accounts so standings, ratings and profiles have something to show (don't run it on a real site).

Deploying

CodeArena needs a long-running server with a persistent disk: the SQLite database and ~220 MB of generated test data live on disk, judge workers poll a queue continuously, and submissions are compiled and executed with real toolchains inside a process sandbox. The Docker image does everything on first boot (seeds the problem set, imports the archive) — you only set ADMIN_PASSWORD and attach a volume at /data.

Host Steps
Railway New Project → Deploy from GitHub repo (uses railway.json + Dockerfile) → add a Volume mounted at /data → Variables: ADMIN_PASSWORD → Settings → Networking → Generate domain.
Fly.io fly launch --copy-config --no-deploy → fly volumes create codearena_data --size 5 → fly secrets set ADMIN_PASSWORD=… → fly deploy (see fly.toml). Runs in a micro-VM, so submissions also get their own empty network namespace.
Render New → Blueprint → this repo (render.yaml, paid instance for the disk) → enter ADMIN_PASSWORD.
Any VPS docker compose up -d --build (below) — the most hardened option.

The first boot takes a minute or two (problem generation + archive import); watch the logs for listening. The Admin → Dashboard shows each judge node's isolation status (own uid / network).

Why not Vercel or Netlify? They run short-lived serverless functions with a read-only, ephemeral filesystem and no background processes. The database and test data would vanish between invocations, nothing could keep judging the queue, and there are no compilers or process sandbox — pages would load but logins, submissions and judging would not work.

Self-hosted (Docker Compose)

cp .env.example .env                        # set ADMIN_PASSWORD, SITE_NAME; SECURE_COOKIE=false only for plain-HTTP testing
docker compose up -d --build
docker compose up -d --scale judge=3        # more judging capacity

docker-compose.yml runs two roles from one image:

  • web — API + SPA as an unprivileged user. It never runs user code: custom test runs and answer generation are queued to the judge (adhoc_jobs).
  • judge — worker pool with no network, read-only root filesystem, all capabilities dropped except what setuid needs, no-new-privileges, a PID limit, and every submission executed as its own unprivileged uid.

Put nginx/Caddy in front for TLS (TRUST_PROXY=true is the image default).

Architecture

 Browser (SPA)  ──HTTP/JSON──►  web: Express API ──► SQLite (WAL)  ◄── judge worker pool(s)
                                                       ▲    ▲              │ claim: UPDATE … RETURNING
                                   archive importers ──┘    │              ▼
                               (Codeforces, AtCoder,        │      sandbox.py --serve (one per worker)
                                UVa, CSES public APIs)      │        ├─ compile  (limits, own uid)
                                                            │        └─ run each test (CPU/mem/output/pids)
                                                  data/tests/<problem>/<n>.in|.out  (owner-only)
  • Submissions are a queue in the submissions table. Workers claim jobs with one atomic UPDATE … WHERE status='queued' … RETURNING, so any number of worker processes can share the DB. Crashed workers are detected (dead PID / stale lock) and their jobs requeued.
  • Judge nodes publish heartbeats (languages, load); the UI only offers languages a live node can judge.
  • Judging stops at the first failed test (configurable). Verdicts refresh per-user/per-problem counters.

Sandbox & security model

server/judge/sandbox.py is a long-lived supervisor (one per worker) that runs each program with:

Linux (production) Windows (development)
CPU time RLIMIT_CPU + live /proc polling (cut at TL), exact wait4 rusage Job Object accounting, polled
Memory RLIMIT_AS (native) / max RSS check (JVM, V8, Go) Job Object hard commit limit + peak
Processes RLIMIT_NPROC per uid, process-group kill, uid-wide sweep after every run Job active-process limit, kill-on-close
Output pipe byte counter + RLIMIT_FSIZE pipe byte counter
Identity SANDBOX_UID + worker (no files, no login), minimal env same user as the server
Filesystem data dir 0700 (enforced on boot), work dir 0711, per-run dir 0700 not isolated
Network fresh empty network namespace per run (needs root + CAP_SYS_ADMIN, e.g. Fly.io VMs); network_mode: none in Compose not isolated

Verified against hostile submissions (reading /etc/shadow at compile or run time, reading test answers, listing other runs, fork bombs, orphaned daemons holding stdout, memory bombs, output floods, busy loops, sleeping, opening outbound sockets). Windows mode is for development only — a submission there runs as your user. For a public deployment use the Linux judge container (optionally add seccomp/nsjail for defence in depth).

Problem archive

npm run import [source…] (or Admin → Archive → Sync now) pulls metadata only — title, tags, difficulty, solve counts, link — from:

Source Endpoint
Codeforces official API problemset.problems
AtCoder AtCoder Problems community dataset (kenkoooo) — problems + difficulty model
UVa uHunt API
CSES the public problem list page

Statements and test data belong to the original judges and are never copied; every archive entry links to the original statement. SPOJ blocks automated access (HTTP 403) and LightOJ has no public API, so they are not scraped; add their problems (or any other judge's) with a JSON list instead:

npm run import -- --file spoj.json --source spoj
[{ "external_id": "TEST", "title": "Life, the Universe, and Everything",
   "url": "https://www.spoj.com/problems/TEST/", "difficulty": 800, "tags": ["implementation"] }]

Set ARCHIVE_AUTO_SYNC_HOURS=24 to refresh automatically.

Adding problems

Admin → Problems → New problem:

  1. Write the statement (markdown with $inline$ / $$display$$ TeX), limits, checker, tags, difficulty.
  2. Add tests: paste them, or drop files named 1.in + 1.out (or .ans); mark samples to show them in the statement.
  3. Optionally upload only inputs and generate expected outputs from a reference solution.
  4. Set visibility to public, or put the problem into a contest — it stays hidden from the problemset until the contest ends.

Built-in problems live in seed/problems/*.js (statement + deterministic generator + reference solver); seed/solutions/ holds independent reference solutions, and npm run verify re-checks every test file through the real judge.

Configuration

All settings are environment variables (or a .env file) — see .env.example. The most important ones:

Variable Default Meaning
PORT / HOST 3000 / 0.0.0.0 HTTP listener
DATA_DIR data SQLite database and test data
ADMIN_PASSWORD – creates the ADMIN_HANDLE admin if missing; changing the value resets that admin's password once on the next boot (recovery without shell access)
JUDGE_EMBEDDED true run judge workers inside the web process
JUDGE_WORKERS CPUs − 1 (max 4) parallel judgements per process
SANDBOX_UID / SANDBOX_GID – Linux: base uid for submissions (supervisor must be root)
SANDBOX_PYTHON python3 / python interpreter for the supervisor
SUBMIT_COOLDOWN_MS 5000 minimum gap between a user's submissions
AUTO_SEED / AUTO_IMPORT_ARCHIVE off (on in Docker) build problems / import the archive on first boot
SECURE_COOKIE, TRUST_PROXY off enable behind HTTPS / a reverse proxy
ARCHIVE_AUTO_SYNC_HOURS 0 periodic archive refresh

Scripts

Command Purpose
npm start / npm run dev start the server (dev = auto-restart)
npm run judge standalone judge worker pool (with JUDGE_EMBEDDED=false on the web node)
npm run seed [codes…] create admin, build problems/tests, demo contests
npm run import [sources…] sync the archive
npm run verify [codes…] run reference solutions against all tests through the judge
npm run smoke end-to-end test on a throw-away instance (41 checks: auth, contest, verdicts, standings, ratings, visibility); SMOKE_SPLIT=1 tests web + separate judge
npm test unit tests (checkers, rating system)
npm run demo optional demo contest data

JSON API

Everything the UI does goes through /api (see the About & API page). Mutating requests need a session cookie and the header X-Requested-With: CodeArena.

GET  /api/problems?q=&tags=dp,graphs&minDiff=&maxDiff=&status=&sort=&page=
GET  /api/problems/:code[?contest=id]
POST /api/submissions          { problem, language, source, contest? }
GET  /api/submissions?user=&problem=&verdict=&language=&contest=&mine=1
GET  /api/submissions/:id
POST /api/run                  { language, source, input }
GET  /api/contests  |  /api/contests/:id  |  /api/contests/:id/standings
GET  /api/users/:handle  |  /api/users/rankings?by=rating|solved
GET  /api/archive?source=&q=&tags=&minDiff=&maxDiff=&status=&sort=  |  /api/archive/random
/api/admin/*                   problem, test, contest, user, archive and announcement management

Project layout

server/
  index.js, app.js, config.js, db.js, schema.sql
  judge/      languages.js  sandbox.py  sandbox.js  checker.js  judge.js  tasks.js  worker.js
  routes/     auth  problems  submissions  contests  users  archive  admin  meta
  services/   contests (standings, ratings)  rating  problems  tests  stats  archive/
  middleware/ auth.js      util/ http, validate, security, ratelimit
public/       index.html  css/app.css  js/{app,api,ui,markdown,editor}.js  js/pages/*.js
seed/         seed.js  lib.js  problems/set1-4.js  solutions/*.py|*.cpp
scripts/      import-archive.js  verify-solutions.js  smoke-test.js  demo-data.js
Dockerfile, docker-compose.yml, railway.json, fly.toml, render.yaml
test/         checker.test.js  rating.test.js

Scaling & next steps

  • SQLite in WAL mode comfortably handles a busy community site on one host. For multi-host deployments, move server/db.js to PostgreSQL and the rate limiter to Redis — the SQL is standard and the job claim maps to SELECT … FOR UPDATE SKIP LOCKED.
  • Natural extensions: custom checkers / interactive problems (the checker module is the extension point), virtual participation, clarifications, blogs/editorials, team contests, and seccomp/nsjail profiles.

About

Online judge like Codeforces/LightOJ: sandboxed multi-language judge, ICPC contests, Elo ratings, 40 original problems and a 26k-problem archive from Codeforces, AtCoder, UVa and CSES

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages