# Bộ cài đặt BKDN-OJ sử dụng Docker-compose
### TODO: viết cụ thể cách `mount` folder `/problems`
Bộ cài đặt bao gồm:
* `bkdn-oj`, được chỉnh sửa từ DMOJ
* `bkdn-oj-docker`, chứa các file cần thiết để khởi tạo và chạy ứng dụng với docker-compose
* Tài liệu hướng dẫn này
Bằng việc cài đặt trên Docker sẽ giảm thiểu xung đột với những phần mềm khác được cài trên máy, hoạt động ổn định hơn.
Hiện tại, cách cài đặt đã được test trên:
* ✅ Ubuntu Desktop 20.04
* ✅ Ubuntu Desktop 18.04
Ước tính tài nguyên cần thiết:
* Thời gian cài đặt: `40~70 phút`
* Không gian tối thiểu cài đặt: `8~15GB`
Nếu cài đặt sử dụng máy ảo cần phải tính thêm kích cỡ OS, tổng cộng khoảng `17~20GB`
# Cài đặt
## 🔴 Cài đặt Docker
* **Thời gian ước tính**: 7 phút
### Update hệ thống
```bash=
sudo apt-get update
sudo apt-get upgrade
```
### Chuẩn bị cài đặt Docker
Cách bước bên dưới được trích từ Document chính thức của Docker.
#### Nâng cấp apt để hỗ trợ https
```bash=
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
```
#### Thêm khóa GPG chính thức của Docker
```bash=
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
```
#### Set-up nơi cài bản Docker stable
```bash=
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```
### Cài Docker Engine
#### Update lại để cập nhập những cấu hình ở trên
```bash=
sudo apt-get update
```
#### Tải Engine Docker (mới nhất) về (~500MB)
```bash=
sudo apt-get install docker-ce docker-ce-cli containerd.io
```
#### Test thử Engine Docker đã đc cài đặt đúng chưa
```bash=
sudo docker run hello-world
```
Lúc này, nếu cài đặt thành công, terminal sẽ output thông điệp tương tự như sau:
```
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
```
Hiện tại, chỉ có `sudo` mới có thể chạy lệnh của Docker. Để các user khác cũng chạy được, cần bỏ họ vào group user `docker`.
Các lỗi như "`docker: Got permission denied while trying to connect to the Docker daemon..`" thường là do thiếu `sudo` trước câu lệnh.
## 🔴 Cài đặt Docker-compose
* **Thời gian ước tính**: 7 phút
Docker-compose là cách để set-up một mạng lưới các docker và liên kết chúng lại. Bằng cách này, mỗi server sẽ được tách biệt trong một docker riêng, lúc chỉnh sửa ta chỉ cần khởi động lại một docker/server mà không ảnh hưởng tới các docker/server khác.
### Tải binary chính thức từ github docker-compose (v1.29.2)
```bash=
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
```
### Thêm quyền thực thi cho file binary
```bash=
sudo chmod +x /usr/local/bin/docker-compose
```
### Thử cài đặt thành công hay chưa
```bash=
sudo docker-compose --version
```
Sẽ cho output tương tự như sau:
```
docker-compose version 1.29.2, build 5becea4c
```
Nếu không thực thi được, có thể do biến môi trường `PATH` không chứa thư mục dẫn đến file binary `docker-compose`.
## 🔴 Cài đặt BKDN-OJ
* **Thời gian ước tính**: 35 phút
* Phụ thuộc tốc độ đường truyền mạng và tốc độ đọc ghi đĩa
### Clone the repository
```bash=
git clone https://github.com/BKDN-University/bkdn-oj-docker
cd bkdn-oj-docker
git submodule update --init --recursive
cd dmoj
```
Kể từ lúc này, các câu lệnh đằng sau sẽ giả định rằng thư mục hiện hành là `/dmoj`
### Thay đổi cấu hình
Đây là bước sẽ thay đổi cấu hình cho server nhằm phù hợp hơn với mục đích cài đặt và tăng tính bảo mật cho server.
Các file cấu hình mặc định đi kèm với `bkdn-oj-docker` đã được cấu hình sẵn và sẽ hoạt động mà không cần chỉnh sửa gì. Nếu bạn giữ nguyên mặc định, tối thiểu nhất hãy thay đổi biến `SECRET_KEY` được mô tả phía bên dưới vì lý do bảo mật.
Có 3 nơi mà bạn có thể chỉnh sửa:
#### `dmoj/environment/`
Nơi này chứa các biến môi trường của server. Tại đây chứa 3 file: `mysql-admin.env`, `mysql.env`, `site.env`
##### 1. `mysql.env`
```
MYSQL_DATABASE=dmoj
MYSQL_USER=dmoj
MYSQL_PASSWORD=<Thay đổi trường này>
```
##### 2. `mysql-admin.env`
```
MYSQL_ROOT_PASSWORD=<Thay đổi trường này>
```
##### 3. `site.env`
```
HOST=<Thay đổi trường này>
DEBUG=0
SECRET_KEY=<Thay đổi trường này>
```
Với:
* `HOST` là một string biểu diễn host/domain name mà Django sẽ phản hồi yêu cầu. Có thể để `*` nếu bạn muốn mọi host đều truy cập được. Mặc định: `*`.
* `DEBUG` là biến môi trường quyết định Django có chạy ở chế độ Debug hay không. Nếu chạy ở chế độ Debug, mỗi lần Django gặp exception nó sẽ hiển thị tất cả thông tin lên trên trình duyệt. Chỉ chạy với `DEBUG=1` với mục đích testing.
* `SECRET_KEY` là một chuỗi string bất kỳ, là mã bí mật của server.
#### `dmoj/nginx/conf.d/nginx.conf`
Cấu hình tên `server_name`.
#### `dmoj/local_settings.py`
Tại đây chứa các cấu hình customize cho backend. Đa số key có tên đã giải thích được mục đích của nó. Ví dụ như key sau đây:
```
REGISTRATION_OPEN = True
```
Nghĩa là cho phép đăng ký tài khoản mới.
### Khởi tạo trước khi build
```bash=
./scripts/initialize
```
### Build image
* Thời gian cài: ~25p
```bash=
sudo docker-compose build
```
Đây là bước chạy khá lâu, vì nó cần tải tất cả thư viện cần thiết, cài đặt và compile các docker.
Trong lúc cài, sẽ hiển thị nhiều dòng chữ màu đỏ với nội dung như:
```
npm WARN ...
```
Đây là cảnh báo rằng phiên bản của thư viện đang cài đã lạc hậu (deprecated), vẫn có thể bỏ qua cảnh báo này.
### Khởi động thành phần site để cấu hình
Bước này sẽ khởi động docker chứa back end django để cấu hình.
```bash=
sudo docker-compose up -d site
```
### Khởi tạo bảng cho Database
Bước này sẽ khởi tạo các table trong Database
```bash=
sudo ./scripts/migrate
```
### Khởi tạo các file static
Bước này sẽ compile và khởi tạo các file static như css, js, ... và copy chúng vào thư mục web.
```bash=
sudo ./scripts/copy_static
```
### Load dữ liệu cần thiết cho Website
Bước này sẽ khởi tạo trước dữ liệu để cài đặt của chúng ta không hoàn toàn rỗng.
```bash=
sudo ./scripts/manage.py loaddata navbar
```
```bash=
sudo ./scripts/manage.py loaddata language_small
```
```bash=
sudo ./scripts/manage.py loaddata demo
```
Dữ liệu khởi tạo bao gồm:
* Highlight cho C++, Python,...
* Bài đăng "First Post"
* Problem "A + B"
* Tài khoản superuser "admin":
* Username: `admin`
* Password: `admin`
## 🔴 Khởi động BKDN-OJ
Tại bước này, cài đặt đã hoàn tất. Chạy câu lệnh bên dưới để khởi động tất cả các docker trong mạng lưới.
```bash=
sudo docker-compose up -d
```
Lựa chọn `-d` sẽ chạy ngầm (detached) các container. Lúc này đã có thể truy cập đến website.
##### Màn hình kết quả

##### Đăng nhập thành công, Màn hình yêu cầu đổi mật khẩu vì lý do bảo mật

# Quản lý
## 🔴 Web Admin BKDN-OJ
Trang Web Admin có thể làm nhiều điều mà không cần phải truy cập trực tiếp vào Server thông qua Terminal.
Nếu tài khoản đăng nhập là `superuser` và `staff`, người dùng đó có quyền truy cập vào Admin Page, nằm tại menu như sau:
#### Context menu

#### Trang admin:

### Thiết lập tiêu đề của trang Admin:
1. Truy cập Dashboard, tìm ô Site như hình sau và truy cập vào:

2. Sẽ cho ta thấy:

3. Edit các trường lại thành:

4. Nội dung của tiêu đề Header trang Admin đã thay đổi. Và nếu ta trỏ chuột vào tiêu đề, sẽ thấy nó đang trỏ đến `localhost`, giống như trường `Domain name` chúng ta đã nhập.

## 🔴 Test Data
### Upload bộ Test lên problem

Phía tay phải mỗi problem, tồn tại một Panel điều khiển. Thông qua Panel này, lựa chọn "Edit test data" để truy cập vào upload tests cho problem.

*Giao diện chỉnh sửa Test Data của một Problem*

Chuẩn bị một file zip với các file `.in` và `.out` là đầu vào và kết quả tương ứng. Nếu file zip có `A.in` thì nó bắt buộc phải chứa `A.out`, tương tự ngược lại. Nếu không, server sẽ ko nhận file zip.

Sau đó, upload file zip lên và nhấn Submit. Sau đó, nhấn lựa chọn `Fill testcases` để một cách tự động điền các trường input output. Nhấn Submit một lần nữa để lưu.
Tại đây có hai mode điền:
* **ICPC**: Các test đầu tiên luôn là 0 điểm. Test cuối là 100 điểm.
* **OI**: Mọi test đều có giá trị 1 điểm.
Lý do cho việc này là vì, đối với các test 0 điểm, nếu chấm không cho kết quả `Accepted` thì bài làm sẽ bị ngừng chấm. Điều này cho phép ta linh hoạt giữa hai mode ICPC và OI, cũng như sử dụng các test 0 điểm làm Pretest cho các cuộc thi có sử dụng System Test.
### Thư mục chứa Test Data
Quay lại phía Terminal, chúng ta đang ở thư mục hiện hành là:
```
.../dmoj
```
Thư mục chứa Test Data của cả hệ thống chính là:
```
.../dmoj/problems
```
Nếu ta thử liệt kê nội dung thư mục thì sẽ thấy file zip của problem "A + B" mà chúng ta vừa up lên tại phần trước.
# Thiết lập Máy chấm
## 🔴 Máy chấm - PyPI
Tại bước này chúng ta sẽ thử setup một server chấm theo cách cấu hình thủ công.
### Update danh sách package
```bash=
sudo apt-get update
```
### Cài đặt thư viện + máy chấm
```bash=
sudo apt install python3-dev python3-pip build-essential libseccomp-dev
pip3 install dmoj
```
Thử chạy `dmoj` để xác nhận là `dmoj` có trong PATH. Nếu không tìm thấy câu lệnh `dmoj`, bạn cần thêm đường dẫn `/home/<user>/.local/bin` vào biến môi trường PATH.
### Cấu hình máy chấm
Tạo một file có tên là `judge.yml` và ghi những thông tin sau vào file:
```
id: '<judge name>'
key: '<judge authentication key>'
problem_storage_root:
- /mnt/problems
```
Tại đây:
* **id**: Tên của Máy chấm đặt trong cặp dấu nháy, được tạo trên `admin/judge/judge/`
* **key**: Khóa xác thực của Máy chấm đặt trong cặp dấu nháy, tạo trên `admin/judge/judge/`
* **problem_storage_root**: Đây là đường dẫn đến Folder Test Data đã được nhắc đến bên trên.
* Nếu máy chấm và web chạy trên cùng host, thay `/mnt/problems` bằng đường dẫn đến thư mục `../dmoj/problems`
* Nếu máy chấm là máy remote, chúng ta phải mount thư mục `../dmoj/problems` từ xa vào `/mnt/problems` của máy chấm. Việc này có thể sử dụng `sshfs` để mount folder từ xa.
* Lưu ý **`sshfs` cần mount với option `allow_other`**
```
sudo addgroup <user> root
sudo sshfs <user>@<ip>:.../dmoj/problems /mnt/problems -o allow_other
```
Quay lại terminal, chạy lệnh:
```bash=
dmoj-autoconf
```
Câu lệnh này sẽ phát hiện những Ngôn ngữ lập trình đã có sẵn trên máy chấm (thông qua biến PATH) và in ra. Ví dụ, khi chạy lệnh bạn sẽ có output tương tự như sau:
```
nvat@nvatVB:~$ dmoj-autoconf
...
Auto-configuring C: Using /usr/bin/gcc (native target)
Auto-configuring C11: Using /usr/bin/gcc (native target)
...
Auto-configuring CPP11: Using /usr/bin/g++ (native target)
Auto-configuring CPP14: Using /usr/bin/g++ (native target)
Auto-configuring CPP17: Using /usr/bin/g++ (native target)
...
Auto-configuring JAVA11: Could not find JVM
Auto-configuring JAVA8: Could not find JVM
...
Auto-configuring PAS: Failed to find "fpc"
Auto-configuring PY2: Failed to find "python"
Auto-configuring PY3: Using /usr/bin/python3
Auto-configuring PYPY: Failed to find "pypy"
Auto-configuring PYPY3: Failed to find "pypy3"
...
Configuration result:
runtime:
as_x64: /usr/bin/x86_64-linux-gnu-as
as_x86: /usr/bin/as
awk: /usr/bin/mawk
cat: /usr/bin/cat
g++: /usr/bin/g++
g++11: /usr/bin/g++
g++14: /usr/bin/g++
g++17: /usr/bin/g++
gcc: /usr/bin/gcc
gcc11: /usr/bin/gcc
ld_x64: /usr/bin/x86_64-linux-gnu-ld
ld_x86: /usr/bin/ld
perl: /usr/bin/perl
python3: /usr/bin/python3
sed: /usr/bin/sed
```
Như ví dụ trên thì máy chấm sẽ không có ngôn ngữ Java và Pascal. Cái chúng ta quan tâm là khối text "`runtime`". Chúng ta sẽ copy nó và append vào cuối cùng của file `judge.yml` đã vừa tạo.
#### Ví dụ file `judge.yml`
Giả sử trường hợp sau:
* Máy chấm của bạn là remote, bạn đã mount thư mục Test Data phía web vào đường dẫn `/mnt/problems` ở phía máy chấm.
* Trên Web Admin, tồn tại một máy chấm có tên là `judgetest`, key là `qwerty` và nó đang offline.
* Máy chấm chạy `dmoj-autoconf` cho output giống như trên.
Vậy, file `judge.yml` của chúng ta sẽ trông như sau:
```
id: 'judgetest'
key: 'qwerty'
problem_storage_root:
- /mnt/problems
runtime:
as_x64: /usr/bin/x86_64-linux-gnu-as
as_x86: /usr/bin/as
awk: /usr/bin/mawk
cat: /usr/bin/cat
g++: /usr/bin/g++
g++11: /usr/bin/g++
g++14: /usr/bin/g++
g++17: /usr/bin/g++
gcc: /usr/bin/gcc
gcc11: /usr/bin/gcc
ld_x64: /usr/bin/x86_64-linux-gnu-ld
ld_x86: /usr/bin/ld
perl: /usr/bin/perl
python3: /usr/bin/python3
sed: /usr/bin/sed
```
### Kết nối Máy chấm đến Web BKDN-OJ
Tại folder chứa file `judge.yml`, ta kết nối bằng lệnh:
```bash=
dmoj -c judge.yml -p $BRIDGE_PORT $BRIDGE_IPADDRESS
```
Với:
* `$BRIDGE_PORT` là port ta thiết lập trong file `dmoj/local_setting.py`. Mặc định là `9999`.
* `$BRIDGE_IPADDRESS` là địa chỉ IP của website BKDN-OJ.
Giả sử, cổng `$BRIDGE_PORT` giữ nguyên như ban đầu, máy chấm và BKDN-OJ nằm cùng một host, câu lệnh kết nối sẽ như sau:
```
dmoj -c judge.yml -p 9999 127.0.0.1
```
##### Screenshot kết nối thành công

## 🔴 Máy chấm - Docker
*Chưa có steps*
# Lỗi trong lúc cài đặt
## 🔴 Lỗi 502 Bad Gateway
Đây là lỗi xảy ra khi container Nginx được restart trước các container khác, xảy ra bởi cơ chế caching của Nginx.
Để sửa, khởi động lại container của nginx. Nếu tên các container được giữ nguyên trong quá trình cài đặt thì câu lệnh sau sẽ khởi động lại `nginx`:
```bash=
sudo docker container restart dmoj_nginx
```
## 🔴 Git: Could not resolve host github.com
Lỗi xảy ra trong quá trình cài đặt với Ubuntu 18, do settings proxy của git không hợp lệ. Ta gỡ những settings này với:
```bash=
git config --global --unset http.proxy
git config --global --unset https.proxy
```
# Phụ lục
## 🔴 Sơ đồ các dịch vụ của DMOJ
BKDN-OJ là bản fork của DMOJ nên cũng sẽ yêu cầu chạy các dịch vụ của DMOJ.

*Nguồn ở trên hình*
Tại đây:
* Judge Bridge (hay chỉ Bridge): là server kết nối với tất cả các máy chấm. Nó làm trung gian giữa máy chấm và Backend Django.
* Event Server: là server websocket, phản hồi thời gian thực những sự kiện như: Testcase chấm xong,...
* Database Server: DMOJ sử dụng MariaDB một dạng MySQL
## 🔴 Cấu trúc thư mục của BKDN-OJ-Docker
Sau khi cài đặt BKDN-OJ như trên, nếu chạy lệnh `ls` tại thư mục `../dmoj` sẽ cho ta output:

Thư mục:
* `base`, `bridged`, `celery`, `database`, `mathoid` `nginx`, `site`, `texoid`, `wsevent` là các thư mục chuyên dụng của từng dịch vụ mà DMOJ sử dụng và tương ứng với một Docker.
* `media` là thư mục chứa media của Django.
* `environment` là thư mục chứa các file là biến môi trường (environment variable).
* `scripts` là thư mục chứa các kịch bản thông dụng, ví dụ: Copy file từ bên ngoài vào trong docker, Chạy một câu lệnh bên trong Docker,...
* `problems` là folder Test Data của hệ thống.
* `logs` là nơi chứa log của tất cả dịch vụ của DMOJ.
* `repo` đây là folder mã nguồn của Project DMOJ.
## 🔴 Chỉnh sửa BKDN-OJ
Folder `repo` ở đây chính là toàn bộ repository của DMOJ được clone vào folder `repo`. Bên trong nó có một số thư mục tiêu biểu như:
* `repo/dmoj/` nơi chứa settings cho project Django. Ví dụ như: các Port, Timezone, cho phép đăng ký tài khoản mới hay không, thiết lập tự gửi mail tự động,...
* `repo/dmoj/urls.py` là file mapping URL đến views tương ứng.
* `repo/judge/` phần lớn mã nguồn của backend DMOJ.
* `repo/judge/models` là lớp Model trong mô hình MVC, mô tả các đối tượng trong DB.
* `repo/judge/views` là lớp View, chứa các phương thức xử lý hay render HTML.
* `repo/templates` đây là Giao diện Web của Django. Chứa chủ yếu file HTML đã chèn code Django Template. Chỉnh sửa face trang web tại đây.
* `repo/resources` chứa các tài nguyên như ảnh, file js, file css. Chỉnh sửa style tại đây.
Sau khi chỉnh sửa mã nguồn, các thành phần Docker-compose đôi khi cần khởi động lại để có hiệu ứng. Nếu bạn không nhận thấy sự thay đổi, hãy thử lần lượt các bước sau đây, xếp theo thứ tự từ nhanh đến chậm:
1. Nếu chỉnh sửa HTML, Hard Reload trình duyệt (`Ctrl` + `Shift` + `R`)
2. Nếu là chỉnh sửa CSS, hình ảnh,... trong thư mục `resources`, chạy file `dmoj/scripts/copy_static` và kết hợp Hard Reload trình duyệt.
3. Thường thì sẽ không cần, nhưng chỉnh sửa một số file của Django (vd như: `models/`) bạn cần phải migrate lại:
```bash=
sudo ./scripts/migrate
```
5. Nếu vẫn không có hiệu lực, hãy restart toàn bộ docker-compose tại folder `../dmoj`:
```bash=
sudo docker-compose restart
```
6. Nếu tất cả step trên đều fail, lựa chọn cuối cùng là build lại docker-compose:
```bash=
sudo docker-compose build
```
## 🔴 Link repo
- BKDN-OJ: https://github.com/BKDN-University/bkdn-oj
- BKDN-OJ-Docker: https://github.com/BKDN-University/bkdn-oj-docker
## 🔴 Tham khảo
- DMOJ: https://github.com/DMOJ/online-judge
- DMOJ-Docker: https://github.com/Ninjaclasher/dmoj-docker
- LQDOJ: https://github.com/LQDJudge/online-judge