# bkdnOJ v2 - Cài đặt môi trường Dev (update 2022/10/19) Cập nhập hệ thống: ``` sudo apt update && sudo apt upgrade ``` # 1. Database: - Chúng ta sẽ cài đặt Database trước, hệ DB được sử dụng ở đây là Postgresql. ## 1.a Cài đặt DB (Postgres 14): * Tham khảo: [Techviewleo](https://techviewleo.com/how-to-install-postgresql-database-on-ubuntu/) ``` sudo apt -y install gnupg2 wget vim sudo apt-cache search postgresql | grep postgresql sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt update sudo apt -y install postgresql-14 ``` Lúc này, nếu không có thông báo lỗi, hệ thống đã cài đặt thành công Postgresql v.14. Chạy lệnh sau để confirm DB đang hoặc động (Active): ``` systemctl status postgresql ``` ![](https://i.imgur.com/ZMUz2Xl.png) Postgres sau khi cài chỉ mặc định chỉ chạy local, không mở cổng ra ngoài internet. ## 1.b Edit user DB: Postgresql mặc định có user DB là `postgres`, xác thực với DB không sử dụng mật khẩu. Ta sẽ đổi về lại xác thực với mật khẩu để dễ thiết lập cho Backend và DB nói chuyện với nhau. Kết nối tới DB: ``` sudo -u postgres psql ``` ![](https://i.imgur.com/Kn7kke9.png) Tại giao diện như trên, nhập lệnh `\password postgres` để đổi mật khẩu cho user `postgres`: ``` \password postgres Enter new password: <db-password> Enter it again: <db-password> ``` Với `<db-password>` sẽ là mật khẩu mới của user `postgres`. Thoát khỏi giao diện với: ``` \q ``` Mở file cấu hình DB bằng: ``` sudo nano /etc/postgresql/14/main/pg_hba.conf ``` Tìm dòng ``` local all postgres peer ``` Edit lại thành ``` local all postgres md5 ``` Lưu và khởi động lại postgres với câu lệnh: ``` sudo service postgresql restart ``` Để confirm, chạy lệnh: ``` sudo -u postgres psql ``` Lúc này, Postgres sẽ yêu cầu mật khẩu, nhập `<db-password>` như đã tạo: ![](https://i.imgur.com/Vsx9ptH.png) ## 1.c Tạo DB: Xong bước trước, ta đang ở giao diện CLI của DB, chạy lệnh: ``` CREATE DATABASE <db-name>; ``` Để tạo ra DB tên là `<db-name>`. Sau đó chạy lệnh thoát khỏi CLI của DB: ``` \q ``` ## 1.d Tổng kết: - DB User: `postgres` - DB User Password: `<db-password>` - Tên DB sử dụng: `<db-name>` # 2. Backend - Server Backend của chúng ta sẽ gồm DB, Django API Server, và phục vụ các static file như PDF... - Ta sẽ gọi đường dẫn đến nơi cài đặt Backend là `~/backend`. - Tạo folder, cd, và pull project backend xuống nơi cài đặt: ``` mkdir ~/backend cd ~/backend git clone https://github.com/BKDN-University/bkdnOJ-reborn-backend . ``` ## 2.a Cấu hình biến môi trường: Project có đính kèm file `~/backend/.env.copy` là template cho file `.env` của chúng ta. Tạo bản copy và điền thông tin: ``` cp .env.copy .env nano .env ``` ![](https://i.imgur.com/dp6GTM7.png) Chỉ lại tham số cho phù hợp rồi lưu file. ## 2.c Cài đặt dependency - Project được code bằng Python 3.8, ta sẽ sử dụng 3.8 để chạy backend. Nếu hệ thống bạn có mặc định version Python khác, tham khảo các bước sau để đổi phiên bản: - [Cài thêm python3.8 cho hệ thống](https://tecadmin.net/install-python-3-8-ubuntu/) - Xong, tạo môi trường ảo `venv` với lệnh ``` cd ~/backend python3.8 -m venv venv ``` - Kích hoạt vào môi trường ảo: ``` source venv/bin/activate ``` - Lúc này, bên trái shell sẽ có `(venv)`, nghĩa là bạn đang ở trong môi trường ảo và `python`, `phython3` lúc này sẽ ở phiên bản 3.8 ```python -V``` cho ra ```#=> Python 3.8.3``` - Mọi bước tiếp theo thực hiện với Python 3.8 * Chạy lệnh sau để cài đặt các thư viện cần thiết: ``` sudo apt install libpq-dev python3-dev libjpeg-dev libjpeg8-dev libfreetype6-dev build-essential libseccomp-dev pip3 install -r requirements.txt ``` * Chạy migrate lần đầu tiên để tạo table cho DB: ``` python3 manage.py migrate ``` * Nếu bạn dự định sẽ sử dụng web admin của django, cần chạy lệnh sau để django tạo file CSS cho web ``` python3 manage.py collectstatic ``` * Chạy lệnh sau để tạo Superuser đầu tiên cho hệ thống: ``` python3 manage.py createsuperuser ``` Nhập các thông tin tương ứng trên màn hình. * Sau đó, chạy lệnh sau để tạo ra khởi tạo table Language với các ngôn ngữ Python, C/C++, Java, Pascal cho DB: ``` python3 manage.py loaddata lang_small ``` * Sau đó, để public API server ra internet với cổng 8000, chạy vĩnh viễn: ``` python3 manage.py runserver 0.0.0.0:8000 ``` Lệnh trên cùng với thiết lập `ALLOWED_HOSTS` ở phần **2.b** sẽ cho phép ta truy cập trực tiếp tới backend thông qua `12.23.34.45:8000`. * Ta có thể truy cập đến `12.23.34.45:8000/admin`, đăng nhập với tài khoản Superuser tạo ra để confirm mọi thứ vẫn hoạt động. ## 2.d Tổng kết: - Backend chúng ta chạy ở `<api-host>:8000`. Để gửi và nhận yêu cầu, Frontend chúng ta sẽ gửi yêu cầu trỏ đến địa chỉ này. - Tiến trình Bridged dùng để giao tiếp với máy chấm ta đã cấu hình cho nó chạy ở `<bridge-ip>:<bridge-port>`, sẽ sử dụng ở phần **4**. - Ở phiên bản production, ta sẽ không sử dụng lệnh runserver vì nó chậm, không đảm bảo được tốc độ cũng như chống spam. ## 2.e Cấu hình Celery+Redis - Đối với các tác vụ nền hao tốn thời gian, không nên thực hiện chúng trong vòng đời của request vì như vậy làm giảm tính tương tác với người dùng. - Hệ thống có hai chức năng sau tốn rất nhiều thời gian: Rejudge và Recompute Standing. Hai chức năng này hệ thống sẽ được thực hiện nền sử dụng celery và redis. - Cài đặt [dịch vụ Redis ](https://redis.io/docs/getting-started/installation/install-redis-on-linux/): ``` curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list sudo apt-get update sudo apt-get install redis - Khởi chạy dịch vụ redis: ``` service redis-server start ``` - Để tạo một worker celery, chạy vĩnh viễn lệnh sau tại root project (`~/backend`): ``` celery -A dmoj_celery worker ``` - Lưu ý: với settings `DEBUG = True`, celery hiện có một bug khiến hệ thống bị leak memory, nếu chạy lâu dài server sẽ hết RAM. ## 2.f Cập nhập backend: * Vì folder `backend` chúng ta là một git repo, để cập nhập ta chỉ cần (đang ở trong môi trường ảo `venv`): ``` git pull pip install -r requirements.txt python3 manage.py collectstatic python3 manage.py migrate ``` * Và `runserver` lại. - **UPDATE 28.09.2022**: Chúng ta không chạy lệnh `python3 manage.py makemigrations` nữa khi update. Việc nhiều máy khác nhau chạy lệnh này sẽ khiến django tạo ra những file migration khác nhau dẫn dến mâu thuẫn DB. # 3. Client - Frontend - Folder chứa project frontend của chúng ta sẽ là: `~/frontend` - Tạo folder, cd, clone project: ``` mkdir ~/frontend cd ~/frontend git clone https://github.com/BKDN-University/bkdnOJ-reborn-frontend.git . ``` - Thỉnh thoảng, project sẽ được build thành phiên bản production tối ưu (folder `/build`). Để chạy frontend, chúng ta có thể serve folder là đủ. ## 3.a Cài đặt dependency: * Cài đặt nodejs và npm: ``` sudo apt install nodejs sudo apt install npm ``` ## 3.b Cài đặt, cấu hình và khởi chạy React Dev Server * Cài đặt các thư viện cần thiết ``` sudo npm i ``` Quá trình này khá ngốn RAM, nếu hệ thống freeze khi đang cài, hãy cấu hình thêm phân vùng swap hoặc swap file, tham khảo phần `5.1`. * Cấu hình file môi trường `.env`, project có bao gồm một file template là `.env.copy`, copy và chỉnh sửa nó: ``` cp .env.copy .env nano .env ``` ![](https://i.imgur.com/J5Z0mya.png) React sẽ sử dụng `DEV_BACKEND_URL:DEV_BACKEND_PORT` khi chúng ta khởi chạy React Dev server. Ngược lại, khi chúng ta build và serve build đó, React sẽ sử dụng `BACKEND_URL:BACKEND_PORT`. Chúng ta có thể trỏ cả hai cùng một địa chỉ vào backend là ```<api-host>:8000``` * Chạy React Dev Server với câu lệnh: ``` npm start ``` Sẽ chạy React dev server tại `localhost:3000`. ## 3.c Build project * Ta có thể build project sang phiên bản production bằng câu lệnh `run build`. Vì chúng ta vừa sửa file môi trường `.env`, chúng ta cần build lại để những thay đổi có tác dụng: ``` npm run build ``` Quá trình này khá ngốn RAM, ta có thể build tại một máy khác mạnh hơn và sau đó gửi thư mục `build/` về server. ## 3.d Serve build * Ta có thể sử dụng một web server đơn giản như package `serve` của npm: ``` sudo npm i serve -g ``` * Vấn ở tại `~/frontend`, để bắt đầu server ở localhost ta chạy lệnh sau vĩnh viễn: ``` serve -s build/ ``` Ta cũng có thể chạy server ra internet bằng lệnh: ``` HOST='0.0.0.0' PORT=3000 serve -s build ``` hoặc sử dụng cổng 80 ``` sudo HOST='0.0.0.0' PORT=80 serve -s build ``` ## 3.e Tổng kết: - Folder `build/` là phiên bản tối ưu của project. Build với `npm run build`. - Chạy React dev server với `npm start`. - App sử dụng địa chỉ trong file `.env` để gửi yêu cầu. Sau khi build, giá trị trong `.env` được nhúng vào trong build, vì vậy, muốn thay đổi thì phải build lại. ## 3.f Cập nhập: * Cũng là một repo git, ta có thể pull thay đổi mới bằng: ``` git pull ``` * Nếu thay đổi mới chưa có `build/`, ta có thể tự build và serve: ``` npm run build HOST='0.0.0.0' PORT=3000 serve -s build ``` # 4. Judge Bridge và Judge Server * Project hiện tại đang sử dụng các thiết lập máy chấm thông qua PyPI của DMOJ -> [docs.dmoj.ca](https://docs.dmoj.ca/#/judge/setting_up_a_judge?id=through-pypi) * Hiện tại, vì ta cài backend tại `~/backend`, nơi chứa dữ liệu problem sẽ là: `~/backend/media/problem_data` ## 4.a Chạy tiến trình Bridge - Sẽ đảm nhiệm kết nối và gửi thông tin chấm cho các máy chấm. - Tại `/backend`, log vào `venv`, chạy vĩnh viễn tiến trình sau: ``` python3 manage.py runbridged ``` Lúc này nếu không có lỗi, tiến trình sẽ đang lắng nghe yêu cầu kết nối của máy chấm. ## 4.b Thiết lập Máy chấm (theo 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. Các bước sau thực hiện tại máy chấm. ### Update danh sách package ```bash= sudo apt-get update ``` ### Cài đặt thư viện và máy chấm * Cài `pip3` nếu chưa có. * Thư viện ```bash= mkdir ~/judge cd ~/judge sudo apt install python3-dev python3-pip build-essential libseccomp-dev -y sudo apt-get install gcc libpq-dev -y sudo apt-get install python-dev python-pip -y sudo apt-get install python3-dev python3-pip python3-venv python3-wheel -y pip3 install wheel ``` Nếu máy không đủ RAM, có thể thiết lập swap file (phần **5.1**) * Cài đặt máy chấm DMOJ ```bash= git clone --recursive https://github.com/DMOJ/judge-server.git . pip3 install -e . ``` * Thử chạy `dmoj --help` để xác nhận là cài đặt thành công ```bash dmoj --help ``` * Nếu gặp lỗi `dmoj not found`, có thể hệ thống đã cài ở `~/.local/bin/dmoj` ```bash ~/.local/bin/dmoj --help ``` Trường hợp này, ta thêm đường dẫn này vào `PATH`: ``` export PATH="$PATH:~/.local/bin/" source ~/.profile dmoj --help ``` ### 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: - <đường dẫn đến thư mục `problem_data` ở backend> ``` Tại đây: * **id**: Judge Name, Tên của Máy chấm, đặt trong cặp dấu nháy, thiết lập trên Admin * **key**: Judge Auth Key, Khóa xác thực của Máy chấm đặt trong cặp dấu nháy, thiết lập trên Admin ![](https://i.imgur.com/piwfVVZ.png) * **problem_storage_root**: Đây là đường dẫn đến nơi chưa dữ liệu problem. * Nếu máy chấm và backend chạy trên cùng host, để nó mang giá trị `~/backend/media/problem_data` * Nếu máy chấm là máy remote, chúng ta phải mount thư mục `~/backend/media/problem_data` của Backend vào `/mnt/problems` của máy chấm, nên ta sẽ để `/mnt/problems` vào phần đó. *Chúng ta sẽ thử kết nối máy chấm local và remote ở phần bên dưới.* Quay lại terminal, vẫn ở `~/judge` trên máy chấm, 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: ```bash ubuntu@judge-server:~$ 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ậy, để máy chấm hỗ trợ thêm ngôn ngữ, ta chỉ cần cài bộ biên dịch/thông dịch cho ngôn ngữ đó. VD với java 8 ta cài như sau: ``` sudo apt-get update sudo apt-get install openjdk-8-jdk java -version ``` và chạy lại `dmoj-autoconf` để lấy khối text "`runtime`" mới. ### Một ví dụ `judge.yml` Giả sử trường hợp sau: * Máy chấm của bạn là local, nơi cài đặt backend là `~/backend`. * Trên web admin, tồn tại một máy chấm có tên là `judge1`, key là `judge1keysecret`, đang offline và không bị block. * 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: 'judge1' key: 'judge1keysecret' problem_storage_root: - ~/backend/media/problem_data 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 ``` ## 4.c Kết nối máy chấm #### i. Máy chấm localhost * Sau khi thiết lập xong, giả sử máy chấm ta đang local với backend, tại folder `~/judge`, ta kết nối bằng lệnh: ```bash dmoj -c judge.yml -p 9999 127.0.0.1 ``` * Với: * `9999` là cổng của `BRIDGED_JUDGE_ADDRESS` mà ta thiết lập ở phần **2.b**. * Nếu kết nối thành công, phía `backend` ở màn hình tiến trình `runbridged` sẽ có output tương tự như sau: ![](https://i.imgur.com/FCZeAtN.png) với `Earth` ở đây là tên của máy chấm. #### ii. Máy chấm remote * Với máy chấm remote, ta cần một cơ chế chia sẻ file nhưng vẫn đảm bảo bảo mật. Ta sẽ cấu hình `ssh` và sử dụng `sshfs` để mount thư mục dữ liệu problem từ backend đến máy chấm. * Ở ví dụ này, ta xem backend của chúng ta đang chạy ở `12.23.34.45:8000`, bridge ở cổng `7999`. User ở phía backend là `ubuntu`. Ta có thể test kết nối trước khi mount thư mục: ``` dmoj -c judge.yml -p 7999 12.23.34.45 ``` * Ta tạo khóa ssh cho máy chấm. Tại backend, chạy: ``` ssh-keygen ``` ``` Generating public/private rsa key pair. Enter file in which to save the key (/your_home/.ssh/id_rsa): ~/.ssh/judge-name ``` Không sử dụng passphrase. * Thêm khóa public vừa tạo ra vào những khóa chấp nhận bởi server backend: ``` cat ~/.ssh/judge-name.pub >> ~/.ssh/authorized_keys ``` * Copy khóa private sang máy chấm. Có nhiều cách, ta có thể copy thủ công. Tại backend, copy nội dung của file `~/.ssh/judge-name`, là khối text tương tự như sau: ![](https://i.imgur.com/7NKwV8H.png) Sang máy chấm, dán nội dung đó vào file `~/judge/judge-name.key`: ![](https://i.imgur.com/CYtd6qX.png) Từ máy chấm, thử SSH sang backend bằng private key này: ![](https://i.imgur.com/6W5KUiX.png) Nếu kết nối thành công, ta có thể chuyển sang bước tiếp theo là mount thư mục. * Tại máy chấm, thực hiện các bước: Cài đặt `sshfs` ``` sudo apt install sshfs ``` Tạo thư mục chuẩn bị mount: ``` sudo mkdir /mnt/problems ``` Mount: ``` sudo sshfs -o allow_other,default_permissions,reconnect -o IdentityFile=~/judge/judge-name.key ubuntu@12.23.34.45:/home/ubuntu/backend/media/problem_data/ /mnt/problems ``` * Trong đó: * `-o allow_other,default_permissions,reconnect` cho phép các user khác truy cập và quyền truy cập như bình thường. Và sẽ thử reconnect/remount lại nếu có sự cố về mạng. * `-o IdentityFile=~/judge/judge-name.key` cho phép lựa chọn file key để kết nối tới backend, với `~/judge/judge-name.key` là file private key mà ta đã dùng để `ssh -i` thành công ở bước trước. * `ubuntu@12.23.34.45`: user và địa chỉ phía backend * `/home/ubuntu/backend/media/problem_data/` là nơi chưa dữ liệu problem. Chắc chắn rằng folder `.../media/problem_data` có tồn tại, vì khi tạo mới hệ thống, chưa có problem, folder này sẽ không được tạo ra. * `/mnt/problems` nơi mà folder dữ liệu problem sẽ được mount ở backend. * Kiểm tra thư mục mount: ``` ls /mnt/problems ``` hoặc sử dụng `dmoj-cli` để check: ``` dmoj-cli -c <path file cấu hình judge.yml> ... dmoj> problems ``` ![](https://i.imgur.com/I0HkpzT.png) Tuy nhiên, nếu máy chấm đột ngột reboot, ta buộc phải chạy lệnh `sshfs` để mount lại. Một giải pháp để auto mount mỗi lần reboot máy chấm là [sử dụng fstab](https://askubuntu.com/a/1242516). (chưa test) * Kết nối máy chấm tới backend: ``` dmoj -c judge.yml -p 7999 12.23.34.45 ``` # 5. Phụ lục ## 5.1: Thiết lập Swap file cho các máy không đủ bộ nhớ Swap là khi máy sử dụng ổ đĩa để lưu dữ liệu tạm thời khi sử dụng hết RAM. Bạn có thể tạo phân vùng swap thay cho file swap, tuy nhiên nó sẽ hoạt động tương tự. Các bước tạo swap file: 1. Tạo file rỗng (`1K * 2M` = `2 GiB`, tùy nhu cầu ta sẽ đổi giá trị) ``` sudo mkdir -v /var/cache/swap cd /var/cache/swap sudo dd if=/dev/zero of=swapfile bs=1K count=2M sudo chmod 600 swapfile ``` 2. Chuyển đổi file tạo ra thành không gian swap. ``` sudo mkswap swapfile ``` 3. Kích hoạt cho phép paging và swaping lên file ``` sudo swapon swapfile ``` 4. Xác thực bằng `swapon -s` hoặc `top`: ``` top -bn1 | grep -i swap ``` Sẽ hiển thị dòng như sau: `MiB Swap: 2048.0 total, 2048.0 free` 5. Có thể tắt swap bằng: ``` sudo swapoff swapfile ``` 6. Có thể thêm nó vào `fstab` để luôn có swap file mỗi lần khởi động lại máy: ``` echo "/var/cache/swap/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab ``` 7. Test lại cấu hình `fstab` bằng: ``` sudo swapoff swapfile sudo swapon -va ``` *Note: lệnh trên sẽ confirm file fstab đúng ngữ pháp, nếu không đúng, Linux sẽ gặp vấn đề ở lần boot tiếp theo.*