` 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 |  | 
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 |
267 |
268 |
269 |
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 |
--------------------------------------------------------------------------------