├── backlog.txt ├── tests ├── secrets │ ├── boca_key.txt │ ├── db_name.txt │ ├── db_user.txt │ ├── db_password.txt │ ├── boca_password.txt │ ├── db_super_user.txt │ ├── db_super_password.txt │ ├── README.md │ └── docker-compose.yml ├── env │ ├── .env2 │ ├── .env │ ├── docker-compose.yml │ └── README.md ├── migrations │ ├── backups │ │ └── boca-db.dump │ ├── init │ │ └── init.sh │ ├── README.md │ └── docker-compose.yml ├── e2e │ ├── package.json │ ├── docker-compose.yml │ ├── tests │ │ ├── setup.ts │ │ └── login.spec.ts │ └── playwright.config.ts ├── healthcheck │ ├── README.md │ └── docker-compose.yml ├── volumes │ ├── README.md │ └── docker-compose.yml ├── networks │ ├── README.md │ └── docker-compose.yml └── platforms │ ├── README.md │ └── docker-compose.yml ├── imgs ├── arquitetura-boca.png └── arquitetura-boca-docker.png ├── .dockleignore ├── .github ├── super-linter.env ├── dependabot.yml └── workflows │ ├── close-stale.yml │ ├── clean-cache.yml │ ├── lint-files.yml │ ├── scan-images.yml │ ├── read-matrix.yml │ ├── lint-images.yml │ ├── e2e-tests.yml │ ├── clean-packages.yml │ ├── ci.yml │ └── build-images.yml ├── .gitignore ├── .dockerignore ├── docker ├── build │ ├── README.md │ └── matrix.json └── dev │ ├── jail │ ├── init.sh │ └── Dockerfile │ ├── web │ ├── Dockerfile │ └── init.sh │ └── base │ └── Dockerfile ├── SECURITY.md ├── docker-compose.yml ├── CONTRIBUTING.md ├── docker-compose.dev.yml ├── docker-compose.prod.yml ├── boca ├── src │ └── private │ │ ├── createdb.php │ │ └── conf.php └── tools │ └── boca-createjail ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md └── README.md /backlog.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/secrets/boca_key.txt: -------------------------------------------------------------------------------- 1 | mykey -------------------------------------------------------------------------------- /tests/secrets/db_name.txt: -------------------------------------------------------------------------------- 1 | mydb -------------------------------------------------------------------------------- /tests/secrets/db_user.txt: -------------------------------------------------------------------------------- 1 | myuser -------------------------------------------------------------------------------- /tests/secrets/db_password.txt: -------------------------------------------------------------------------------- 1 | mypass -------------------------------------------------------------------------------- /tests/secrets/boca_password.txt: -------------------------------------------------------------------------------- 1 | mypassword -------------------------------------------------------------------------------- /tests/secrets/db_super_user.txt: -------------------------------------------------------------------------------- 1 | superuser -------------------------------------------------------------------------------- /tests/secrets/db_super_password.txt: -------------------------------------------------------------------------------- 1 | superpass -------------------------------------------------------------------------------- /imgs/arquitetura-boca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaofazolo/boca-docker/HEAD/imgs/arquitetura-boca.png -------------------------------------------------------------------------------- /imgs/arquitetura-boca-docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaofazolo/boca-docker/HEAD/imgs/arquitetura-boca-docker.png -------------------------------------------------------------------------------- /tests/env/.env2: -------------------------------------------------------------------------------- 1 | # database configuration 2 | # privileged boca user 3 | POSTGRES_USER=superuser 4 | POSTGRES_PASSWORD=superpass -------------------------------------------------------------------------------- /tests/migrations/backups/boca-db.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaofazolo/boca-docker/HEAD/tests/migrations/backups/boca-db.dump -------------------------------------------------------------------------------- /.dockleignore: -------------------------------------------------------------------------------- 1 | # boca-jail must run as root 2 | CIS-DI-0001 3 | # Found in the used Ubuntu release images 4 | CIS-DI-0009 5 | # Allow installing security updates 6 | DKL-DI-0003 7 | -------------------------------------------------------------------------------- /.github/super-linter.env: -------------------------------------------------------------------------------- 1 | VALIDATE_ALL_CODEBASE=false 2 | VALIDATE_MARKDOWN=true 3 | VALIDATE_YAML=true 4 | VALIDATE_DOCKERFILE_HADOLINT=true 5 | VALIDATE_PHP_BUILTIN=true 6 | VALIDATE_BASH=true 7 | BASH_SEVERITY=error 8 | FILTER_REGEX_EXCLUDE=.*boca/tools/.* -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Set update schedule for GitHub Actions 3 | 4 | version: 2 5 | 6 | updates: 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | # Check for updates to GitHub Actions every weekday 11 | interval: "daily" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/balloons 2 | src/private/agentcode 3 | src/private/comp 4 | src/private/problemtmp 5 | src/private/runslog 6 | src/private/runtmp 7 | src/private/scoretmp 8 | .DS_Store 9 | node_modules/ 10 | **/test-results/ 11 | **/playwright-report/ 12 | **/playwright/.auth/ 13 | **/playwright/.cache/ -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.DS_Store 4 | **/.env 5 | **/.git 6 | **/.github 7 | **/.gitignore 8 | **/.project 9 | **/.settings 10 | **/.toolstarget 11 | **/.vs 12 | **/.vscode 13 | **/docker-compose* 14 | **/Dockerfile* 15 | **/imgs 16 | **/tests 17 | LICENSE 18 | README.md -------------------------------------------------------------------------------- /tests/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boca-e2e-tests", 3 | "version": "1.0.0", 4 | "description": "End-to-end automated testing of the BOCA Online Contest Administrator", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "keywords": [], 8 | "author": "Rodrigo Laiola Guimaraes", 9 | "license": "GPL-3.0", 10 | "devDependencies": { 11 | "@playwright/test": "^1.51.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/env/.env: -------------------------------------------------------------------------------- 1 | # database configuration 2 | BOCA_DB_HOST=boca-db 3 | # unprivileged boca user 4 | BOCA_DB_USER=bocauser 5 | BOCA_DB_PASSWORD=dAm0HAiC 6 | BOCA_DB_NAME=bocadb 7 | # privileged boca user 8 | BOCA_DB_SUPER_USER=postgres 9 | BOCA_DB_SUPER_PASSWORD=dAm0HAiC 10 | # initial password that is used by the admin user (web app) 11 | # If not set, the default value is 'boca' 12 | BOCA_PASSWORD=mypassword 13 | # secret key to be used in HTTP headers 14 | # MUST set it with any random large enough sequence 15 | BOCA_KEY=mykey -------------------------------------------------------------------------------- /docker/build/README.md: -------------------------------------------------------------------------------- 1 | # Build matrix for GitHub Actions 2 | 3 | The JSON file(s) in this folder will be read in the GitHub Actions workflows and used as shown below. 4 | 5 | :warning: Edit the `matrix.json` file to add/remove supported image releases/tags (e.g., `latest`, `1.2`, `1.2.0`), parent/base images (e.g., `ubuntu:jammy`, `ubuntu:focal`), and platforms (e.g., `linux/amd64`, `linux/arm/v7`, `linux/ppc64le`). **Untagged or unsupported build images will be deleted periodically.** :warning: 6 | 7 | ```yml 8 | jobs: 9 | # Calling a reusable workflow 10 | setup: 11 | uses: ./.github/workflows/read-matrix-file.yml 12 | with: 13 | matrix-path: docker/build/matrix.json 14 | 15 | check-setup: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | parent: ${{ fromJSON(needs.setup.outputs.parent) }} 20 | needs: 21 | - setup 22 | ``` 23 | -------------------------------------------------------------------------------- /tests/migrations/init/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | set -e 23 | 24 | # With psql 25 | # Supports: plain text SQL restore only 26 | if [[ -f "$BOCA_DB_DUMP_FILENAME" ]]; 27 | then 28 | # Set ON_ERROR_STOP=OFF, otherwise restore does not work as expected 29 | psql \ 30 | -f "$BOCA_DB_DUMP_FILENAME" \ 31 | -v ON_ERROR_STOP=OFF \ 32 | postgres 33 | fi 34 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Reporting a bug 4 | 5 | Please use the [Issues](https://github.com/joaofazolo/boca-docker/issues/new) section to report security bugs/vulnerabilities. Include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 6 | 7 | * Type of issue 8 | * Full paths of source file(s) related to the manifestation of the issue 9 | * The location of the affected source code (tag/branch/commit or direct URL) 10 | * Any special configuration required to reproduce the issue 11 | * Step-by-step instructions to reproduce the issue 12 | * Proof-of-concept or exploit code (if possible) 13 | * Impact of the issue, including how an attacker might exploit the issue 14 | 15 | We'll strive to acknowledge and send a more detailed response to your report ASAP. After the initial reply to your submission, the maintainers will endeavor to keep you informed of the progress being made towards a fix and full announcement, and may ask for additional information or guidance surrounding the reported issue. 16 | 17 | ## Comments on this policy 18 | 19 | If you have suggestions on how this process could be improved please submit a 20 | [pull request](https://github.com/joaofazolo/boca-docker/pulls) or 21 | [file an issue](https://github.com/joaofazolo/boca-docker/issues/new) to discuss. 22 | -------------------------------------------------------------------------------- /docker/build/matrix.json: -------------------------------------------------------------------------------- 1 | { 2 | "//comment1": "Matrix of supported image tags/releases (latest: first value)", 3 | "release": [ 4 | "1.2.2", 5 | "1.2.2-jammy", 6 | "1.2.2-focal", 7 | "1.2", 8 | "1.2-jammy", 9 | "1.2-focal", 10 | "latest", 11 | "nightly", 12 | "nightly-jammy", 13 | "nightly-focal", 14 | "master", 15 | "master-jammy", 16 | "master-focal", 17 | "1.2.1", 18 | "1.2.1-jammy", 19 | "1.2.1-focal", 20 | "1.2.0", 21 | "1.2.0-jammy", 22 | "1.2.0-focal", 23 | "1.1.0", 24 | "1.0.0" 25 | ], 26 | "//comment2": "Matrix of parent/base images used in builds (default: first value)", 27 | "parent": [ 28 | "ubuntu:jammy", 29 | "ubuntu:focal" 30 | ], 31 | "//comment3": "Matrix of target os/platforms (multi-arch)", 32 | "platform": [ 33 | "linux/amd64", 34 | "linux/arm/v7", 35 | "linux/arm64/v8", 36 | "linux/ppc64le", 37 | "linux/s390x" 38 | ], 39 | "//comment4": "Git URL of the BOCA repository to clone from (empty for https://github.com/cassiopc/boca.git)", 40 | "repository": "https://github.com/cassiopc/boca.git", 41 | "//comment5": "The branch, tag or SHA to point to in the cloned BOCA repository", 42 | "ref": "master" 43 | } -------------------------------------------------------------------------------- /tests/healthcheck/README.md: -------------------------------------------------------------------------------- 1 | # Healthcheck for boca-docker 2 | 3 | This demo shows how to make the `boca-web` and `boca-jail` services wait for the database container (`boca-db`) to startup and be ready to accept requests before continuing. The `healthcheck` has been configured to periodically check if PostgreSQL is ready using the [`pg_isready`](https://www.postgresql.org/docs/14/app-pg-isready.html) command. 4 | If the check is successful the container will be marked as `healthy`. Until then it will remain in an `unhealthy` state. For more details about the parameters `interval`, `timeout`, `retries`, and `start_period` see the documentation [here](https://docs.docker.com/engine/reference/builder/#healthcheck). 5 | 6 | > **NOTE:** `depends_on` with `healthcheck` does not work when used with `docker stack deploy`. For more information, see the discussion thread [here](https://github.com/docker/compose/issues/4305). 7 | 8 | ## Example 9 | 10 | * Launch the application: 11 | 12 | **... via docker compose** 13 | 14 | ```sh 15 | docker compose -f tests/healthcheck/docker-compose.yml up -d 16 | ``` 17 | 18 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login use the default credentials (Name: _system_ | Password: _boca_). 19 | 20 | * To bring it down: 21 | 22 | **... via docker-compose** 23 | 24 | ```sh 25 | docker compose -f tests/healthcheck/docker-compose.yml down 26 | ``` 27 | -------------------------------------------------------------------------------- /tests/secrets/README.md: -------------------------------------------------------------------------------- 1 | # Docker Secrets for boca-docker 2 | 3 | As an alternative to passing sensitive information via environment variables, `_FILE` may be appended to some of the supported environment variables, causing the initialization scripts to load the values for those variables from files present in the containers. In particular, this can be used to load passwords from Docker secrets stored in `/run/secrets/` files. 4 | 5 | Currently, this is supported for `BOCA_DB_SUPER_USER`, `BOCA_DB_SUPER_PASSWORD`, `BOCA_DB_USER`, `BOCA_DB_PASSWORD`, `BOCA_DB_NAME`, `BOCA_PASSWORD`, `BOCA_KEY`, `POSTGRES_PASSWORD`, and `POSTGRES_USER`. 6 | 7 | ## Example 8 | 9 | * Launch the application: 10 | 11 | **... via docker compose** 12 | 13 | ```sh 14 | docker compose -f tests/secrets/docker-compose.yml up -d 15 | ``` 16 | 17 | **... or docker stack deploy** 18 | 19 | ```sh 20 | docker stack deploy --compose-file tests/secrets/docker-compose.yml boca-stack-secrets 21 | ``` 22 | 23 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login with the _system_ user, find the password in the `tests/secrets/boca_password.txt` file (stored on GitHub for demo purposes only); 24 | 25 | * To bring it down: 26 | 27 | **... via docker compose** 28 | 29 | ```sh 30 | docker compose -f tests/secrets/docker-compose.yml down 31 | ``` 32 | 33 | **... or docker stack rm** 34 | 35 | ```sh 36 | docker stack rm boca-stack-secrets 37 | ``` 38 | -------------------------------------------------------------------------------- /tests/volumes/README.md: -------------------------------------------------------------------------------- 1 | # Managing data in boca-docker 2 | 3 | This demo shows how to persist data in a dockerized BOCA setup. Docker offers two options for containers to store files on the host machine, so that the files are persisted even after the container stops: _volumes_, and _bind mounts_. While bind mounts are dependent on the directory structure and OS of the host machine, volumes are completely managed by Docker. 4 | Volumes are often the preferred mechanism for persisting data generated by and used by Docker containers. No matter which type of mount you choose to use, the data looks the same from within the container. For more information, refer to the [documentation](https://docs.docker.com/storage/). 5 | 6 | ## Example 7 | 8 | * Launch the application: 9 | 10 | **... via docker compose** 11 | 12 | ```sh 13 | docker compose -f tests/volumes/docker-compose.yml up -d 14 | ``` 15 | 16 | **... or docker stack deploy** 17 | 18 | ```sh 19 | docker stack deploy --compose-file tests/volumes/docker-compose.yml boca-stack-volume 20 | ``` 21 | 22 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login use the default credentials (Name: _system_ | Password: _boca_); 23 | 24 | * To bring it down: 25 | 26 | **... via docker compose** 27 | 28 | ```sh 29 | docker compose -f tests/volumes/docker-compose.yml down 30 | ``` 31 | 32 | **... or docker stack rm** 33 | 34 | ```sh 35 | docker stack rm boca-stack-volume 36 | ``` 37 | -------------------------------------------------------------------------------- /tests/networks/README.md: -------------------------------------------------------------------------------- 1 | # Networking for boca-docker 2 | 3 | This demo shows how to add network isolation between services in the _boca-docker_ application. Docker's networking subsystem uses drivers to provide specific networking functionality. In the example, we use the _bridge_ network driver (default). It is usually used when applications run in standalone containers that need to communicate. 4 | If deploying the application on a swarm cluster, it will be necessary to edit the `tests/networks/docker-compose.yml` file and set the _overlay_ network driver to allow the communication between services/containers on different Docker daemons. For more information, refer to the [documentation](https://docs.docker.com/network/#network-drivers). 5 | 6 | ## Example 7 | 8 | * Launch the application: 9 | 10 | **... via docker compose** 11 | 12 | ```sh 13 | docker compose -f tests/networks/docker-compose.yml up -d 14 | ``` 15 | 16 | **... or docker stack deploy (set the _overlay_ network driver first)** 17 | 18 | ```sh 19 | docker stack deploy --compose-file tests/networks/docker-compose.yml boca-stack-networks 20 | ``` 21 | 22 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login use the default credentials (Name: _system_ | Password: _boca_); 23 | 24 | * To bring it down: 25 | 26 | **... via docker compose** 27 | 28 | ```sh 29 | docker compose -f tests/networks/docker-compose.yml down 30 | ``` 31 | 32 | **... or docker stack rm** 33 | 34 | ```sh 35 | docker stack rm boca-stack-networks 36 | ``` 37 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | services: 23 | 24 | # web app 25 | boca-web: 26 | environment: 27 | # database configuration 28 | # privileged user password 29 | - BOCA_DB_SUPER_PASSWORD=superpass 30 | ports: 31 | - 8000:80 32 | 33 | # online judge 34 | boca-jail: 35 | privileged: true 36 | 37 | # database 38 | boca-db: 39 | image: postgres:14-alpine 40 | environment: 41 | # database configuration 42 | # privileged user password 43 | - POSTGRES_PASSWORD=superpass 44 | -------------------------------------------------------------------------------- /tests/platforms/README.md: -------------------------------------------------------------------------------- 1 | # Multiple platforms for boca-docker 2 | 3 | From release `1.2.0` the `boca-web` and `boca-jail` images can support multiple platforms, which means that each single image contains variants for different architectures (all of which are for the Linux operating system). 4 | When pulling images of these services, Docker automatically selects the ones that match the host machine OS and architecture. Conversely, you can make use of the `platform` parameter with the _os[/arch[/variant]]_ syntax to specify which target platform containers for these services will run on. 5 | 6 | Architectures currently supported: 7 | - Linux x86-64 (`amd64`) 8 | - ARMv7 32-bit (`arm/v7`) 9 | - ARMv8 64-bit (`arm64/v8`) 10 | - IBM POWER8 (`ppc64le`) 11 | - IBM z Systems (`s390x`) 12 | 13 | As an example, this demo shows the `boca-web` service using the `linux/ppc64le` and the `boca-jail` the `linux/arm/v7` target platform. For more information, refer to the [documentation](https://docs.docker.com/compose/compose-file/compose-file-v2/#platform). 14 | 15 | > **NOTE:** The `platform` parameter does not work when used with `docker stack deploy`. 16 | 17 | ## Example 18 | 19 | * Launch the application: 20 | 21 | **... via docker compose** 22 | 23 | ```sh 24 | docker compose -f tests/platforms/docker-compose.yml up -d 25 | ``` 26 | 27 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login use the default credentials (Name: _system_ | Password: _boca_); 28 | 29 | * To bring it down: 30 | 31 | **... via docker-compose** 32 | 33 | ```sh 34 | docker compose -f tests/platforms/docker-compose.yml down 35 | ``` 36 | -------------------------------------------------------------------------------- /tests/e2e/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | services: 25 | 26 | # web app 27 | boca-web: 28 | image: "${PACKAGE}/boca-web:${TAG}" 29 | platform: ${PLATFORM} 30 | environment: 31 | # database configuration 32 | # privileged user password 33 | - BOCA_DB_SUPER_PASSWORD=superpass 34 | ports: 35 | - 8000:80 36 | 37 | # online judge 38 | boca-jail: 39 | image: "${PACKAGE}/boca-jail:${TAG}" 40 | platform: ${PLATFORM} 41 | privileged: true 42 | 43 | # database 44 | boca-db: 45 | image: postgres:14-alpine 46 | environment: 47 | # database configuration 48 | # privileged user password 49 | - POSTGRES_PASSWORD=superpass 50 | -------------------------------------------------------------------------------- /tests/platforms/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | services: 25 | 26 | # web app 27 | boca-web: 28 | image: ghcr.io/joaofazolo/boca-docker/boca-web:1.2-focal 29 | platform: linux/ppc64le 30 | environment: 31 | # database configuration 32 | # privileged user password 33 | - BOCA_DB_SUPER_PASSWORD=superpass 34 | ports: 35 | - 8000:80 36 | 37 | # online judge 38 | boca-jail: 39 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:1.2.0-jammy 40 | platform: linux/arm/v7 41 | privileged: true 42 | 43 | # database 44 | boca-db: 45 | image: postgres:14-alpine 46 | environment: 47 | # database configuration 48 | # privileged user password 49 | - POSTGRES_PASSWORD=superpass 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Code of Conduct 7 | 8 | This project has a 9 | [Code of Conduct](https://github.com/joaofazolo/boca-docker/blob/master/CODE_OF_CONDUCT.md) 10 | to which all contributors must adhere. 11 | 12 | ## Code Reviews 13 | 14 | All submissions, including submissions by project members, require review. We 15 | use GitHub pull requests for this purpose. Consult 16 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 17 | information on using pull requests. 18 | 19 | ## Developer's Certificate of Origin 1.1 20 | 21 |
22 | By making a contribution to this project, I certify that:
23 | 
24 |  (a) The contribution was created in whole or in part by me and I
25 |      have the right to submit it under the open source license
26 |      indicated in the file; or
27 | 
28 |  (b) The contribution is based upon previous work that, to the best
29 |      of my knowledge, is covered under an appropriate open source
30 |      license and I have the right under that license to submit that
31 |      work with modifications, whether created in whole or in part
32 |      by me, under the same open source license (unless I am
33 |      permitted to submit under a different license), as indicated
34 |      in the file; or
35 | 
36 |  (c) The contribution was provided directly to me by some other
37 |      person who certified (a), (b) or (c) and I have not modified
38 |      it.
39 | 
40 |  (d) I understand and agree that this project and the contribution
41 |      are public and that a record of the contribution (including all
42 |      personal information I submit with it, including my sign-off) is
43 |      maintained indefinitely and may be redistributed consistent with
44 |      this project or the open source license(s) involved.
45 | 
46 | -------------------------------------------------------------------------------- /.github/workflows/close-stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close stale issues and PRs 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | on: 13 | schedule: 14 | # Run daily at 6:30 PM UTC 15 | - cron: '30 18 * * *' 16 | # or on button click 17 | workflow_dispatch: 18 | 19 | env: 20 | ISSUE_WARN_MESSAGE: > 21 | This issue is stale because it has been open 90 days with no activity. 22 | Remove stale label or comment or this will be closed in 7 days. 23 | 24 | PR_WARN_MESSAGE: > 25 | This PR is stale because it has been open 45 days with no activity. 26 | 27 | ISSUE_CLOSE_MESSAGE: > 28 | This issue was closed because it has been stalled for 7 days with no 29 | activity. Feel free to reopen if this issue is still relevant, or to ping 30 | the collaborator who labelled it stalled if you have any questions. 31 | 32 | jobs: 33 | stale: 34 | runs-on: ubuntu-latest 35 | permissions: 36 | # https://github.com/actions/stale#recommended-permissions 37 | issues: write 38 | pull-requests: write 39 | 40 | steps: 41 | # Close Stale Issues and PRs 42 | # https://github.com/actions/stale 43 | - 44 | uses: actions/stale@v10 45 | with: 46 | # Run the stale workflow as dry-run (no actions will be taken) 47 | # debug-only: true 48 | stale-issue-label: stale 49 | stale-issue-message: ${{ env.ISSUE_WARN_MESSAGE }} 50 | stale-pr-message: ${{ env.PR_WARN_MESSAGE }} 51 | close-issue-message: ${{ env.ISSUE_CLOSE_MESSAGE }} 52 | days-before-stale: 90 53 | days-before-pr-stale: 45 54 | days-before-close: 7 55 | # Never close a PR 56 | days-before-pr-close: -1 57 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | services: 23 | 24 | boca-base: 25 | image: boca-base:latest 26 | build: 27 | context: . 28 | dockerfile: docker/dev/base/Dockerfile 29 | 30 | boca-web: 31 | image: boca-web:latest 32 | build: 33 | context: . 34 | dockerfile: docker/dev/web/Dockerfile 35 | # CAUTION: this bind mount will overwrite BOCA website. 36 | # For future development... 37 | # volumes: 38 | # - ./src:/var/www/boca/src 39 | 40 | boca-jail: 41 | image: boca-jail:latest 42 | build: 43 | context: . 44 | dockerfile: docker/dev/jail/Dockerfile 45 | 46 | # boca-db: 47 | # volumes: 48 | # - db-data:/var/lib/postgresql/data 49 | 50 | boca-adminer: 51 | image: adminer:latest 52 | environment: 53 | - ADMINER_DEFAULT_SERVER=boca-db 54 | ports: 55 | - 8080:8080 56 | 57 | # volumes: 58 | # 59 | # db-data: 60 | -------------------------------------------------------------------------------- /tests/networks/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | # using extension fields to reduce duplication 25 | x-networks: &net-common 26 | driver: bridge 27 | 28 | services: 29 | 30 | # web app 31 | boca-web: 32 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 33 | environment: 34 | # database configuration 35 | # privileged user password 36 | - BOCA_DB_SUPER_PASSWORD=superpass 37 | ports: 38 | - 8000:80 39 | networks: 40 | - frontend 41 | - backend 42 | 43 | # online judge 44 | boca-jail: 45 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 46 | privileged: true 47 | networks: 48 | - backend 49 | 50 | # database 51 | boca-db: 52 | image: postgres:14-alpine 53 | environment: 54 | # database configuration 55 | # privileged user password 56 | - POSTGRES_PASSWORD=superpass 57 | networks: 58 | - backend 59 | 60 | networks: 61 | 62 | # visible to the internet 63 | frontend: 64 | <<: *net-common 65 | 66 | # hidden from the outside world 67 | backend: 68 | <<: *net-common 69 | internal: true 70 | -------------------------------------------------------------------------------- /tests/env/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | # example 1: using extension fields to reduce duplication 25 | x-env: &db-common 26 | # database configuration 27 | BOCA_DB_HOST: boca-postgres 28 | # unprivileged boca user 29 | BOCA_DB_USER: myuser 30 | BOCA_DB_PASSWORD: mypass 31 | BOCA_DB_NAME: mydb 32 | 33 | services: 34 | 35 | # web app 36 | boca-app: 37 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 38 | environment: 39 | # example 1: continuation... 40 | <<: *db-common 41 | # example 2: passing env vars directly in docker-compose 42 | BOCA_DB_SUPER_USER: superuser 43 | BOCA_DB_SUPER_PASSWORD: superpass 44 | # example 3: passing specific env vars from file 45 | BOCA_PASSWORD: ${BOCA_PASSWORD} 46 | BOCA_KEY: ${BOCA_KEY} 47 | ports: 48 | - 8000:80 49 | 50 | # online judge 51 | boca-jail: 52 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 53 | privileged: true 54 | environment: 55 | <<: *db-common 56 | # web app 57 | BOCA_WEB_HOST: boca-app 58 | 59 | # database 60 | boca-postgres: 61 | image: postgres:14-alpine 62 | # example 4: passing all env vars from file 63 | env_file: 64 | - .env2 65 | -------------------------------------------------------------------------------- /tests/healthcheck/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | services: 25 | 26 | # web app 27 | boca-web: 28 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 29 | environment: 30 | # database configuration 31 | # privileged user password 32 | - BOCA_DB_SUPER_PASSWORD=superpass 33 | ports: 34 | - 8000:80 35 | depends_on: 36 | boca-db: 37 | condition: service_healthy 38 | 39 | # online judge 40 | boca-jail: 41 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 42 | privileged: true 43 | depends_on: 44 | boca-db: 45 | condition: service_healthy 46 | 47 | # database 48 | boca-db: 49 | image: postgres:14-alpine 50 | environment: 51 | # database configuration 52 | # privileged user password 53 | - POSTGRES_PASSWORD=superpass 54 | healthcheck: 55 | # determine whether or not it is healthy 56 | test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] 57 | interval: 10s # interval between health checks 58 | timeout: 5s # timeout for each health checking 59 | retries: 20 # how many times retries 60 | start_period: 10s # estimated time to boot 61 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | services: 23 | # web app 24 | boca-web: 25 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 26 | depends_on: 27 | - boca-db 28 | deploy: 29 | mode: global 30 | placement: 31 | constraints: 32 | - "node.role==manager" 33 | resources: 34 | limits: 35 | cpus: '0.50' 36 | memory: 1024M 37 | reservations: 38 | cpus: '0.25' 39 | memory: 512M 40 | restart_policy: 41 | condition: on-failure 42 | 43 | # online judge 44 | boca-jail: 45 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 46 | depends_on: 47 | - boca-db 48 | deploy: 49 | mode: replicated 50 | replicas: 1 51 | placement: 52 | max_replicas_per_node: 8 53 | resources: 54 | limits: 55 | cpus: '0.50' 56 | memory: 1024M 57 | reservations: 58 | cpus: '0.25' 59 | memory: 512M 60 | restart_policy: 61 | condition: on-failure 62 | 63 | # database 64 | boca-db: 65 | deploy: 66 | mode: global 67 | placement: 68 | constraints: 69 | - "node.role==manager" 70 | resources: 71 | limits: 72 | cpus: '0.50' 73 | memory: 2048M 74 | reservations: 75 | cpus: '0.25' 76 | memory: 1024M 77 | restart_policy: 78 | condition: on-failure 79 | -------------------------------------------------------------------------------- /tests/migrations/README.md: -------------------------------------------------------------------------------- 1 | # Data backup and restore in boca-docker 2 | 3 | This demo illustrates how to migrate database data from one PostgreSQL instance to another. First, to backup data we use `pg_dump`. This utility dumps BOCA's database and makes consistent backups even if the application is being used concurrently. These dumps can be output either in plain-text script (.sql), archive file or directory formats. 4 | Concomitantly, we illustrate the use of `psql` and `pg_restore` commands to reconstruct BOCA's database to the state it was in at the time the data was saved. On the one hand, `psql` makes use of plain-text files containing SQL commands to rebuild the database during initialization[^1]. 5 | On the other hand, `pg_restore` provides a more flexible restore mechanism to examine the archive and/or select which parts of the database are to be restored (for instance, it allows for selection and reordering of all archived items, support parallel restoration, and compression). For more information, refer to the [documentation](https://www.postgresql.org/docs/current/app-pgdump.html). 6 | 7 | [^1]: Scripts in the initialization folder only run if starting the database container with a data directory that is empty; otherwise, any pre-existing database will be left untouched on container startup. 8 | 9 | > **NOTE:** This example uses [profiles](https://docs.docker.com/compose/profiles/) with Compose which does not work with `docker stack deploy`. 10 | 11 | ## Example 12 | 13 | * Launch the application: 14 | 15 | **... via docker compose** 16 | 17 | ```sh 18 | docker compose -f tests/migrations/docker-compose.yml up -d 19 | ``` 20 | 21 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login use the default credentials (Name: _admin_ | Password: _boca_); 22 | 23 | > **NOTE:** The contest restored from the `tests/migrations/backups/boca-db.sql` contains some example [problems](http://localhost:8000/boca/admin/problem.php). 24 | 25 | * To backup the database (the dump is saved locally in `/test/migrations/backups/boca-db.tar`): 26 | 27 | > **NOTE:** Try different archive formats by changing the `BOCA_DB_DUMP_FORMAT` and `BOCA_DB_DUMP_FILENAME` variables in the `tests/migrations/docker-compose.yml` file. 28 | 29 | **... via docker compose** 30 | 31 | ```sh 32 | docker compose --profile backup -f tests/migrations/docker-compose.yml up -d 33 | ``` 34 | 35 | * To restore the database to an alternative state (the dump used is in `/test/migrations/backups/boca-db.dump`): 36 | 37 | **... via docker compose** 38 | 39 | ```sh 40 | docker compose --profile restore -f tests/migrations/docker-compose.yml up -d 41 | ``` 42 | 43 | * To bring it down: 44 | 45 | **... via docker compose** 46 | 47 | ```sh 48 | docker compose --profile backup --profile restore -f tests/migrations/docker-compose.yml down 49 | ``` 50 | -------------------------------------------------------------------------------- /boca/src/private/createdb.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | . 18 | //////////////////////////////////////////////////////////////////////////////// 19 | //Last updated 06/aug/2012 by cassio@ime.usp.br 20 | $ds = DIRECTORY_SEPARATOR; 21 | if($ds=="") $ds = "/"; 22 | 23 | if(is_readable('/etc/boca.conf')) { 24 | $pif=parse_ini_file('/etc/boca.conf'); 25 | $bocadir = trim($pif['bocadir']) . $ds . 'src'; 26 | } else { 27 | $bocadir = getcwd(); 28 | } 29 | 30 | if(is_readable($bocadir . $ds . '..' .$ds . 'db.php')) { 31 | require_once($bocadir . $ds . '..' .$ds . 'db.php'); 32 | @include_once($bocadir . $ds . '..' .$ds . 'version.php'); 33 | } else { 34 | if(is_readable($bocadir . $ds . 'db.php')) { 35 | require_once($bocadir . $ds . 'db.php'); 36 | @include_once($bocadir . $ds . 'version.php'); 37 | } else { 38 | echo "unable to find db.php"; 39 | exit; 40 | } 41 | } 42 | if (getIP()!="UNKNOWN" || php_sapi_name()!=="cli") exit; 43 | ini_set('memory_limit','600M'); 44 | ini_set('output_buffering','off'); 45 | ini_set('implicit_flush','on'); 46 | @ob_end_flush(); 47 | 48 | // if(system('test "`id -u`" -eq "0"',$retval)===false || $retval!=0) { 49 | // echo "Must be run as root\n"; 50 | // exit; 51 | // } 52 | echo "\nThis will erase all the data in your bocadb database."; 53 | echo "\n***** YOU WILL LOSE WHATEVER YOU HAVE THERE!!! *****"; 54 | echo "\nType YES and press return to continue or anything else will abort it: "; 55 | $resp = strtoupper(trim(fgets(STDIN))); 56 | if($resp != 'YES') exit; 57 | 58 | echo "\ndropping database\n"; 59 | DBDropDatabase(); 60 | echo "creating database\n"; 61 | DBCreateDatabase(); 62 | echo "creating tables\n"; 63 | DBCreateContestTable(); 64 | DBCreateSiteTable(); 65 | DBCreateSiteTimeTable(); 66 | DBCreateUserTable(); 67 | DBCreateLogTable(); 68 | DBCreateProblemTable(); 69 | DBCreateAnswerTable(); 70 | DBCreateTaskTable(); 71 | DBCreateLangTable(); 72 | DBCreateRunTable(); 73 | DBCreateClarTable(); 74 | DBCreateBkpTable(); 75 | echo "creating initial fake contest\n"; 76 | DBFakeContest(); 77 | ?> -------------------------------------------------------------------------------- /docker/dev/jail/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | # usage: file_env VAR [DEFAULT] 23 | # ie: file_env 'XYZ_DB_PASSWORD' 'example' 24 | # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of 25 | # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) 26 | # Ref: https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh 27 | file_env() { 28 | local var="$1" 29 | local fileVar="${var}_FILE" 30 | local def="${2:-}" 31 | if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; 32 | then 33 | echo >&2 "error: both $var and $fileVar are set (but are exclusive)" 34 | exit 1 35 | fi 36 | local val="$def" 37 | if [ "${!var:-}" ]; 38 | then 39 | val="${!var}" 40 | elif [ "${!fileVar:-}" ]; 41 | then 42 | val="$(< "${!fileVar}")" 43 | fi 44 | export "$var"="$val" 45 | unset "$fileVar" 46 | } 47 | 48 | # Loads various settings that are used elsewhere in the script 49 | # This should be called before any other functions 50 | # Ref: https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh 51 | docker_setup_env() { 52 | # If variables are not set or null, use default values. 53 | export BOCA_DB_HOST="${BOCA_DB_HOST:-boca-db}" 54 | export BOCA_WEB_HOST="${BOCA_WEB_HOST:-boca-web}" 55 | 56 | file_env 'BOCA_DB_USER' 'bocauser' 57 | file_env 'BOCA_DB_PASSWORD' 'dAm0HAiC' 58 | file_env 'BOCA_DB_NAME' 'bocadb' 59 | } 60 | 61 | docker_setup_env 62 | 63 | echo "bocadir=/var/www/boca" > /etc/boca.conf && \ 64 | echo "bdserver=$BOCA_DB_HOST" >> /etc/boca.conf && \ 65 | echo "bdcreated=y" >> /etc/boca.conf 66 | 67 | printf "#!/bin/sh\n\ 68 | BOCAIP=%s" "$BOCA_WEB_HOST" > /etc/bocaip 69 | 70 | until PGPASSWORD=$BOCA_DB_PASSWORD \ 71 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_USER" -d "$BOCA_DB_NAME" -c '\q'; 72 | do 73 | >&2 echo "PostgreSQL server is unavailable - sleeping" 74 | sleep 1 75 | done 76 | 77 | >&2 echo "PostgreSQL server is up - executing command" 78 | 79 | # Use exec format to run program directly as pid 1 80 | # https://www.padok.fr/en/blog/docker-processes-container 81 | exec boca-autojudge 82 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "docker.commands.composeUp": [ 3 | { 4 | "label": "dev-match", 5 | "template": "docker compose -f docker-compose.yml ${configurationFile} up --build", 6 | "match": "dev" 7 | }, 8 | { 9 | "label": "prod-match", 10 | "template": "docker compose -f docker-compose.yml ${configurationFile} up", 11 | "match": "prod" 12 | }, 13 | { 14 | "label": "env-match", 15 | "template": "docker compose --env-file=tests/env/.env ${configurationFile} up", 16 | "match": "env" 17 | }, 18 | { 19 | "label": "secrets-match", 20 | "template": "docker compose ${configurationFile} up", 21 | "match": "secrets" 22 | }, 23 | { 24 | "label": "networks-match", 25 | "template": "docker compose ${configurationFile} up", 26 | "match": "networks" 27 | }, 28 | { 29 | "label": "volumes-match", 30 | "template": "docker compose ${configurationFile} up", 31 | "match": "volumes" 32 | }, 33 | { 34 | "label": "migrations-match", 35 | "template": "docker compose ${configurationFile} up", 36 | "match": "migrations" 37 | }, 38 | { 39 | "label": "healthcheck-match", 40 | "template": "docker compose ${configurationFile} up", 41 | "match": "healthcheck" 42 | }, 43 | { 44 | "label": "platforms-match", 45 | "template": "docker compose ${configurationFile} up", 46 | "match": "platforms" 47 | } 48 | ], 49 | "docker.commands.composeDown": [ 50 | { 51 | "label": "env-match", 52 | "template": "docker compose ${configurationFile} down", 53 | "match": "env" 54 | }, 55 | { 56 | "label": "secrets-match", 57 | "template": "docker compose ${configurationFile} down", 58 | "match": "secrets" 59 | }, 60 | { 61 | "label": "networks-match", 62 | "template": "docker compose ${configurationFile} down", 63 | "match": "networks" 64 | }, 65 | { 66 | "label": "volumes-match", 67 | "template": "docker compose ${configurationFile} down", 68 | "match": "volumes" 69 | }, 70 | { 71 | "label": "migrations-match", 72 | "template": "docker compose --profile backup --profile restore ${configurationFile} down", 73 | "match": "migrations" 74 | }, 75 | { 76 | "label": "healthcheck-match", 77 | "template": "docker compose ${configurationFile} down", 78 | "match": "healthcheck" 79 | }, 80 | { 81 | "label": "platforms-match", 82 | "template": "docker compose ${configurationFile} down", 83 | "match": "platforms" 84 | }, 85 | { 86 | "template": "docker compose -f docker-compose.yml ${configurationFile} down" 87 | } 88 | ] 89 | } -------------------------------------------------------------------------------- /.github/workflows/clean-cache.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete GitHub Actions cache for repository 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | on: 13 | # Run on Sundays at 10:15 AM UTC 14 | schedule: 15 | - cron: '15 10 * * 0' 16 | # or on button click 17 | workflow_dispatch: 18 | 19 | env: 20 | # Use docker.io for Docker Hub if empty 21 | REGISTRY_HOST: ghcr.io 22 | # Use github.repository (/) 23 | REPOSITORY_NAME: ${{ github.repository }} 24 | 25 | jobs: 26 | cleanup: 27 | permissions: 28 | # `actions:write` permission is required to delete caches 29 | # See also: 30 | # https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id 31 | actions: write 32 | contents: read 33 | runs-on: ubuntu-latest 34 | 35 | steps: 36 | # Checkout a repository, so the workflow can access it 37 | # https://github.com/actions/checkout 38 | - 39 | name: Checkout repository 40 | uses: actions/checkout@v5 41 | 42 | # Necessary if testing locally with 'act' 43 | # https://github.com/nektos/act 44 | 45 | # Install GH CLI (self-hosted runners do not come with it out of the box) 46 | # https://github.com/dev-hanz-ops/install-gh-cli-action 47 | # - 48 | # name: Install GH CLI 49 | # uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 50 | # with: 51 | # gh-cli-version: 2.14.2 # optional 52 | 53 | # Force deletion of caches overriding default cache eviction policy 54 | # https://github.com/actions/cache 55 | - 56 | name: Delete all cache entries 57 | run: | 58 | 59 | gh extension install actions/gh-actions-cache 60 | 61 | while true 62 | do 63 | echo 'Fetching list of cache keys' 64 | CACHE_KEYS=$(gh actions-cache list \ 65 | --limit 100 \ 66 | --sort size \ 67 | --order desc \ 68 | -R ${{ env.REPOSITORY_NAME }} | cut -f 1) 69 | 70 | # Remove newlines, tabs, carriage returns and double quotes 71 | CACHE_KEYS=$(echo $CACHE_KEYS | tr -d "\n\t\r|\"") 72 | # Split string into an array 73 | IFS=', ' read -r -a CACHE_KEYS <<< "$CACHE_KEYS" 74 | # Get length of $CACHE_KEYS array 75 | LEN=${#CACHE_KEYS[@]} 76 | echo "Number of cache entries ${LEN}" 77 | 78 | if [[ $LEN -eq 0 ]]; 79 | then 80 | echo 'Done' 81 | break 82 | fi 83 | 84 | # Setting this to not fail the workflow while deleting cache keys. 85 | set +e 86 | echo 'Deleting caches...' 87 | for cachekey in ${CACHE_KEYS[@]} 88 | do 89 | gh actions-cache delete ${cachekey} --confirm 90 | done 91 | done 92 | env: 93 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | -------------------------------------------------------------------------------- /.github/workflows/lint-files.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint code base 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | on: 13 | # Run on all pushes (except on master/main branch) 14 | push: 15 | branches-ignore: [master, main] 16 | # Remove the line above to run when pushing to master 17 | # PRs on master/main branch 18 | pull_request: 19 | branches: [master, main] 20 | # on button click 21 | workflow_dispatch: 22 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs 23 | inputs: 24 | ref: 25 | # The branch, tag or SHA to checkout for linting. If empty, check out 26 | # the repository that triggered the workflow. 27 | description: | 28 | The branch, tag or SHA to checkout (empty for current branch) 29 | required: false 30 | type: string 31 | # or on calling as reusable workflow 32 | workflow_call: 33 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 34 | inputs: 35 | ref: 36 | # The branch, tag or SHA to checkout for linting. If empty, check out 37 | # the repository that triggered the workflow. 38 | description: | 39 | The branch, tag or SHA to checkout (empty for current branch) 40 | required: false 41 | type: string 42 | 43 | jobs: 44 | lint: 45 | name: Lint code base 46 | runs-on: ubuntu-latest 47 | # Grant status permission for MULTI_STATUS 48 | permissions: 49 | contents: read 50 | packages: read 51 | statuses: write 52 | 53 | steps: 54 | # Checkout a repository, so the workflow can access it 55 | # https://github.com/actions/checkout 56 | - 57 | name: Checkout repository (no ref input) 58 | uses: actions/checkout@v5 59 | if: ${{ inputs.ref == '' }} 60 | with: 61 | # Full git history is needed to get a proper 62 | # list of changed files within `super-linter` 63 | fetch-depth: 0 64 | 65 | - 66 | name: Checkout repository (with ref input) 67 | uses: actions/checkout@v5 68 | if: ${{ inputs.ref != '' }} 69 | with: 70 | ref: '${{ inputs.ref }}' 71 | # Full git history is needed to get a proper 72 | # list of changed files within `super-linter` 73 | fetch-depth: 0 74 | 75 | # Load environment variables before running the GitHub Actions job 76 | # https://github.com/super-linter/super-linter/blob/main/docs/run-linter-locally.md 77 | - 78 | run: cat .github/super-linter.env >> "$GITHUB_ENV" 79 | 80 | # Run Linter against code base 81 | # https://github.com/super-linter/super-linter 82 | - 83 | name: Run Super-Linter on code base 84 | #uses: github/super-linter@v5 85 | uses: super-linter/super-linter/slim@v8 86 | env: 87 | DEFAULT_BRANCH: master 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | -------------------------------------------------------------------------------- /tests/e2e/tests/setup.ts: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // Copyright Universidade Federal do Espirito Santo (Ufes) 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // This program is released under license GNU GPL v3+ license. 18 | // 19 | //======================================================================== 20 | 21 | import { test as setup, expect, Page } from '@playwright/test'; 22 | 23 | const emptyAuthFile = 'playwright/.auth/anonymous.json'; 24 | const sysAuthFile = 'playwright/.auth/system.json'; 25 | 26 | setup('Authenticate as anonymous', async ({ page }) => { 27 | // Perform authentication steps 28 | await page.goto('/boca'); 29 | // Wait until the page receives the cookies 30 | 31 | // End of authentication steps 32 | await page.context().storageState({ path: emptyAuthFile }); 33 | }); 34 | 35 | setup('Authenticate as system', async ({ page }) => { 36 | // Perform authentication steps 37 | await page.goto('/boca'); 38 | await page.locator('input[name="name"]').fill('system'); 39 | await page.locator('input[name="password"]').fill('boca'); 40 | await page.getByRole('button', { name: 'Login' }).click(); 41 | // Wait until the page receives the cookies 42 | 43 | // End of authentication steps 44 | await page.context().storageState({ path: sysAuthFile }); 45 | }); 46 | 47 | const checkLoginPage = async ( 48 | page: Page 49 | ): Promise => { 50 | // Expect title "to contain" a substring 51 | await expect(page).toHaveTitle(/Login/) != undefined; 52 | // Expect page "to contain" a string 53 | await expect(page.getByText('BOCA Login') !== undefined).toBeTruthy(); 54 | // Expect page to have input fields 55 | await expect(page.locator('input[name="name"]') !== undefined) 56 | .toBeTruthy(); 57 | await expect(page.locator('input[name="password"]') !== undefined) 58 | .toBeTruthy(); 59 | // Expect page to have a button 60 | await expect(page.getByRole('button', { name: 'Login' }) !== undefined) 61 | .toBeTruthy(); 62 | } 63 | 64 | const checkContestPage = async ( 65 | page: Page 66 | ): Promise => { 67 | // Expect title "to contain" a substring 68 | await expect(page).toHaveTitle(/System's/) != undefined; 69 | // Expect page to have links 70 | await expect( 71 | page.getByRole('link', { name: 'Contest' }) !== undefined 72 | ).toBeTruthy(); 73 | await expect( 74 | page.getByRole('link', { name: 'Options' }) !== undefined 75 | ).toBeTruthy(); 76 | await expect( 77 | page.getByRole('link', { name: 'Logout' }) !== undefined 78 | ).toBeTruthy(); 79 | }; 80 | 81 | export { 82 | checkLoginPage, 83 | checkContestPage, 84 | } 85 | -------------------------------------------------------------------------------- /boca/src/private/conf.php: -------------------------------------------------------------------------------- 1 | . 17 | //////////////////////////////////////////////////////////////////////////////// 18 | // Last modified 05/aug/2012 by cassio@ime.usp.br 19 | 20 | function globalconf() { 21 | $conf["dbencoding"]= getenv('BOCA_DB_ENCODING') ? getenv('BOCA_DB_ENCODING') : "UTF8"; 22 | $conf["dbclientenc"]= getenv('BOCA_DB_CLIENT_ENCODING') ? getenv('BOCA_DB_CLIENT_ENCODING') : "UTF8"; 23 | $conf['doenc']= getenv('BOCA_DO_ENCRYPTION') ? getenv('BOCA_DO_ENCRYPTION') : false; 24 | 25 | $conf["dblocal"]= getenv('BOCA_IS_DB_LOCAL') ? getenv('BOCA_IS_DB_LOCAL') : "false"; // use unix socket to connect? 26 | $conf["dbhost"]= getenv('BOCA_DB_HOST') ? getenv('BOCA_DB_HOST') : "localhost"; 27 | $conf["dbport"]= getenv('BOCA_DB_PORT') ? getenv('BOCA_DB_PORT') : "5432"; 28 | 29 | $conf["dbname"]= getenv('BOCA_DB_NAME') ? getenv('BOCA_DB_NAME') : "bocadb"; // name of the boca database 30 | 31 | $conf["dbuser"]= getenv('BOCA_DB_USER') ? getenv('BOCA_DB_USER') : "bocauser"; // unprivileged boca user 32 | $conf["dbpass"]= getenv('BOCA_DB_PASSWORD') ? getenv('BOCA_DB_PASSWORD') : "dAm0HAiC"; 33 | 34 | $conf["dbsuperuser"]= getenv('BOCA_DB_SUPER_USER') ? getenv('BOCA_DB_SUPER_USER') : "bocauser"; // privileged boca user 35 | $conf["dbsuperpass"]= getenv('BOCA_DB_SUPER_PASSWORD') ? getenv('BOCA_DB_SUPER_PASSWORD') : "dAm0HAiC"; 36 | 37 | // note that it is fine to use the same user 38 | 39 | // initial password that is used for the user admin -- set it 40 | // to something hard to guess if the server is available 41 | // online even in the moment you are creating the contest 42 | // In this way, the new accounts for system and admin that are 43 | // eventually created come already with the password set to this 44 | // value. It is your task later to update these passwords to 45 | // some other values within the BOCA web interface. 46 | $conf["basepass"]= getenv('BOCA_PASSWORD') ? getenv('BOCA_PASSWORD') : "boca"; 47 | 48 | // secret key to be used in HTTP headers 49 | // you MUST set it with any random large enough sequence 50 | $conf["key"]= getenv('BOCA_KEY') ? getenv('BOCA_KEY') : "GG56KFJtNDBGjJprR6ex"; 51 | 52 | // the following field is used by the autojudging script 53 | // set it with the ip of the computer running the script 54 | // The real purpose of it is only to differentiate between 55 | // autojudges when multiple computers are used as autojudges 56 | $conf["ip"]= getenv('BOCA_JAIL_HOST') ? getenv('BOCA_JAIL_HOST') : 'local'; 57 | 58 | return $conf; 59 | } 60 | ?> 61 | -------------------------------------------------------------------------------- /tests/volumes/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | services: 25 | 26 | # web app 27 | boca-web: 28 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 29 | environment: 30 | # database configuration 31 | # privileged user password 32 | - BOCA_DB_SUPER_PASSWORD=superpass 33 | ports: 34 | - 8000:80 35 | 36 | # online judge 37 | boca-jail: 38 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 39 | privileged: true 40 | 41 | # database 42 | boca-db: 43 | image: postgres:14-alpine 44 | environment: 45 | # database configuration 46 | # privileged user password 47 | - POSTGRES_PASSWORD=superpass 48 | # this optional variable can be used to define another location - 49 | # like a subdirectory - for the database files. The default is 50 | # /var/lib/postgresql/data. If the data volume you're using is a 51 | # filesystem mountpoint (like with GCE persistent disks) or remote 52 | # folder that cannot be chowned to the postgres user (like some 53 | # NFS mounts), Postgres initdb recommends a subdirectory be created 54 | # to contain the data. 55 | - PGDATA=/var/lib/postgresql/data/pgdata 56 | volumes: 57 | # volume mount to container's fs 58 | - boca-data:/var/lib/postgresql/data 59 | 60 | volumes: 61 | 62 | # example 1: when launching boca-docker for the first time Docker will 63 | # create a named volume (boca-data). Onwards every time one brings the 64 | # application down and then rerun it `docker compose` will try to create 65 | # a volume named `boca-data` but it would notice that a volume with that 66 | # name already exists for this compose file. Then it will helpfully mount 67 | # the same volume again. 68 | boca-data: 69 | 70 | # example 2: conversely, one can create and manage a volume outside of the 71 | # docker-compose file. For that, it's necessary to create it first using 72 | # the`docker volume create boca-data` command, declare it in the compose 73 | # file under volumes and set the property `external: true`. Then, when 74 | # launching the application Docker will find out if the volume exists; but 75 | # if it doesn’t, an error will be reported. 76 | # boca-data: 77 | # external: true 78 | -------------------------------------------------------------------------------- /tests/secrets/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | # using extension fields to reduce duplication 25 | x-env: &db-common 26 | # unprivileged boca user 27 | BOCA_DB_USER_FILE: /run/secrets/db_user 28 | BOCA_DB_PASSWORD_FILE: /run/secrets/db_password 29 | BOCA_DB_NAME_FILE: /run/secrets/db_name 30 | 31 | services: 32 | 33 | # web app 34 | boca-web: 35 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 36 | environment: 37 | # database configuration 38 | # privileged boca user 39 | BOCA_DB_SUPER_USER_FILE: /run/secrets/db_super_user 40 | BOCA_DB_SUPER_PASSWORD_FILE: /run/secrets/db_super_password 41 | <<: *db-common 42 | # initial password that is used by the admin user (web app) 43 | # If not set, the default value is 'boca' 44 | BOCA_PASSWORD_FILE: /run/secrets/boca_password 45 | # secret key to be used in HTTP headers 46 | # MUST set it with any random large enough sequence 47 | BOCA_KEY_FILE: /run/secrets/boca_key 48 | secrets: 49 | - db_super_user 50 | - db_super_password 51 | - db_user 52 | - db_password 53 | - db_name 54 | - boca_password 55 | - boca_key 56 | ports: 57 | - 8000:80 58 | 59 | # online judge 60 | boca-jail: 61 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 62 | privileged: true 63 | environment: 64 | # database configuration 65 | <<: *db-common 66 | secrets: 67 | - db_user 68 | - db_password 69 | - db_name 70 | 71 | # database 72 | boca-db: 73 | image: postgres:14-alpine 74 | environment: 75 | # database configuration 76 | # privileged boca user 77 | POSTGRES_USER_FILE: /run/secrets/db_super_user 78 | POSTGRES_PASSWORD_FILE: /run/secrets/db_super_password 79 | secrets: 80 | - db_super_user 81 | - db_super_password 82 | 83 | secrets: 84 | 85 | db_super_user: 86 | file: ./db_super_user.txt 87 | 88 | db_super_password: 89 | file: ./db_super_password.txt 90 | 91 | db_user: 92 | file: ./db_user.txt 93 | 94 | db_password: 95 | file: ./db_password.txt 96 | 97 | db_name: 98 | file: ./db_name.txt 99 | 100 | boca_password: 101 | file: ./boca_password.txt 102 | 103 | boca_key: 104 | file: ./boca_key.txt 105 | -------------------------------------------------------------------------------- /tests/e2e/playwright.config.ts: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // Copyright Universidade Federal do Espirito Santo (Ufes) 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // This program is released under license GNU GPL v3+ license. 18 | // 19 | //======================================================================== 20 | 21 | import { defineConfig, devices } from '@playwright/test'; 22 | 23 | /** 24 | * Read environment variables from file. 25 | * https://github.com/motdotla/dotenv 26 | */ 27 | // require('dotenv').config(); 28 | 29 | /** 30 | * See https://playwright.dev/docs/test-configuration. 31 | */ 32 | export default defineConfig({ 33 | testDir: './tests', 34 | /* Run tests in files in parallel */ 35 | fullyParallel: true, 36 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 37 | forbidOnly: !!process.env.CI, 38 | /* Retry on CI only */ 39 | retries: process.env.CI ? 2 : 0, 40 | /* Opt out of parallel tests on CI. */ 41 | workers: process.env.CI ? 1 : undefined, 42 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 43 | reporter: 'html', 44 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 45 | use: { 46 | /* Base URL to use in actions like `await page.goto('/')`. */ 47 | baseURL: 'http://localhost:8000', 48 | 49 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 50 | trace: 'on-first-retry', 51 | }, 52 | 53 | /* Configure projects for major browsers */ 54 | projects: [ 55 | // Setup project 56 | { name: 'setup', testMatch: /.*\.setup\.ts/ }, 57 | 58 | { 59 | name: 'chromium', 60 | use: { ...devices['Desktop Chrome'], 61 | // Use prepared auth state. 62 | storageState: 'playwright/.auth/system.json', 63 | }, 64 | dependencies: ['setup'], 65 | }, 66 | 67 | { 68 | name: 'firefox', 69 | use: { ...devices['Desktop Firefox'], 70 | // Use prepared auth state. 71 | storageState: 'playwright/.auth/system.json', 72 | }, 73 | dependencies: ['setup'], 74 | }, 75 | 76 | { 77 | name: 'webkit', 78 | use: { ...devices['Desktop Safari'], 79 | // Use prepared auth state. 80 | storageState: 'playwright/.auth/system.json', 81 | }, 82 | dependencies: ['setup'], 83 | }, 84 | 85 | /* Test against mobile viewports. */ 86 | // { 87 | // name: 'Mobile Chrome', 88 | // use: { ...devices['Pixel 5'] }, 89 | // }, 90 | // { 91 | // name: 'Mobile Safari', 92 | // use: { ...devices['iPhone 12'] }, 93 | // }, 94 | 95 | /* Test against branded browsers. */ 96 | // { 97 | // name: 'Microsoft Edge', 98 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 99 | // }, 100 | // { 101 | // name: 'Google Chrome', 102 | // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, 103 | // }, 104 | ], 105 | 106 | /* Run your local dev server before starting the tests */ 107 | // webServer: { 108 | // command: 'npm run start', 109 | // url: 'http://127.0.0.1:3000', 110 | // reuseExistingServer: !process.env.CI, 111 | // }, 112 | }); 113 | -------------------------------------------------------------------------------- /docker/dev/jail/Dockerfile: -------------------------------------------------------------------------------- 1 | #======================================================================== 2 | # Copyright Universidade Federal do Espirito Santo (Ufes) 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | # This program is released under license GNU GPL v3+ license. 18 | # 19 | #======================================================================== 20 | 21 | # Build on base image 22 | ARG BASE_IMAGE=boca-base 23 | # hadolint ignore=DL3006 24 | FROM --platform=${BUILDPLATFORM:-linux/amd64} ${BASE_IMAGE} 25 | 26 | ARG TARGETPLATFORM 27 | ARG BUILDPLATFORM 28 | ARG TARGETOS 29 | ARG TARGETARCH 30 | 31 | LABEL authors="Joao Vitor Alves Fazolo, Rodrigo Laiola Guimaraes" 32 | ENV CREATED_AT 2020-06-26 33 | ENV UPDATED_AT 2023-06-01 34 | 35 | # Redundant but to ensure we are not going to break anything 36 | # https://github.com/cassiopc/boca/tree/master/tools 37 | # hadolint ignore=DL3002 38 | USER root 39 | 40 | # Install dependencies 41 | # hadolint ignore=DL3008,DL3015 42 | RUN apt-get -y update \ 43 | && apt-get -y install \ 44 | # Package: boca-autojudge 45 | # https://github.com/cassiopc/boca/blob/master/debian/control 46 | # Depends: 47 | build-essential \ 48 | debootstrap \ 49 | makepasswd \ 50 | quotatool \ 51 | schroot \ 52 | && rm -rf /var/lib/apt/lists/* 53 | 54 | # https://bugs.launchpad.net/ubuntu/+source/ca-certificates-java/+bug/2019908 55 | COPY boca/tools/boca-createjail /var/www/boca/tools/ 56 | COPY boca/tools/safeexec.c /var/www/boca/tools/ 57 | 58 | WORKDIR /var/www/boca 59 | RUN \ 60 | # install-bocaautojudge 61 | # https://github.com/cassiopc/boca/blob/master/Makefile 62 | mkdir -p /usr/sbin/ \ 63 | && mkdir -p /usr/bin/ \ 64 | && mkdir -p /etc/ \ 65 | && gcc tools/safeexec.c -o tools/safeexec \ 66 | && install tools/safeexec /usr/bin/safeexec \ 67 | && install tools/boca-createjail /usr/sbin/boca-createjail \ 68 | && install tools/boca-autojudge.sh /usr/sbin/boca-autojudge \ 69 | && chmod 4555 /usr/bin/safeexec \ 70 | && chmod 700 /usr/sbin/boca-createjail \ 71 | && chmod 700 /usr/sbin/boca-autojudge 72 | # boca-autojudge.postinst 73 | # https://github.com/cassiopc/boca/blob/master/debian/boca-autojudge.postinst 74 | # Done before 75 | # && chmod 4555 /usr/bin/safeexec \ 76 | # && chmod 700 /usr/sbin/boca-createjail \ 77 | # && chmod 700 /usr/sbin/boca-autojudge 78 | 79 | RUN boca-createjail || true 80 | 81 | # OpenJDK 17 installation with chroot does not work as expected 82 | # Workaround: Install OpenJDK 17 manually 83 | # https://www.linuxcapable.com/how-to-install-openjdk-17-on-ubuntu-linux/ 84 | # hadolint ignore=DL3003 85 | RUN wget --progress=dot:giga https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz \ 86 | && tar -xvf openjdk-17* \ 87 | && cd jdk-17* \ 88 | && cp -rf bin/* /home/bocajail/usr/bin/ \ 89 | && cp -rf lib/* /home/bocajail/usr/lib/ \ 90 | && cd .. \ 91 | && rm -rf jdk-17* \ 92 | && rm openjdk-17* 93 | 94 | COPY --chmod=755 docker/dev/jail/init.sh / 95 | 96 | # Add HEALTHCHECK instruction to the container image 97 | HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=5 \ 98 | CMD ps ax | grep -v grep | grep php | grep autojudging.php > /dev/null || exit 1 99 | 100 | # Use exec format to run program directly as pid 1 101 | # https://www.padok.fr/en/blog/docker-processes-container 102 | ENTRYPOINT ["/init.sh"] 103 | -------------------------------------------------------------------------------- /docker/dev/web/Dockerfile: -------------------------------------------------------------------------------- 1 | #======================================================================== 2 | # Copyright Universidade Federal do Espirito Santo (Ufes) 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | # This program is released under license GNU GPL v3+ license. 18 | # 19 | #======================================================================== 20 | 21 | # Build on base image 22 | ARG BASE_IMAGE=boca-base 23 | # hadolint ignore=DL3006 24 | FROM --platform=${BUILDPLATFORM:-linux/amd64} ${BASE_IMAGE} 25 | 26 | ARG TARGETPLATFORM 27 | ARG BUILDPLATFORM 28 | ARG TARGETOS 29 | ARG TARGETARCH 30 | 31 | LABEL authors="Joao Vitor Alves Fazolo, Rodrigo Laiola Guimaraes" 32 | ENV CREATED_AT 2020-06-26 33 | ENV UPDATED_AT 2023-06-01 34 | 35 | # Apache settings 36 | ENV APACHE_RUN_USER www-data 37 | ENV APACHE_RUN_GROUP www-data 38 | ENV APACHE_PID_FILE /var/run/apache2/apache2.pid 39 | ENV APACHE_DIR /etc/apache2 40 | ENV APACHE_RUN_DIR /var/run/apache2 41 | ENV APACHE_LOCK_DIR /var/lock/apache2 42 | ENV APACHE_LOG_DIR /var/log/apache2 43 | 44 | # Redundant but to ensure we are not going to break anything 45 | # https://github.com/cassiopc/boca/tree/master/doc 46 | USER root 47 | 48 | # Install dependencies 49 | # hadolint ignore=DL3008,DL3015,DL4006 50 | RUN apt-get -y update \ 51 | && echo N | apt-get -y install \ 52 | # Package: boca-web 53 | # https://github.com/cassiopc/boca/blob/master/debian/control 54 | # Depends: 55 | apache2 \ 56 | libapache2-mod-php \ 57 | php \ 58 | php-fpm \ 59 | python3-matplotlib \ 60 | && rm -rf /var/lib/apt/lists/* 61 | 62 | RUN mkdir -p $APACHE_LOCK_DIR \ 63 | && mkdir -p $APACHE_LOG_DIR \ 64 | && mkdir -p $APACHE_RUN_DIR \ 65 | && echo "ServerName localhost" >> $APACHE_DIR/apache2.conf \ 66 | && ln -sf /proc/self/fd/1 $APACHE_LOG_DIR/access.log \ 67 | && ln -sf /proc/self/fd/1 $APACHE_LOG_DIR/error.log \ 68 | && chown -R "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOCK_DIR" \ 69 | && chown -R "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR" \ 70 | && chown -R "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_RUN_DIR" 71 | 72 | WORKDIR /var/www/boca 73 | RUN \ 74 | # install-bocaapache 75 | # https://github.com/cassiopc/boca/blob/master/Makefile 76 | mkdir -p $APACHE_DIR/sites-available/ \ 77 | && cp tools/000-boca.conf $APACHE_DIR/sites-available/000-boca.conf \ 78 | # install-scripts 79 | # https://github.com/cassiopc/boca/blob/master/Makefile 80 | && mkdir -p /usr/sbin/ \ 81 | && install tools/dump.sh /usr/sbin/boca-dump \ 82 | && chmod 700 /usr/sbin/boca-dump \ 83 | # boca-web.postinst 84 | # https://github.com/cassiopc/boca/blob/master/debian/boca-web.postinst 85 | && chown -R "$APACHE_RUN_USER:$APACHE_RUN_GROUP" /var/www/boca \ 86 | && chmod -R go-rwx /var/www/boca/src/private \ 87 | && a2enmod ssl \ 88 | # Necessary SSLCertificateKeyFile 89 | # && a2ensite default-ssl \ 90 | && mkdir -p $APACHE_DIR/sites-enabled \ 91 | && cp tools/000-boca.conf $APACHE_DIR/sites-enabled/000-boca.conf \ 92 | && a2enmod socache_shmcb \ 93 | && a2enmod proxy_fcgi setenvif \ 94 | # && a2enconf php8.1-fpm \ 95 | && apache2ctl configtest 96 | 97 | COPY --chmod=755 --chown="$APACHE_RUN_USER:$APACHE_RUN_GROUP" docker/dev/web/init.sh / 98 | 99 | # Create a non-root user for the container 100 | USER $APACHE_RUN_USER 101 | 102 | # Add HEALTHCHECK instruction to the container image 103 | HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=5 \ 104 | CMD wget --no-verbose --tries=1 --spider http://localhost:80/boca/ || exit 1 105 | 106 | EXPOSE 80 107 | 108 | # Use exec format to run program directly as pid 1 109 | # https://www.padok.fr/en/blog/docker-processes-container 110 | ENTRYPOINT ["/init.sh"] 111 | -------------------------------------------------------------------------------- /docker/dev/web/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | # usage: file_env VAR [DEFAULT] 23 | # ie: file_env 'XYZ_DB_PASSWORD' 'example' 24 | # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of 25 | # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) 26 | # Ref: https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh 27 | file_env() { 28 | local var="$1" 29 | local fileVar="${var}_FILE" 30 | local def="${2:-}" 31 | if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; 32 | then 33 | echo >&2 "error: both $var and $fileVar are set (but are exclusive)" 34 | exit 1 35 | fi 36 | local val="$def" 37 | if [ "${!var:-}" ]; 38 | then 39 | val="${!var}" 40 | elif [ "${!fileVar:-}" ]; 41 | then 42 | val="$(< "${!fileVar}")" 43 | fi 44 | export "$var"="$val" 45 | unset "$fileVar" 46 | } 47 | 48 | # Loads various settings that are used elsewhere in the script 49 | # This should be called before any other functions 50 | # Ref: https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh 51 | docker_setup_env() { 52 | # If variable is not set or null, use default value. 53 | export BOCA_DB_HOST="${BOCA_DB_HOST:-boca-db}" 54 | 55 | file_env 'BOCA_DB_SUPER_USER' 'postgres' 56 | file_env 'BOCA_DB_SUPER_PASSWORD' 57 | file_env 'BOCA_DB_USER' 'bocauser' 58 | file_env 'BOCA_DB_PASSWORD' 'dAm0HAiC' 59 | file_env 'BOCA_DB_NAME' 'bocadb' 60 | file_env 'BOCA_PASSWORD' 61 | file_env 'BOCA_KEY' 62 | } 63 | 64 | docker_setup_env 65 | 66 | until PGPASSWORD=$BOCA_DB_SUPER_PASSWORD \ 67 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_SUPER_USER" -c '\q'; 68 | do 69 | >&2 echo "PostgreSQL server is unavailable - sleeping" 70 | sleep 1 71 | done 72 | 73 | >&2 echo "PostgreSQL server is up - executing command" 74 | 75 | # https://stackoverflow.com/questions/14549270/check-if-database-exists-in-postgresql-using-shell 76 | if ! PGPASSWORD=$BOCA_DB_PASSWORD \ 77 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_USER" -lqt | \ 78 | cut -d \| -f 1 | grep -qw "$BOCA_DB_NAME"; 79 | then 80 | echo "Create unprivileged user" 81 | PGPASSWORD=$BOCA_DB_SUPER_PASSWORD \ 82 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_SUPER_USER" -t -c \ 83 | "DROP USER IF EXISTS $BOCA_DB_USER;\ 84 | CREATE USER $BOCA_DB_USER WITH PASSWORD '$BOCA_DB_PASSWORD';" 85 | 86 | echo "Grant privileges to unprivileged user" 87 | PGPASSWORD=$BOCA_DB_SUPER_PASSWORD \ 88 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_SUPER_USER" -d "$BOCA_DB_NAME" \ 89 | -t -c \ 90 | "GRANT ALL PRIVILEGES ON DATABASE $BOCA_DB_NAME TO $BOCA_DB_USER; \ 91 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public \ 92 | TO $BOCA_DB_USER; \ 93 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public \ 94 | TO $BOCA_DB_USER; \ 95 | ALTER DATABASE $BOCA_DB_NAME SET lo_compat_privileges=on; \ 96 | GRANT USAGE ON SCHEMA public TO $BOCA_DB_USER;" 97 | 98 | # Create database only if it does not exist 99 | if ! PGPASSWORD=$BOCA_DB_PASSWORD \ 100 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_USER" -lqt | \ 101 | cut -d \| -f 1 | grep -qw "$BOCA_DB_NAME"; 102 | then 103 | echo "Create database" 104 | # https://stackoverflow.com/questions/5891888/piping-data-into-command-line-php 105 | cd /var/www/boca/src && echo "YES" | php private/createdb.php 106 | 107 | echo "Grant privileges to unprivileged user" 108 | PGPASSWORD=$BOCA_DB_SUPER_PASSWORD \ 109 | psql -h "$BOCA_DB_HOST" -U "$BOCA_DB_SUPER_USER" -t -c \ 110 | "GRANT ALL PRIVILEGES ON DATABASE $BOCA_DB_NAME TO $BOCA_DB_USER;" 111 | else 112 | echo "Database already exists" 113 | fi 114 | else 115 | echo "Database and unprivileged user already exist" 116 | fi 117 | 118 | # Use exec format to run program directly as pid 1 119 | # https://www.padok.fr/en/blog/docker-processes-container 120 | exec apache2 -DFOREGROUND 121 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | In the interest of fostering an open and welcoming environment, we as 4 | contributors and maintainers pledge to make participation in our project and our 5 | community a harassment-free experience for everyone, regardless of age, body 6 | size, disability, ethnicity, gender identity and expression, level of 7 | experience, nationality, personal appearance, race, religion, or sexual identity 8 | and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to creating a positive environment include: 13 | 14 | * Using welcoming and inclusive language. 15 | * Being respectful of differing viewpoints and experiences. 16 | * Gracefully accepting constructive criticism. 17 | * Focusing on what is best for the community. 18 | * Showing empathy towards other community members. 19 | 20 | Examples of unacceptable behavior by participants include: 21 | 22 | * The use of sexualized language or imagery and unwelcome sexual attention or 23 | advances. 24 | * Trolling, insulting/derogatory comments, and personal or political attacks. 25 | * Public or private harassment. 26 | * Publishing others' private information, such as a physical or electronic 27 | address, without explicit permission. 28 | * Conduct which could reasonably be considered inappropriate for the forum in 29 | which it occurs. 30 | 31 | All project forums and spaces are meant for professional interactions, and any behavior which could reasonably be considered inappropriate in a professional setting is unacceptable. 32 | 33 | ## Our Responsibilities 34 | 35 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 36 | 37 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 38 | 39 | ## Scope 40 | 41 | The Code of Conduct applies within project spaces and in public spaces whenever an individual is representing this project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed or de facto representative at an online or offline event. 42 | 43 | ## Conflict Resolution 44 | 45 | Conflicts in an open source project can take many forms, from someone having a bad day and using harsh and hurtful language in the issue queue, to more serious instances such as sexist/racist statements or threats of violence, and everything in between. 46 | 47 | If the behavior is threatening or harassing, or for other reasons requires immediate escalation, please see below. 48 | 49 | However, for the vast majority of issues, we aim to empower individuals to first resolve conflicts themselves, asking for help when needed, and only after that fails to escalate further. This approach gives people more control over the outcome of their dispute. 50 | 51 | If you are experiencing or witnessing conflict, we ask you to use the following escalation strategy to address the conflict: 52 | 53 | 1. Address the perceived conflict directly with those involved, preferably in a 54 | real-time medium. 55 | 2. If this fails, get a third party (e.g. a mutual friend, and/or someone with 56 | background on the issue, but not involved in the conflict) to intercede. 57 | 3. If you are still unable to resolve the conflict, and you believe it rises to 58 | harassment or another code of conduct violation, report it. 59 | 60 | ## Reporting Violations 61 | 62 | Violations of the Code of Conduct can be reported to the project maintainers. They will determine whether the Code of Conduct was violated, and will issue an appropriate sanction, possibly including a written warning or expulsion from the project, project sponsored spaces, or project forums. 63 | We ask that you make a good-faith effort to resolve your conflict via the conflict resolution policy before submitting a report. 64 | 65 | Violations of the Code of Conduct can occur in any setting, even those unrelated to the project. We will only consider complaints about conduct that has occurred within one year of the report. 66 | 67 | ## Enforcement 68 | 69 | If the project maintainers receive a report alleging a violation of the Code of Conduct, they will notify the accused of the report, and provide them an opportunity to discuss the report before a sanction is issued. The maintainers will do their utmost to keep the reporter anonymous. 70 | If the act is ongoing (such as someone engaging in harassment), or involves a threat to anyone's safety (e.g. threats of violence), the project maintainers may issue sanctions without notice. 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at [https://contributor-covenant.org/version/1/4](https://contributor-covenant.org/version/1/4), and includes some aspects of the Geek Feminism Code of Conduct and the Drupal Code of Conduct. 75 | -------------------------------------------------------------------------------- /boca/tools/boca-createjail: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | homejail=/home/bocajail 3 | [ "$1" != "" ] && homejail=$1 4 | echo "=================================================================================" 5 | echo "============= CREATING $homejail (this might take some time) ===============" 6 | echo "=================================================================================" 7 | for i in setquota ln id chown chmod dirname useradd mkdir cp rm mv apt-get dpkg uname debootstrap schroot; do 8 | p=`which $i` 9 | if [ -x "$p" ]; then 10 | echo -n "" 11 | else 12 | echo command "$i" not found 13 | exit 1 14 | fi 15 | done 16 | if [ "`id -u`" != "0" ]; then 17 | echo "Must be run as root" 18 | exit 1 19 | fi 20 | if [ ! -r /etc/lsb-release ]; then 21 | echo "File /etc/lsb-release not found. Is this a ubuntu or debian-like distro?" 22 | echo "If so, execute the command" 23 | echo "" 24 | echo "DISTRIB_CODENAME=WXYZ > /etc/lsb-release" 25 | echo "" 26 | echo "to save the release name to that file (replace WXYZ with your distro codename)" 27 | exit 1 28 | fi 29 | . /etc/lsb-release 30 | if [ -d /bocajail/ ]; then 31 | echo "You seem to have already a /bocajail installed" 32 | echo "If you want to reinstall, remove it first (e.g. rm /bocajail) and then run /etc/icpc/createbocajail.sh" 33 | exit 1 34 | fi 35 | 36 | if [ -f $homejail/proc/cpuinfo ]; then 37 | echo "You seem to have already installed /bocajail and the /bocajail/proc seems to be mounted" 38 | chroot $homejail umount /sys >/dev/nul 2>/dev/null 39 | chroot $homejail umount /proc >/dev/nul 2>/dev/null 40 | echo "Please reboot the system to remove such mounted point" 41 | exit 1 42 | fi 43 | 44 | id -u bocajail >/dev/null 2>/dev/null 45 | if [ $? != 0 ]; then 46 | useradd -m -s /bin/bash -d $homejail -g users bocajail 47 | cat < /var/lib/AccountsService/users/bocajail 48 | [User] 49 | SystemAccount=true 50 | EOF 51 | sleep 1 52 | else 53 | echo "user bocajail already exists" 54 | echo "if you want to proceed, first remove it (e.g. userdel bocajail) and then run /etc/icpc/createbocajail.sh" 55 | exit 1 56 | fi 57 | setquota -u bocajail 0 500000 0 10000 -a 58 | 59 | rm -rf /bocajail 60 | mkdir -p $homejail/tmp 61 | chmod 1777 $homejail/tmp 62 | ln -s $homejail /bocajail 63 | #for i in usr lib var bin sbin etc dev; do 64 | # [ -d $homejail/$i ] && rm -rf $homejail/$i 65 | # cp -ar /$i $homejail 66 | #done 67 | #rm -rf $homejail/var/lib/postgres* 68 | #rm -rf $homejail/var/www/* 69 | #mkdir -p $homejail/proc 70 | #mkdir -p $homejail/sys 71 | uname -m | grep -q 64 72 | if [ $? == 0 ]; then 73 | archt=amd64 74 | else 75 | archt=i386 76 | fi 77 | 78 | cat < /etc/schroot/chroot.d/bocajail.conf 79 | [bocajail] 80 | description=Jail 81 | directory=$homejail 82 | root-users=root 83 | type=directory 84 | users=bocajail,nobody,root 85 | FIM 86 | 87 | #debootstrap --arch $archt $DISTRIB_CODENAME $homejail 88 | debootstrap $DISTRIB_CODENAME $homejail 89 | if [ $? != 0 ]; then 90 | echo "bocajail failed to debootstrap" 91 | exit 1 92 | else 93 | schroot -l | grep -q bocajail 94 | if [ $? == 0 ]; then 95 | echo "bocajail successfully installed at $homejail" 96 | else 97 | echo "*** some error has caused bocajail not to install properly -- I will try it again with different parameters" 98 | echo "location=$homejail" >> /etc/schroot/chroot.d/bocajail.conf 99 | debootstrap $DISTRIB_CODENAME $homejail 100 | schroot -l | grep -q bocajail 101 | if [ $? == 0 ]; then 102 | echo "*** bocajail successfully installed at $homejail" 103 | else 104 | echo "*** bocajail failed to install" 105 | exit 1 106 | fi 107 | fi 108 | fi 109 | 110 | echo "*** Populating $homejail" 111 | cat < /home/bocajail/tmp/populate.sh 112 | #!/bin/bash 113 | mount -t proc proc /proc 114 | 115 | echo "LC_ALL=en_US.UTF-8" > /etc/default/locale 116 | echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen 117 | /usr/sbin/locale-gen 118 | /usr/sbin/update-locale 119 | apt-get -y update 120 | apt-get -y install software-properties-common 121 | add-apt-repository -y ppa:icpc-latam/maratona-linux 122 | apt-get -y update 123 | apt-get -y upgrade 124 | apt-get -y install maratona-linguagens --no-install-recommends --allow-unauthenticated 125 | apt-get -y clean 126 | 127 | # OpenJDK 17 installation with chroot does not work as expected 128 | # Workaround: install OpenJDK 11 instead of 17 (security issue) 129 | # ATTENTION: this method no longer works. Keep it here for reference. 130 | # https://bugs.launchpad.net/ubuntu/+source/ca-certificates-java/+bug/2019908 131 | # apt-get -y install --no-install-recommends \ 132 | # build-essential \ 133 | # debconf \ 134 | # openjdk-11-jdk \ 135 | # gdb \ 136 | # python3 \ 137 | # python-is-python3 \ 138 | # pyflakes3 \ 139 | # pylint \ 140 | # python3-distutils \ 141 | # unzip \ 142 | # wget \ 143 | # valgrind \ 144 | # && rm -rf /var/lib/apt/lists/* 145 | 146 | umount /proc 147 | EOF 148 | mkdir -p /bocajail/usr/bin 149 | [ -x /usr/bin/safeexec ] && cp -a /usr/bin/safeexec /bocajail/usr/bin/ 150 | cp -f /etc/apt/sources.list $homejail/etc/apt/ 151 | chmod 755 /home/bocajail/tmp/populate.sh 152 | 153 | export LC_ALL=en_US.UTF-8 154 | cd / ; chroot $homejail /tmp/populate.sh 155 | -------------------------------------------------------------------------------- /tests/env/README.md: -------------------------------------------------------------------------------- 1 | # Environment variables for boca-docker 2 | 3 | The _boca-docker_ application uses several environment variables which are easy to miss. The variables required are `POSTGRES_PASSWORD` (`boca-db` service) and `BOCA_DB_SUPER_PASSWORD` (`boca-web` service), the rest are optional. 4 | 5 | > **NOTE:** DO NOT set the optional `POSTGRES_DB` environment variable in the `boca-db` service. Find out the reasons for that [here](https://github.com/joaofazolo/boca-docker/issues/17). 6 | > 7 | > **NOTE:** From release `1.1.0` some environment variables received a `BOCA_` prefix. These are: `BOCA_DB_HOST`, `BOCA_DB_SUPER_USER`, `BOCA_DB_SUPER_PASSWORD`, `BOCA_DB_USER`, `BOCA_DB_PASSWORD`, and `BOCA_DB_NAME`. 8 | 9 | **`BOCA_DB_HOST`** 10 | 11 | This optional environment variable, which can be set in the `boca-web` and `boca-jail` services, defines the address/host name of the PostgreSQL server. This is useful if you are connecting to an external server or a docker container named something other than _boca-db_ (default). 12 | 13 | **`BOCA_DB_SUPER_USER`** 14 | 15 | This optional environment variable is used in conjunction with `BOCA_DB_SUPER_PASSWORD` (`boca-web` service) to manage the database. This environment variable must be the superuser for PostgreSQL. If it is not specified, then the default user of _postgres_ will be used. 16 | 17 | > **NOTE:** If specified, this parameter and the `POSTGRES_USER` variable (`boca-db` service) must be set with the same value. 18 | 19 | **`BOCA_DB_SUPER_PASSWORD`** 20 | 21 | This environment variable is required for the `boca-web` service (web app) to manage the database (`boca-db` service). It must not be empty or undefined. This environment variable must be the superuser password for PostgreSQL. The default superuser is defined by the `BOCA_DB_SUPER_USER` environment variable. 22 | 23 | > **NOTE:** It must have the same value of the `POSTGRES_PASSWORD` variable (`boca-db` service). 24 | 25 | **`BOCA_DB_USER`** and **`BOCA_DB_PASSWORD`** 26 | 27 | Optional in the `boca-web` and `boca-jail` services, these variables are used in conjunction to change the credentials of the unprivileged database user in the web app and online judge, respectively, that can manage the PostgreSQL database but not alter its schema. If they are not specified, then the default values will be used. 28 | 29 | **`BOCA_DB_NAME`** 30 | 31 | This optional environment variable can be used to define a different name for the default database that is created when the `boca-web` service is first started. If it is not specified, then a default value will be used. 32 | 33 | **`BOCA_PASSWORD`** 34 | 35 | This optional environment variable can be specified in the `boca-web` service (web app) to define the initial password for the system and admin users. If not set, the default value is used (_boca_). These passwords can be individually updated later on via the web interface. 36 | 37 | **`BOCA_KEY`** 38 | 39 | This optional environment variable can be specified in the `boca-web` service to set the secret key in HTTP headers. It must be any random large sequence of characters. If it is undefined, then the default value will be used. 40 | 41 | **`POSTGRES_USER`** 42 | 43 | This optional environment variable is used in conjunction with `POSTGRES_PASSWORD` (`boca-db` service) to set a user and its password. This variable will create the specified user with superuser power and a database with the same name. If it is not specified, then the default user of _postgres_ will be used. 44 | 45 | > **NOTE:** Be aware that if this parameter is specified, the `BOCA_DB_SUPER_USER` variable (`boca-web` service) must be defined with the same value. 46 | 47 | **`POSTGRES_PASSWORD`** 48 | 49 | This environment variable is required to setup the `boca-db` service (PostgreSQL database). It must not be empty or undefined. This environment variable sets the superuser password for PostgreSQL. The default superuser is defined by the `POSTGRES_USER` environment variable. 50 | 51 | > **NOTE:** The `BOCA_DB_SUPER_PASSWORD` variable (`boca-web` service) must be set with the same value. 52 | 53 | **`BOCA_WEB_HOST`** 54 | 55 | This optional environment variable, which can be set in the `boca-jail` service, defines the address/host name of the web application. Although `boca-jail` does not interact with the `boca-web` service directly, the original configuration keeps this reference (not certain why!). Nevertheless, if set, it must be named after the web app service (default: _boca-web_). 56 | 57 | ## Example 58 | 59 | This demo shows different approaches on how to pass environment variables to keep the application secure, flexible and organized. 60 | 61 | * Launch the application: 62 | 63 | **... via docker compose** 64 | 65 | ```sh 66 | docker compose --env-file=tests/env/.env -f tests/env/docker-compose.yml up -d 67 | ``` 68 | 69 | **... or docker stack deploy** 70 | 71 | ```sh 72 | export $(grep -v '^#' tests/env/.env | xargs) && docker stack deploy --compose-file tests/env/docker-compose.yml boca-stack-env 73 | ``` 74 | 75 | * Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). To login with the _system_ user, use as password the value of the `BOCA_PASSWORD` variable set in the `tests/env/docker-compose.yml` file (sensitive information stored on GitHub for demo purposes only). 76 | 77 | * To bring it down: 78 | 79 | **... via docker compose** 80 | 81 | ```sh 82 | docker compose -f tests/env/docker-compose.yml down 83 | ``` 84 | 85 | **... or docker stack rm** 86 | 87 | ```sh 88 | docker stack rm boca-stack-env 89 | ``` 90 | -------------------------------------------------------------------------------- /.github/workflows/scan-images.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security scan of multi-platform Docker images on ghcr.io 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | # Reusing workflows 13 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 14 | 15 | on: 16 | # Run as reusable workflow 17 | workflow_call: 18 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 19 | inputs: 20 | images: 21 | description: 22 | Matrix of images to scan (i.e., boca-base, boca-web and boca-jail) 23 | required: true 24 | type: string 25 | parents: 26 | description: Matrix of parent/base images used in builds 27 | required: true 28 | type: string 29 | platforms: 30 | description: Matrix of target os/platforms (multi-arch) 31 | required: true 32 | type: string 33 | tag: 34 | description: Image tag 35 | required: true 36 | type: string 37 | 38 | env: 39 | # Use docker.io for Docker Hub if empty 40 | REGISTRY_HOST: ghcr.io 41 | # Use github.repository (/) 42 | REPOSITORY_NAME: ${{ github.repository }} 43 | 44 | jobs: 45 | scan: 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | # for github/codeql-action/upload-sarif to upload SARIF results 50 | security-events: write 51 | # only required for a private repository by 52 | # github/codeql-action/upload-sarif to get the Action run status 53 | actions: read 54 | strategy: 55 | # If is set to true (default), GitHub will cancel all in-progress and 56 | # queued jobs in the matrix if any job in the matrix fails. 57 | fail-fast: false 58 | matrix: 59 | image: ${{ fromJSON(inputs.images) }} 60 | parent: ${{ fromJSON(inputs.parents) }} 61 | platform: ${{ fromJSON(inputs.platforms) }} 62 | 63 | steps: 64 | # Setting output parameters between steps, jobs and/or workflows 65 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions 66 | - 67 | name: Write variables to GITHUB_OUTPUT 68 | id: setup 69 | run: | 70 | 71 | # Set OS release 72 | PARENT="${{ matrix.parent }}" 73 | # Replace 'ubuntu:' with '' 74 | RELEASE=${PARENT//ubuntu:/} 75 | echo "$RELEASE" 76 | echo "release_name=$RELEASE" >> "$GITHUB_OUTPUT" 77 | 78 | # Set tags 79 | TAGS="${{ inputs.tag }}" 80 | echo "$TAGS" 81 | # Split string into an array 82 | IFS=', ' read -r -a TAGS <<< "$TAGS" 83 | 84 | IMG="${{ env.REGISTRY_HOST }}/${{ env.REPOSITORY_NAME }}/" 85 | IMG+="${{ matrix.image }}:${TAGS[0]}-$RELEASE" 86 | echo "$IMG" 87 | echo "image_name=$IMG" >> "$GITHUB_OUTPUT" 88 | 89 | # Set architecture 90 | PLATFORM="${{ matrix.platform }}" 91 | # Replace 'linux/' with '' and '/' with '-' 92 | ARCH=${PLATFORM//linux\//} 93 | ARCH=${ARCH//\//-} 94 | echo "$ARCH" 95 | echo "arch_name=$ARCH" >> "$GITHUB_OUTPUT" 96 | 97 | REPORT="trivy-${{ matrix.image }}-$RELEASE-$ARCH-image-results" 98 | echo "$REPORT" 99 | echo "report_name=$REPORT" >> "$GITHUB_OUTPUT" 100 | 101 | # Create and boot a builder that can be used in the following steps of 102 | # the workflow 103 | # https://github.com/docker/setup-buildx-action 104 | - 105 | name: Set up Docker Buildx 106 | uses: docker/setup-buildx-action@v3 107 | 108 | # Login to a Docker registry (except on PR) 109 | # https://github.com/docker/login-action 110 | - 111 | name: Login to GitHub Container Registry 112 | uses: docker/login-action@v3 113 | with: 114 | registry: ${{ env.REGISTRY_HOST }} 115 | username: ${{ github.actor }} 116 | password: ${{ secrets.GITHUB_TOKEN }} 117 | 118 | - 119 | name: Pull Docker image from ghcr.io 120 | run: | 121 | 122 | IMG="${{ steps.setup.outputs.image_name }}" 123 | docker pull --platform ${{ matrix.platform }} ${IMG} 124 | docker image ls -a 125 | 126 | # Run Trivy vulnerability scanner on image 127 | # https://github.com/aquasecurity/trivy-action 128 | - 129 | name: Run Trivy vulnerability scanner on image 130 | uses: aquasecurity/trivy-action@master 131 | with: 132 | image-ref: '${{ steps.setup.outputs.image_name }}' 133 | format: 'sarif' 134 | output: '${{ steps.setup.outputs.report_name }}.sarif' 135 | ignore-unfixed: true 136 | vuln-type: 'os,library' 137 | severity: 'CRITICAL,HIGH' 138 | timeout: 10m 139 | 140 | # Upload results to GitHub so they can be displayed in the repository' 141 | # security tab 142 | # https://github.com/github/codeql-action 143 | - 144 | name: Upload Trivy image scan results to GitHub Security tab 145 | uses: github/codeql-action/upload-sarif@v4 146 | with: 147 | sarif_file: '${{ steps.setup.outputs.report_name }}.sarif' 148 | category: 'image' 149 | -------------------------------------------------------------------------------- /.github/workflows/read-matrix.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Read build matrix from file 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | # Reusing workflows 13 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 14 | 15 | on: 16 | # Run as reusable workflow 17 | workflow_call: 18 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 19 | inputs: 20 | matrix-path: 21 | required: true 22 | type: string 23 | # Map the workflow outputs to job outputs 24 | outputs: 25 | release: 26 | description: Matrix of supported image tags/releases (e.g., 1.0.0) 27 | value: ${{ jobs.get-matrix.outputs.release }} 28 | default_release: 29 | description: Default image tag/release 30 | value: ${{ jobs.get-matrix.outputs.default_release }} 31 | parent: 32 | description: Matrix of parent/base images used in builds 33 | value: ${{ jobs.get-matrix.outputs.parent }} 34 | default_parent: 35 | description: Default parent/base image 36 | value: ${{ jobs.get-matrix.outputs.default_parent }} 37 | platform: 38 | description: Matrix of os/platform-specific target builds (multi-arch) 39 | value: ${{ jobs.get-matrix.outputs.platform }} 40 | repository: 41 | description: Git URL of the BOCA repository to clone from 42 | value: ${{ jobs.get-matrix.outputs.repository }} 43 | ref: 44 | description: 45 | The branch, tag or SHA to point to in the cloned BOCA repository 46 | value: ${{ jobs.get-matrix.outputs.ref }} 47 | 48 | jobs: 49 | get-matrix: 50 | runs-on: ubuntu-latest 51 | outputs: 52 | release: ${{ steps.parse-matrix.outputs.release }} 53 | default_release: ${{ steps.parse-matrix.outputs.default_release }} 54 | parent: ${{ steps.parse-matrix.outputs.parent }} 55 | default_parent: ${{ steps.parse-matrix.outputs.default_parent }} 56 | platform: ${{ steps.parse-matrix.outputs.platform }} 57 | repository: ${{ steps.parse-matrix.outputs.repository }} 58 | ref: ${{ steps.parse-matrix.outputs.ref }} 59 | 60 | steps: 61 | # Checkout a repository, so the workflow can access it 62 | # https://github.com/actions/checkout 63 | - 64 | name: Checkout repository 65 | uses: actions/checkout@v5 66 | 67 | # Setting output parameters between steps, jobs and/or workflows 68 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter 69 | - 70 | id: set-json 71 | name: Get build matrix from file 72 | run: | 73 | 74 | # Specify path of matrix file 75 | JSON=${{ inputs.matrix-path }} 76 | echo "${JSON}" 77 | 78 | # Passing filename between jobs and/or workflows 79 | echo "json=${JSON}" >> "$GITHUB_OUTPUT" 80 | 81 | - 82 | id: parse-matrix 83 | name: Set build matrix of releases, parent images and os/architectures 84 | run: | 85 | 86 | # Read matrix from file and set variables 87 | CONTENT=`cat ${{ steps.set-json.outputs.json }}` 88 | echo "$CONTENT" 89 | RELEASES=$(echo $CONTENT | jq ".release") 90 | echo "$RELEASES" 91 | PARENTS=$(echo $CONTENT | jq ".parent") 92 | echo "$PARENTS" 93 | PLATFORMS=$(echo $CONTENT | jq ".platform") 94 | echo "$PLATFORMS" 95 | REPOSITORY=$(echo $CONTENT | jq ".repository") 96 | echo "$REPOSITORY" 97 | REF=$(echo $CONTENT | jq ".ref") 98 | echo "$REF" 99 | 100 | # Remove square brackets and double quotes 101 | DEFAULT_RELEASE=$(echo $RELEASES | tr -d "[]|\"") 102 | DEFAULT_PARENT=$(echo $PARENTS | tr -d "[]|\"") 103 | # Split string into an array 104 | IFS=', ' read -r -a DEFAULT_RELEASE <<< "$DEFAULT_RELEASE" 105 | IFS=', ' read -r -a DEFAULT_PARENT <<< "$DEFAULT_PARENT" 106 | # Set default release and base image (parent) as the first value of 107 | # the array 108 | DEFAULT_RELEASE=${DEFAULT_RELEASE[0]} 109 | echo "$DEFAULT_RELEASE" 110 | DEFAULT_PARENT=${DEFAULT_PARENT[0]} 111 | echo "$DEFAULT_PARENT" 112 | 113 | # Passing matrix between jobs and/or workflows 114 | echo "release="${RELEASES} >> "$GITHUB_OUTPUT" 115 | echo "default_release=${DEFAULT_RELEASE}" >> "$GITHUB_OUTPUT" 116 | echo "parent="${PARENTS} >> "$GITHUB_OUTPUT" 117 | echo "default_parent=${DEFAULT_PARENT}" >> "$GITHUB_OUTPUT" 118 | echo "platform"=${PLATFORMS} >> "$GITHUB_OUTPUT" 119 | echo "repository"=${REPOSITORY} >> "$GITHUB_OUTPUT" 120 | echo "ref"=${REF} >> "$GITHUB_OUTPUT" 121 | 122 | # check-setup: 123 | # runs-on: ubuntu-latest 124 | # strategy: 125 | # matrix: 126 | # parent: ${{ fromJSON(needs.get-matrix.outputs.parent) }} 127 | # needs: 128 | # - get-matrix 129 | 130 | # steps: 131 | # - 132 | # name: Check build matrix 133 | # run: | 134 | 135 | # # Print current parent image and platforms 136 | # echo "${{ matrix.parent }}" 137 | # PLATFORM=${{ toJSON(needs.get-matrix.outputs.platform) }} 138 | # echo "$PLATFORM" 139 | # # Remove square bracket, white spaces and double quotes 140 | # PLATFORM=$(echo $PLATFORM | tr -d "[]| |\"") 141 | # echo "$PLATFORM" 142 | -------------------------------------------------------------------------------- /tests/migrations/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #======================================================================== 3 | # Copyright Universidade Federal do Espirito Santo (Ufes) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | # This program is released under license GNU GPL v3+ license. 19 | # 20 | #======================================================================== 21 | 22 | version: '3.8' 23 | 24 | services: 25 | 26 | # web app 27 | boca-web: 28 | image: ghcr.io/joaofazolo/boca-docker/boca-web:latest 29 | environment: 30 | # database configuration 31 | # privileged user password 32 | - BOCA_DB_SUPER_PASSWORD=superpass 33 | ports: 34 | - 8000:80 35 | 36 | # online judge 37 | boca-jail: 38 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:latest 39 | privileged: true 40 | 41 | # database 42 | boca-db: 43 | image: postgres:14-alpine 44 | environment: 45 | # database configuration 46 | # privileged user password 47 | - POSTGRES_PASSWORD=superpass 48 | # specifies the location of the target archive file 49 | # in which the data is saved 50 | - BOCA_DB_DUMP_FILENAME=/in/boca-db.sql 51 | volumes: 52 | # run any *.sql, *.sql.gz, or *.sh scripts found in that directory 53 | # to do further initialization before starting the service. 54 | - ./init:/docker-entrypoint-initdb.d 55 | - ./backups:/in 56 | 57 | # backup service 58 | boca-backup: 59 | image: postgres:14-alpine 60 | environment: 61 | - BOCA_DB_HOST=boca-db 62 | - BOCA_DB_SUPER_USER=postgres 63 | - BOCA_DB_SUPER_PASSWORD=superpass 64 | - BOCA_DB_NAME=bocadb 65 | # specifies the format of the archive file. It can be: 66 | # 1. plain text SQL backup (does not work with pg_restore) 67 | # - BOCA_DB_DUMP_FORMAT=p 68 | # 2. custom backup (compressed by default and most likely the best 69 | # option to use for creating the backup) 70 | # - BOCA_DB_DUMP_FORMAT=c 71 | # 3. directory backup (compressed by default) 72 | # - BOCA_DB_DUMP_FORMAT=d 73 | # 4. tar backup (does not support compression) 74 | - BOCA_DB_DUMP_FORMAT=t 75 | # Specifies the location of the target archive file (or directory, 76 | # for a directory-format archive) in which the data will be saved. 77 | # - BOCA_DB_DUMP_FILENAME=/out/boca-db.sql 78 | # - BOCA_DB_DUMP_FILENAME=/out/boca-db.dump 79 | # - BOCA_DB_DUMP_FILENAME=/out/boca-db-dir 80 | - BOCA_DB_DUMP_FILENAME=/out/boca-db.tar 81 | volumes: 82 | # folder in which the backups will be save 83 | - ./backups:/out 84 | command: 85 | - bash 86 | - -c 87 | - | 88 | export PGPASSWORD="$$BOCA_DB_SUPER_PASSWORD" 89 | pg_dump \ 90 | -h "$$BOCA_DB_HOST" \ 91 | -U "$$BOCA_DB_SUPER_USER" \ 92 | -F "$$BOCA_DB_DUMP_FORMAT" \ 93 | --clean --create \ 94 | -f "$$BOCA_DB_DUMP_FILENAME" \ 95 | "$$BOCA_DB_NAME" 96 | depends_on: 97 | - boca-db 98 | profiles: 99 | - backup 100 | 101 | # restore service 102 | boca-restore: 103 | image: postgres:14-alpine 104 | environment: 105 | - BOCA_DB_HOST=boca-db 106 | - BOCA_DB_PORT=5432 107 | - BOCA_DB_SUPER_USER=postgres 108 | - BOCA_DB_SUPER_PASSWORD=superpass 109 | - BOCA_DB_NAME=bocadb 110 | # specifies the location of the archive file (or directory, for 111 | # a directory-format archive) to be restored. 112 | # 1. plain text SQL restore (does not work with pg_restore) 113 | # 2. custom restore 114 | - BOCA_DB_DUMP_FILENAME=/in/boca-db.dump 115 | # 3. directory restore 116 | # - BOCA_DB_DUMP_FILENAME=/in/boca-db-dir/ 117 | # 4. tar restore 118 | # - BOCA_DB_DUMP_FILENAME=/in/boca-db.tar 119 | volumes: 120 | # folder containing the archive file (or directory) to be restored 121 | - ./backups:/in 122 | command: 123 | - bash 124 | - -c 125 | - | 126 | export PGPASSWORD="$$BOCA_DB_SUPER_PASSWORD" 127 | 128 | if [[ -f "$$BOCA_DB_DUMP_FILENAME" ]] || \ 129 | [[ -d "$$BOCA_DB_DUMP_FILENAME" ]]; 130 | then 131 | pg_restore \ 132 | -h "$$BOCA_DB_HOST" \ 133 | -p "$$BOCA_DB_PORT" \ 134 | -U "$$BOCA_DB_SUPER_USER" \ 135 | -d "$$BOCA_DB_NAME" \ 136 | -c "$$BOCA_DB_DUMP_FILENAME" 137 | fi 138 | depends_on: 139 | - boca-db 140 | profiles: 141 | - restore 142 | -------------------------------------------------------------------------------- /.github/workflows/lint-images.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint for best practices and CIS benchmarks of multi-platform Docker 3 | images on ghcr.io 4 | 5 | # This workflow uses actions that are not certified by GitHub. 6 | # They are provided by a third-party and are governed by 7 | # separate terms of service, privacy policy, and support 8 | # documentation. 9 | 10 | # GitHub Actions Documentation 11 | # https://docs.github.com/en/github-ae@latest/actions 12 | 13 | # Reusing workflows 14 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 15 | 16 | on: 17 | # Run as reusable workflow 18 | workflow_call: 19 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 20 | inputs: 21 | images: 22 | description: 23 | Matrix of images to lint (i.e., boca-base, boca-web and boca-jail) 24 | required: true 25 | type: string 26 | parents: 27 | description: Matrix of parent/base images used in builds 28 | required: true 29 | type: string 30 | platforms: 31 | description: Matrix of target os/platforms (multi-arch) 32 | required: true 33 | type: string 34 | tag: 35 | description: Image tag 36 | required: true 37 | type: string 38 | 39 | env: 40 | # Use docker.io for Docker Hub if empty 41 | REGISTRY_HOST: ghcr.io 42 | # Use github.repository (/) 43 | REPOSITORY_NAME: ${{ github.repository }} 44 | 45 | jobs: 46 | lint: 47 | runs-on: ubuntu-latest 48 | permissions: 49 | contents: read 50 | # for github/codeql-action/upload-sarif to upload SARIF results 51 | security-events: write 52 | # only required for a private repository by 53 | # github/codeql-action/upload-sarif to get the Action run status 54 | actions: read 55 | strategy: 56 | # If is set to true (default), GitHub will cancel all in-progress and 57 | # queued jobs in the matrix if any job in the matrix fails. 58 | fail-fast: false 59 | matrix: 60 | image: ${{ fromJSON(inputs.images) }} 61 | parent: ${{ fromJSON(inputs.parents) }} 62 | platform: ${{ fromJSON(inputs.platforms) }} 63 | 64 | steps: 65 | # Setting output parameters between steps, jobs and/or workflows 66 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions 67 | - 68 | name: Write variables to GITHUB_OUTPUT 69 | id: setup 70 | run: | 71 | 72 | # Set OS release 73 | PARENT="${{ matrix.parent }}" 74 | # Replace 'ubuntu:' with '' 75 | RELEASE=${PARENT//ubuntu:/} 76 | echo "$RELEASE" 77 | echo "release_name=$RELEASE" >> "$GITHUB_OUTPUT" 78 | 79 | # Set tags 80 | TAGS="${{ inputs.tag }}" 81 | echo "$TAGS" 82 | # Split string into an array 83 | IFS=', ' read -r -a TAGS <<< "$TAGS" 84 | 85 | IMG="${{ env.REGISTRY_HOST }}/${{ env.REPOSITORY_NAME }}/" 86 | IMG+="${{ matrix.image }}:${TAGS[0]}-$RELEASE" 87 | echo "$IMG" 88 | echo "image_name=$IMG" >> "$GITHUB_OUTPUT" 89 | 90 | # Set architecture 91 | PLATFORM="${{ matrix.platform }}" 92 | # Replace 'linux/' with '' and '/' with '-' 93 | ARCH=${PLATFORM//linux\//} 94 | ARCH=${ARCH//\//-} 95 | echo "$ARCH" 96 | echo "arch_name=$ARCH" >> "$GITHUB_OUTPUT" 97 | 98 | # Set report name 99 | REPORT="dockle-${{ matrix.image }}-$RELEASE-$ARCH-image-results" 100 | echo "$REPORT" 101 | echo "report_name=$REPORT" >> "$GITHUB_OUTPUT" 102 | 103 | # Checkout a repository, so the workflow can use the .dockleignore file 104 | # https://github.com/actions/checkout 105 | - 106 | name: Checkout repository 107 | uses: actions/checkout@v5 108 | 109 | # Create and boot a builder that can be used in the following steps of 110 | # the workflow 111 | # https://github.com/docker/setup-buildx-action 112 | - 113 | name: Set up Docker Buildx 114 | uses: docker/setup-buildx-action@v3 115 | 116 | # Login to a Docker registry (except on PR) 117 | # https://github.com/docker/login-action 118 | - 119 | name: Login to GitHub Container Registry 120 | uses: docker/login-action@v3 121 | with: 122 | registry: ${{ env.REGISTRY_HOST }} 123 | username: ${{ github.actor }} 124 | password: ${{ secrets.GITHUB_TOKEN }} 125 | 126 | - 127 | name: Pull Docker image from ghcr.io 128 | run: | 129 | 130 | IMG="${{ steps.setup.outputs.image_name }}" 131 | 132 | docker pull --platform ${{ matrix.platform }} ${IMG} 133 | docker image ls -a 134 | 135 | # Run Dockle best practices linter on image 136 | # https://github.com/erzz/dockle-action 137 | - 138 | name: Run Dockle best practices linter on image 139 | uses: erzz/dockle-action@v1 140 | with: 141 | image: ${{ steps.setup.outputs.image_name }} 142 | report-format: sarif 143 | report-name: '${{ steps.setup.outputs.report_name }}' 144 | failure-threshold: fatal 145 | accept-filenames: settings.py 146 | # Change to 1 in order to stop the pipeline on failure 147 | # exit-code: 1 148 | timeout: 10m 149 | 150 | # Upload results to GitHub so they can be displayed in the repository' 151 | # security tab 152 | # https://github.com/github/codeql-action 153 | - 154 | name: Upload Dockle image lint results to GitHub Security tab 155 | uses: github/codeql-action/upload-sarif@v4 156 | with: 157 | sarif_file: '${{ steps.setup.outputs.report_name }}.sarif' 158 | category: 'image' 159 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: End-to-end test of multi-platform Docker images 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | # Reusing workflows 13 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 14 | 15 | on: 16 | # Run as reusable workflow 17 | workflow_call: 18 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 19 | inputs: 20 | parents: 21 | description: Matrix of parent/base images used in builds 22 | required: true 23 | type: string 24 | platforms: 25 | description: Matrix of target os/platforms (multi-arch) 26 | required: true 27 | type: string 28 | tag: 29 | description: Image tag 30 | required: true 31 | type: string 32 | 33 | env: 34 | # Use docker.io for Docker Hub if empty 35 | REGISTRY_HOST: ghcr.io 36 | # Use github.repository (/) 37 | REPOSITORY_NAME: ${{ github.repository }} 38 | 39 | jobs: 40 | test: 41 | runs-on: ubuntu-latest 42 | timeout-minutes: 5 43 | strategy: 44 | # If is set to true (default), GitHub will cancel all in-progress and 45 | # queued jobs in the matrix if any job in the matrix fails. 46 | fail-fast: false 47 | matrix: 48 | parent: ${{ fromJSON(inputs.parents) }} 49 | platform: ${{ fromJSON(inputs.platforms) }} 50 | 51 | steps: 52 | - 53 | name: Write variables to GITHUB_OUTPUT 54 | id: setup 55 | run: | 56 | 57 | # Set OS release 58 | PARENT="${{ matrix.parent }}" 59 | # Replace 'ubuntu:' with '' 60 | RELEASE=${PARENT//ubuntu:/} 61 | echo "$RELEASE" 62 | 63 | # Prepare tag 64 | TAGS="${{ inputs.tag }}" 65 | echo "$TAGS" 66 | # Split string into an array 67 | IFS=', ' read -r -a TAGS <<< "$TAGS" 68 | 69 | # Set package and tag 70 | PACKAGE="${{ env.REGISTRY_HOST }}/${{ env.REPOSITORY_NAME }}" 71 | TAG="${TAGS[0]}-$RELEASE" 72 | echo "$PACKAGE" 73 | echo "$TAG" 74 | echo "image_package=$PACKAGE" >> "$GITHUB_OUTPUT" 75 | echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" 76 | 77 | # Set architecture 78 | PLATFORM="${{ matrix.platform }}" 79 | # Replace 'linux/' with '' and '/' with '-' 80 | ARCH=${PLATFORM//linux\//} 81 | ARCH=${ARCH//\//-} 82 | echo "$ARCH" 83 | 84 | # Set report name 85 | REPORT="playwright-$RELEASE-$ARCH-report" 86 | echo "$REPORT" 87 | echo "report_name=$REPORT" >> "$GITHUB_OUTPUT" 88 | 89 | # Checkout a repository, so the workflow can access it 90 | # https://github.com/actions/checkout 91 | - 92 | uses: actions/checkout@v5 93 | 94 | - 95 | name: Launch the application 96 | run: | 97 | 98 | # Create .env file with environment variables 99 | # https://docs.docker.com/compose/environment-variables/set-environment-variables/ 100 | cat <> .env 101 | PACKAGE=${{ steps.setup.outputs.image_package }} 102 | TAG=${{ steps.setup.outputs.image_tag }} 103 | PLATFORM=${{ matrix.platform }} 104 | EOT 105 | 106 | cat .env 107 | 108 | docker compose config 109 | 110 | IMG_WEB="${{ steps.setup.outputs.image_package }}/" 111 | IMG_WEB+="boca-web:${{ steps.setup.outputs.image_tag }}" 112 | docker pull --platform ${{ matrix.platform }} ${IMG_WEB} 113 | 114 | IMG_JAIL="${{ steps.setup.outputs.image_package }}/" 115 | IMG_JAIL+="boca-jail:${{ steps.setup.outputs.image_tag }}" 116 | docker pull --platform ${{ matrix.platform }} ${IMG_JAIL} 117 | 118 | # List available images 119 | docker images 120 | 121 | # Launch application 122 | docker compose up -d 123 | 124 | # List running containers 125 | docker container ls -a 126 | 127 | working-directory: ./tests/e2e 128 | 129 | # Set up GitHub Actions workflow with a specific version of node.js 130 | # https://github.com/actions/setup-node 131 | - 132 | uses: actions/setup-node@v6 133 | with: 134 | node-version: 18 135 | 136 | # Performs a clean installation of all dependencies in the 137 | # `package.json` file 138 | # For more information, see https://docs.npmjs.com/cli/ci.html 139 | - 140 | name: Install dependencies 141 | run: npm ci 142 | working-directory: ./tests/e2e 143 | 144 | - 145 | name: Install Playwright Browsers 146 | run: npx playwright install --with-deps 147 | working-directory: ./tests/e2e 148 | 149 | - 150 | name: Prepare for running tests 151 | run: | 152 | 153 | mkdir -p ./playwright/.auth 154 | echo "{}" > ./playwright/.auth/anonymous.json 155 | echo "{}" > ./playwright/.auth/system.json 156 | working-directory: ./tests/e2e 157 | 158 | - 159 | name: Run Playwright tests 160 | run: npx playwright test 161 | working-directory: ./tests/e2e 162 | 163 | - 164 | uses: actions/upload-artifact@v4 165 | if: always() 166 | with: 167 | name: ${{ steps.setup.outputs.report_name }} 168 | path: ./tests/e2e/playwright-report/ 169 | retention-days: 30 170 | 171 | - 172 | name: Bring the application down 173 | run: | 174 | 175 | # Stop containers 176 | docker compose down 177 | 178 | # Remove old images/containers 179 | docker system prune 180 | 181 | working-directory: ./tests/e2e 182 | -------------------------------------------------------------------------------- /docker/dev/base/Dockerfile: -------------------------------------------------------------------------------- 1 | #======================================================================== 2 | # Copyright Universidade Federal do Espirito Santo (Ufes) 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | # 17 | # This program is released under license GNU GPL v3+ license. 18 | # 19 | #======================================================================== 20 | 21 | # Build on base image (default: ubuntu:jammy) 22 | # Snap did not build boca-jail properly on ubuntu:bionic 23 | # error: cannot list snaps: cannot communicate with server: Get http://localhost/v2/snaps: dial unix /run/snapd.socket: connect: no such file or directory 24 | # https://groups.google.com/g/boca-users/c/QrgnJl-KAKw/m/uSuRO64_CQAJ 25 | ARG BASE_IMAGE=ubuntu:jammy 26 | 27 | # This stage clones BOCA's repository 28 | # hadolint ignore=DL3006 29 | FROM --platform=${BUILDPLATFORM:-linux/amd64} ${BASE_IMAGE} AS bocarepo 30 | # Git URL of the BOCA repository to clone from (default: cassiopc/boca) 31 | ARG REPOSITORY=https://github.com/cassiopc/boca.git 32 | # The branch, tag or SHA to point to in the cloned BOCA repository 33 | ARG REF=master 34 | # Using ARG to set ENV 35 | ENV ENV_REPOSITORY=$REPOSITORY 36 | ENV ENV_REF=$REF 37 | 38 | # hadolint ignore=DL3008,DL3015 39 | RUN apt-get -y update \ 40 | && apt-get -y install \ 41 | git \ 42 | && rm -rf /var/lib/apt/lists/* 43 | WORKDIR / 44 | RUN git clone "${ENV_REPOSITORY}" 45 | WORKDIR /boca 46 | RUN git checkout "${ENV_REF}" 47 | 48 | # The efficient way to publish multi-arch containers from GitHub Actions 49 | # https://actuated.dev/blog/multi-arch-docker-github-actions 50 | # hadolint ignore=DL3006 51 | FROM --platform=${BUILDPLATFORM:-linux/amd64} ${BASE_IMAGE} 52 | 53 | ARG TARGETPLATFORM 54 | ARG BUILDPLATFORM 55 | ARG TARGETOS 56 | ARG TARGETARCH 57 | 58 | LABEL authors="Joao Vitor Alves Fazolo, Rodrigo Laiola Guimaraes" 59 | ENV CREATED_AT 2020-06-26 60 | ENV UPDATED_AT 2023-06-01 61 | 62 | # No interactive frontend during docker build 63 | ENV DEBIAN_FRONTEND noninteractive 64 | ENV DEBCONF_NONINTERACTIVE_SEEN true 65 | 66 | # Locale and encoding settings 67 | ENV LANG_WHICH en 68 | ENV LANG_WHERE US 69 | ENV ENCODING UTF-8 70 | ENV LC_ALL C.${ENCODING} 71 | ENV LANGUAGE ${LANG_WHICH}_${LANG_WHERE}.${ENCODING} 72 | ENV LANG ${LANGUAGE} 73 | 74 | # Timezone settings 75 | # Full list at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 76 | # e.g. "US/Pacific" for Los Angeles, California, USA 77 | # e.g. ENV TZ "US/Pacific" 78 | ENV TZ America/Sao_Paulo 79 | 80 | # Apache settings 81 | ENV APACHE_RUN_USER www-data 82 | ENV APACHE_RUN_GROUP www-data 83 | 84 | # Redundant but to ensure we are not going to break anything 85 | # https://github.com/cassiopc/boca/tree/master/doc 86 | # https://github.com/cassiopc/boca/tree/master/tools 87 | # hadolint ignore=DL3002 88 | USER root 89 | 90 | # Install dependencies 91 | # hadolint ignore=DL3005,DL3008,DL3015 92 | RUN apt-get -y update \ 93 | && apt-get -y dist-upgrade \ 94 | && apt-get -y install \ 95 | # Miscellaneous 96 | locales \ 97 | tzdata \ 98 | # Package: boca-common 99 | # https://github.com/cassiopc/boca/blob/master/debian/control 100 | # Pre-Depends: 101 | debconf \ 102 | makepasswd \ 103 | sharutils \ 104 | # Depends: 105 | libany-uri-escape-perl \ 106 | openssl \ 107 | php-cli \ 108 | php-gd \ 109 | php-pgsql \ 110 | php-xml \ 111 | php-zip \ 112 | postgresql-client \ 113 | wget \ 114 | && rm -rf /var/lib/apt/lists/* 115 | 116 | RUN echo "Setting time zone to '${TZ}'" ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ 117 | && echo $TZ > /etc/timezone \ 118 | && echo "Etc/UTC" > /etc/timezone; dpkg-reconfigure -f noninteractive tzdata 119 | 120 | # Copy BOCA repository from base image 121 | COPY --from=bocarepo --chown="$APACHE_RUN_USER:$APACHE_RUN_GROUP" /boca/doc /var/www/boca/doc 122 | COPY --from=bocarepo --chown="$APACHE_RUN_USER:$APACHE_RUN_GROUP" /boca/src /var/www/boca/src 123 | COPY --from=bocarepo --chown="$APACHE_RUN_USER:$APACHE_RUN_GROUP" /boca/tools /var/www/boca/tools 124 | # Copy modified local source code file(s) 125 | # Use getenv() function to dynamically set up BOCA 126 | COPY --chown="$APACHE_RUN_USER:$APACHE_RUN_GROUP" boca/src/private/conf.php /var/www/boca/src/private 127 | # The script to create the database can run as a non-root user 128 | COPY --chown="$APACHE_RUN_USER:$APACHE_RUN_GROUP" boca/src/private/createdb.php /var/www/boca/src/private 129 | 130 | WORKDIR /var/www/boca 131 | RUN \ 132 | # install-bocawww 133 | # https://github.com/cassiopc/boca/blob/master/Makefile 134 | mkdir -p /usr/sbin /etc/cron.d /var/www/boca/ \ 135 | # Done above 136 | # && cp -r src /var/www/boca/ \ 137 | # Docs not necessary 138 | # && cp -r doc /var/www/boca/ \ 139 | && install tools/boca-fixssh /usr/sbin/ \ 140 | && install tools/cron-boca-fixssh /etc/cron.d/ \ 141 | && chmod 700 /usr/sbin/boca-fixssh \ 142 | # install-bocacommon 143 | # https://github.com/cassiopc/boca/blob/master/Makefile 144 | && mkdir -p /usr/sbin/ \ 145 | && mkdir -p /etc/ \ 146 | && cp tools/boca.conf /etc/ \ 147 | && install tools/boca-config-dbhost.sh /usr/sbin/boca-config-dbhost \ 148 | && chmod 700 /usr/sbin/boca-config-dbhost \ 149 | # boca-common.postinst 150 | # https://github.com/cassiopc/boca/blob/master/debian/boca-common.postinst 151 | && chmod 600 /var/www/boca/src/private/conf.php 152 | # Done above 153 | # && chown "$APACHE_RUN_USER:$APACHE_RUN_GROUP" /var/www/boca/src/private/conf.php 154 | -------------------------------------------------------------------------------- /tests/e2e/tests/login.spec.ts: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // Copyright Universidade Federal do Espirito Santo (Ufes) 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // This program is released under license GNU GPL v3+ license. 18 | // 19 | //======================================================================== 20 | 21 | import { test, expect } from '@playwright/test'; 22 | 23 | import { 24 | checkLoginPage, 25 | checkContestPage 26 | } from "./setup"; 27 | 28 | test.describe.serial("Login test scenarios", () => { 29 | 30 | test.use({ storageState: 'playwright/.auth/anonymous.json' }); 31 | 32 | test.beforeEach(async ({ page }) => { 33 | // Register a dialog handler before authentication fails 34 | page.on('dialog', async dialog => { 35 | // Verify type of dialog 36 | expect(dialog.type()).toContain('alert'); 37 | 38 | // Verify dialog message 39 | expect([ 40 | 'User does not exist or incorrect password.', 41 | 'Incorrect password.' 42 | ]).toContain(dialog.message()); 43 | 44 | // Click on OK Button 45 | try { 46 | await dialog.accept(); 47 | } catch(error) { 48 | console.error(`${error}`); 49 | } 50 | }); 51 | 52 | // Go to the starting url before each test 53 | await page.goto('/boca'); 54 | 55 | // Check if this is the login page 56 | await checkLoginPage(page); 57 | }); 58 | 59 | test.describe("Negative testing", () => { 60 | 61 | test('Missing username', async ({ page }) => { 62 | // Perform authentication steps 63 | await page.locator('input[name="name"]').fill(''); 64 | await page.locator('input[name="password"]').fill('boca'); 65 | await page.getByRole('button', { name: 'Login' }).click(); 66 | // Wait until the page receives the cookies 67 | 68 | // Check if redirected back to login page 69 | await checkLoginPage(page); 70 | 71 | // End of authentication steps 72 | }); 73 | 74 | test('Missing password', async ({ page }) => { 75 | // Perform authentication steps 76 | await page.locator('input[name="name"]').fill('system'); 77 | await page.locator('input[name="password"]').fill(''); 78 | await page.getByRole('button', { name: 'Login' }).click(); 79 | // Wait until the page receives the cookies 80 | 81 | // Check if redirected back to login page 82 | await checkLoginPage(page); 83 | 84 | // End of authentication steps 85 | }); 86 | 87 | test('Login fails as admin user', async ({ page }) => { 88 | // Perform authentication steps 89 | await page.locator('input[name="name"]').fill('admin'); 90 | await page.locator('input[name="password"]').fill('boca'); 91 | await page.getByRole('button', { name: 'Login' }).click(); 92 | // Wait until the page receives the cookies 93 | 94 | // Check if redirected back to login page 95 | await checkLoginPage(page); 96 | 97 | // End of authentication steps 98 | }); 99 | 100 | test('Login fails as team user', async ({ page }) => { 101 | // Perform authentication steps 102 | await page.locator('input[name="name"]').fill('team1'); 103 | await page.locator('input[name="password"]').fill('boca'); 104 | await page.getByRole('button', { name: 'Login' }).click(); 105 | // Wait until the page receives the cookies 106 | 107 | // Check if redirected back to login page 108 | await checkLoginPage(page); 109 | 110 | // End of authentication steps 111 | }); 112 | 113 | test('Login fails as judge user', async ({ page }) => { 114 | // Perform authentication steps 115 | await page.locator('input[name="name"]').fill('judge1'); 116 | await page.locator('input[name="password"]').fill('boca'); 117 | await page.getByRole('button', { name: 'Login' }).click(); 118 | // Wait until the page receives the cookies 119 | 120 | // Check if redirected back to login page 121 | await checkLoginPage(page); 122 | 123 | // End of authentication steps 124 | }); 125 | 126 | }); 127 | 128 | test.describe("Positive testing", () => { 129 | 130 | test("Has title", async ({ page }) => { 131 | // Expect title "to contain" a substring 132 | await expect(page).toHaveTitle(/Login/); 133 | }); 134 | 135 | test("Has text", async ({ page }) => { 136 | // Expect page "to contain" a string 137 | await expect(page.getByText('BOCA Login') !== undefined).toBeTruthy(); 138 | }); 139 | 140 | test("Has input fields", async ({ page }) => { 141 | // Expect page to have input fields 142 | await expect(page.locator('input[name="name"]') !== undefined) 143 | .toBeTruthy(); 144 | await expect(page.locator('input[name="password"]') !== undefined) 145 | .toBeTruthy(); 146 | }); 147 | 148 | test("Has button", async ({ page }) => { 149 | // Expect page to have a button 150 | await expect(page.getByRole('button', { name: 'Login' }) !== undefined) 151 | .toBeTruthy(); 152 | }); 153 | 154 | test('Login as system user', async ({ page }) => { 155 | // Fill in user credentials 156 | await page.locator('input[name="name"]').fill('system'); 157 | await page.locator('input[name="password"]').fill('boca'); 158 | await page.getByRole('button', { name: 'Login' }).click(); 159 | // Wait until the page receives the cookies 160 | 161 | // Check if redirected to contest page 162 | await checkContestPage(page); 163 | 164 | // End of authentication steps 165 | }); 166 | 167 | }); 168 | 169 | }); 170 | -------------------------------------------------------------------------------- /.github/workflows/clean-packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Delete untagged and/or unsupported Docker images on ghcr.io 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | # Reusing workflows 13 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 14 | 15 | on: 16 | # Run on every saturday at 10:15 AM UTC 17 | schedule: 18 | - cron: '15 10 * * SAT' 19 | # on button click 20 | workflow_dispatch: 21 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs 22 | inputs: 23 | tags: 24 | description: Spare unsupported tags (whitespace-separated) 25 | required: false 26 | type: string 27 | # or on calling as reusable workflow 28 | workflow_call: 29 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 30 | inputs: 31 | tags: 32 | description: Spare unsupported tags (whitespace-separated) 33 | required: false 34 | type: string 35 | 36 | env: 37 | # Use docker.io for Docker Hub if empty 38 | REGISTRY_HOST: ghcr.io 39 | # Use github.repository (/) 40 | REPOSITORY_NAME: ${{ github.repository }} 41 | # Use github.repository_owner () 42 | OWNER_NAME: ${{ github.repository_owner }} 43 | 44 | jobs: 45 | # Calling a reusable workflow 46 | setup: 47 | uses: ./.github/workflows/read-matrix.yml 48 | with: 49 | matrix-path: docker/build/matrix.json 50 | 51 | # Ensure that the repository is given Admin access by going on 52 | # Package settings -> Manage Actions access 53 | # https://github.com/actions/delete-package-versions/issues/74 54 | cleanup: 55 | runs-on: ubuntu-latest 56 | permissions: 57 | # can upload and download package, as well as read/write package metadata 58 | packages: write 59 | strategy: 60 | matrix: 61 | image: 62 | - boca-base 63 | - boca-web 64 | - boca-jail 65 | needs: setup 66 | 67 | steps: 68 | # Setting output parameters between steps, jobs and/or workflows 69 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter 70 | - 71 | name: Write package_name variable to GITHUB_ENV 72 | id: setup 73 | run: | 74 | 75 | OWNER="${{ env.OWNER_NAME }}" 76 | REPO="${{ env.REPOSITORY_NAME }}" 77 | # Remove owner from repository name 78 | PACKAGE=${REPO//$OWNER\//} 79 | echo "$PACKAGE" 80 | echo "package_name=${PACKAGE}" >> "$GITHUB_OUTPUT" 81 | 82 | # Set up GitHub Actions workflow with a specific version of Go 83 | # https://github.com/actions/setup-go 84 | - 85 | uses: actions/setup-go@v6 86 | with: 87 | go-version: 1.15 88 | cache: false 89 | 90 | # Install and setup crane 91 | # https://github.com/imjasonh/setup-crane 92 | - 93 | uses: imjasonh/setup-crane@v0.4 94 | 95 | # Necessary if testing locally with 'act' 96 | # https://github.com/nektos/act 97 | 98 | # Install GH CLI (self-hosted runners do not come with it out of the box) 99 | # https://github.com/dev-hanz-ops/install-gh-cli-action 100 | # - 101 | # name: Install GH CLI 102 | # uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 103 | # with: 104 | # gh-cli-version: 2.14.2 # optional 105 | 106 | - 107 | name: Compile untagged versions/releases to ignore 108 | id: untagged 109 | run: | 110 | 111 | # Get supported releases 112 | RELEASES=${{ toJSON(needs.setup.outputs.release) }} 113 | # Remove square brackets and double quotes 114 | RELEASES=$(echo $RELEASES | tr -d "[]|\"") 115 | # Split string into an array 116 | IFS=', ' read -r -a RELEASES <<< "$RELEASES" 117 | # Include custom tags provided as argument 118 | echo "${{ inputs.tags }}" 119 | if [[ ! -z "${{ inputs.tags }}" ]]; 120 | then 121 | read -a TAGS <<< "${{ inputs.tags }}" 122 | for tag in ${TAGS[@]}; 123 | do 124 | echo "${tag}" 125 | RELEASES+=("$tag") 126 | done 127 | fi 128 | echo "${RELEASES[@]}" 129 | 130 | # Unset variable 131 | unset DIGEST_KEYS 132 | # Set image 133 | IMG="${{ env.REGISTRY_HOST }}/" 134 | IMG+="${{ env.REPOSITORY_NAME }}/" 135 | IMG+="${{ matrix.image }}" 136 | echo "$IMG" 137 | 138 | # Iterate over releases to get digests 139 | for version in ${RELEASES[@]}; 140 | do 141 | echo "${version}" 142 | 143 | # If manifest does not exist just skip image 144 | MANIFEST=$(crane manifest ${IMG}:${version} || echo "") 145 | if [[ -z "${MANIFEST}" ]]; 146 | then 147 | continue 148 | fi 149 | 150 | # Get digest key(s) of regular image (if not multi-arch) 151 | DIGEST_KEYS="${DIGEST_KEYS} \ 152 | `echo $MANIFEST | \ 153 | jq 'select (.config != null) | .config.digest'`" 154 | # or of multi-platform images builds 155 | DIGEST_KEYS="${DIGEST_KEYS} \ 156 | `echo $MANIFEST | \ 157 | jq 'select (.manifests != null) | .manifests[].digest'`" 158 | done 159 | 160 | # Remove newlines, tabs, carriage returns and double quotes 161 | DIGEST_KEYS=$(echo $DIGEST_KEYS | tr -d "\n\t\r|\"") 162 | # Replace white spaces with '|' 163 | DIGEST_KEYS="${DIGEST_KEYS// /|}" 164 | echo "$DIGEST_KEYS" 165 | 166 | # Passing env variable between steps 167 | echo "digest_keys=${DIGEST_KEYS}" >> "$GITHUB_OUTPUT" 168 | 169 | # Delete package versions 170 | # https://github.com/actions/delete-package-versions 171 | - 172 | name: Delete untagged Docker images on ghcr.io 173 | uses: actions/delete-package-versions@v5 174 | with: 175 | package-name: 176 | '${{ steps.setup.outputs.package_name }}/${{ matrix.image }}' 177 | package-type: 'container' 178 | delete-only-untagged-versions: 'true' 179 | ignore-versions: '${{ steps.untagged.outputs.digest_keys }}' 180 | token: ${{ secrets.GITHUB_TOKEN }} 181 | 182 | - 183 | name: Compile unsupported versions/releases to delete 184 | id: unsupported 185 | run: | 186 | 187 | # Set image 188 | IMG="${{ env.REGISTRY_HOST }}/" 189 | IMG+="${{ env.REPOSITORY_NAME }}/" 190 | IMG+="${{ matrix.image }}" 191 | echo "$IMG" 192 | 193 | # Get supported releases 194 | RELEASES=${{ toJSON(needs.setup.outputs.release) }} 195 | # Remove square brackets and double quotes 196 | RELEASES=$(echo $RELEASES | tr -d "[]|\"") 197 | # Split string into an array 198 | IFS=', ' read -r -a RELEASES <<< "$RELEASES" 199 | # Include custom tags provided as argument 200 | echo "${{ inputs.tags }}" 201 | if [[ ! -z "${{ inputs.tags }}" ]]; 202 | then 203 | read -a TAGS <<< "${{ inputs.tags }}" 204 | for tag in ${TAGS[@]}; 205 | do 206 | echo "${tag}" 207 | RELEASES+=("$tag") 208 | done 209 | fi 210 | echo "${RELEASES[@]}" 211 | 212 | # Get all releases/tags 213 | ALL_RELEASES=$(crane ls ${IMG} || echo "") 214 | # If list of tags is empty just skip it 215 | if [[ -z "${ALL_RELEASES}" ]]; 216 | then 217 | exit 0 218 | fi 219 | 220 | # Remove newlines, tabs, carriage returns and double quotes 221 | ALL_RELEASES=$(echo $ALL_RELEASES | tr -d "\n\t\r|\"") 222 | # Split string into an array 223 | IFS=', ' read -r -a ALL_RELEASES <<< "$ALL_RELEASES" 224 | echo "${ALL_RELEASES[@]}" 225 | 226 | # Get unsupported releases 227 | DEPRECATED=() 228 | for i in "${ALL_RELEASES[@]}"; 229 | do 230 | skip= 231 | for j in "${RELEASES[@]}"; 232 | do 233 | [[ $i == $j ]] && { skip=1; break; } 234 | done 235 | [[ -n $skip ]] || DEPRECATED+=("$i") 236 | done 237 | declare -p DEPRECATED 238 | echo "${DEPRECATED[@]}" 239 | 240 | PACKAGE=${{ steps.setup.outputs.package_name }} 241 | ENCODED_PACKAGE="${PACKAGE}/${{ matrix.image }}" 242 | # Replace '/' with '%2F' 243 | ENCODED_PACKAGE="${ENCODED_PACKAGE//\//\%2F}" 244 | echo "$ENCODED_PACKAGE" 245 | 246 | PACKAGES_URL="/users/${{ env.OWNER_NAME }}/" 247 | PACKAGES_URL+="packages/container/${ENCODED_PACKAGE}/versions" 248 | echo "$PACKAGES_URL" 249 | 250 | PACKAGES_JSON=$(gh api \ 251 | -H "Accept: application/vnd.github+json" \ 252 | -H "X-GitHub-Api-Version: 2022-11-28" ${PACKAGES_URL} || echo "") 253 | echo "$PACKAGES_JSON" 254 | 255 | # If list of package versions does not exist just fail 256 | if [[ -z "${PACKAGES_JSON}" ]]; 257 | then 258 | exit 1 259 | fi 260 | 261 | # Unset variable 262 | unset VERSION_IDS 263 | 264 | # Iterate over releases to get digests 265 | for version in ${DEPRECATED[@]}; 266 | do 267 | echo "${version}" 268 | 269 | # Only proceed in case an unsupported version does not coexit 270 | # with a supported one 271 | CURR_VERSIONS=`echo $PACKAGES_JSON | \ 272 | jq --arg var $version \ 273 | '.[] | select(any(.metadata.container.tags[]; . == $var)) | \ 274 | .metadata.container.tags'` 275 | # Remove newlines, tabs, carriage returns and double quotes 276 | CURR_VERSIONS=$(echo $CURR_VERSIONS | tr -d "\n\t\r|\"") 277 | # Split string into an array 278 | IFS=', ' read -r -a CURR_VERSIONS <<< "$CURR_VERSIONS" 279 | echo "${CURR_VERSIONS[@]}" 280 | 281 | unset SHARED 282 | for curr in ${CURR_VERSIONS[@]}; 283 | do 284 | if [[ " ${RELEASES[*]} " =~ " ${curr} " ]]; 285 | then 286 | SHARED=true 287 | break 288 | fi 289 | done 290 | 291 | if [[ ! -z "${SHARED}" ]]; 292 | then 293 | echo "Skipping ${version} (conflicting)" 294 | continue 295 | fi 296 | 297 | VERSION_IDS="${VERSION_IDS} \ 298 | `echo $PACKAGES_JSON | \ 299 | jq --arg var $version \ 300 | '.[] | select(any(.metadata.container.tags[]; . == $var)) | \ 301 | .id'`" 302 | done 303 | 304 | # Remove newlines, tabs, carriage returns and double quotes 305 | VERSION_IDS=$(echo ${VERSION_IDS} | tr -d "\n\t\r|\"") 306 | # Replace white spaces with ', ' 307 | VERSION_IDS="${VERSION_IDS// /, }" 308 | # Split string into an array 309 | IFS=', ' read -r -a VERSION_IDS <<< "$VERSION_IDS" 310 | echo "${VERSION_IDS[@]}" 311 | 312 | # Keep unique version ids only 313 | UNIQIDS=($(printf "%s\n" "${VERSION_IDS[@]}" | sort -u)) 314 | echo "${UNIQIDS[@]}" 315 | 316 | # Convert to string 317 | UNIQSTR=$(echo $(IFS=, ; echo "${UNIQIDS[*]}")) 318 | # Add white space after ',' 319 | UNIQSTR="${UNIQSTR//,/, }" 320 | echo "$UNIQSTR" 321 | # Passing env variable between steps 322 | echo "version_ids=${UNIQSTR}" >> "$GITHUB_OUTPUT" 323 | env: 324 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 325 | 326 | - 327 | name: Delete multiple specific Docker images on ghcr.io 328 | uses: actions/delete-package-versions@v5 329 | if: ${{ steps.unsupported.outputs.version_ids != '' }} 330 | with: 331 | package-version-ids: '${{ steps.unsupported.outputs.version_ids }}' 332 | package-name: 333 | '${{ steps.setup.outputs.package_name }}/${{ matrix.image }}' 334 | package-type: 'container' 335 | token: ${{ secrets.GITHUB_TOKEN }} 336 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | # Reusing workflows 13 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 14 | 15 | on: 16 | schedule: 17 | # Run daily at 9:15 AM UTC (nightly builds) 18 | # Publish "nightly" build as release 19 | - cron: '15 9 * * *' 20 | # Run once a week on Mondays at 6:15 AM UTC (rebuild latest) 21 | - cron: '15 6 * * MON' 22 | push: 23 | # Publish semver tags as releases (e.g., 1.0.0) 24 | tags: ['*.*.*'] 25 | # or on button click 26 | workflow_dispatch: 27 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs 28 | inputs: 29 | tags: 30 | description: Image tags (whitespace-separated) 31 | required: true 32 | type: string 33 | latest: 34 | # The latest tag is automatically handled through the new tag/release 35 | # event. Set for conditionally tagging with the latest tag. 36 | description: For conditionally tagging with the latest tag 37 | required: false 38 | type: boolean 39 | ref: 40 | # The branch, tag or SHA to checkout in builds. If empty, check out 41 | # the repository that triggered the workflow. 42 | description: 43 | The branch, tag or SHA to checkout (empty for current branch) 44 | required: false 45 | type: string 46 | boca_repository: 47 | # Git URL of the BOCA repository to clone from. 48 | description: >- 49 | Git URL of the BOCA repository to clone from (empty for the one 50 | specified in docker/build/matrix.json) 51 | required: false 52 | type: string 53 | boca_ref: 54 | # The branch, tag or SHA to point to in the cloned BOCA repository. 55 | description: >- 56 | The branch, tag or SHA to point to in the cloned BOCA repository 57 | (empty for repository's HEAD) 58 | required: false 59 | type: string 60 | attempt_limit: 61 | description: Set number of retries if workflow fails (default is 3) 62 | required: false 63 | type: number 64 | attempt_delay: 65 | description: Set delay between retries in seconds (default is 60) 66 | required: false 67 | type: number 68 | 69 | env: 70 | # Number of retries if workflow fails (default is 3) 71 | RETRY_LIMIT: 3 72 | # Delay between retries in seconds (default is 60) 73 | RETRY_DELAY: 60 74 | 75 | # Save computation power by stopping obsolete jobs for the current workflow 76 | # https://docs.github.com/en/enterprise-cloud@latest/actions/using-jobs/using-concurrency 77 | concurrency: 78 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 79 | cancel-in-progress: true 80 | 81 | jobs: 82 | # Calling a reusable workflow 83 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 84 | setup-matrix: 85 | uses: ./.github/workflows/read-matrix.yml 86 | with: 87 | matrix-path: docker/build/matrix.json 88 | 89 | setup-inputs: 90 | runs-on: ubuntu-latest 91 | # Map the workflow outputs to job outputs 92 | outputs: 93 | tags: ${{ steps.set-inputs.outputs.tags }} 94 | latest: ${{ steps.set-inputs.outputs.latest }} 95 | ref: ${{ steps.set-inputs.outputs.ref }} 96 | boca_repository: ${{ steps.set-inputs.outputs.boca_repository }} 97 | boca_ref: ${{ steps.set-inputs.outputs.boca_ref }} 98 | retry_countdown: ${{ steps.set-inputs.outputs.retry_countdown }} 99 | retry_delay: ${{ steps.set-inputs.outputs.retry_delay }} 100 | needs: 101 | - setup-matrix 102 | 103 | steps: 104 | # Checkout a repository, so the workflow can access it 105 | # https://github.com/actions/checkout 106 | - 107 | name: Checkout repository 108 | uses: actions/checkout@v5 109 | with: 110 | ref: '${{ inputs.ref }}' 111 | 112 | # Setting output parameters between steps, jobs and/or workflows 113 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions 114 | - 115 | name: Prepare inputs 116 | id: set-inputs 117 | run: | 118 | 119 | LATEST="${{ needs.setup-matrix.outputs.default_release }}" 120 | 121 | # Check if the input tag is equivalent to latest 122 | # or it is a schedule to rebuild latest 123 | CRON_EVENT="${{ github.event.schedule }}" 124 | if [[ "${LATEST}" == "${{ inputs.tags }}" ]] || 125 | [[ "${CRON_EVENT}" == '15 6 * * MON' ]]; 126 | then 127 | # Check if tag exists 128 | ret_code=$(git show-ref --tags \ 129 | --verify \ 130 | --quiet "refs/tags/${LATEST}" || echo 1) 131 | echo "${ret_code}" 132 | if [[ "${ret_code}" == "1" ]]; 133 | then 134 | echo "Tag ${LATEST} does not exist" 135 | 136 | # Create tag/release latest if it does not exist 137 | try=$(gh release create ${LATEST} \ 138 | --title "${LATEST} (beta)" \ 139 | --notes "This release has been automatically generated." \ 140 | --prerelease || echo "Could not create tag/release.") 141 | else 142 | echo "Tag ${LATEST} exists" 143 | fi 144 | 145 | # Set alternative tag 146 | SHORT_VERSION=${LATEST%.*} 147 | TAGS="${LATEST} ${SHORT_VERSION}" 148 | echo "${TAGS}" 149 | echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" 150 | 151 | echo 'true' 152 | echo "latest=true" >> "$GITHUB_OUTPUT" 153 | 154 | echo "${LATEST}" 155 | echo "ref=${LATEST}" >> "$GITHUB_OUTPUT" 156 | # Otherwise, this is the regular path 157 | else 158 | TAGS="${{ inputs.tags }}" 159 | 160 | # Is it a nightly build? 161 | if [[ "${CRON_EVENT}" == '15 9 * * *' ]]; 162 | then 163 | # If so, include tag (fallback for retry workflow) 164 | TAGS="nightly" 165 | fi 166 | 167 | # Is tag empty? 168 | if [[ -z "${TAGS}" ]]; 169 | then 170 | TAGS="${GITHUB_REF#refs/*/}" 171 | fi 172 | 173 | echo "$TAGS" 174 | echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" 175 | 176 | echo "${{ inputs.latest }}" 177 | echo "latest=${{ inputs.latest == true }}" >> "$GITHUB_OUTPUT" 178 | 179 | echo "${{ inputs.ref }}" 180 | echo "ref=${{ inputs.ref }}" >> "$GITHUB_OUTPUT" 181 | fi 182 | 183 | # Prepare the Git URL of the BOCA repository to clone from and 184 | # the branch, tag or SHA to point to in the cloned BOCA repository 185 | BOCA_REPOSITORY="${{ inputs.boca_repository }}" 186 | if [[ -z "$BOCA_REPOSITORY" ]]; 187 | then 188 | BOCA_REPOSITORY="${{ needs.setup-matrix.outputs.repository }}" 189 | fi 190 | echo $BOCA_REPOSITORY 191 | echo "boca_repository=${BOCA_REPOSITORY}" >> "$GITHUB_OUTPUT" 192 | 193 | BOCA_REF="${{ inputs.boca_ref }}" 194 | if [[ -z "$BOCA_REF" ]]; 195 | then 196 | BOCA_REF="${{ needs.setup-matrix.outputs.ref }}" 197 | fi 198 | echo $BOCA_REF 199 | echo "boca_ref=${BOCA_REF}" >> "$GITHUB_OUTPUT" 200 | 201 | # Prepare retry limit and retry delay 202 | ATTEMPTS="${{ inputs.attempt_limit }}" 203 | if [[ -z "$ATTEMPTS" ]]; 204 | then 205 | ATTEMPTS=${{ env.RETRY_LIMIT }} 206 | else 207 | ATTEMPTS=$(($ATTEMPTS)) 208 | fi 209 | echo $ATTEMPTS 210 | echo "retry_countdown=${ATTEMPTS}" >> "$GITHUB_OUTPUT" 211 | 212 | DELAY="${{ inputs.attempt_delay }}" 213 | if [[ -z "$DELAY" ]]; 214 | then 215 | DELAY=${{ env.RETRY_DELAY }} 216 | else 217 | DELAY=$(($DELAY)) 218 | fi 219 | echo $DELAY 220 | echo "retry_delay=${DELAY}" >> "$GITHUB_OUTPUT" 221 | env: 222 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 223 | 224 | build-base: 225 | uses: ./.github/workflows/build-images.yml 226 | with: 227 | images: '[ "boca-base" ]' 228 | parents: ${{ needs.setup-matrix.outputs.parent }} 229 | default_parent: ${{ needs.setup-matrix.outputs.default_parent }} 230 | platforms: ${{ needs.setup-matrix.outputs.platform }} 231 | tags: ${{ needs.setup-inputs.outputs.tags }} 232 | latest: ${{ needs.setup-inputs.outputs.latest == 'true' }} 233 | ref: ${{ needs.setup-inputs.outputs.ref }} 234 | boca_repository: ${{ needs.setup-inputs.outputs.boca_repository }} 235 | boca_ref: ${{ needs.setup-inputs.outputs.boca_ref }} 236 | needs: 237 | - setup-matrix 238 | - setup-inputs 239 | 240 | build-web: 241 | uses: ./.github/workflows/build-images.yml 242 | with: 243 | images: '[ "boca-web" ]' 244 | parents: ${{ needs.setup-matrix.outputs.parent }} 245 | default_parent: ${{ needs.setup-matrix.outputs.default_parent }} 246 | platforms: ${{ needs.setup-matrix.outputs.platform }} 247 | tags: ${{ needs.setup-inputs.outputs.tags }} 248 | latest: ${{ needs.setup-inputs.outputs.latest == 'true' }} 249 | ref: ${{ needs.setup-inputs.outputs.ref }} 250 | needs: 251 | - setup-matrix 252 | - setup-inputs 253 | - build-base 254 | 255 | build-jail: 256 | uses: ./.github/workflows/build-images.yml 257 | with: 258 | images: '[ "boca-jail" ]' 259 | parents: ${{ needs.setup-matrix.outputs.parent }} 260 | default_parent: ${{ needs.setup-matrix.outputs.default_parent }} 261 | platforms: ${{ needs.setup-matrix.outputs.platform }} 262 | tags: ${{ needs.setup-inputs.outputs.tags }} 263 | latest: ${{ needs.setup-inputs.outputs.latest == 'true' }} 264 | ref: ${{ needs.setup-inputs.outputs.ref }} 265 | needs: 266 | - setup-matrix 267 | - setup-inputs 268 | - build-base 269 | 270 | lint-base: 271 | uses: ./.github/workflows/lint-images.yml 272 | with: 273 | images: '[ "boca-base" ]' 274 | parents: ${{ needs.setup-matrix.outputs.parent }} 275 | platforms: ${{ needs.setup-matrix.outputs.platform }} 276 | tag: ${{ needs.setup-inputs.outputs.tags }} 277 | needs: 278 | - setup-matrix 279 | - setup-inputs 280 | - build-base 281 | 282 | scan-base: 283 | uses: ./.github/workflows/scan-images.yml 284 | with: 285 | images: '[ "boca-base" ]' 286 | parents: ${{ needs.setup-matrix.outputs.parent }} 287 | platforms: ${{ needs.setup-matrix.outputs.platform }} 288 | tag: ${{ needs.setup-inputs.outputs.tags }} 289 | needs: 290 | - setup-matrix 291 | - setup-inputs 292 | - build-base 293 | 294 | lint-web: 295 | uses: ./.github/workflows/lint-images.yml 296 | with: 297 | images: '[ "boca-web" ]' 298 | parents: ${{ needs.setup-matrix.outputs.parent }} 299 | platforms: ${{ needs.setup-matrix.outputs.platform }} 300 | tag: ${{ needs.setup-inputs.outputs.tags }} 301 | needs: 302 | - setup-matrix 303 | - setup-inputs 304 | - build-web 305 | 306 | scan-web: 307 | uses: ./.github/workflows/scan-images.yml 308 | with: 309 | images: '[ "boca-web" ]' 310 | parents: ${{ needs.setup-matrix.outputs.parent }} 311 | platforms: ${{ needs.setup-matrix.outputs.platform }} 312 | tag: ${{ needs.setup-inputs.outputs.tags }} 313 | needs: 314 | - setup-matrix 315 | - setup-inputs 316 | - build-web 317 | 318 | lint-jail: 319 | uses: ./.github/workflows/lint-images.yml 320 | with: 321 | images: '[ "boca-jail" ]' 322 | parents: ${{ needs.setup-matrix.outputs.parent }} 323 | platforms: ${{ needs.setup-matrix.outputs.platform }} 324 | tag: ${{ needs.setup-inputs.outputs.tags }} 325 | needs: 326 | - setup-matrix 327 | - setup-inputs 328 | - build-jail 329 | 330 | scan-jail: 331 | uses: ./.github/workflows/scan-images.yml 332 | with: 333 | images: '[ "boca-jail" ]' 334 | parents: ${{ needs.setup-matrix.outputs.parent }} 335 | platforms: ${{ needs.setup-matrix.outputs.platform }} 336 | tag: ${{ needs.setup-inputs.outputs.tags }} 337 | needs: 338 | - setup-matrix 339 | - setup-inputs 340 | - build-jail 341 | 342 | e2e-test: 343 | uses: ./.github/workflows/e2e-tests.yml 344 | with: 345 | parents: ${{ needs.setup-matrix.outputs.parent }} 346 | platforms: ${{ needs.setup-matrix.outputs.platform }} 347 | tag: ${{ needs.setup-inputs.outputs.tags }} 348 | needs: 349 | - setup-matrix 350 | - setup-inputs 351 | - build-web 352 | - build-jail 353 | 354 | # Restart the scheduled workflow when it failed (on schedule only) 355 | # https://www.eliostruyf.com/restart-github-actions-workflow-failed/ 356 | retry-builds: 357 | runs-on: ubuntu-latest 358 | if: | 359 | failure() 360 | needs: 361 | - setup-inputs 362 | - build-base 363 | - build-web 364 | - build-jail 365 | 366 | steps: 367 | 368 | - 369 | name: Retry the workflow 370 | run: | 371 | 372 | ATTEMPT=$((${{ needs.setup-inputs.outputs.retry_countdown }})) 373 | echo $ATTEMPT 374 | if [[ "$ATTEMPT" -le "0" ]]; 375 | then 376 | exit 1 377 | fi 378 | ATTEMPT=$((ATTEMPT-1)) 379 | echo $ATTEMPT 380 | 381 | # Wait before restarting 382 | DELAY=$((${{ needs.setup-inputs.outputs.retry_delay }})) 383 | echo $DELAY 384 | sleep $DELAY 385 | 386 | WORKFLOWS_URL="/repos/${{ github.repository }}/" 387 | WORKFLOWS_URL+="actions/workflows" 388 | echo "$WORKFLOWS_URL" 389 | 390 | # List repository workflows 391 | # https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#list-repository-workflows 392 | WORKFLOWS_JSON=$(gh api \ 393 | -H "Accept: application/vnd.github+json" \ 394 | -H "X-GitHub-Api-Version: 2022-11-28" ${WORKFLOWS_URL} || echo "") 395 | echo "$WORKFLOWS_JSON" 396 | 397 | # If list of workflows does not exist just fail 398 | if [[ -z "${WORKFLOWS_JSON}" ]]; 399 | then 400 | exit 1 401 | fi 402 | 403 | # Get id of current workflow 404 | WORKFLOW_ID=`echo $WORKFLOWS_JSON | \ 405 | jq --arg var "${{ github.workflow }}" \ 406 | '.workflows[] | select(.name == $var) | .id'` 407 | echo $WORKFLOW_ID 408 | 409 | # Prepare api endpoint 410 | URL="https://api.github.com/repos/${{ github.repository }}/\ 411 | actions/workflows/$WORKFLOW_ID/dispatches" 412 | echo $URL 413 | 414 | # Set tags 415 | # API throws error if JSON has a whitespace-separated string 416 | # Thus, sending the most relevant tag only 417 | TAGS="${{ needs.setup-inputs.outputs.tags }}" 418 | echo "$TAGS" 419 | # Split string into an array 420 | IFS=', ' read -r -a TAGS <<< "$TAGS" 421 | echo "${TAGS[0]}" 422 | 423 | # Prepare api payload 424 | JSON_STRING=$( jq -c -n \ 425 | --arg tag ${TAGS[0]} \ 426 | --arg limit $ATTEMPT \ 427 | --arg delay $DELAY \ 428 | '{ref: "${{ github.ref }}", 429 | inputs: { 430 | tags: $tag, 431 | latest: "${{ needs.setup-inputs.outputs.latest == 'true' }}", 432 | ref: "${{ needs.setup-inputs.outputs.ref }}", 433 | attempt_limit: $limit, 434 | attempt_delay: $delay 435 | } 436 | }' ) 437 | echo $JSON_STRING 438 | 439 | # Create a workflow dispatch event 440 | # https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event 441 | curl -L \ 442 | -X POST \ 443 | -H "Accept: application/vnd.github+json" \ 444 | -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ 445 | -H "X-GitHub-Api-Version: 2022-11-28" \ 446 | $URL \ 447 | -d $JSON_STRING 448 | env: 449 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 450 | -------------------------------------------------------------------------------- /.github/workflows/build-images.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build and publish multi-platform Docker images on ghcr.io 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | # GitHub Actions Documentation 10 | # https://docs.github.com/en/github-ae@latest/actions 11 | 12 | # Reusing workflows 13 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 14 | 15 | on: 16 | # Run as reusable workflow 17 | workflow_call: 18 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputs 19 | inputs: 20 | images: 21 | description: 22 | Matrix of images to build (i.e., boca-base, boca-web and boca-jail) 23 | required: true 24 | type: string 25 | parents: 26 | description: Matrix of parent/base images used in builds 27 | required: true 28 | type: string 29 | default_parent: 30 | description: Default parent/base image 31 | required: true 32 | type: string 33 | platforms: 34 | description: Matrix of target os/platforms (multi-arch) 35 | required: true 36 | type: string 37 | tags: 38 | description: Image tags (whitespace-separated) 39 | required: true 40 | type: string 41 | latest: 42 | # The latest tag is automatically handled through the new tag/release 43 | # event. Set for conditionally tagging with the latest tag. 44 | description: For conditionally tagging with the latest tag 45 | required: false 46 | type: boolean 47 | ref: 48 | # The branch, tag or SHA to checkout in builds. If empty, check out 49 | # the repository that triggered the workflow. 50 | description: 51 | The branch, tag or SHA to checkout (empty for current branch) 52 | required: false 53 | type: string 54 | boca_repository: 55 | # Git URL of the BOCA repository to clone from. 56 | description: >- 57 | Git URL of the BOCA repository to clone from 58 | (empty for https://github.com/cassiopc/boca.git) 59 | required: false 60 | type: string 61 | boca_ref: 62 | # The branch, tag or SHA to point to in the cloned BOCA repository. 63 | description: 64 | The branch, tag or SHA to checkout (empty for BOCA repository's HEAD) 65 | required: false 66 | type: string 67 | 68 | env: 69 | # Use docker.io for Docker Hub if empty 70 | REGISTRY_HOST: ghcr.io 71 | # Use github.repository (/) 72 | REPOSITORY_NAME: ${{ github.repository }} 73 | # Use github.repository_owner () 74 | OWNER_NAME: ${{ github.repository_owner }} 75 | # Use GitHub local artifacts 76 | DIGESTS_PATH: /tmp/digests 77 | 78 | jobs: 79 | # Distribute build of each platform across multiple runners and 80 | # push by digest 81 | build: 82 | runs-on: ubuntu-latest 83 | permissions: 84 | # for actions/checkout to fetch code 85 | contents: read 86 | # can upload and download package, as well as read/write package metadata 87 | packages: write 88 | strategy: 89 | # If is set to true (default), GitHub will cancel all in-progress and 90 | # queued jobs in the matrix if any job in the matrix fails. 91 | fail-fast: false 92 | matrix: 93 | image: ${{ fromJSON(inputs.images) }} 94 | parent: ${{ fromJSON(inputs.parents) }} 95 | platform: ${{ fromJSON(inputs.platforms) }} 96 | name: 97 | build (${{ matrix.image }}, ${{ matrix.parent }}, ${{ matrix.platform }}) 98 | 99 | steps: 100 | # Setting output parameters between steps, jobs and/or workflows 101 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions 102 | - 103 | name: Write variables to GITHUB_OUTPUT 104 | id: setup 105 | run: | 106 | 107 | # Set OS release 108 | PARENT="${{ matrix.parent }}" 109 | # Replace 'ubuntu:' with '' 110 | RELEASE=${PARENT//ubuntu:/} 111 | echo "$RELEASE" 112 | echo "release_name=$RELEASE" >> "$GITHUB_OUTPUT" 113 | 114 | # Set architecture 115 | PLATFORM="${{ matrix.platform }}" 116 | # Replace 'linux/' with '' and '/' with '-' 117 | ARCH=${PLATFORM//linux\//} 118 | ARCH=${ARCH//\//-} 119 | echo "$ARCH" 120 | echo "arch_name=$ARCH" >> "$GITHUB_OUTPUT" 121 | 122 | # Set base image (used in build) 123 | if [[ "${{ matrix.image }}" == 'boca-base' ]]; 124 | then 125 | BASE_IMAGE="${{ matrix.parent }}" 126 | else 127 | BASE_IMAGE="${{ env.REGISTRY_HOST }}/${{ env.REPOSITORY_NAME }}/" 128 | BASE_IMAGE+="boca-base:${{ github.ref_name }}" 129 | fi 130 | echo "$BASE_IMAGE" 131 | echo "base_image=$BASE_IMAGE" >> "$GITHUB_OUTPUT" 132 | 133 | # Set image 134 | IMG="${{ env.REGISTRY_HOST }}/${{ env.REPOSITORY_NAME }}/" 135 | IMG+="${{ matrix.image }}" 136 | echo "$IMG" 137 | echo "image_name=$IMG" >> "$GITHUB_OUTPUT" 138 | 139 | # Set folder 140 | DIR="${{ matrix.image }}" 141 | # Remove 'boca-' from image name 142 | DIR=${DIR//boca-/} 143 | echo "$DIR" 144 | echo "folder_name=$DIR" >> "$GITHUB_OUTPUT" 145 | 146 | # Set digests path 147 | DIGESTS_PATH="${{ env.DIGESTS_PATH }}/${{ matrix.image }}-${RELEASE}" 148 | echo "$DIGESTS_PATH" 149 | echo "digests_path=$DIGESTS_PATH" >> "$GITHUB_OUTPUT" 150 | 151 | # Set the cache-to output 152 | # https://docs.docker.com/build/cache/backends/gha/ 153 | echo "${{ github.ref_name }}-${{ matrix.image }}-$RELEASE-$ARCH" 154 | CACHE_TO="type=gha,scope=${{ github.ref_name }}-" 155 | CACHE_TO+="${{ matrix.image }}-$RELEASE-$ARCH" 156 | echo "cache-to=$CACHE_TO" >> "${GITHUB_OUTPUT}" 157 | 158 | # Set the cache-from output 159 | if [[ "${{ github.event_name }}" == 'push' ]]; then 160 | echo "${{ github.ref_name }}-${{ matrix.image }}-$RELEASE-$ARCH" 161 | CACHE_FROM="type=gha,scope=${{ github.ref_name }}-" 162 | CACHE_FROM+="${{ matrix.image }}-$RELEASE-$ARCH" 163 | echo "cache-from=$CACHE_FROM" >> "${GITHUB_OUTPUT}" 164 | else 165 | # Use cache from target branch too when building a pull request 166 | # In this case, it has to be a multiline string 167 | # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings 168 | SCOPE_REF_NAME="${{ github.ref_name }}-${{ matrix.image }}" 169 | SCOPE_BASE_REF="${{ github.base_ref }}-${{ matrix.image }}" 170 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) 171 | echo "cache-from<<${EOF}" >> "${GITHUB_OUTPUT}" 172 | printf '%s\n' \ 173 | "type=gha,scope=$SCOPE_REF_NAME-$RELEASE-$ARCH" \ 174 | "type=gha,scope=$SCOPE_BASE_REF-$RELEASE-$ARCH" \ 175 | >> "${GITHUB_OUTPUT}" 176 | echo "${EOF}" >> "${GITHUB_OUTPUT}" 177 | fi 178 | 179 | # Checkout a repository, so the workflow can access it 180 | # https://github.com/actions/checkout 181 | - 182 | name: Checkout repository 183 | uses: actions/checkout@v5 184 | with: 185 | ref: '${{ inputs.ref }}' 186 | 187 | # Add support for more platforms with QEMU (optional) 188 | # https://github.com/docker/setup-qemu-action 189 | - 190 | name: Set up QEMU 191 | uses: docker/setup-qemu-action@v3 192 | 193 | # Create and boot a builder that can be used in the following steps of 194 | # the workflow 195 | # https://github.com/docker/setup-buildx-action 196 | - 197 | name: Set up Docker Buildx 198 | uses: docker/setup-buildx-action@v3 199 | 200 | # Login to a Docker registry (except on PR) 201 | # https://github.com/docker/login-action 202 | - 203 | name: Login to GitHub Container Registry 204 | uses: docker/login-action@v3 205 | with: 206 | registry: ${{ env.REGISTRY_HOST }} 207 | username: ${{ github.actor }} 208 | password: ${{ secrets.GITHUB_TOKEN }} 209 | 210 | # Extract metadata (tags, labels) for Docker 211 | # https://github.com/docker/metadata-action 212 | - 213 | name: Extract Docker metadata (${{ matrix.image }}) 214 | id: meta 215 | uses: docker/metadata-action@v5 216 | with: 217 | images: ${{ steps.setup.outputs.image_name }} 218 | labels: >- 219 | org.opencontainers.image.title=${{ matrix.image }} 220 | 221 | org.opencontainers.image.description=A multi-platform/arch 222 | image of the boca-docker project 223 | 224 | org.opencontainers.image.url=${{ steps.setup.outputs.image_name }} 225 | 226 | - 227 | name: Build and push image remotely (${{ matrix.image }}) 228 | id: build 229 | uses: docker/build-push-action@v6 230 | with: 231 | context: . 232 | file: docker/dev/${{ steps.setup.outputs.folder_name }}/Dockerfile 233 | platforms: ${{ matrix.platform }} 234 | labels: ${{ steps.meta.outputs.labels }} 235 | build-args: | 236 | BASE_IMAGE=${{ steps.setup.outputs.base_image }} 237 | REPOSITORY=${{ inputs.boca_repository }} 238 | REF=${{ inputs.boca_ref }} 239 | # If true, it provides build attestations describing the image 240 | # contents and how they were built. 241 | provenance: false 242 | outputs: >- 243 | type=image,name=${{ steps.setup.outputs.image_name }}, 244 | push-by-digest=true,name-canonical=true,push=true 245 | cache-from: >- 246 | ${{ steps.setup.outputs.cache-from }} 247 | cache-to: >- 248 | ${{ steps.setup.outputs.cache-to }} 249 | 250 | - name: Export digest 251 | id: export 252 | run: | 253 | 254 | mkdir -p ${{ steps.setup.outputs.digests_path }} 255 | digest="${{ steps.build.outputs.digest }}" 256 | touch "${{ steps.setup.outputs.digests_path }}/${digest#sha256:}" 257 | 258 | # Set digests name (used in the next step) 259 | DIGESTS_NAME="digests-${{ matrix.image }}-" 260 | DIGESTS_NAME+="${{ steps.setup.outputs.release_name }}-" 261 | DIGESTS_NAME+="${{ steps.setup.outputs.arch_name }}" 262 | echo "$DIGESTS_NAME" 263 | echo "digests_name=$DIGESTS_NAME" >> "$GITHUB_OUTPUT" 264 | 265 | # Upload artifacts from workflow allowing to share data between jobs 266 | # https://github.com/actions/upload-artifact 267 | - name: Upload digest 268 | uses: actions/upload-artifact@v4 269 | with: 270 | name: 271 | ${{ steps.export.outputs.digests_name }} 272 | path: ${{ steps.setup.outputs.digests_path }}/* 273 | if-no-files-found: error 274 | retention-days: 1 275 | 276 | # This job creates a manifest list and push it to GitHub Container Registry. 277 | merge: 278 | runs-on: ubuntu-latest 279 | strategy: 280 | fail-fast: false 281 | matrix: 282 | image: ${{ fromJSON(inputs.images) }} 283 | parent: ${{ fromJSON(inputs.parents) }} 284 | needs: 285 | - build 286 | 287 | steps: 288 | - 289 | name: Write variables to GITHUB_OUTPUT 290 | id: setup 291 | run: | 292 | 293 | # Set OS release 294 | PARENT="${{ matrix.parent }}" 295 | # Replace 'ubuntu:' with '' 296 | RELEASE=${PARENT//ubuntu:/} 297 | echo "$RELEASE" 298 | echo "release_name=$RELEASE" >> "$GITHUB_OUTPUT" 299 | 300 | # Set image name 301 | IMG="${{ env.REGISTRY_HOST }}/${{ env.REPOSITORY_NAME }}/" 302 | IMG+="${{ matrix.image }}" 303 | echo "$IMG" 304 | echo "image_name=$IMG" >> "$GITHUB_OUTPUT" 305 | 306 | # Set digests path 307 | DIGESTS_PATH="${{ env.DIGESTS_PATH }}/${{ matrix.image }}-${RELEASE}" 308 | echo "$DIGESTS_PATH" 309 | echo "digests_path=$DIGESTS_PATH" >> "$GITHUB_OUTPUT" 310 | 311 | # Set digests pattern 312 | DIGESTS_PATTERN="digests-${{ matrix.image }}-$RELEASE" 313 | echo "$DIGESTS_PATTERN" 314 | echo "digests_pattern=$DIGESTS_PATTERN" >> "$GITHUB_OUTPUT" 315 | 316 | # Set tags 317 | TAGS="${{ inputs.tags }}" 318 | echo "$TAGS" 319 | # Split string into an array 320 | IFS=', ' read -r -a TAGS <<< "$TAGS" 321 | 322 | # Prepare variables 323 | unset EXTRA_TAGS 324 | ENABLED=${{ matrix.parent == inputs.default_parent }} 325 | 326 | # Set tags dynamically 327 | # Nightly build 328 | EXTRA_TAGS="${EXTRA_TAGS} 329 | type=schedule,pattern=nightly,suffix=-${RELEASE} 330 | type=schedule,pattern=nightly,enable=${ENABLED}" 331 | # New release 332 | EXTRA_TAGS="${EXTRA_TAGS} 333 | type=semver,pattern={{raw}},priority=900,suffix=-${RELEASE} 334 | type=semver,pattern={{raw}},priority=1000,enable=${ENABLED}" 335 | EXTRA_TAGS="${EXTRA_TAGS} 336 | type=semver,pattern={{major}}.{{minor}},priority=900," 337 | EXTRA_TAGS="${EXTRA_TAGS}suffix=-${RELEASE} 338 | type=semver,pattern={{major}}.{{minor}},priority=1000," 339 | EXTRA_TAGS="${EXTRA_TAGS}enable=${ENABLED}" 340 | # Tag event (same as new release) 341 | EXTRA_TAGS="${EXTRA_TAGS} 342 | type=ref,event=tag,priority=900,suffix=-${RELEASE} 343 | type=ref,event=tag,priority=1000,enable=${ENABLED}" 344 | # Branch event 345 | EXTRA_TAGS="${EXTRA_TAGS} 346 | type=ref,event=branch,priority=900,suffix=-${RELEASE} 347 | type=ref,event=branch,priority=1000,enable=${ENABLED}" 348 | 349 | for t in ${TAGS[@]}; 350 | do 351 | echo "${t}" 352 | 353 | EXTRA_TAGS="${EXTRA_TAGS} 354 | type=raw,value=${t},priority=1000,enable=${ENABLED} 355 | type=raw,value=${t},priority=900,suffix=-${RELEASE},enable=true" 356 | done 357 | 358 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) 359 | echo "extra_tags<<${EOF}" >> "${GITHUB_OUTPUT}" 360 | printf '%s\n' \ 361 | "${EXTRA_TAGS}" >> "${GITHUB_OUTPUT}" 362 | echo "${EOF}" >> "${GITHUB_OUTPUT}" 363 | 364 | # Download artifacts that have been uploaded from previous jobs 365 | # https://github.com/actions/download-artifact 366 | - 367 | name: Download digests 368 | uses: actions/download-artifact@v5 369 | with: 370 | pattern: 371 | ${{ steps.setup.outputs.digests_pattern }}-* 372 | merge-multiple: true 373 | path: ${{ steps.setup.outputs.digests_path }} 374 | 375 | - 376 | name: Set up Docker Buildx 377 | uses: docker/setup-buildx-action@v3 378 | 379 | - 380 | name: Extract Docker metadata (${{ matrix.image }}) 381 | id: meta 382 | uses: docker/metadata-action@v5 383 | with: 384 | images: ${{ steps.setup.outputs.image_name }} 385 | tags: | 386 | ${{ steps.setup.outputs.extra_tags }} 387 | flavor: >- 388 | latest=${{ matrix.parent == inputs.default_parent && 389 | (github.event_name == 'push' && contains(github.ref, 390 | 'refs/tags/') || inputs.latest == true ) }} 391 | labels: >- 392 | org.opencontainers.image.title=${{ matrix.image }} 393 | 394 | org.opencontainers.image.description=A multi-platform/arch 395 | image of the boca-docker project 396 | 397 | org.opencontainers.image.url=${{ steps.setup.outputs.image_name }} 398 | 399 | - 400 | name: Login to GitHub Container Registry 401 | uses: docker/login-action@v3 402 | with: 403 | registry: ${{ env.REGISTRY_HOST }} 404 | username: ${{ github.actor }} 405 | password: ${{ secrets.GITHUB_TOKEN }} 406 | 407 | - 408 | name: Create manifest list and push 409 | working-directory: ${{ steps.setup.outputs.digests_path }} 410 | run: | 411 | 412 | docker buildx imagetools create \ 413 | $(jq -r '"-t " + (.tags | join(" -t "))' <<< \ 414 | '${{ steps.meta.outputs.json }}') \ 415 | $(printf '${{ steps.setup.outputs.image_name }}@sha256:%s ' *) 416 | 417 | - 418 | name: Inspect image 419 | run: | 420 | 421 | IMG="${{ steps.setup.outputs.image_name }}:" 422 | IMG+="${{ steps.meta.outputs.version }}" 423 | docker buildx imagetools inspect "${IMG}" 424 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :balloon: boca-docker 2 | 3 | [![Build and publish multi-platform_Docker images on ghcr.io workflow][build_publish_workflow_badge]][build_publish_workflow_link] 4 | [![Delete GitHub Actions cache for repository workflow][cache_cleanup_workflow_badge]][cache_cleanup_workflow_link] 5 | [![Delete_untagged_and/or_unsupported_Docker_images_on_ghcr.io_workflow][packages_cleanup_workflow_badge]][packages_cleanup_workflow_link] 6 | [![Close_stale_issues_and_PRs_workflow][close_stale_workflow_badge]][close_stale_workflow_link] 7 | 8 | [![Ubuntu JAMMY][ubuntu_jammy_badge]][ubuntu_jammy_link] 9 | [![Ubuntu FOCAL][ubuntu_focal_badge]][ubuntu_focal_link] 10 | [![Multi-Architecture][arch_badge]][arch_link] 11 | 12 | [![Google_Groups][groups_badge]][groups_link] 13 | 14 | [build_publish_workflow_badge]: https://img.shields.io/github/actions/workflow/status/joaofazolo/boca-docker/ci.yml?label=build%20images&logo=github 15 | [build_publish_workflow_link]: https://github.com/joaofazolo/boca-docker/actions?workflow=CI "build and publish multi-platform images" 16 | [cache_cleanup_workflow_badge]: https://img.shields.io/github/actions/workflow/status/joaofazolo/boca-docker/clean-cache.yml?label=clean%20cache&logo=github 17 | [cache_cleanup_workflow_link]: https://github.com/joaofazolo/boca-docker/actions?workflow=delete%20GitHub "delete github actions cache" 18 | [packages_cleanup_workflow_badge]: https://img.shields.io/github/actions/workflow/status/joaofazolo/boca-docker/clean-packages.yml?label=clean%20packages&logo=github 19 | [packages_cleanup_workflow_link]: https://github.com/joaofazolo/boca-docker/actions?workflow=delete%20untagged "delete untagged/unsupported images" 20 | [close_stale_workflow_badge]: https://img.shields.io/github/actions/workflow/status/joaofazolo/boca-docker/close-stale.yml?label=close%20stale&logo=github 21 | [close_stale_workflow_link]: https://github.com/joaofazolo/boca-docker/actions?workflow=close%20stale "close stale issues and prs" 22 | [ubuntu_jammy_badge]: https://img.shields.io/badge/ubuntu-jammy-E95420.svg?logo=Ubuntu 23 | [ubuntu_focal_badge]: https://img.shields.io/badge/ubuntu-focal-E95420.svg?logo=Ubuntu 24 | [ubuntu_jammy_link]: https://hub.docker.com/_/ubuntu/tags?page=1&name=jammy "ubuntu:jammy image" 25 | [ubuntu_focal_link]: https://hub.docker.com/_/ubuntu/tags?page=1&name=focal "ubuntu:focal image" 26 | [arch_badge]: https://img.shields.io/badge/multi--arch-%20amd64%20|%20arm/v7%20|%20arm64/v8%20|%20ppc64le%20|%20s390x%20-lightgray.svg?logo=Docker&logoColor=white 27 | [arch_link]: #how-to-run-on-different-ubuntu-release-images "multi-arch images" 28 | [groups_badge]: https://img.shields.io/badge/join-boca--users%20group-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAJBlWElmTU0AKgAAAAgABgEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEaAAUAAAABAAAAVgEbAAUAAAABAAAAXgEoAAMAAAABAAIAAIdpAAQAAAABAAAAZgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAACCgAwAEAAAAAQAAACAAAAAAF9yy1AAAAAlwSFlzAAALEwAACxMBAJqcGAAAAm1pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDYuMC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpQaG90b21ldHJpY0ludGVycHJldGF0aW9uPjI8L3RpZmY6UGhvdG9tZXRyaWNJbnRlcnByZXRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzI8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjcyPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpDb21wcmVzc2lvbj4xPC90aWZmOkNvbXByZXNzaW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KsVruIwAABUVJREFUWAnlV81v3EQUfzP22Nn1pmmQ0kqVUAqCSlA4ckNlScKh4sApSPwBSEUExAlIAqoDNO0R0SIqlb+gPXKgICVZ8XGCG0olBFLUQxBN0kTVrr27/pjhvbG96/V+sFJ7QGKiWb9583vf88YOwP99sHESsLiojP2zoLEz26Bu3WLxILlxcYNkh/AUI6XFzYSncs6PiytqgiSqfjZxyABTRM1f9l5QcTxPNDOMjY1l5xeiEww9x8ElGELnRy6KPJvoxIG59fpVQ1SWeJoHicmPw8a1zZXJd0ClWWBMjcTlgila4UUGrauuMimquc8a79mTlaU48KKw6YU0iSYe7QEapvmvONSV6Oy3NtCBmssiDWVwIfQ1SZkS6WSax+CtjjqkR+AuEK6jsyOUEP0loLRiVC9eVtNW3PiTGeZjSsZ0FjKsYtxgyDtqm81ZUmNHpbvImx6Ii6PDwKg89dMyO9Ilo6zlRn8GUoAWYGyfcawGgMzJyJS3//MHM3WauDcchzq0LlJQME6sfgeQ2amXguuirB0gr8N0Ks1T8BWuk4H0CNx1AnV0piLZI0trts49sy7wrpp2eYmlXaCwC6K2j13g9HTBy5fq18RE5W3qFvJW4dGJghQ3oguGO+C6vAoXOR2e+fX6s0rCOfKOcfhhY2XyDkVUuwgxrAGrYiYJt/Bp/RnJ4CVKrDLNH7c+tLer7hbiqvGg9Gt99FMcdNNl1+35L5TdbNSf4JKdADBB8nCvVJnc+fZd1s7LLVw5nIrBOm0oYzpWKhRx8Nf3Hx/f0Rg62OgouCx/lvRWXwYWb6Lx1/GudxWfE/4yJvNNbk7MGlZSgziIQUatu5iLG5urzqWFK2oqjrxPGIM3DKs8w01UiTWIggCUDPHNwT5H3NdkzUWdbsGJHgcy46+43qlYqO8sx3kuaoWg4kCiTu09CnAwLG6VBTw4OPjVMMypyvTxpwOvjQYjwuAktcrgosQQCkHDu70XOa9tuyygwPKZ6DqQ9j/WtsKF95s54ZyOWqg1uYBy3UKXH5dBGEY7+3uWxTnMnjgZcM5NpVB5fjA8Iwoiq+LYgedtbq5U5vPbRHcEqms1nWNDeF8Kp2PczmMSYfQZDd27v2e127584NflwdGBhbcX6sI85YcC0mljBtqYzbn59cb7tI1B6t4mWmcgq838euuMUvHvZAEn7nUTRGAywLEfvWYD/vh7FyzDSJC4c+bU4yCEheuCE7iHWiQYgqs4vOeEzpPfuMxP9Scpq6WZkCp6VZTKKALYxUXjxMaB7EbT14YpYDx8EMYx+C0faTbQAYSR8ciwnZO+7Z0jNYs3k+x3SqB1K3g+9X+odYqwHQaYiV5IG0+91tHL1rzsB48L5eIsrffvJBF2aqFBDJxiGTUfPwHwqX0jB6SUOvKsSOSLxJsqQQz1IEJRUyp1LNWpH70O5HeIxozSn2k7hk5YmnJRmkZjDTCyr5Q4Agt5ZqmEzhWVJGv0zxRY3ahF9e+OUQ6QccBe5mHLu41RHqKYyTHAY0Kpw6ABEotO9mz84CgLBUHTo3etYtmXUmoHI8feBTM+hD0RyhvE1te4S3fr4EHplqLkGGGzsbb10SRCR4/d0duFXSxe+mrucYAOcYqUouyI0PfdzdXJNXqhYPfiVg1n8qxlJHKIXa0SoX+IGDp05Lnvgh4H8IAJcgFvQTv0G9p4cj3TIez9ktEWal072qHucjjl9m7xwnLXLOGRb/urFHlqHMs8wHiv4MOuqKEAzrv3j9H/AFqbPkgJ/2G1jynfNUZX85hCjxo2+F+sR23lP6XvH9zRm0CuC6knAAAAAElFTkSuQmCC 29 | [groups_link]: https://groups.google.com/g/boca-users "boca-users@Google Groups" 30 | 31 | ## Table of Contents 32 | 33 | - [What Is BOCA?](#what-is-boca) 34 | - [Why boca-docker?](#why-boca-docker) 35 | - [Requirements](#requirements) 36 | - [Quick Start](#quick-start) 37 | - [How To Deploy It To A Swarm](#how-to-deploy-it-to-a-swarm) 38 | - [How To Add Custom Configuration](#how-to-add-custom-configuration) 39 | - [How To Run On Different Ubuntu Release Images](#how-to-run-on-different-ubuntu-release-images) 40 | - [How To Build It (For Development)](#how-to-build-it-for-development) 41 | - [How To Publish It](#how-to-publish-it) 42 | - [How To Contribute](#how-to-contribute) 43 | - [Known Issues](#known-issues) 44 | - [Who Is Using It?](#who-is-using-it) 45 | - [License](#license) 46 | - [Support](#support) 47 | 48 | ## What Is BOCA? 49 | 50 | BOCA Online Contest Administrator (known simply as BOCA) is an administration system to held programming contests (e.g., ACM-ICPC, Maratona de Programação da SBC). 51 | According to the developers, its main features are portability, concurrency control, multi-site and distributed contests, and a simple web interface (for details refer to [https://github.com/cassiopc/boca](https://github.com/cassiopc/boca)). 52 | 53 | BOCA is implemented mainly in PHP and makes use of a PostgreSQL database in the backend (see architecture below). It is a good example of a monolithic system, in which the user interface and database access are all interwoven, rather than containing architecturally separate components. 54 | The problem is compound due to the low readability and complex code structuring, which is hard to adapt and to extend. 55 | 56 | ## Why boca-docker? 57 | 58 | The _boca-docker_ project is a use case of how we can take advantage of microservices architecture and containerization technology (i.e., Docker) to deploy applications in a more convenient and faster way (see illustration below). 59 | After quite some reverse engineering, we provide a multi-platform/arch Docker version of BOCA's main components (web app, online automated judge and database) aiming at easing the customization, extensibility and automation of the operational effort required to deploy, run and scale BOCA. 60 | 61 | Original architecture | _boca-docker_ architecture 62 | :-------------------------:|:-------------------------: 63 | ![Alt text](/imgs/arquitetura-boca.png?raw=true "BOCA architecture") | ![Alt text](/imgs/arquitetura-boca-docker.png?raw=true "boca-docker architecture") 64 | 65 | This work started as part of the undergraduate final year project carried out by João Vitor Alves Fazolo under supervision of Prof. Dr. Rodrigo Laiola Guimaraes at Universidade Federal do Espirito Santo ([UFES](https://www.ufes.br/)). 66 | 67 | ## Requirements 68 | 69 | - Install [Docker Desktop](https://www.docker.com/get-started); 70 | - Install [Git](https://github.com/git-guides/install-git) (only for building and publishing). 71 | 72 | ## Quick Start 73 | 74 | - Open a Terminal window and make sure the Docker engine is up and running: 75 | 76 | ```sh 77 | # List docker images 78 | docker image ls 79 | # List containers 80 | docker container ls -a 81 | ``` 82 | 83 | - Download the `docker-compose.yml` and `docker-compose.prod.yml` files, and place them in the current work directory. Then, 84 | 85 | ```sh 86 | docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d 87 | ``` 88 | 89 | - Voilà! The application should be running now. Open a web browser and visit the URL [http://localhost:8000/boca](http://localhost:8000/boca). First, create and activate a BOCA contest (user: _system_ | password: _boca_). Then, login as admin (user: _admin_ | password: _boca_) to manage users, problems, languages etc; 90 | 91 | > **NOTE:** consider changing these passwords later on. Find out more information on how to setup a contest in the [documentation](https://github.com/cassiopc/boca/tree/master/doc). For general questions about BOCA consider looking at this [forum](https://groups.google.com/g/boca-users). 92 | 93 | - To stop the application (considering that the shell is in the same directory): 94 | 95 | ```sh 96 | docker compose -f docker-compose.yml -f docker-compose.prod.yml down 97 | ``` 98 | 99 | ## How To Deploy It To A Swarm 100 | 101 | - Create the stack (make sure Docker Engine is already running in [swarm mode](https://docs.docker.com/engine/swarm/swarm-mode/)): 102 | 103 | ```sh 104 | docker stack deploy --compose-file docker-compose.yml -c docker-compose.prod.yml boca-stack 105 | ``` 106 | 107 | - Then, check if the stack is running: 108 | 109 | ```sh 110 | docker stack services boca-stack 111 | ``` 112 | 113 | - Open a web browser and follow the instructions described above; 114 | 115 | - To bring the stack down: 116 | 117 | ```sh 118 | docker stack rm boca-stack 119 | ``` 120 | 121 | ## How To Add Custom Configuration 122 | 123 | There are many ways to customize the _boca-docker_ application. Without trying to support every possible use case, here are just a few that we have found useful. 124 | 125 | - **Environment Variables:** shows the correct syntax for the various ways one can change predefined configuration values to keep the _boca-docker_ application flexible and organized. See [documentation](tests/env/README.md); 126 | 127 | - **Docker Secrets:** an alternative way to passing sensitive information via environment variables, causing the initialization scripts to load the values for those variables from files present in the containers. See [documentation](tests/secrets/README.md); 128 | 129 | - **Networking:** shows how to add network isolation between services in the _boca-docker_ application. See [documentation](tests/networks/README.md); 130 | 131 | - **Volumes:** demonstrates how to store data outside the database container, so that the state of the application persists even after the container stops. See [documentation](tests/volumes/README.md); 132 | 133 | - **Migrations:** illustrates how to backup and restore BOCA's database to facilitate migration from one _boca-docker_ instance to another. See [documentation](tests/migrations/README.md); 134 | 135 | - **Healthcheck:** allows a check to be configured in order to determine whether or not the PostgreSQL container is "healthy." This is a particularly neat use case given that the other services depend on that to work. See [documentation](tests/healthcheck/README.md); 136 | 137 | - **Multiple Platforms:** shows the syntax for selecting an image that matches a specific OS and architecture (alternatively, Docker does that automatically). See [documentation](tests/platforms/README.md). 138 | 139 | ## How To Run On Different Ubuntu Release Images 140 | 141 | To run the _boca-docker_ application built on top of different versions of Ubuntu images, please edit the `docker-compose.prod.yml` file with an alternative tag from the table below. 142 | 143 | Tag name | BOCA version | Ubuntu version | Code name | Architecture 144 | -------- | ------------ | -------------- | --------- | ------------ 145 | `latest`, `1.2`, `1.2-jammy`, `1.2.2`, `1.2.2-jammy` | 1.5 | 22.04 LTS | Jammy Jellyfish | `amd64`, `arm/v7`, `arm64/v8`, `ppc64le`, `s390x` 146 | `1.2-focal`, `1.2.2-focal` | 1.5 | 20.04 LTS | Focal Fossa | `amd64`, `arm/v7`, `arm64/v8`, `ppc64le`, `s390x` 147 | `nightly`, `nightly-jammy` | 1.5 | 22.04 LTS | Jammy Jellyfish | `amd64`, `arm/v7`, `arm64/v8`, `ppc64le`, `s390x` 148 | `nightly-focal` | 1.5 | 20.04 LTS | Focal Fossa | `amd64`, `arm/v7`, `arm64/v8`, `ppc64le`, `s390x` 149 | 150 | For example, to use BOCA version 1.5 running on Ubuntu 20.04 LTS (Focal Fossa) on any supported architecture: 151 | 152 | ```sh 153 | ... 154 | services: 155 | ... 156 | 157 | # web app 158 | boca-web: 159 | image: ghcr.io/joaofazolo/boca-docker/boca-web:1.2-focal 160 | ... 161 | 162 | ... 163 | # online judge 164 | boca-jail: 165 | image: ghcr.io/joaofazolo/boca-docker/boca-jail:1.2-focal 166 | ... 167 | ``` 168 | 169 | ### Deprecated Image Tags 170 | 171 | The following image tags have been deprecated and are no longer receiving updates: 172 | 173 | - 1.2.1 174 | - 1.2.0 175 | - 1.1.0 176 | - 1.0.0 177 | 178 | ## How To Build It (For Development) 179 | 180 | - Clone this repository and set it as your working directory: 181 | 182 | ```sh 183 | git clone https://github.com/joaofazolo/boca-docker.git 184 | cd boca-docker 185 | ``` 186 | 187 | - Then, build the images (this might take a while, sit back and relax): 188 | 189 | ```sh 190 | docker build -t boca-base . -f docker/dev/base/Dockerfile 191 | docker build -t boca-web . -f docker/dev/web/Dockerfile 192 | docker build -t boca-jail . -f docker/dev/jail/Dockerfile 193 | ``` 194 | 195 | > **NOTE:** Keep in mind that these Docker images are created for and to run on the default platform (i.e., `linux/amd64`). This works for the majority of development machines and cloud provider versions. To build target-specific or multi-platform Docker images consult the [documentation](https://docs.docker.com/build/building/multi-platform/); 196 | 197 | - To compose it up use the command below: 198 | 199 | ```sh 200 | docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d 201 | ``` 202 | 203 | - Follow the instructions [above](#quick-start) to set up the application; 204 | 205 | - To stop it: 206 | 207 | ```sh 208 | docker compose -f docker-compose.yml -f docker-compose.dev.yml down 209 | ``` 210 | 211 | ## How To Publish It 212 | 213 | > **NOTE:** These instructions take into account the Docker images generated in the previous section (no multi-platform support). 214 | 215 | - After building, set the user and image tags accordingly. The IMAGE_ID's will show up with the `docker image ls`; 216 | 217 | ```sh 218 | docker image ls 219 | # boca-base only necessary for development 220 | # docker tag IMAGE_ID_BOCA_BASE ghcr.io/joaofazolo/boca-docker/boca-base:1.2.2 221 | docker tag IMAGE_ID_BOCA_WEB ghcr.io/joaofazolo/boca-docker/boca-web:1.2.2 222 | docker tag IMAGE_ID_BOCA_JAIL ghcr.io/joaofazolo/boca-docker/boca-jail:1.2.2 223 | ``` 224 | 225 | - Log in into GitHub's Container Registry using your username and personal access token (see [documentation](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry)); 226 | 227 | ```sh 228 | docker login ghcr.io 229 | ``` 230 | 231 | - Push the container images to repository. 232 | 233 | ```sh 234 | # boca-base only necessary for development 235 | # docker push ghcr.io/joaofazolo/boca-docker/boca-base:1.2.2 236 | docker push ghcr.io/joaofazolo/boca-docker/boca-web:1.2.2 237 | docker push ghcr.io/joaofazolo/boca-docker/boca-jail:1.2.2 238 | ``` 239 | 240 | ## How To Contribute 241 | 242 | If you would like to help contribute to this project, please see [CONTRIBUTING](https://github.com/joaofazolo/boca-docker/blob/master/CONTRIBUTING.md). 243 | 244 | Before submitting a PR consider building and testing a Docker image locally and checking your code with Super-Linter: 245 | 246 | ```sh 247 | docker run --rm \ 248 | -e ACTIONS_RUNNER_DEBUG=true \ 249 | -e RUN_LOCAL=true \ 250 | --env-file ".github/super-linter.env" \ 251 | -v "$PWD":/tmp/lint \ 252 | ghcr.io/super-linter/super-linter:latest 253 | ``` 254 | 255 | ## Known Issues 256 | 257 | - Rosetta for x86_64/amd64 emulation must be disabled on Apple Silicon 258 | (ARM-based chips) for the online automated judge (boca-jail) to work (tested 259 | on Apple M1, Docker Desktop 4.28.0, Engine: 25.0.3, Compose: v2.24.6-desktop.1, 260 | Mar 2024); 261 | 262 | ## Who Is Using It? 263 | 264 |

265 | 266 | Ufes 267 | 268 | 269 | Unisinos 270 | 271 |

272 | 273 | ## License 274 | 275 | Copyright Universidade Federal do Espirito Santo (Ufes) 276 | 277 | This program is free software: you can redistribute it and/or modify 278 | it under the terms of the GNU General Public License as published by 279 | the Free Software Foundation, either version 3 of the License, or 280 | (at your option) any later version. 281 | 282 | This program is distributed in the hope that it will be useful, 283 | but WITHOUT ANY WARRANTY; without even the implied warranty of 284 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 285 | GNU General Public License for more details. 286 | 287 | You should have received a copy of the GNU General Public License 288 | along with this program. If not, see . 289 | 290 | This program is released under license GNU GPL v3+ license. 291 | 292 | ## Support 293 | 294 | Please report any issues with _boca-docker_ at [https://github.com/joaofazolo/boca-docker/issues](https://github.com/joaofazolo/boca-docker/issues) 295 | --------------------------------------------------------------------------------