Getting Started

Pomelo

What is Pomelo?

Pomelo is an open-source competitive programming contest platform for hosting coding contests with automated code evaluation powered by Citron, its sandboxed execution engine.

It provides the pieces needed to run a local contest environment, including a participant-facing web app, an Express API, admin tooling, and Docker services for the backing stack.

Use this documentation to install dependencies, configure environment variables, start the services, and troubleshoot setup issues.

Installation

Run the following command to automatically install and set up Pomelo:

Terminal
$ curl -fsSL pomelo.sosc.org.in/install.sh | sudo bash

During Installation

When you run the installation script, it will prompt you to select which version of Pomelo to install (defaults to the latest release).

Next Steps

Once the installation finishes and the Pomelo daemon is running, you can start the entire platform with:

Terminal
$ pomelo start

To access the built-in administrator interface to manage your instance, simply run:

Terminal
$ pomelo ui

Workspace Setup

Follow these steps to set up the Pomelo workspace on your local machine.

Prerequisites

The Pomelo stack requires specific system tools to run locally and build. Make sure the following are installed:

  • Node.js (node): Required for the Next.js frontend and Express backend.
  • Bun (bun): Used specifically to compile the CLI and Daemon binaries.
  • pnpm: The strict package manager used across the monorepo workspace.
  • Docker & Docker Compose: Required for the database and the Citron execution environment.

1. Clone the repository

Clone the repository to your local machine:

Terminal
$ git clone https://github.com/so-sc/pomelo && cd pomelo

2. Install workspace dependencies

Run pnpm install at the repository root to install all workspace dependencies.

Terminal
$ pnpm install

3. Set up environment variables

Copy .env.example to .env:

Terminal
$ cp .env.example .env

4. Start development mode

Run pnpm dev to start the development environment:

Terminal
$ pnpm dev

Environment Variables

Developers must copy the template .env.example file to .env before running the stack. Once .env is set up, running pnpm dev automatically initializes all services and Docker containers.

Terminal
$ cp .env.example .env

Development Variables

VariableDefault ValueDescription
AUTH_SECRET(required)Long random secret used by NextAuth for session encryption.
PORT8080Port the Express API listens on.
MONGODB_URImongodb://localhost:27017/pomeloMongoDB connection string.
CITRON_URLhttp://citron:2358URL of the Citron execution engine.
CITRON_TOKEN(optional)Sent as the X-Judge-Token header. Only needed if Citron is configured with an auth token.
DOMAINlocalhost:3000Frontend domain used by the Next.js client.
PROTOCOLhttpProtocol for the frontend app.
BACKEND_URLhttp://localhost:8080URL used by server-side code to call the backend.

AUTH_SECRET, MONGODB_URI, and CITRON_URL are required; the stack refuses to start without them.

Dev Setup

Docker Setup

Running pnpm dev automatically handles setting up and starting all required Docker containers (MongoDB and Citron).

If you prefer to manually control or inspect the backing services, Pomelo includes Docker Compose definitions in docker/app/ and docker/citron/.

Manual Container Management

Terminal
docker compose --project-name pomelo --env-file .env \
-f docker/app/docker-compose.dev.yaml \
-f docker/citron/docker-compose.dev.yaml \
--project-directory . up mongo citron -d

Services

mongo

MongoDB database for contest and user data.

:27017
server

Express API for auth, contests, and submissions.

:8080
client

Participant-facing web app.

:3000
citron

Sandboxed execution engine that compiles and runs submissions.

:2358
caddy

Reverse proxy and automatic SSL.

:80/443

Citron runs untrusted code, so it publishes no port in production and is reached only over the internal Compose network at http://citron:2358. In development it binds to 127.0.0.1:2358 for inspection.

Stop containers

Terminal
$ docker compose --project-name pomelo down

Remove volumes

Terminal
$ docker compose --project-name pomelo down -v

Local Installation

Packaging Script

The script at scripts/package.sh compiles the workspace into a standalone build.tar.gz archive containing only pre-built artifacts.

Usage

Run the script without arguments to create the default archive:

Terminal
$ ./scripts/package.sh

Or specify a custom output filename:

Terminal
$ ./scripts/package.sh custom-name.tar.gz

Installation Script

The script at scripts/install.sh deploys Pomelo to a host machine under /opt/pomelo.

Usage

To install from a local tarball:

Terminal
$ sudo bash scripts/install.sh --archive build.tar.gz

Uninstalling

To cleanly remove the installation:

Terminal
$ sudo bash scripts/install.sh --uninstall

Admin Tooling

Pomelo CLI

The admin directory contains the source for the administrator tooling.

The CLI and daemon are written in TypeScript and compiled into single-file executables using Bun. You must have Bun installed to compile these tools from source.

Daemon

pomelod is the background orchestrator daemon for Pomelo. Once installed, it monitors contest states and manages background tasks.

Systemd Policy

During installation, Pomelo sets up a persistent systemd service for the daemon at /etc/systemd/system/pomelod.service.

This policy ensures that the daemon automatically starts on boot and restarts on failure. If systemd is unavailable on the host system or the installation is not run as root, the daemon will start as a standard non-persistent background process instead.

CLI

pomelo is the terminal utility for administrators. It is symlinked to /usr/local/bin/pomelo during installation and provides a centralized way to interact with the platform services.

Commands

CommandDescription
pomelo startStarts all Pomelo backend services.
pomelo stopStops the running Pomelo services.
pomelo restartRestarts the Pomelo services.
pomelo statusChecks the status of the daemon and Docker containers.
pomelo logsStreams logs from the running services.
pomelo uiOpens the web admin dashboard on port 8462.
pomelo uninstallUninstalls Pomelo and cleans up the systemd service.

CLI Development

This section covers how to work on the CLI locally, which is located in the @pomelo/admin package.

Viewing the UI

To start the Vite UI on port 8462:

Terminal
$ pnpm --filter @pomelo/admin dev

Building Binaries

To compile the TypeScript files into pomelo and pomelod binaries using Bun:

Terminal
$ pnpm --filter @pomelo/admin build:cli

Hot-Swapping (Testing Local CLI)

You can test local changes by running the hot-swap script.

Terminal
$ sudo bash scripts/install-cli.sh

WARNING: This script requires sudo privileges. You must have already installed Pomelo to /opt/pomelo using install.sh first. This script only copies the newly built binaries over the existing installation and restarts the pomelod systemd service.

Troubleshooting

Common Issues

Open the issue that matches what you are seeing, then compare the guidance with your local setup.

Toolchain pnpm install fails

Confirm pnpm 10.0.0 or newer is installed.

Confirm Node.js is installed.

Confirm Git is available in PATH.

Environment pnpm dev fails

Confirm .env exists (cp .env.example .env).

Confirm Docker desktop/daemon is running so containers can be started automatically.

Confirm AUTH_SECRET is set to a non-empty value.

Containers Docker containers fail to start

Confirm Docker and Docker Compose v2 are installed and running.

Confirm .env exists in the repository root.

Confirm AUTH_SECRET, MONGODB_URI, and CITRON_URL are set.

Check container status or run pnpm dev to trigger automated setup.

Database Backend cannot connect to MongoDB

Verify MONGODB_URI is correct and reachable.

Confirm the mongo container or service is running.

Check logs with docker logs pomelo-mongo-1.

Citron Submissions fail or time out

Verify CITRON_URL is correct (http://citron:2358 when self-hosted).

Confirm the citron container is running with pomelo logs citron.

If Citron is configured with an auth token, confirm CITRON_TOKEN matches it.

A busy engine returns 503 and the submission is retried automatically; repeated failures usually mean the container is down.

API Reference

Authentication API

The Pomelo frontend uses NextAuth for authentication. API routes enforce authentication using the following middleware:

MiddlewareDescription
requireAuth()Enforces standard authentication for routes.
isAdminEnforces admin-only access for elevated routes.

Contests API

Endpoints for managing and participating in contests.

MethodEndpointDescription
POST/api/contests/joinValidates a join code and returns a contestId.
GET/api/contests/:idFetches public contest landing data.
POST/api/contests/startInitializes an attempt session for a user.
GET/api/contests/:id/dataFetches the active questions and test data for an ongoing attempt.
POST/api/contests/:id/endSubmits the final test and locks the session.
GET/api/contests/:id/leaderboardAdmin-only route to view ranked scores.

Submissions API

Endpoints for handling code execution and test submissions.

MethodEndpointDescription
POST/api/contests/:id/runPerforms a dry-run code execution against sample test cases.
POST/api/submitThe unified submission endpoint for all question types.