前言
鄙人是个脚本小子,从隔壁acm跳到CTF乱学,学艺不精而且有点眼高手低,曾经憧憬学点网安能够驰骋赛场大杀四方,后来发现原来只有我被薄纱,最近参与完了人生最后一场xcpc,以大败而终。打完一看,哇塞,该举办校赛了(学校任务,由于acm实验室刚成立社团,需要完成指标),遂开始琢磨使用什么平台,DOMjudge好呢还是学长他们自己弄的oj平台好呢,虽然最终权衡之下还是选择了本校的oj,原因是当时觉得md文档处理成pdf比较麻烦,然后就仍旧使用的oj平台,但是为了后续校内赛事的举办,遂半夜开始写这篇DOMjudge使用教程(个人学识浅薄,此教程只够举行简单的校内赛事,轻喷),同时下次或者以后再办算法赛一类,都可以查找此文章,方便借鉴。
在开始之前,我们先理清一下我们的搭建思路,我们使用的主要工具是docker,所以除了UI工具,几乎全都是使用的docker容器进行搭建(借用学长的一句话:docker大法好啊~!),容器大致架构图如下:
此教程拉起容器全程未使用 -d
参数(方便查看各个容器的日志输出),故建议搭配 screen -S
一同食用。
正文
一、容器搭建
1.主要服务
首先打开docker,直接搜索 domjudge
(此处需要使用魔法手段),如图所示,第一个容器就是 server
:
点开这个容器呢,右侧已经能够看到容器说明,按照说明操作就好:
首先,执行以下命令拉起 mariadb
容器:
docker run -it --name dj-mariadb -e MYSQL_ROOT_PASSWORD=rootpw -e MYSQL_USER=domjudge -e MYSQL_PASSWORD=djpw -e MYSQL_DATABASE=domjudge -p 13306:3306 mariadb --max-connections=1000
参数详解:额……没什么好解释的样子,不懂百度……
然后,新开一个终端窗口,拉起DOMjudge的 server
容器,使用 --link
参数连接到 mariadb
容器中,此处 -p
参数指向的 12345
,也就是服务会打开在 12345
端口,可以自行根据需求调整,完整命令:
docker run --link dj-mariadb:mariadb -it -e MYSQL_HOST=mariadb -e MYSQL_USER=domjudge -e MYSQL_DATABASE=domjudge -e MYSQL_PASSWORD=djpw -e MYSQL_ROOT_PASSWORD=rootpw -p 12345:80 --name domserver domjudge/domserver:latest
#鼠鼠写这篇文章的时候domjudge版本大概在8.3.2
随即打开浏览器访问相关主机的 12345
端口即可看到DOMjudge首页:
然后打开一个新的终端窗口,执行以下命令用于获取admin用户密码和评测姬接口密钥:
#admin用户密码
docker exec -it domserver cat /opt/domjudge/domserver/etc/initial_admin_password.secret
#output示例:
8uOFWj2nR0_gl7aL
#judgehost密钥
docker exec -it domserver cat /opt/domjudge/domserver/etc/restapi.secret
#output示例:
# Randomly generated on host 9e46e4e0a88b, Thu May 22 18:36:52 CEST 2025
# Format: '<ID> <API url> <user> <password>'
default http://localhost//api judgehost 4zyQYS7LpchIIa52EFEl6qtgG7Lsb3hF
然后回到浏览器页面进行登陆,此处请注意,除了admin用户还有一个demo用户,其密码为demo
,该账户可用于测试,但是我们现在需要登陆的是admin用户,登陆进去访问jury页面,如图所示:
至此,基础的容器就拉好了。
2.连接 ICPC Tools
和 judgehost
(1. 配置CDS(icpctools)
通过以下命令拉起 icpctools
镜像容器:
docker run --name cds --rm -it -p 8080:8080 -p 8443:8443 -e CCS_URL=http://<your-domjudge-host>/api/contests/<contest id> -e CCS_USER=admin -e CCS_PASSWORD=<admin password> ghcr.io/icpctools/cds:2.6.1194
参数详解:
-p
: 8080端口:http端口 | 8443端口(可选):https端口-e
参数:- CCS_URL:
http://<your-domjudge-host>/api/contests/<contest id>
,尖括号中的内容按照需求替换填写即可,其中<contest id>
参数查看方式为:jury页面
->Before contest:
->Contests
->All available contests
->CID
特别注意,此处的<your-domjudge-host>
参数,由于各个容器都是在容器中启动,所以这里应当使用物理机在docker网络中的IP,如172.17.0.1,后面加上服务端口即可。 - CCS_USER/CCS_PASSWORD:admin用户名(默认为admin)/admin用户密码(前文已通过命令获取)
- CCS_URL:
ghcr.io/icpctools/cds:2.6.1194
:此参数为容器镜像名,最后一个冒号后面对应的是镜像版本,此处用的是2.6.1194(此时最新),请根据实际情况进行修改。
成功拉起容器后,访问相关页面,看到如下图所示,即CDS连接成功:
然后打开终端,执行以下命令获取CDS的默认密码:
docker exec -it cds cat /opt/wlp/usr/servers/cds/config/accounts.yaml
#output示例:
- username: admin
password: __ADMIN_PASSWORD__
type: admin
- username: presAdmin
password: __PRESADMIN_PASSWORD__
type: presAdmin
- username: blue
password: __BLUE_PASSWORD__
type: staff
- username: balloon
password: __BALLOON_PASSWORD__
type: __BALLOON_PASSWORD__
- username: presentation
password: __PRESENTATION_PASSWORD__
type: public
- username: myicpc
password: __MYICPC_PASSWORD__
type: analyst
- username: live
password: __LIVE_PASSWORD__
type: analyst
- username: team1
password: __TEAM_PASSWORD__
type: team
team_id: '1'
至此,榜单显示的后端程序就已经完全就绪,接下来是连接 judgehost
,也就是评测姬
(2. 配置评测机(judgehost)
同样的,在docker中的domserver容器简介页面划到最底部,会发现有judgehost的启动命令:
评测机默认是运行在linux系统中,以上命令启动的前置条件是需要修改一下 /etc/default/grub
文件的 GRUB_CMDLINE_LINUX_DEFAULT
字段,执行以下命令:
sudo vim /etc/default/grub
找到以下行:
GRUB_CMDLINE_LINUX_DEFAULT=""
修改为:
GRUB_CMDLINE_LINUX_DEFAULT="quiet cgroup_enable=memory swapaccount=1 systemd.unified_cgroup_hierarchy=0"
之后继续执行:
sudo update-grub
sudo reboot
然后等待重启即可
重启完成过后,执行以下命令拉起评测机容器:
docker run -it --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --name judgehost-0 --hostname judgehost-0 -e DAEMON_ID=0 -e CONTAINER_TIMEZONE=Asia/Shanghai -e JUDGEDAEMON_PASSWORD=<restapi.secret password> -e DOMSERVER_BASEURL=http://<server host>/ domjudge/judgehost
其中 restapi.secret password
参数在前文已经通过以下命令获取到:
docker exec -it domserver cat /opt/domjudge/domserver/etc/restapi.secret
<server host>
参数根据实际填写即可
其余参数大致解释:
--name judgehost-0
参数:容器名为judgehost-0,若有增加评测机的需求,递增后面的数字即可。--hostname judgehost-0
参数:设置容器内部的主机名为 judgehost-0,若有增加评测机的需求,递增后面的数字即可。-e DAEMON_ID=0
参数:指定该 JudgeHost 的守护进程 ID 为 0,若有增加评测机的需求,递增后面的数字即可。
执行指令过后,我们回到登陆过admin账号的浏览器页面中,访问judgehosts,会发现如下图所示,即评测机部署完成。
至此,整体的平台容器服务就已经完全搭建好了。
二、赛前准备
1.创建比赛
访问server页面,使用admin登陆,访问 jury/Contests
页面,页面最下方有 Add new contest 按钮,点击进去按照需求勾选填写,最后点击保存(save)即可(此时无需添加题目)。
2.上传题目
访问server页面,使用admin登陆,访问 jury/Problems
页面,即可发现存在一个import problem,点击进去过后,下图红色部分即为上传题目功能区:
此处,Contest选择刚才创建的比赛即可,上传的文件为zip压缩包,包名为题目的题号,即A题,B题之类的,如A.zip,B.zip,一个zip包仅包含一个题目的信息
zip包的构造如下:
your_problem.zip
├── problem.yaml # 题目元数据,必须位于压缩包根目录
├── problem.pdf # 题目描述文件
└── data
├── sample # 样例输入输出
│ ├── 1.in
│ └── 1.ans
└── secret # 评测数据
├── 2.in
└── 2.ans
其中 problem.yaml
文件内容示例如下:
short-name: "problenD" # 题目短名(唯一标识符,需与比赛配置中的 problems.id 一致)
name: "Hello World" # 题目名称(显示在榜单和提交页面)
time_limit: 1.0 # 时间限制(单位:秒,支持小数)
memory_limit: 512 # 内存限制(单位:MB,默认不限制)
interactive: false # 是否为交互题(默认 false,若为 true 需提供交互器)
validation: default # 验证模式,可选值:default/custom/float(见下文详解)
……(按需求添加)
validation
参数解释:
default
默认模式,直接比对选手输出和答案文件(逐字符严格匹配)。custom
使用自定义校验器(如特判题)。需提供校验器程序(如checker.cpp
),并在problem.yaml
中指定:validation: custom
validator: checker # 校验器可执行文件名(需提前编译)
validator_flags: [--case-sensitive] # 传递给校验器的参数float
浮点数判题模式,允许输出与答案在一定误差内匹配。需配置误差范围(在problem.yaml中添加):validation: float
float_tolerance: 1e-9 # 绝对误差(默认 1e-6)
上传好zip包过后,我们回到contests页面,编辑刚刚创建好的比赛:
即可在此处编辑题号和题目颜色,在此处多讲两句,上传的pdf,也就是题目描述,众所周知,我们的题目一般都是markdown文件,那么,这个题目怎么转成pdf呢,也就是此篇博客诞生的原因,区区一个markdown转pdf难不成还能成为压死骆驼的稻草吗,万万不可能。教程如下:
首先打开我们的VScode,搜索扩展:Markdown PDF
,安装好之后先别急着用,因为此时的字体之类的样式都还没有调整,默认字体……有点丑了,按下 ctrl+,
打开设置,搜索:markdown-pdf.styles
这一项设置,在里面添加一项 markdown-pdf.css
然后将下方的 Markdown-pdf: Styles Relative Path File
勾上,左侧竖线变蓝生效即可:
然后打开我们的markdown文件的文件夹,在同文件夹下创建一个文件,文件名为 markdown-pdf.css
,内容如下:
body {
font-family: "Microsoft YaHei", sans-serif;
font-size: 11pt;
line-height: 1.6;
color: #000000;
}
code {
font-family: "Consolas", monospace;
background: #ffffff;
}
具体参数信息自行百度,以上配置反正能用
然后此时,打开我们的markdown文件,右键,会出现相关选项,直接转换即可在当前目录下生成题目的pdf文件,虽然但是,此时的pdf文件会有一个页眉,你可以通过以下方法关掉页眉:
在VScode窗口中按下 Ctrl+,
进入设置,搜索 markdown-pdf.headerTemplate
将里面的值改成 ""
即可,此时再重新生成的pdf就不会出现页眉了。当然,如果需要指定页眉,也可以在这里进行调整编写,具体自行GPT。
至此,题目信息也已经完全具备,一场比赛的准备工作就差添加队伍选手了。
3.创建队伍和用户
对于参赛人数较多的情况下,手动一个一个进行录入花费时间较大,我们主要以文件直接导入为主,无论是三人acm赛制还是单人赛均可使用此创建方法:
首先创建用户,仍旧是去到 jury -> Administrator -> Import / export
进入导入页面过后,划到最底部,即可选择上传文件导入accounts和teams,此处我们选择使用json和yaml:
(1.导入teams
我们导入teams选择使用json文件,文件示例内容如下:
[{
"id": "3",
"label": "coder",
"group_ids": ["3"],
"display_name": "display003",
"name": "test003"
}, {
"id": "4",
"label": "coder2",
"group_ids": ["3"],
"name": "test004",
"display_name": "display004"
}]
其中:
id
:团队 ID,必须唯一,请注意,通过文件导入到系统中的teams,id可能会因为删除前面的队伍而发生变化,故而建议从1001开始。icpc_id
(可选) :来自外部系统(例如来自 ICPC CMS)的 ID 可能为空group_ids
:具有一个元素的数组:此团队所属的类别 ID,默认情况可访问http://<server host>/jury/categories页面进行查看ID字段name
:在 Web 界面中使用的团队名称members
(可选) :团队成员(一个长字符串)display_name
(可选) :团队显示名称。如果提供,将在某些地方显示此名称而不是团队名称,例如记分板organization_id
:此团队所属团队的 IDroom
(可选) :团队的房间
(2.导入accounts
我们导入accounts选择使用yaml文件,文件示例内容如下:
- id: team001
username: team001
password: P3xm33imve
type: team
name: team001
team_id: "3"
- id: team002
username: team002
password: qd4WHeJXbd
type: team
name: team002
team_id: "4"
- id: john
username: john
password: Uf4PYRA7mJ
type: judge
name: John Doe
其中:
id
:账号 ID。必须唯一username
:账户用户名。必须唯一password
:用于帐户的密码。type
:用户类型,team
,judge
,admin
或者balloon
之一,其中jury
等于judge
team_id
:(可选)此帐户所属团队的 ID,请注意,通过文件导入到系统中的teams,id可能会因为删除前面的队伍而发生变化,请严格保证相关账号对应的team_id与系统中显示的一致(teams页面ID字段)name
:(可选)帐户的全名ip
(可选):要链接到此帐户的 IP 地址
导入完成过后,去到 Users
页面,查看信息是否一致即可,若不一致请手动编辑调整:
至此基本的赛前准备就已经完全准备好了,关于打印机之类的……就懒得配置了,直接手打excel表格打印就行了。
三、榜单显示配置
前文已经将icpctools配置完成,也就是榜单显示的后端功能已经具备,只需要再配置一下前端窗口即可,此处我们需要下载两个不同的文件,操作均在win系统上进行,一个是Presentation Admin,另一个是Presentation,具体信息可以查看相关文档,下载地址为:https://github.com/icpctools/icpctools/releases,进到页面随便选择一个版本,分别下载PresentationAdmin.zip和Presentation.zip两个压缩包文件即可。其中,顾名思义,admin为窗口管理工具,另一个就是显示窗口。
下载完成并解压完成之后,首先去到admin工具的文件夹中,在该文件夹的bash环境终端(git bash也是bash)中执行以下命令启动窗口管理程序:
ICPC_FONT="DengXian" ./presAdmin.bat https://<server ip>:8443 presAdmin <presAdmin password>
参数解释:
ICPC_FONT
:设置字体为“等线”,保证中文显示正常<server ip>
:请根据实际替换<presAdmin password>
:在前文通过命令获取过,自行替换即可
执行过后出现新的窗口如下图所示即启动成功:
然后再在其它设备或者本设备上启动pres窗口程序,也就是去到Presentation.zip这个压缩包解压的文件夹里面,仍旧是在该文件夹的bash环境终端(git bash也是bash)中执行以下命令启动窗口程序:
ICPC_FONT="Microsoft Yahei" ./client.bat https://<server ip>:8443/api/contests/<cid> presentation <presentation password> --name <client name>
参数与上面的命令大差不差,其中 --name <client name>
参数是这个窗口的名字,可以随意设置,对应的 <cid>
和 <presentation password>
前文都讲过查看方法,请自行上滑。
执行过后可以看到设备整个屏幕变成了下图所示:
此时,我们的Admin程序也能看到上线了一个新的窗口:
该窗口右侧可以控制指定屏幕显示内容,可以跟随比赛进行自行调整。
至此,榜单显示以及后续滚榜的程序也全都搭建完成。一场比赛就可以正常地进行了。
三、有待改进
该博客仅仅只配置了比赛的基础项,若是比赛有气球发票的需求,到时候再说吧,看起来应该是办不到那么好的🙂,还有就是……没有配置打印机……队伍的账号信息有点麻烦需要手动录入打印,其它还有什么不足的呢……不知道了……