Dokumentacja AlgoJudge0.1

Pierwsza instalacja

Od pustego katalogu do instalacji, która ocenia zgłoszenie.

Ustaw AJ_ADMIN_TOKEN przed pierwszym `up`

Dopóki ta wartość jest pusta, /api/v1/admin/** jest zamknięte — a to obejmuje endpoint, który ustawia hasło administratora. Hasło konta admin, zakładanego przy pierwszym starcie, to dwadzieścia losowych znaków, które nigdy nie trafiają do logu, na ekran ani w żadne czytelne miejsce.

Instalacja uruchomiona bez tego tokenu nie ma administratora, na którego konto ktokolwiek mógłby wejść, a jedyne wyjście to skasować bazę i zacząć od nowa.

Obrazy, i dlaczego nowy może się nie pobrać

Osiem pakietów — algojudge-server, algojudge-client, algojudge-runner, lang-gcc, lang-clang, lang-python, lang-pypy i algojudge-external-runner — jest opublikowanych i publicznych. docker compose pull nie wymaga logowania do rejestru, z żadnej maszyny.

Pakiet GHCR utworzony pierwszym pushem jest jednak prywatny i żaden workflow tego nie zmieni: ktoś z dostępem do pakietów organizacji przestawia go raz na publiczny. Warto o tym wiedzieć, bo wraca z każdym nowym obrazem, a objawem jest denied przy docker compose pull w skądinąd poprawnej instalacji.

Cztery obrazy językowe nie są usługami

docker compose pull pobiera sześć z ośmiu. lang-gcc, lang-clang, lang-python i lang-pypy to wartości przekazywane Runnerowi, a nie usługi, więc Compose ich nie dotyka: Runner pobiera obraz, gdy hosta go nie ma, a scripts/update.sh pobiera wszystkie cztery przy aktualizacji instalacji.

1. Klon i konfiguracja

git clone https://github.com/AlgoJudge/AlgoJudge-Ops.git /opt/algojudge-ops
cd /opt/algojudge-ops
cp .env.example .env
chmod 600 .env

Trzy wartości nie mają domyślnych i bez nich stos nie wystartuje. Każdy sekret generuj osobno: jeśli ten sam trafi w dwa miejsca, jeden wyciek kompromituje oba.

AJ_ADMIN_TOKEN=          # openssl rand -base64 36
POSTGRES_PASSWORD=       # openssl rand -base64 36, inny
RUNNER_WORK_DIR=         # BEZWZGLĘDNA ścieżka na hoście, np. /srv/algojudge/runner-work

RUNNER_WORK_DIR musi być bezwzględna, bo Runner podaje ten napis demonowi Dockera, a ścieżka, której demon nie potrafi otworzyć, staje się pustym katalogiem, nie błędem. Każde zgłoszenie ocenia się wtedy na pustce i żaden test nie zawodzi w widoczny sposób. Właśnie dlatego preflight.sh odrzuca ścieżkę względną.

Załóż ten katalog sam przed pierwszym uruchomieniem albo pozwól utworzyć go Compose:

sudo mkdir -p /srv/algojudge/runner-work

Compose zakłada brakujący katalog montowany z hosta jako root, z prawami 0755 — i o to właśnie chodzi: Runner zapisuje tu jako root, a każdy kontener zadania montuje ten katalog tylko do odczytu i czyta go jako uid 65534. Katalog ręcznie zamknięty przed innymi przechodzi próbę Runnera, a nie przechodzi próby zadania, i wtedy każde zgłoszenie kończy się komunikatem Permission denied (os error 13). preflight.sh sprawdza obie połowy — zapis jako root i odczyt jako 65534 — i mówi, która zawiodła.

chown 65532:65532 też zadziała i nie jest potrzebny — usługa Runnera działa jako root, żeby móc mierzyć. Psuje to dopiero katalog ręcznie zamknięty przed innymi.

Jeszcze dwie wartości warto przeczytać przed pierwszym startem:

  • TRUSTED_PROXY_NETWORKS decyduje, czyim informacjom o adresie odwiedzającego Server ma wierzyć, a bez tego Server nie wystartuje. Domyślnie wskazuje sieć Compose, w której stoi dołączony nginx. Musi to być adres sieci: 172.28.0.5/24 odrzucamy przy starcie, podając nazwę zmiennej i adres, który powinien się tam znaleźć. Za własnym proxy wpisz sieć tego proxy; jeśli proxy nie ma w ogóle, none jest pełną odpowiedzią.
  • DOCKER_GID to grupa, do której należy gniazdo demona; preflight.sh powie, jeśli u Ciebie jest zła. Patrz Czego wymaga host.

2. Certyfikat

Umieść fullchain.pem i privkey.pem w certs/. Montujemy je tylko do odczytu; nic tutaj nie wystawia certyfikatu, a odnawianiem zajmuje się nadal to, czego już używasz.

/.well-known/acme-challenge/ jest serwowane na obu portach, więc wyzwanie HTTP-01 nigdy nie zostanie przekierowane. Po odnowieniu:

docker compose exec nginx nginx -s reload

Jeśli nie masz certyfikatu, a chcesz tylko rzucić okiem:

./scripts/render-tls.sh twoja.domena

Samopodpisany, i każda przeglądarka to powie. Jest po to, żeby pierwszy start dał działającą instancję z ostrzeżeniem, a nie nginx, który nie wstaje, i błąd, którego nikt nie powiąże z brakującym certyfikatem. Skrypt odmawia nadpisania certyfikatu, który już tam leży.

3. Start

./scripts/preflight.sh
docker compose up -d --wait

preflight.sh odmawia od razu i jednym zdaniem, zamiast w połowie podnoszenia stosu: puste hasło, brakujący lub zbyt krótki token, CIDR z bitami hosta, względny katalog pracy, katalog pracy, którego kontener zadania nie przeczyta, cgroup v1 albo nieznany sterownik cgroup, .env śledzony przez Git. make up uruchamia go najpierw, więc zwykłą drogę sprawdzamy niezależnie od tego, czy ktoś pamięta, że ten skrypt istnieje.

Profil runner uruchamia cztery Runnery, od runner-1 do runner-4. Cztery pasują do ośmiu rdzeni fizycznych; na mniejszej maszynie uruchom mniej, bo więcej Runnerów niż rdzeni fizycznych nie przyspiesza oceniania, a psuje jego rzetelność — tabelę ma Czego wymaga host.

4. Administrator

docker compose exec -it server aj-admin password

Potem zaloguj się na https://twoja.domena/ jako admin.

Narzędzie prosi o hasło bez wyświetlania go i nigdy nie przyjmuje go jako argumentu — argumenty widzi przez ps każdy inny proces, a do tego lądują w historii powłoki. W skrypcie narzędzie czyta pierwszą linię standardowego wejścia:

printf '%s' "$NEW_PASSWORD" | docker compose exec -T server aj-admin password

Obowiązuje polityka haseł, minimum dwanaście znaków, a odmowa niczego nie zmienia — stare hasło dalej działa, więc literówka nie zostawi instalacji bez wejścia. Komenda zdejmuje też blokadę konta: dziesięć nietrafionych prób to zwykle właśnie to, co poprzedza sięgnięcie po nią.

aj-admin działa wewnątrz kontenera Servera i czyta token ze środowiska tego kontenera, więc token nigdy nie trafia do Twojej historii powłoki. To jedyna wspierana droga do powierzchni operatora: /api/v1/admin/** odpowiada na własnym interfejsie pętli zwrotnej Servera, a żądanie przez nginx — albo przez opublikowane 127.0.0.1:8080 — dociera jako brama mostka i dostaje 404. To zachowanie zmierzone, nie domysł.

5. Zatwierdź Runnery

Nowy Runner rejestruje się i czeka. To nie jest usterka i nie ma limitu czasu. Nic nie zostanie ocenione, dopóki administrator go nie zatwierdzi — i to właśnie powstrzymuje kogoś przed podpięciem własnej maszyny do Twojej instalacji.

W panelu: Runnery, i zatwierdź każdy z czterech, które się pojawiły. Dopóki tego nie zrobisz, ich logi powtarzają waiting: this Runner has not been approved yet, za każdym razem z dłuższą przerwą — bo cała pracownia Runnerów zarejestrowanych naraz zasypywałaby inaczej Server jedną falą pytań przez cały czas, którego ktoś potrzebuje, żeby dojść do panelu. Niezatwierdzony Runner po prostu stoi bezczynnie, a kolejkę niosą pozostałe, więc zapomniane zatwierdzenie wygląda na wolną instalację, a nie na błąd.

Potem wyślij coś i zobacz, jak dostaje werdykt. Dopóki to się raz nie wydarzy, nie wiadomo, czy instalacja działa.

6. Opcjonalnie: harmonogram

./scripts/install-cron.sh --print     # co by zainstalował
./scripts/install-cron.sh

Nic nie planuje się samo. Co skrypt instaluje, a co celowo zostawia zakomentowane, opisuje Praca rutynowa i harmonogram.

Praca na obrazach zbudowanych lokalnie

Tak się tego stosu normalnie nie uruchamia — od tego są obrazy opublikowane, które pobierze każdy. To jest sposób na pracę z kodem, który nie jest wydany: ze zmianą w repozytorium produktowym albo z poprawką w drodze do pull requesta. Zbuduj ją, otaguj tam, gdzie stałby obraz opublikowany, a compose.yaml zadziała bez zmian. Ścieżkę po -f liczy się względem katalogu, w którym stoisz, a nie względem kontekstu budowania — linia Servera jako jedyna zawiera ścieżkę, więc to ją warto sprawdzić.

Buduj od nowa, zamiast sięgać po tag, który już masz: te obrazy są przypięte ruchomym tagiem, więc stary lokalny :0 jest po cichu tym, co zbudowałeś miesiąc temu.

docker build -f AlgoJudge-Server/AlgoJudge.Server/Dockerfile -t ghcr.io/algojudge/algojudge-server:0 AlgoJudge-Server
docker build -t ghcr.io/algojudge/algojudge-client:0 AlgoJudge-Client
docker build -t ghcr.io/algojudge/algojudge-runner:0 AlgoJudge-Runner
# Kontekstem jest `images`, nie `images/$lang`: wszystkie cztery budują w nim
# pośrednika pomiarowego z `images/shim`, więc musi się w nim znaleźć.
for lang in gcc clang python pypy; do
    docker build -t "ghcr.io/algojudge/lang-$lang:0" \
        -f "AlgoJudge-Runner/images/$lang/Dockerfile" AlgoJudge-Runner/images
done

# Tylko dla profilu sędziego zewnętrznego.
docker build -t ghcr.io/algojudge/algojudge-external-runner:0 AlgoJudge-External-Runner

update.sh wciąż potrzebuje rejestru, z którego pobiera. Aktualizację i wycofanie ćwiczymy bez opublikowanego rejestru na lokalnym registry:2, z REGISTRY=localhost:5000/algojudge.

Na tej stronie