前言

最近要准备一次月赛,本来没准备启用Domjudge的,但是截止前一天忽然多了好多人。做了一下简单的压力测试,原来的学校OJ至少会爆炸20分钟…因此选用了腾讯云按量付费+Domjudge的方案进行配置。

但是这个坑,可以说不是一般的多啊…这里简单列列安装过程和坑点,希望能帮到后来者QAQ

不过由于作者已经把服务器释放掉了(按量付费的,买了高配,再烧就没钱了),所以缺少一些插图。

也就是下午一点的比赛我早上十一点才配完环境做好测试差点凉了

部署

数据库

这里我采用的是Docker版的部署方法。

参考链接:

选用一台干净的Linux服务器,本人使用的是Centos7.8 64位。

首先部署数据库:

1
docker run -d -it --name dj-mariadb -e MYSQL_ROOT_PASSWORD=ROOT998244353 -e MYSQL_USER=domjudge -e CONTAINER_TIMEZONE=Asia/Shanghai -e MYSQL_PASSWORD=DOM998244353 -e MYSQL_DATABASE=domjudge -p 13306:3306 mariadb --max-connections=1000 --max-allowed-packet=102400000 --innodb-log-file-size=202400000

重点:

  • MySQL的root密码被设置为:ROOT998244353,如有需要请自己修改
  • MySQL的domjudge密码被设置为:DOM998244353,如有需要请自己修改
  • 设置了--max-connections1000,保证能够顺利进行数据库连接
  • 设置了--max-allowed-packet102400000特别注意,底层单位为B,而不是KB。此外,貌似你设置的值大于$1GB$,就会按照$1GB$​​来计算。因此我直接在原来的基础(第一次安装时记错了单位,以为底层是KB)上敲多了两个零。此处的值主要取决于你最大的测试数据点,一般选为最大测试点的两倍
  • 设置了--innodb-log-file-size202400000,单位同上。此处的值同样主要取决于你最大的测试数据点,一般选为最大测试点的十倍。此外,该变量与上面的不同,需要重启数据库才能更改,因此如果你使用docker部署数据库,请务必在启用时就做好参数的指定。

Domjudge Server

而后,部署domjudge server:

1
docker run --link dj-mariadb:mariadb -d -it -e MYSQL_HOST=mariadb -e MYSQL_USER=domjudge -e MYSQL_DATABASE=domjudge -e CONTAINER_TIMEZONE=Asia/Shanghai -e MYSQL_PASSWORD=DOM998244353 -e MYSQL_ROOT_PASSWORD=ROOT998244353 -p 80:80 --name domserver domjudge/domserver:7.3.3

重点:

  • 这里讲domjudge-server的访问端口直接设为了主机的80端口,如果使用云服务商请注意是否开放安全组。
  • 这里虽然指定了时区为Asia/Shanghai,但实测没什么用,还是欧洲中部时区,不过不影响正常比赛。
  • 使用link连接前面部署好的数据库,需要注意数据库密码一一对应。
  • 指定了domjudge版本为7.3.3,下面的judgehost最好选用相同版本,否则容易出现未知BUG。

此时就可以通过80端口进行访问了。我们先停止部署,查看一下对应的API KEYADMIN SECRET

Domjudge后台管理员的初始密码:

1
docker exec -it domserver cat /opt/domjudge/domserver/etc/initial_admin_password.secret

DomjudgeAPI KEY,配置judgehost需要:

1
docker exec -it domserver cat /opt/domjudge/domserver/etc/restapi.secret

Judgehost

保存好上面的API KEY。在开始部署judgehost前,你还需要修改一下grub

请编辑/etc/default/grub,修改:

1
GRUB_CMDLINE_LINUX_DEFAULT="quiet cgroup_enable=memory swapaccount=1"

如果GRUB_CMDLINE_LINUX_DEFAULT有初始内容,可以将这段内容粘贴到原本的内容最后。

而后,开始配置judgehost

1
docker run -d -it --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --name judgehost-0 --link domserver:domserver --hostname judgedaemon-0 -e DAEMON_ID=0 -e JUDGEDAEMON_PASSWORD=UCgqHXW1WajmF9AG -e CONTAINER_TIMEZONE=Asia/Shanghai domjudge/judgehost:7.3.3
  • judgehost需要以特权模式进行,请记得加上参数--privileged
  • UCgqHXW1WajmF9AG为我的API KEY,每个人的KEY不同,请更改该项参数后再执行命令。
  • 指定了judgehost版本为7.3.3,上面的domjudge最好选用相同版本,否则容易出现未知BUG。
  • 在此之前你需要修改grub,别忘了。

如果你需要部署多台judgehost,请保证:

  • --hostname各不相同,如judgedaemon-1judgedaemon-2
  • DAEMON_ID各不相同,如DAEMON_ID=1DAEMON_ID=2;(此处为CPU核心,如果核数较少就不要设了)
  • --name各不相同,如judgehost-1judgehost-2

如果你需要配置某台judgehost不运行某类程序(某种语言,某道题的提交等…),请在后台设置restriction

此时你可以使用自带的账号进行测试。

你或许需要去Users里面Set一下dummy的密码,然后再用这个账号登陆。

交一发HelloWorld!,如果顺利,C/C++都是没问题的。

但是Java/PythonRuntime Error,原因是Java Not Found / Python Not Found

不难推测是Docker里面有点问题,使用以下命令进入某台评测机:

1
docker exec -it <ID> /bash/bin

发现judgehost里面使用chroot进行隔离,命令在chroot /chroot/domjudge/下进行,于是:

1
2
3
chroot /chroot/domjudge/
python -v
java -version

在这一步可以发现,返回结果都是Not Found,于是考虑在里面更新环境。默认源安装太慢,考虑换源。

注意,尽管默认源安装较慢,但是换源之后gpg证书会提示找不到公钥!而如果你要导入证书,需要gnupg,但是judgehost里是不带这个的。

因此,你需要先:

1
2
apt update
apt-get install gnupg

忍受一会龟速后,你可以导入新的公钥了,这时我们先换源,而后执行apt update

如果服务器在海外当我没说。

由于我们换的是chroot里面的源,你最好在主机内,使用类似下面的命令:

1
docker cp ./sources.list <ID>:chroot/domjudge/etc/apt/sources.list

将你准备好的sources.list传入docker内,执行:

1
apt update

此时,不出意外,会报错:

1
2
GPG error: The following signatures couldn't be verified because the public key is not available
NO_PUBKEY <XXXXXXXXX>

使用以下命令,将出现的不能被验证的公钥导入apt内:

1
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys <XXXXXXXXX>

此时,再执行apt update,就能完美更新源了,接下来我们安装Python环境和Java环境。

特别注意,请不要使用openjdk-8-jdk,版本太老旧了,安装之后仍然会报错无法评测java程序。

如果你安装了,会报错:

1
Error: A JNI error has occurred, please check your installation and try again

按照网上的说法,这一般是由于javajavac的版本不一致造成的问题,但是,如果你安装了这个老旧的版本,你会发现,虽然版本都是1.8.0_231,但后台依然会报错。

我后续安装的是openjdk-13-jdk,源更换的是腾讯云的Ubuntu源。

此外,你还需要特别注意在docker里安装openjdk的坑,尤其是还在chroot下。

参考链接:

解决方案:

chroot里,运行:

1
2
mount -t proc none /proc
mkdir -p /usr/share/man/man1

而后,顺利的话,就可以直接安装openjdk了。

Python环境安装没什么坑点,直接apt install就好了。

至于其他的配置,如针对某项语言设置时限倍数,直接在后台摸索就可以了。

配置比赛

主要分为几类:配置题目数据(以及spj),外部导入队伍和登录账号,配置比赛信息。

后台体验卡:DOMjudge - Online Demo

题目数据

Domjudge的后台只能单点的修改数据,我们填入基本的题目信息后,在Problem Achieve里面把题目下载到本地,此时题目是一个压缩包,解压之后,把内部填成这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
│  domjudge-problem.ini
│ problem.pdf
│ problem.yaml

└─data
└─secret
1.ans
1.in
2.ans
2.in
3.ans
3.in
4.ans
4.in
5.ans
5.in

简单解析:

  • problem.pdf,为题面,可以选择html/pdf/忘了三种格式,在比赛后台可以上传,也可以直接在这里上传。

  • domjudge-problem.ini,存放该题的评测细节,如果是传统题,里面只有时限,大概长这样:

    1
    timelimit='1'
  • problem.yaml,存放该题的评测细节,如果是传统题,里面应该只有题目名字和内存限制(单位MB):

    1
    2
    3
    4
    # Problem exported by DOMjudge on 2021-12-11T13:38:42+01:00
    name: 'Hard & Crazy Problem'
    limits:
    memory: 512
  • /data/secret,存放评测数据,用于后台评测,选手无法获得这些数据。

    特别注意,数据需要有相同的文件名,并且输入数据后缀为.in,输出数据后缀为.ans

    可以使用Renamer一类的工具批量改名。

  • /data/sample,如果有这个文件夹,里面的数据将在选手的Problem Set里显示,并允许下载。

    数据格式要求与secret一样。

然后我们重新打包成zip,上传到domjudge,在编辑题目的地方,最下面有个import,将压缩包选中并上传就可以了。如果你这一步出现了ERROR 2006 (HY000):MySQL server has gone away或者500错误的话,请检查你的数据库max-connections--max-allowed-packetinnodb-log-file-size参数是否合理。

一个简单检查的方式是,在jury界面,有个config cheker,点击进入后domjudge会进行一些简单的判断。

或直接在地址栏输入http://[Server IP/Other]/jury/config/check,进入检查界面,看看参数设置是否合理。

拓展链接:4.6 检查确保一切就绪 - DOMjudge 中文文档

如此,你应该顺利的上传了对应的文件,看起来一切正常。

那么,有spj的题该怎么上传?

以下是一份有(更准确的,是复用了以前的)spj的题的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
│  domjudge-problem.ini
│ problem.yaml

├─data
│ └─secret
000.ans
000.in
001.ans
001.in
002.ans
002.in
│ example0.ans
│ example0.in
│ example1.ans
│ example1.in

└─output_validators
└─validate
checker.cpp
testlib.h

其中,problem.yaml有所更改。具体为:增加了validation: custom一行:

不使用 SPJ 不需要定义 validation: 'custom',也不需要 output_validators

1
2
3
4
5
# Problem exported by DOMjudge on 2021-12-11T13:38:57+01:00
name: 'Ball?'
validation: custom
limits:
memory: 512

·

而后,在/output_validators/validate/里放入checker.cpp(自己所编写的spj)即可。

所使用的testlib.h需要修改一下,貌似是某个魔改版?

我所参考的题包和testlib.h来源于:

特别注意的是,如果你是复用SPJ,请参考官方文档修改返回值:Output validator - Problem Archive

A validator program is required to report its judgment by exiting with specific exit codes:

  • If the output is a correct output for the input file (i.e., the submission that produced the output is to be Accepted), the validator exits with exit code 42.
  • If the output is incorrect (i.e., the submission that produced the output is to be judged as Wrong Answer), the validator exits with exit code 43.

修改完毕后,按照上面的目录格式填入checker.cpp,以及修改problem.yaml,其他保持不变,打包上传即可。

如果使用Polygon出题,貌似有一个可以转换的工具,之前没有注意到没去测试,见:

导入队伍和登录账号

Domjudge里,队伍和账号是分开的。我的需求是,利用Domjudge举办单人赛,因此每个队伍只有一个人。此外,比赛只面向校内,因此不配置国家,学校等信息。由于相当于月考,也不配置气球和打印了…

官方文档:Contest Control System Requirements - ICPC-Contest Control Standard

参考链接:

简要过程:准备两个文件,team.tsvaccount.tsv,注:tsv代表用tab进行分隔。

一个简单的方案是,在Excel中将数据处理好,然后粘贴到notepad++里,得到的就是以tab分隔的数据。

对于team.tsv,第一行总是teams[tab]1。然后第二行开始,如下:

  • 第一个是【唯一队伍编号】,你可以从某个数字任意开始,但是不能够与以往的数据重复;
  • 第二个是【ICPC ID】,说实话这个我也不用配置,不过顺势配上去了;
  • 第三个是【队伍类型】,选择$3$​,代表Participants
  • 第四个是【队伍名称】,将会显示在榜单上。后面接上四个[tab],代表这些数据设空。这样就能做完一个最基础的team.tsv了。

image-20211212014430407

account.tsv中,你需要构造一个账号,对应上【唯一队伍编号】。

下面列出的匹配方式搬运自上面的北航春训 DOMjudge 配置坑点 - Chielo’s Blog

accounts.tsv 导入的时候也会自动去挂队伍ID,但方式是跳过 username 前面的非数字字符、再跳过 0 以后,以剩下的数字字符作为对应的队伍ID(剩下的不是数字就不挂队伍了)。对应的源码:

1
$teamid = preg_replace('/^[^0-9]*0*([0-9]+)$/', '\1', $line[2]);

更具体的,我所构造的account.tsv如下:

image-20211212015158048

简单解释,第一行固定为accounts[tab]1/。从第二行开始:

  • 第一个为【账号类型】,这里固定为[team]
  • 第二个是【名称】,在管理员界面点入队伍再点入对应用户可以看到对应的真实姓名是谁,在导出榜单时无用;
  • 第三个是【登陆账号】,匹配方式上面已经提到;
  • 第四个是登陆密码,我在Excel里使用randbetween生成的。

由此就能完成team.tsvaccount.tsv,先导入team,再导入account即可。

发放账号

为了防止有心人通过非正当手段查看代码,以及减轻压力(其实用邮箱也行,但许多人平时并没有查看邮箱的习惯),账号采用【问卷星平台的对外查询】的方式发放。

image-20211212020812968

类似这样,整理好一个表格后,上传到问卷星的【表单】,选择【导入Excel文件】,然后【配置对外查询】。

我所配置的是,你需要输入正确的姓名,学号,手机号,才能够查到对应的信息。

配置比赛信息

配置比赛信息非常的简单,只需要打开后台按着操作就可以了…

需要注意的是,建议对Java程序的评测,内存开到512M以上,在一些题目中,Java的代码非常容易因为空间不够RE,并会暂停judgehost评测java,需要手动调整后再继续评测。

赛后

导出代码

可以使用:LaiJunBin/domjudge-code-download-tool: Download domjudge contest source code tool.

.env里配置对应信息后按照指示操作即可。

代码查重

可以使用:fanghon/antiplag: a code-similarity, text-similarity and image-similarity computation software for the codes, documents and images of assignment.

虽然样式丑了一点,但是功能相当强大,个人一般使用jplag查重。

导出ghost文件

可以使用:verngutz/CFgym-ghost-file-generator: generate Codeforces Gym ghost file .dat format from other popular formats like PC^2 and DOMjudge

需要自行修改一下result_map,添加对OLE的处理。其他按照代码中所说即可。

  1. Install requests.
  2. Run the DOMjudge server.
  3. Change the constants below.
  4. python3 DOMjudge-to-CFgym.py > contest.dat

批量数字签名 & 发放证书

可以使用:PDF Signer – Digital Signature Software

先利用PS内的变量功能+证书模板,生成带名字的证书。

然后选用特定的签名图片和数字证书进行签名即可。

其他

对于比赛平台,你可以使用一些手段进行压力测试,防止开局就崩了(

简单的压测可以使用:phper-hejing/insane: HTTP负载测试,压力测试,专为API设计的测试工具。

关于滚榜,打印等比较进阶的内容后续再研究(或许会再更的!)