部署 LibreOJ

· · 科技·工程

前言

本文将会带你一步一步部署 LibreOJ 的基础服务,包括前端、后端和评测机。LibreOJ 的 Github 为 LibreOJ · Github。本文借鉴了 xzm111 的搭建 Lyrio。

本文将在同一台服务器上的 /opt 目录部署 LibreOJ。当然,评测机最好单独部署,本文会在介绍评测机需要的前置时进行提示。尖括号包住的字符串是变量,请根据实际情况填写。

前置

服务器 & 系统

你需要至少一台装有 Linux 系统的服务器来部署 LibreOJ,若条件有限,可以使用虚拟机。本文使用的系统是 Ubuntu 22.04。

安装时请切换到 root 用户。

Node.js

评测机需用。注意不能使用最新版,否则在 yarn 构建时会报错,若已安装 node.js 要先卸载。这里使用 v18.15.0 版。

cd /opt
curl -O https://nodejs.org/dist/v18.15.0/node-v18.15.0-linux-x64.tar.xz
tar -xvf node-v18.15.0-linux-x64.tar.xz
mv node-v18.15.0-linux-x64 node
export PATH=$PATH:/opt/node/bin/ # 加入到 PATH 中,临时加入

用以下命令检查。

node -v
npm -v

Yarn

评测机需用。用以下命令安装 Yarn。

npm install yarn
export PATH=$PATH:/opt/node_modules/yarn/bin/ # 加入到 PATH 中,临时加入

用以下命令检查。

yarn -v

在部署时若更换了终端,请执行以下命令。

export PATH=$PATH:/opt/node/bin/
export PATH=$PATH:/opt/node_modules/yarn/bin/

数据库

本文选择安装 MariaDB,也可以安装 MySQL。

curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash
apt install mariadb-server
systemctl enable mariadb # 设置开机自启

测试 MariaDB,输入 exit; 退出。

mariadb

Redis

用以下命令安装 Redis。

apt install redis

nginx

用以下命令安装 nginx。

apt install nginx
systemctl start nginx

访问服务器的 IP 地址(域名),若成功出现 nginx 的网页则成功。若失败,大概率为 80 端口被占用,排查是否在安装系统时安装了其他占用 80 端口的软件。用下面的命令查询哪些进程占用了 80 端口。

lsof -i:80

:::align{center}

nginx 安装成功后访问 IP 地址(域名)出现的网页 :::

最后执行以下命令停止 nginx 服务。

systemctl stop nginx

MinIO

下载 MinIO,将 dl.min.io 改为 dl.minio.org.cn 可以更快的下载。

cd /opt
curl -O https://dl.min.io/server/minio/release/linux-amd64/minio
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc

授权。

chmod +x ./minio
chmod +x ./mc

g++ & fmt & zstd & CMake

仅评测机需用。在评测机执行以下命令。

apt install g++ # Ubuntu 22.04 安装的是 g++-9
apt install libfmt-dev
apt install zstd # 解压用
apt install cmake # Ubuntu 22.04 安装的是 cmake 3.16.3

注意 cmake 的版本要为 3.0 及以上 3.31.11 及以下。

后端

克隆仓库并构建,然后创建配置文件。

cd /opt
git clone https://github.com/LibreOJ/backend.git
cd backend
yarn
cp config-example.yaml config.yaml

数据库

mariadb 进入数据库,创建 LibreOJ 数据库和数据库用户,使用随机字符串替换 <db-password> 并记下。

CREATE DATABASE `lyrio` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON `lyrio`.* TO "lyrio"@"localhost" IDENTIFIED BY "<db-password>";
exit;

MinIO

用以下命令启动 MinIO,使用随机字符串替换 <minio-user><minio-password> 并记下它们。可以使用 nohup 等工具让 MinIO 在后台运行,也可以新开一个终端。

MINIO_ROOT_USER=<minio-user> MINIO_ROOT_PASSWORD=<minio-password> ./minio server /mnt/data # /mnt/data 是数据目录

使用 mc 创建存储桶。

./mc alias set minio http://127.0.0.1:9000 "<minio-user>" "<minio-password>"
./mc mb -p minio/lyrio-files

配置文件

编辑配置文件 /opt/backend/config.yaml

有尖括号的地方需要修改:

注意 <server-addr> 千万不能设置为本地回环地址

...
services:
  database:
    type: mariadb
    host: 127.0.0.1
    port: 3306
    username: lyrio
    password: <db-password>
    database: lyrio
  minio:
    default:
      endpoint: http://<server-addr>:9000
      signEndpoint: null
    forUser: null
    forJudge: null
    accessKey: <minio-user>
    secretKey: <minio-password>
    bucket: lyrio-files
...
security:
  crossOrigin:
    enabled: true
    whiteList:
      - http://127.0.0.1
      - http://<server-addr>
  sessionSecret: <session-secret>
  maintainceKey: <maintaince-key>
...
preference:
  siteName: <site-name>
...

requireEmailVerification 可以先改为 false 关闭邮箱验证,后续再配置。

启动

cd /opt/backend
LYRIO_CONFIG_FILE=./config.yaml yarn start

出现类似 [Nest] xxxx - xx/xx/xxxx, xx:xx:xx xx LOG [Bootstrap] @lyrio/lyrio is listening on 127.0.0.1:2002 代表成功。

用以下命令测试:

curl http://127.0.0.1:2002/api/auth/getSessionInfo?jsonp=1&token=

若返回响应且不是错误则成功。

前端

克隆仓库并构建。

cd /opt
git clone https://github.com/LibreOJ/frontend.git
cd frontend
yarn

启动 & 配置 nginx

用以下命令启动。

cd /opt/frontend
yarn start

备份 /etc/nginx/sites-enabled/default 并打开这个文件,删除所有内容并写入以下内容。

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    server_name <server-addr>;
    listen 80;

    location / {
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Accept-Encoding "";

        sub_filter '__default_title__' '"<title>"';
        sub_filter '__api_endpoint__' '"http://<server-addr>:<back-port>"';
        sub_filter_once on;

        proxy_pass http://127.0.0.1:3000;
    }
}

server {
    server_name <server-addr>;
    listen <back-port>;

    location / {
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_pass http://127.0.0.1:2002;
    }
}

启动 nginx:

systemctl start nginx

这时访问你的服务器 IP(域名)就应该可以看见 LibreOJ 的主页了,注意在开头加上 http://

现在在右上角注册一个账号,如果关闭了邮箱验证则邮箱可以乱填,回到终端打开数据库。

mariadb

id=1 的用户设为管理员。

USE lyrio;
UPDATE user SET isAdmin=1 WHERE id=1; # 

用以下命令检查。

SELECT * FROM user;

看见表上 id=1 一行上的 isAdmin=1 后回到主页,在下拉框中点击编辑资料,找到特权,将自己的特权全部打开,再提交,弹出特权修改成功则为成功。

:::align{center}

打开特权后的特权页面 :::

评测机

配置 Grub

沙箱的宿主机需要带参数启动。注意,若使用虚拟机,则是虚拟机带参数启动,不是主机带参数启动。编辑 /etc/default/grub,用

"quiet splash cgroup_enable=memory swapaccount=1 systemd.unified_cgroup_hierarchy=0 syscall.x32=y"

替换 GRUB_CMDLINE_LINUX_DEFAULT 的值,然后更新配置并重启。

update-grub
reboot

沙箱

下载沙箱并解压,文件约 1.8GB。

cd /opt
mkdir rootfs
cd rootfs
curl -O https://github.com/LibreOJ/sandbox-rootfs/releases/download/alpha2/sandbox-rootfs-ng_alpha2.tar.zst
tar -xvf sandbox-rootfs-ng_alpha2.tar.zst
rm sandbox-rootfs-ng_alpha2.tar.zst

评测系统

克隆仓库并构建。

cd /opt
git clone https://github.com/LibreOJ/judge.git --recursive # 递归克隆,有 testlib
cd judge
export CXX=g++ # 指定 C++ 编译器
yarn

返回主页,点击最底下的评测机按钮,点击添加,填写一个名字,点击钥匙图标,将弹出的文本记下来,这是 key。

:::align{center}

评测机页面,第一次添加的的评测机应与图中评测机 crfandx 相同 :::

创建配置文件,并配置。

cp config-example.yaml config.yaml

配置文件的内容:

serverUrl: http://<server-addr>:<back-port> # 后端 IP 地址和端口号
downloadEndpointOverride: null
key: 40uXJPXzuO2Ha41iuh8Pjw1h0ahvP9i/zJk7Rtn/ # 获得的 key
dataStore: /root/judge/data # 数据存储目录,若不存在会自动创建
binaryCacheStore: /root/judge/cache # 存储编译后的二进制文件的目录
binaryCacheMaxSize: 536870912 # 二进制文件目录的最大大小
taskConsumingThreads: 2 # 同时最多执行的评测任务数
maxConcurrentDownloads: 10 # 同时最多执行的下载任务数
maxConcurrentTasks: 3 # 同时最多执行的任务数,编译、运行一组测试数据等都是任务
taskWorkingDirectories: # 同时进行的任务需要的不同的工作目录,数量应与 maxConcurrentTasks 的值相同,会自动创建
  - /root/judge/1
  - /root/judge/2
  - /root/judge/3
rpcTimeout: 20000 # 与服务器进行 RPC 操作的最大执行时间
downloadTimeout: 20000 # 下载操作的最大执行时间
downloadRetry: 3 # 重试下载的最大次数
sandbox:
  rootfs: /opt/rootfs # 沙箱的目录
  user: sandbox
  hostname: null
  environments:
    PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    HOME: /sandbox
    LC_ALL: en_US.UTF-8
cpuAffinity:
  compiler: [0, 1]
  userProgram: [1]
  interactor: [0]
  checker: [0, 1]

请酌情设置 maxConcurrentTasks 的值,不应该让这个值大于物理核心数,若前后端与评测系统都部署在一台服务器上,就更应注意这个值,避免将前后端卡死。

启动

使用以下命令启动。

LYRIO_JUDGE_CONFIG_FILE=./config.yaml yarn start

启动后根据警告进行调整配置文件。在保证评测机内存充裕时,将工作目录挂载到 tmpfs。

mount tmpfs /root/judge/1 -t tmpfs
mount tmpfs /root/judge/2 -t tmpfs
mount tmpfs /root/judge/3 -t tmpfs

返回评测机页面,刷新,若你的评测机在线,恭喜你,你的评测机连接到了后端,接下来用一道题来测试你的评测机吧!

:::align{center}

某一条 AC 的评测记录 :::

结语

:::align{center}

LibreOJ 主页,关闭了通知 :::

安装好后可以进行配置,一些安全性的方面请自行调整,例如应用一个普通用户运行 LibreOJ,而不是 root 用户。本文仅是讲述如何较快速的部署 LibreOJ。

恭喜你部署好了 LibreOJ!