diff --git a/.gitguardian b/.gitguardian new file mode 100644 index 00000000..809ac388 --- /dev/null +++ b/.gitguardian @@ -0,0 +1,4 @@ +secret: + # Exclude files and paths by globbing + ignored_paths: + - 'provision-contest/ansible/group_vars/**/secret.yml.example' diff --git a/.github/linting.sh b/.github/linting.sh index 65ae7b8a..25c9b386 100755 --- a/.github/linting.sh +++ b/.github/linting.sh @@ -5,10 +5,10 @@ set -euxo pipefail # shellcheck disable=SC2044 for book in $(find ./ -maxdepth 1 -name "*.yml"); do if [ "$book" != "./handlers.yml" ]; then - ansible-lint "$book" -x braces,line-length + ansible-lint "$book" -x 'braces,yaml[line-length]' fi done # shellcheck disable=SC2044 for dir in $(find ./roles -maxdepth 1 -type d); do - ansible-lint "$dir" -x braces,line-length + ansible-lint "$dir" -x 'braces,yaml[line-length]' done diff --git a/.github/workflows/ansible-linting.yml b/.github/workflows/ansible-linting.yml deleted file mode 100644 index 2bc0eeeb..00000000 --- a/.github/workflows/ansible-linting.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Test contest deployment (ansible scripts) - -on: [push,pull_request] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v2 - - name: Install ansible lint tools - run: sudo apt update; sudo pip install ansible-lint - - name: Lint the different scripts - run: | - set -eux - ansible-lint --version - ansible-lint . - working-directory: provision-contest/ansible - - name: Lint the different scripts (Via the script) - run: ../../.github/linting.sh - working-directory: provision-contest/ansible - diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 91187d0a..1594e8eb 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -7,7 +7,7 @@ jobs: name: Shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: diff --git a/contest-api/check-api-consistency.php b/contest-api/check-api-consistency.php index eebfb44c..9229c753 100755 --- a/contest-api/check-api-consistency.php +++ b/contest-api/check-api-consistency.php @@ -36,6 +36,10 @@ function warning($msg) foreach ($feed_json as $row) { $endpoint = $row['type']; + if ($endpoint === 'contest') { + // New feed format uses singular for contest + $endpoint = 'contests'; + } $id = isset($row['data']['id']) ? $row['data']['id'] : '_single_'; $feed_data[$endpoint][$id][] = $row; } @@ -62,30 +66,39 @@ function array_diff_keys($a, $b) error("'state' cannot have ID '$id' set."); } for ($i=0; $i $rows) { - if ($rows[0]['op']!=='create') { + if (isset($rows[0]['op']) && $rows[0]['op']!=='create') { error("'$endpoint/$id' not created first."); } for ($i=1; $i $elements) { foreach ($elements as $id => $rows) { $last = end($rows); - if ($last['op']!=='delete') { + if ((isset($last['op']) && $last['op']!=='delete') || (!isset($last['op']) && isset($last['data'])) ) { if (!isset($endpoint_data[$endpoint][$id])) { error("'$endpoint".($id==='_single_' ? '' : "/$id")."' not found in REST endpoint."); } elseif ($last['data']!==$endpoint_data[$endpoint][$id]) { diff --git a/contest-api/check-api.sh b/contest-api/check-api.sh index 7e611d74..791c1bb7 100755 --- a/contest-api/check-api.sh +++ b/contest-api/check-api.sh @@ -140,17 +140,11 @@ while getopts 'a:Cc:dehj:npt:q' OPT ; do t) FEED_TIMEOUT="$OPTARG" ;; q) QUIET=1 ;; :) - error "option '$OPTARG' requires an argument." - exit 1 - ;; + error "option '$OPTARG' requires an argument." ;; ?) - error "unknown option '$OPTARG'." - exit 1 - ;; + error "unknown option '$OPTARG'." ;; *) - error "unknown error reading option '$OPT', value '$OPTARG'." - exit 1 - ;; + error "unknown error reading option '$OPT', value '$OPTARG'." ;; esac done shift $((OPTIND-1)) @@ -161,7 +155,6 @@ API_URL="$1" if [ -z "$API_URL" ]; then error "API URL argument expected." - exit 1 fi TMP=$(mktemp -d) @@ -327,6 +320,7 @@ $ENDPOINT" fi if [ -n "$CHECK_CONSISTENCY" ]; then + ENDPOINTS_CHECK_CONSISTENT="${ENDPOINTS_CHECK_CONSISTENT/accounts/}" # shellcheck disable=SC2086 eval ${EXTRAPROP:-STRICT=1} "$MYDIR"/check-api-consistency.php "$TMP/$CONTEST" $ENDPOINTS_CHECK_CONSISTENT EXIT=$? diff --git a/contest-api/json-schema/common.json b/contest-api/json-schema/common.json index f7d0811e..e0ed6fd6 100644 --- a/contest-api/json-schema/common.json +++ b/contest-api/json-schema/common.json @@ -27,6 +27,30 @@ ] }, + "endpointssingularcontest": { + "enum": [ + "contest", + "judgement-types", + "languages", + "problems", + "groups", + "organizations", + "persons", + "team-members", + "accounts", + "teams", + "state", + "submissions", + "judgements", + "runs", + "clarifications", + "awards", + "commentary", + "scoreboard", + "event-feed" + ] + }, + "abstime": { "type": "string", "pattern": "^[12][0-9]{3}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](\\.[0-9]{3})?([+-][0-1][0-9](:[0-5][0-9])?|Z)$" @@ -120,10 +144,12 @@ "fileref": { "type": "object", "properties": { - "href": { "type": "string" }, - "mime": { "type": "string" }, - "width": { "type": "integer", "minimum": 1 }, - "height": { "type": "integer", "minimum": 1 } + "href": { "type": "string" }, + "mime": { "type": "string" }, + "hash": { "type": "string" }, + "filename": { "type": "string" }, + "width": { "type": "integer", "minimum": 1 }, + "height": { "type": "integer", "minimum": 1 } }, "required": ["href", "mime"], "$ref": "#/strictproperties" diff --git a/contest-api/json-schema/contest.json b/contest-api/json-schema/contest.json index e2ff4230..33135643 100644 --- a/contest-api/json-schema/contest.json +++ b/contest-api/json-schema/contest.json @@ -12,6 +12,7 @@ "countdown_pause_time": { "$ref": "common.json#/posreltimeornull" }, "duration": { "$ref": "common.json#/posreltime" }, "scoreboard_freeze_duration": { "$ref": "common.json#/posreltimeornull" }, + "scoreboard_thaw_time": { "$ref": "common.json#/abstimeornull" }, "penalty_time": { "type": "integer", "minimum": 0 diff --git a/contest-api/json-schema/event-feed-array.json b/contest-api/json-schema/event-feed-array.json index 69d102c8..0f4d7311 100644 --- a/contest-api/json-schema/event-feed-array.json +++ b/contest-api/json-schema/event-feed-array.json @@ -4,9 +4,12 @@ "description": "JSON array of responses of this NDJSON API call", "type": "array", - "uniqueItems": true, "$ref": "common.json#/nonemptyarray", "items": { - "$ref": "event-feed.json#" + "$comment": "Use anyOf since the event type fields overlap between the current and legacy format.", + "anyOf": [ + { "$ref": "event-feed.json#" }, + { "$ref": "legacy-event-feed.json#" } + ] } } diff --git a/contest-api/json-schema/event-feed.json b/contest-api/json-schema/event-feed.json index 656f5e05..cce6d633 100644 --- a/contest-api/json-schema/event-feed.json +++ b/contest-api/json-schema/event-feed.json @@ -5,13 +5,23 @@ "type": "object", "properties": { - "id": { "$ref": "common.json#/identifier" }, - "type": { "$ref": "common.json#/endpoints" } + "id": { + "oneOf": [ + { "$ref": "common.json#/identifier" }, + { "type": "null" } + ] + }, + "token": { + "oneOf": [ + { "type": "string" }, + { "type": "null" } + ] + }, + "type": { "$ref": "common.json#/endpointssingularcontest" } }, "oneOf": [ { "properties": { - "op": { "enum": [ "create", "update" ] }, "data": { "$comment": "Use anyOf since some types match others without strict attribute checking.", "anyOf": [ @@ -38,18 +48,12 @@ }, { "properties": { - "op": { "enum": [ "delete" ] }, "data": { - "type": "object", - "properties": { - "id": { "$ref": "common.json#/identifier" } - }, - "required": ["id"], - "$ref": "common.json#/strictproperties" + "type": "null" } } } ], - "required": ["id", "type", "op", "data"], + "required": ["id", "type", "data"], "$ref": "common.json#/strictproperties" } diff --git a/contest-api/json-schema/legacy-event-feed.json b/contest-api/json-schema/legacy-event-feed.json new file mode 100644 index 00000000..656f5e05 --- /dev/null +++ b/contest-api/json-schema/legacy-event-feed.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CLICS Contest API: event-feed", + "description": "Single line response of this NDJSON API call", + + "type": "object", + "properties": { + "id": { "$ref": "common.json#/identifier" }, + "type": { "$ref": "common.json#/endpoints" } + }, + "oneOf": [ + { + "properties": { + "op": { "enum": [ "create", "update" ] }, + "data": { + "$comment": "Use anyOf since some types match others without strict attribute checking.", + "anyOf": [ + { "$ref": "contest.json#" }, + { "$ref": "judgement-type.json#" }, + { "$ref": "language.json#" }, + { "$ref": "problem.json#" }, + { "$ref": "group.json#" }, + { "$ref": "organization.json#" }, + { "$ref": "team.json#" }, + { "$ref": "person.json#" }, + { "$ref": "team-member.json#" }, + { "$ref": "account.json#" }, + { "$ref": "state.json#" }, + { "$ref": "submission.json#" }, + { "$ref": "judgement.json#" }, + { "$ref": "run.json#" }, + { "$ref": "clarification.json#" }, + { "$ref": "award.json#" }, + { "$ref": "commentary.json#" } + ] + } + } + }, + { + "properties": { + "op": { "enum": [ "delete" ] }, + "data": { + "type": "object", + "properties": { + "id": { "$ref": "common.json#/identifier" } + }, + "required": ["id"], + "$ref": "common.json#/strictproperties" + } + } + } + ], + "required": ["id", "type", "op", "data"], + "$ref": "common.json#/strictproperties" +} diff --git a/contest-api/json-schema/problem.json b/contest-api/json-schema/problem.json index e570d9a4..6f250687 100644 --- a/contest-api/json-schema/problem.json +++ b/contest-api/json-schema/problem.json @@ -31,7 +31,9 @@ "test_data_count": { "type": "integer", "minimum": 0 - } + }, + "package": { "$ref": "common.json#/filerefsornull" }, + "statement": { "$ref": "common.json#/filerefsornull" } }, "required": ["id", "label", "name", "ordinal", "test_data_count"], "$ref": "common.json#/strictproperties" diff --git a/contest-api/json-schema/scoreboard.json b/contest-api/json-schema/scoreboard.json index aeda4e46..d2cff4bc 100644 --- a/contest-api/json-schema/scoreboard.json +++ b/contest-api/json-schema/scoreboard.json @@ -72,5 +72,5 @@ } } }, - "required": ["event_id", "time", "contest_time", "state", "rows"] + "required": ["time", "contest_time", "state", "rows"] } diff --git a/domlogo/domlogo.py b/domlogo/domlogo.py index f8bd7285..4272537d 100755 --- a/domlogo/domlogo.py +++ b/domlogo/domlogo.py @@ -1,13 +1,80 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 +import subprocess -import PySimpleGUI as sg +import FreeSimpleGUI as sg import glob import os import requests import re import time +import platform +import shlex +import yaml +from PIL import Image, ImageDraw, ImageFont + + +def generate_placeholder(text: str, path: str, background_color, width: int, height: int): + image = Image.new("RGB", (width, height), background_color) + draw = ImageDraw.Draw(image) + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72) + text_bbox = draw.textbbox((0, 0), text, font=font) + text_width = text_bbox[2] - text_bbox[0] + text_height = text_bbox[3] - text_bbox[1] + text_x = (width - text_width) / 2 + text_y = (height - text_height) / 2 + draw.text((text_x, text_y), text, fill=(255,255,255), font=font) + + # Add some hint how to replace the image. + small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16) + placeholder_text = f'(Replace this placeholder image at {path})' + text_bbox = draw.textbbox((0, 0), placeholder_text, font=small_font) + text_width = text_bbox[2] - text_bbox[0] + text_x = (width - text_width) / 2 + text_y = text_y + 1.4*text_height + draw.text((text_x, text_y), placeholder_text, fill=(195,196,199), font=small_font) + image.save(path) + + +def download_image(image_type: str, entity_id: str, file: dict): + href = file['href'] + filename = file['filename'] + photo_head = requests.head(f'{api_url}/{href}', auth=(user, passwd)) + etag = photo_head.headers['ETag'] + etag_file = f'domlogo-files/{image_type}s/{entity_id}.etag.txt' + temp_file = f'domlogo-files/{image_type}s/temp-{entity_id}-{filename}' + existing_etag = None + if os.path.isfile(etag_file): + with open(etag_file) as f: + existing_etag = f.readline().strip() + + if existing_etag != etag: + print(f'Downloading and converting {image_type} for entity with ID {entity_id}...') + os.makedirs(os.path.dirname(temp_file), exist_ok=True) + with open(temp_file, 'wb') as f: + f.write(requests.get(f'{api_url}/{href}', auth=(user, passwd)).content) + + return True, temp_file, etag_file, etag + + return False, None, None, None + font = ('Roboto', 14) +mono_font = ('Liberation Mono', 32) +host = platform.node() +host_bg_color = 'black' + +idle_image = 'domlogo-files/photos/idle.png' +if not os.path.isfile(idle_image): + os.makedirs(os.path.dirname(idle_image), exist_ok=True) + generate_placeholder('judgehost idle', idle_image, (10,75,120), 1024, 768) + +config_file = 'domlogo-files/config.yaml' +if os.path.isfile(config_file) and os.access(config_file, os.R_OK): + with open(config_file, 'r') as f: + config = yaml.safe_load(f) + if 'host-bg-color' in config: + host_bg_color = config['host-bg-color'] + team_image = sg.Image(filename='domlogo-files/photos/idle.png') metadata_text = sg.Text('No submissions in queue.', font=font) results_text = sg.Text('', font=font) @@ -21,9 +88,10 @@ [sg.Image(filename=c[0]), sg.Text(c[1], font=font), sg.Canvas(size=(10,50))] for c in cache ] layout = [ + [sg.Push(), sg.Text(f'{host}', font=mono_font, background_color=host_bg_color), sg.Push()], [sg.Column(current_column), sg.VerticalSeparator(), sg.Column(previous_column)], ] -window = sg.Window('DOMlogo', layout, location=(1000,0), keep_on_top=True) +window = sg.Window('DOMlogo', layout, location=(600,50), keep_on_top=True, no_titlebar=True) with open('etc/restapi.secret', 'r') as secrets: while True: @@ -36,11 +104,72 @@ break print(f'Using {api_url} as endpoint.') -contests = requests.get(f'{api_url}/contests', auth=(user,passwd)).json() -latest_contest = sorted(contests, key=lambda c: c['end_time'])[-1] -cid = latest_contest['id'] -api_url = f'{api_url}/contests/{cid}' -print(f'Contest is {cid}.') +print('Loading teams and organizations from API') +teams = {team['id']: team for team in requests.get(f'{api_url}/teams', auth=(user, passwd)).json()} +for team_id in teams: + if 'display_name' not in teams[team_id]: + teams[team_id]['display_name'] = teams[team_id]['name'] +organizations = {org['id']: org for org in requests.get(f'{api_url}/organizations', auth=(user, passwd)).json()} + +print('Downloading any new or changed logos and photos...') +for organization in organizations.values(): + if 'logo' in organization: + organization_id = organization['id'] + logo = organization['logo'][0] + downloaded, downloaded_to, etag_file, etag = download_image('logo', organization_id, logo) + if downloaded_to: + # Convert to both 64x64 (for sidebar) and 160x160 (for overlay over photo) + downloaded_to_escaped = shlex.quote(downloaded_to) + target = shlex.quote(f'domlogo-files/logos/{organization_id}.png') + command = f'convert {downloaded_to_escaped} -resize 64x64 -background none -gravity center -extent 64x64 {target}' + os.system(command) + + target = shlex.quote(f'domlogo-files/logos/{organization_id}.160.png') + command = f'convert {downloaded_to_escaped} -resize 160x160 -background none -gravity center -extent 160x160 {target}' + os.system(command) + + with open(etag_file, 'w') as f: + f.write(etag) + + os.unlink(downloaded_to) + +for team in teams.values(): + if 'photo' in team and team['display_name'] != 'DOMjudge': + team_id = team['id'] + photo = team['photo'][0] + downloaded, downloaded_to, etag_file, etag = download_image('photo', team_id, photo) + if downloaded_to: + # First convert to a good known size because adding the annotation and logo assumes this + intermediate_target = f'domlogo-files/photos/{team_id}-intermediate.png' + command = f'convert {downloaded_to} -resize 1024x1024 -gravity center {intermediate_target}' + os.system(command) + + # Now add logo and team name. We use subprocess.run here to escape the team name + target = f'domlogo-files/photos/{team_id}.png' + organization_id = team['organization_id'] + logo_file = f'domlogo-files/logos/{organization_id}.png' + command = [ + 'convert', + intermediate_target, + '-fill', 'white', + '-undercolor', '#00000080', + '-gravity', 'south', + '-font', 'Ubuntu', + '-pointsize', '30', + '-annotate', '+5+5', f' {team["display_name"]} ', + logo_file, + '-gravity', 'northeast', + '-composite', + target + ] + + subprocess.run(command) + + with open(etag_file, 'w') as f: + f.write(etag) + + os.unlink(downloaded_to) + os.unlink(intermediate_target) latest_logfile = max(glob.glob('output/log/judge.*-2.log'), key=os.path.getctime) print(f'Checking logfile {latest_logfile}') @@ -50,13 +179,15 @@ results = [] last_seen, needs_update = (None, None) while True: - event, values = window.read(timeout=30) - if event == sg.WIN_CLOSED: - break + if needs_update is None: + event, values = window.read(timeout=1) + if event == sg.WIN_CLOSED: + break line = logfile.readline() # Sleep here for a tiny amount of time to avoid using too much CPU. - if len(line) == 0: - time.sleep(0.01) + if len(line) == 0 and needs_update is None: + time.sleep(0.1) + needs_update = None if 'Working directory:' in line: token = line.strip().split('/') judging_id = token[-1] @@ -69,8 +200,8 @@ team_id = submission_data['team_id'] last_seen = (submission_id, judging_id, team_id) new_filename = f'domlogo-files/photos/{team_id}.png' - if (int)(team_id) >= 120: - new_filename = f'domlogo-files/photos/crew.png' + if not os.path.isfile(new_filename): + generate_placeholder(f'team {team_id}', new_filename, (137,28,28), 1024, 768) team_image.update(filename=new_filename) metadata_text.update(f's{submission_id} / {submission_data["problem_id"]} / {submission_data["language_id"]}') results_text.update('Busy compiling.') @@ -89,7 +220,6 @@ '.{1,78}', ' '.join(results)))) if needs_update: sid, jid, tid = needs_update - needs_update = None judging_data = requests.get(f'{api_url}/judgements/{jid}', auth=(user,passwd)).json() verdict = judging_data['judgement_type_id'] or 'pending' color = 'firebrick1' @@ -99,9 +229,16 @@ color = 'DeepSkyBlue' for i in range(len(cache)-1): cache[i] = cache[i+1] - if (int)(tid) >= 120: - tid = 'DOMjudge' - cache[-1] = (f'domlogo-files/logos/{tid}.png', f's{sid}/j{jid}\n{verdict}', color, jid) + organization_id = None + if tid in teams: + organization_id = teams[tid]['organization_id'] + organization_logo = 'domlogo-files/logos/DOMjudge.png' + # Organization ID is null for internal teams so explicitly check for it + if organization_id: + potential_organization_logo = f'domlogo-files/logos/{organization_id}.png' + if os.path.isfile(potential_organization_logo): + organization_logo = potential_organization_logo + cache[-1] = (organization_logo, f's{sid}/j{jid}\n{verdict}', color, jid) for i in range(len(cache)): previous_column[i][0].update(filename=cache[i][0]) previous_column[i][1].update(cache[i][1]) diff --git a/end-of-contest/README.md b/end-of-contest/README.md new file mode 100644 index 00000000..036142c0 --- /dev/null +++ b/end-of-contest/README.md @@ -0,0 +1,81 @@ +## DOMjudge EOC ansible helper +This ansible playbook should automate most of the tasks DOMjudge needs to perform for the End Of Contest procedures during the ICPC WF's. It was created for the WF 2021 in Dhaka, which took place in 2022. + +Two types of authentication are used and supported. Session based auth and basic auth. + - Session based auth retrieves a CSRF token and fakes a login action. + - Basic auth headers are constructed and used. This is to fully mitigate [annoying basic-auth issues](https://askubuntu.com/questions/1070838/why-wget-does-not-use-username-and-password-in-url-first-time/1070847#1070847), even though the `force_basic_auth` option should already prevent these issues from arising. + +The full playbook can be run using the following command. See the setup section to see what the contents of facts.yml are supposed to be. + +```ansible-playbook main.yml --extra-vars "@facts.yml" --tags all``` + +### Steps automated. +The following steps from the EOC 2022-Oct document have been automated: + +29. `Primary pushes results.tsv into git repo`. Tag for just this action: `results.tsv`. Note, a manual commit is still needed! +32. `Compare scoreboard.json between primary and shadow`. Tag for just this action: `scoreboard`. Note, only if other (shadow or primary) credentials are provided. +33. `Compare awards.json between primary and CDS`. Tag for just this action: `awards`. Note, only if CDS credentials are provided. +36. `Verify that Primary and ... System Test`. Tag for just this action: `fetch`. Note, a manual commit is still needed. The static scoreboard (zip) and an export of the clarifications is also pulled from DOMjudge. + + +## Setup +To run this playbook a facts-file is required. An example of which can be seen below. Facts prefixed with: + - `dj_` are for interaction with a DOMjudge instance. + - `other_` are for interaction with another CLICS spec compliant CCS (commonly shadow if DOMjudge is primary, or vica versa) + - `cds_` are for interaction with the CDS (also CLICS compliant). + +Only the `repo`, `contest`, `contest_id`, and `dj_*` variables are required. When the `other_` or `cds_` facts are missing, steps which would interact with these systems are skipped. + +- `repo` must point to where all retrieved files should be stored, not simply the repo root! +- `contest` must be the externalID. +- `contest_id` must be the internalID. (cID) +- `jd_loc` must point to the location of a [`jd` binary](https://github.com/josephburnett/jd/releases/tag/v1.6.1). Used for comparing diffs. +- `contestutil_loc` must point to the location of [`contestUtil.sh`](https://github.com/icpctools/icpctools/releases). Used for comparing diffs. + +```yaml +repo: /home/mart/icpc/wf_repo +contest: bapc2022 +contest_id: 2 +jd_loc: /home/mart/icpc/EOC/jd +contestutil_loc: /home/mart/Downloads/contestUtil/contestUtil.sh + +dj_url: https://judge.gehack.nl +dj_url_api_suffix: api/v4 +dj_username: mart +dj_password: REDACTED + +other_url: https://judge.gehack.nl +other_url_api_suffix: api/v4 +other_username: mart +other_password: REDACTED + +cds_url: https://cds.gehack.nl +cds_url_api_suffix: api +cds_username: admin +cds_password: REDACTED +``` + +## Selectively running the playbook +The playbook has multiple tags: + - `all`: runs all tasks + - `check`: Checks the consistency of both scoreboard.json and awards.json against the CDS and other CCS when configured. + - `scoreboard`: Verifies scoreboard.json against the CDS and other CCS when configured. + - `awards`: Verifies awards.json against the CDS and other CCS when configured. + - `results.tsv`: Downloads results.tsv from DOMjudge and stores it in the repo. + - `fetch`: Retrieves all files required by the EOC including results.tsv + - `dump`: Dumps all API endpoints, specifically: + - accounts + - awards + - balloons + - clarifications + - problems + - groups + - judgements + - languages + - organizations + - runs + - scoreboard + - teams + +### Known limitations + - The checks that verify whether the awards.json are the same is *very* simple and I (Mart) have not yet found a reasonable way of actually checking them in a nice manner from ansible. Them matching would be a bigger red-flag than them having a difference. Still, the playbook fails when a difference is detected! To aid with (manual) verification the results of fetching the jsons is stored in `/tmp/awards.[sys].json` with `[sys]` either "dj", or "cds". diff --git a/end-of-contest/main.yml b/end-of-contest/main.yml new file mode 100644 index 00000000..750a12ba --- /dev/null +++ b/end-of-contest/main.yml @@ -0,0 +1,237 @@ +--- +- hosts: localhost + connection: local + gather_facts: no + tasks: + - name: Set facts for whether enough variables are set + tags: all, compare, scoreboard, awards, fetch + ansible.builtin.set_fact: + cds_available: "{{ cds_url is defined and cds_url_api_suffix is defined and cds_username is defined and cds_password is defined }}" + other_available: "{{ other_url is defined and other_url_api_suffix is defined and other_username is defined and other_password is defined }}" + + # Do this to prevent the dreaded issues with basic auth. Tedious but it works + - name: Set DOMjudge basic auth fact + tags: all, compare, scoreboard, awards, fetch, dump + ansible.builtin.set_fact: + dj_basic_auth: "Basic {{ (dj_username+':'+dj_password) | b64encode }}" + + - name: Set CDS basic auth fact + tags: all, compare, scoreboard, awards + ansible.builtin.set_fact: + cds_basic_auth: "Basic {{ (cds_username+':'+cds_password) | b64encode }}" + when: cds_available + + - name: Set other basic auth fact + tags: all, compare, scoreboard, awards + ansible.builtin.set_fact: + other_basic_auth: "Basic {{ (other_username+':'+other_password) | b64encode }}" + when: other_available + + - name: Attempt login into DOMjudge + tags: all, fetch, results.tsv + block: + - name: Fetch CSRF from login page + ansible.builtin.uri: + method: GET + return_content: yes + url: "{{ dj_url }}/login" + register: DOMjudge_login_output + + - name: Extract CSRF input field + ansible.builtin.set_fact: + DOMjudge_csrf_field: "{{ DOMjudge_login_output.content | regex_search(']+?_csrf_token[^>]+?>') | regex_search('[^\"]{30,}') }}" + + - name: Login into DOMjudge and retrieve cookie + ansible.builtin.uri: + method: POST + body_format: form-urlencoded + url: "{{ dj_url }}/login" + status_code: 302 + headers: + cookie: "{{ DOMjudge_login_output.cookies_string }}" + body: + _username: "{{ dj_username }}" + _password: "{{ dj_password }}" + _csrf_token: "{{ DOMjudge_csrf_field }}" + register: login_response + + - name: Test succesfull login by going to a restricted page + ansible.builtin.uri: + url: "{{ dj_url }}/jury" + method: GET + headers: + cookie: "{{ login_response.set_cookie }}" + + - name: Download results.tsv + tags: all, results.tsv + ansible.builtin.uri: + url: "{{ dj_url }}/jury/import-export/export/results.tsv" + return_content: yes + method: GET + creates: "{{ repo }}/results.tsv" + dest: "{{ repo }}/results.tsv" + headers: + cookie: "{{ login_response.set_cookie }}" + + - name: Compare scoreboard.json + tags: all, compare, scoreboard + block: + - name: "Fetch scoreboard.json from Dj" + ansible.builtin.uri: + url: "{{ dj_url }}/{{ dj_url_api_suffix }}/contests/{{ contest }}/scoreboard" + dest: "/tmp/scoreboard.dj.json" + return_content: yes + force: yes + headers: + Authorization: "{{ dj_basic_auth }}" + register: dj_scoreboard + + - name: "Fetch scoreboard.json from other" + ansible.builtin.uri: + url: "{{ other_url }}/{{ other_url_api_suffix }}/contests/{{ contest }}/scoreboard" + dest: "/tmp/scoreboard.other.json" + return_content: yes + force: yes + headers: + Authorization: "{{ other_basic_auth }}" + register: other_scoreboard + when: other_available + + - name: Does Dj == other for scoreboard + when: other_available + block: + - name: Check for differences + shell: "{{ contestutil_loc }} ScoreboardUtil /tmp/scoreboard.dj.json /tmp/scoreboard.other.json" + when: other_available + register: cul + + - name: Print output + ansible.builtin.debug: + msg: "{{ cul.stdout | split('\n')}}" + + - name: Fail if the scoreboard does not match + ansible.builtin.fail: + msg: "Scoreboard does not match, check /tmp/scoreboard.dj.json and /tmp/scoreboard.other.json" + when: + - "'No differences found.' not in cul.stdout" + + - name: Compare awards.json + tags: all, compare, awards + block: + - name: Fetch awards.json from Dj + ansible.builtin.uri: + url: "{{ dj_url }}/{{ dj_url_api_suffix }}/contests/{{ contest }}/awards/" + dest: "/tmp/awards.dj.json" + return_content: yes + force: yes + method: GET + headers: + Authorization: "{{ dj_basic_auth }}" + register: dj_awards + + - name: Fetch awards.json from cds + ansible.builtin.uri: + url: "{{ cds_url }}/{{ cds_url_api_suffix }}/contests/{{ contest }}/awards/" + dest: "/tmp/awards.cds.json" + return_content: yes + force: yes + method: GET + headers: + Authorization: "{{ cds_basic_auth }}" + register: cds_awards + when: cds_available + + - name: Does Dj == CDS for awards + shell: "{{ jd_loc }} /tmp/awards.dj.json /tmp/awards.cds.json" + when: cds_available + + - name: "Fetch event-feed.ndjson from Dj" + tags: all, fetch + ansible.builtin.uri: + url: "{{ dj_url }}/{{ dj_url_api_suffix }}/contests/{{ contest }}/event-feed?stream=false" + return_content: yes + method: GET + creates: "{{ repo }}/event-feed.ndjson" + dest: "{{ repo }}/event-feed.ndjson" + headers: + Authorization: "{{ dj_basic_auth }}" + + - name: "Fetch {{ item }}-endpoint from Dj" + tags: all, fetch, dump + ansible.builtin.uri: + url: "{{ dj_url }}/{{ dj_url_api_suffix }}/contests/{{ contest }}/{{ item }}" + return_content: yes + method: GET + creates: "{{ repo }}/{{ item }}.json" + dest: "{{ repo }}/{{ item }}.json" + headers: + Authorization: "{{ dj_basic_auth }}" + with_items: + - accounts + - awards + - balloons + - clarifications + - problems + - groups + - judgements + - languages + - organizations + - runs + - scoreboard + - teams + + - name: "Store final_standings.html (results.html)" + tags: all, fetch + ansible.builtin.uri: + url: "{{ dj_url }}/jury/import-export/export/results.html" + creates: "{{ repo }}/final_standings.html" + dest: "{{ repo }}/final_standings.html" + headers: + cookie: "{{ login_response.set_cookie }}" + + - name: "Store (clarifications.html)" + tags: all, fetch + ansible.builtin.uri: + url: "{{ dj_url }}/jury/import-export/export/clarifications.html" + creates: "{{ repo }}/clarifications.html" + dest: "{{ repo }}/clarifications.html" + headers: + cookie: "{{ login_response.set_cookie }}" + + - name: "Store final scoreboard (contest.zip)" + tags: all, fetch + ansible.builtin.uri: + url: "{{ dj_url }}/public/scoreboard-data.zip?contest={{ contest_id }}" + creates: "{{ repo }}/final_scoreboard.zip" + dest: "{{ repo }}/final_scoreboard.zip" + headers: + cookie: "{{ login_response.set_cookie }}" + + - name: "Fetch DOMjudge submissions" + tags: all, fetch + block: + - name: Retrieve all submissions + ansible.builtin.uri: + url: "{{ dj_url }}/{{ dj_url_api_suffix }}/contests/{{ contest }}/submissions?strict=1" + return_content: yes + method: GET + headers: + Authorization: "{{ dj_basic_auth }}" + register: "submissions" + + - name: Extract submission endpoints + ansible.builtin.set_fact: + submissions: "{{ submissions.json | json_query(jmesquery) }}" + vars: + jmesquery: "[].files[?mime == 'application/zip'][]" + + - name: Download submissions + ansible.builtin.uri: + url: "{{ dj_url }}/{{ dj_url_api_suffix }}/{{ item.href }}" + return_content: yes + method: GET + headers: + Authorization: "{{ dj_basic_auth }}" + creates: "{{ repo }}/{{ item.href.split('/')[3] }}.zip" + dest: "{{ repo }}/{{ item.href.split('/')[3] }}.zip" + loop: "{{ submissions }}" diff --git a/merchandise/DOMjudgelogo+site.pdf b/merchandise/DOMjudgelogo+site.pdf new file mode 100644 index 00000000..0ba270ec Binary files /dev/null and b/merchandise/DOMjudgelogo+site.pdf differ diff --git a/merchandise/DOMjudgelogo-blue-bg.png b/merchandise/DOMjudgelogo-blue-bg.png new file mode 100644 index 00000000..442e6821 Binary files /dev/null and b/merchandise/DOMjudgelogo-blue-bg.png differ diff --git a/merchandise/DOMjudgelogo-blue-bg.svg b/merchandise/DOMjudgelogo-blue-bg.svg new file mode 100644 index 00000000..5ec3e172 --- /dev/null +++ b/merchandise/DOMjudgelogo-blue-bg.svg @@ -0,0 +1,242 @@ + +image/svg+xmlDOM +DOM +judge +judge +/ +/ +\ +\ + diff --git a/merchandise/DOMjudgelogo.svg b/merchandise/DOMjudgelogo.svg new file mode 120000 index 00000000..23bb28b4 --- /dev/null +++ b/merchandise/DOMjudgelogo.svg @@ -0,0 +1 @@ +../website/DOMjudgelogo.svg \ No newline at end of file diff --git a/new_release_howto.md b/new_release_howto.md index 1994c8a1..16b2d356 100644 --- a/new_release_howto.md +++ b/new_release_howto.md @@ -4,6 +4,8 @@ on the account `domjudge@vm-domjudge`): 1. Test everything. (duh...) 1. Commit the correct version number in the `ChangeLog` and `README` files. + 1. Discuss if the current second to last minor version will now be EOL. + 1. Update https://github.com/DOMjudge/domjudge/security/policy to support 2 minor versions, including the one now being released. 1. Change the constants `webapp/src/Controller/API/GeneralInfoController.php` to point to the correct CCS API version. 1. `export TAG=x.y.z` @@ -28,10 +30,11 @@ on the account `domjudge@vm-domjudge`): 1. On the server the tarball will be built, signed and published. 1. Update the DOMjudge homepage: commit changes in the `domjudge-scripts` repository under `website/` and run `make install` as domjudge@domjudge + 1. Remove unsupported versions from the `versions.json` in the next step. 1. If this is a new major or minor version, update the release documentation under `/srv/http/domjudge/docs/manual` by adding a new version to the - file `versions.json` and updating the redirect destination in `index.html`. - The documentation is regenerated once every hour. + file `versions.json` and updating the redirect destination in `index.html` + and `team.html`. The documentation is regenerated once every hour. 1. Bump the Docker images by starting a new pipeline [here](https://gitlab.com/DOMjudge/domjudge-packaging/-/pipelines/new) and setting `DOMJUDGE_VERSION` to the version to release. Set `DOMJUDGE_LATEST` @@ -39,9 +42,9 @@ on the account `domjudge@vm-domjudge`): `Run pipeline`, make sure to click the *play* button next to `release-DOMjudge` to actually build and push the Docker images. You can view the progress by clicking on the job. - 1. Build Debian packages (or make someone - do this). + 1. Update https://gitlab.com/DOMjudge/domjudge-packaging/-/pipeline\_schedules to the latest version. + 1. Build Debian packages (or make someone do this). 1. Put debian packages in `/srv/http/domjudge/debian/mini-dinstall/incoming` and run as domjudge@domjudge: `mini-dinstall -b` - 1. Send an email to `domjudge-announce@domjudge.org`. + 1. Send an email to `domjudge-announce@domjudge.org`, announcing the new version and state which versions are supported. 1. Add the released branch to the dependabot.yml (https://github.com/DOMjudge/domjudge/blob/main/.github/dependabot.yml) diff --git a/provision-contest/ansible/.gitignore b/provision-contest/ansible/.gitignore new file mode 100644 index 00000000..6ff331c7 --- /dev/null +++ b/provision-contest/ansible/.gitignore @@ -0,0 +1 @@ +hosts diff --git a/provision-contest/ansible/Makefile b/provision-contest/ansible/Makefile index 85365fcf..06f39de7 100644 --- a/provision-contest/ansible/Makefile +++ b/provision-contest/ansible/Makefile @@ -6,45 +6,95 @@ default: @echo " - make admin" @echo " - make grafana" @echo " - make cds" + @echo " - make presclient" + @echo " - make presadmin" @echo " - make scoreboard" + @echo " - make autoanalyst" -LIBVENDORTGZ=roles/domjudge_checkout/files/lib-vendor.tgz -SSHKEY=roles/ssh/files/id_rsa +VENDORTGZ=roles/domjudge_checkout/files/vendor.tgz +SSHKEY=roles/ssh/files/id_ed25519 +SSL_DOMSERVER=roles/ssl/files/domserver +SSL_DOMSERVER_FILES=$(addprefix $(SSL_DOMSERVER),.key .crt) +SSL_NODEEXPORT=roles/prometheus_target_all/files/node_exporter +SSL_NODEEXPORT_FILES=$(addprefix $(SSL_NODEEXPORT),.key .crt) SSL_LOCALHOST=roles/ssl/files/localhost SSL_LOCALHOST_FILES=$(addprefix $(SSL_LOCALHOST),.key .crt) +SSL_CDS=roles/ssl/files/cds +SSL_CDS_FILES=$(addprefix $(SSL_CDS),.key .crt) SSL_GRAFANA=roles/grafana/files/ssl SSL_GRAFANA_FILES=$(addprefix $(SSL_GRAFANA),.key .crt) -ifeq ($(wildcard $(LIBVENDORTGZ)),) -LIBVENDOR= +ifeq ($(wildcard $(VENDORTGZ)),) +VENDOR= else -LIBVENDOR=roles/domjudge_checkout/files/lib/vendor -$(LIBVENDOR): $(LIBVENDORTGZ) - -cd roles/domjudge_checkout/files && tar xzf $(notdir $<) +VENDOR=roles/domjudge_checkout/files/webapp/vendor +$(VENDOR): $(VENDORTGZ) + -cd roles/domjudge_checkout/files/webapp && tar xzf ../$(notdir $<) endif -domserver judgehost admin grafana cds scoreboard mgmt: %: %.yml hosts group_vars/all/secret.yml $(LIBVENDOR) $(SSHKEY) $(SSHKEY).pub +ROLES=domserver judgehost admin grafana cds presclient presadmin scoreboard mgmt autoanalyst +$(ROLES): %: %.yml hosts group_vars/all/secret.yml $(VENDOR) $(SSHKEY) $(SSHKEY).pub ansible-playbook -i hosts $< +FAILED_ROLES=$(addprefix failed-,$(ROLES)) +$(FAILED_ROLES): failed-%: %.yml %.retry + ansible-playbook -i hosts --limit @$*.retry $< + +CODEONLY_ROLES=$(addprefix codeonly-,domserver judgehost admin) +$(CODEONLY_ROLES): codeonly-%: + ansible-playbook -i hosts --tags pretask,domjudge_build $*.yml + +powerloss: + ansible-playbook -i hosts emergency.yml --tags powerloss + +lockdown: + ansible-playbook -i hosts emergency.yml --tags full_lockdown + +lockdown-force: + ansible-playbook -i hosts emergency.yml --tags full_lockdown,force_lockdown + +ansible-master: + for book in $(ROLES) ; do \ + ansible-playbook -i hosts $$book.yml --tags install_master ; \ + done + admin: $(SSL_LOCALHOST_FILES) -grafana: $(SSL_GRAFANA_FILES) +grafana: $(SSL_GRAFANA_FILES) $(SSL_NODEEXPORT_FILES) +domserver: $(SSL_DOMSERVER_FILES) $(SSL_NODEEXPORT_FILES) +judgehost: $(SSL_NODEEXPORT_FILES) +cds: $(SSL_CDS_FILES) $(SSL_NODEEXPORT_FILES) +scoreboard: $(SSL_NODEEXPORT_FILES) +mgmt: $(SSL_NODEEXPORT_FILES) $(SSHKEY) $(SSHKEY).pub: - ssh-keygen -t rsa -f $(SSHKEY) -P '' + ssh-keygen -t ed25519 -f $(SSHKEY) -P '' +$(SSL_DOMSERVER_FILES): + openssl req -x509 -nodes -newkey rsa:4096 -subj "/O=DOMjudge/CN=domjudge" \ + -addext "subjectAltName = DNS:wf46-domjudge,DNS:wf47-domjudge,DNS:analyst" \ + -sha256 -days 365 -keyout $(SSL_DOMSERVER).key -out $(SSL_DOMSERVER).crt +$(SSL_NODEEXPORT_FILES): + openssl req -x509 -nodes -newkey rsa:4096 -subj "/O=DOMjudge/CN=metricexporter" \ + -sha256 -days 365 -keyout $(SSL_NODEEXPORT).key -out $(SSL_NODEEXPORT).crt $(SSL_LOCALHOST_FILES): openssl req -x509 -nodes -newkey rsa:4096 -subj "/O=DOMjudge/CN=localhost" \ -sha256 -days 365 -keyout $(SSL_LOCALHOST).key -out $(SSL_LOCALHOST).crt +$(SSL_CDS_FILES): + openssl req -x509 -nodes -newkey rsa:4096 -subj "/O=DOMjudge/CN=cds" \ + -sha256 -days 365 -keyout $(SSL_CDS).key -out $(SSL_CDS).crt $(SSL_GRAFANA_FILES): openssl req -x509 -nodes -newkey rsa:4096 -subj "/O=DOMjudge/CN=grafana" \ -sha256 -days 365 -keyout $(SSL_GRAFANA).key -out $(SSL_GRAFANA).crt clean: - rm -rf $(LIBVENDOR) + rm -rf $(VENDOR) distclean: clean rm -f $(SSHKEY) $(SSHKEY).pub + rm -f $(SSL_DOMSERVER_FILES) + rm -f $(SSL_NODEEXPORT_FILES) rm -f $(SSL_LOCALHOST_FILES) + rm -f $(SSL_CDS_FILES) rm -f $(SSL_GRAFANA_FILES) -.PHONY: default clean distclean domserver judgehost admin grafana cds scoreboard +.PHONY: default clean distclean $(ROLES) $(FAILED_ROLES) $(CODEONLY_ROLES) powerloss lockdown lockdown-force diff --git a/provision-contest/ansible/README.md b/provision-contest/ansible/README.md index 0dcb3631..b8493c8d 100644 --- a/provision-contest/ansible/README.md +++ b/provision-contest/ansible/README.md @@ -13,7 +13,15 @@ This ansible code can deploy four different types of machines: * judgehost: machines running one judgedaemon each. We use as many as necessary and depending on machines available. These should be identical to each other and preferably also to the team machines. -* grafana: TODO +* scoreboard: for some contests (different from the world finals) we + use a mirror of the scoreboard to not overload the server with + requests from viewers. +* cds: machine which hosts the [CDS](https://tools.icpc.global/cds/), if you use + this you should also look at the `presclient` & `presadmin` playbooks to setup + machines which can present those presentations. +* grafana: monitoring machine to observe the performance of both the DOMjudge instance + and the webservers, optionally we monitor the participant machines in case of suspected + hardware issues (loose PSU, CPU performance etc.) ## Code organization @@ -24,13 +32,19 @@ to prevent duplication. Global and group variables are stored under `group_vars`. The file `group_vars/all/secret.yml.example` should be copied to `group_vars/all/secret.yml` and then all variables should be set -and/or modified as required. +and/or modified as required. The script `generate_passwords.py` can be used +to prefill some of those passwords with a xkcd style password. In `secret.yml.example` +the passwords can be listed as either `{some-password}` or `some-password` the `{}` is *not* +required but only used as anchor for the script, so adding the `{}` would make it part of the +password. There are a few places where additional files should/can be added: * SSH public/private keys under `roles/ssh/files/`. * SSL certificates and keys under `roles/ssl/files/`. * Machine/group specific local packages under `roles/base_packages/files/install-*/`. * Judgehost chroot local packages under `roles/judgedaemon/files/install-chroot/`. +* The vendor dependencies under `roles/domjudge_checkout/files/vendor.tgz`. +* Machine/group specific docker containers under `roles/docker/files/containers-*/`. ## TODO diff --git a/provision-contest/ansible/admin.yml b/provision-contest/ansible/admin.yml index c7419c70..39182080 100644 --- a/provision-contest/ansible/admin.yml +++ b/provision-contest/ansible/admin.yml @@ -1,7 +1,7 @@ --- # This playbook an admin machine with a dev DOMjudge instance -- name: setup admin machines +- name: Setup admin machines hosts: admin vars: host_type: admin @@ -30,6 +30,8 @@ tags: domjudge_checkout - role: domjudge_build tags: domjudge_build + vars: + DOMSERVER: https://localhost - role: domserver tags: domserver - role: judgedaemon @@ -38,75 +40,55 @@ tags: clusterssh - role: phpstorm tags: phpstorm + - role: dj_notify + tags: dj_notify - role: prometheus_target_all tags: prometheus_target_all when: GRAFANA_MONITORING handlers: - - include: handlers.yml + - import_tasks: handlers.yml tasks: - - name: install common required/useful packages + - name: Install common required/useful packages tags: packages apt: state: present pkg: - - gitk + - ansible - git-gui + - gitk - makepasswd - mmv + - python3-pdfkit + - xkcdpass - - name: enable developer mode + - name: Enable developer mode lineinfile: regexp: 'APP_ENV=' line: 'APP_ENV=dev' dest: "{{ DJ_DIR }}/webapp/.env.local" - - - name: add cds to hosts file - lineinfile: - dest: /etc/hosts - regexp: 'cds$' - line: "10.3.3.207 cds" - - - name: add kattis to hosts file - lineinfile: - dest: /etc/hosts - regexp: 'kattis$' - line: "10.3.3.212 kattis" - - - name: add domjudge-laptop to hosts file - lineinfile: - dest: /etc/hosts - regexp: 'domjudge-laptop$' - line: "10.3.3.200 domjudge-laptop" - - - name: add nisprint to hosts file - lineinfile: - dest: /etc/hosts - regexp: 'printsrv$' - line: "10.3.3.211 nisprint nismaster printsrv" + create: true - name: Update repo URL based on network set_fact: dj_git_repo_scripts: "{{ DJ_GIT_REPO_SCRIPTS_RESTRICTED if WF_RESTRICTED_NETWORK else DJ_GIT_REPO_SCRIPTS }}" - - name: create working copy of the domjudge-scripts repo - become: true - become_user: domjudge - # We use a different directory here to have one single 'upstream' and not have issues with it - git: repo={{ dj_git_repo_scripts }} dest=/home/domjudge/domjudge-scripts-checkout version=main accept_hostkey=yes update=no - - - name: create working copy of the wf2020 repo - become: true - become_user: domjudge - git: repo=git@cds:wf2020 dest=/home/domjudge/wf2020 version=master accept_hostkey=yes update=no - - - name: copy custom CSS file for admin machines + - name: Copy custom CSS file for admin machines copy: src: admin-machine.css dest: "{{ DJ_DIR }}/webapp/public/css/custom/admin-machine.css" owner: domjudge group: domjudge mode: 0644 - notify: clear application cache + notify: Clear application cache + + - name: Check if netrc file exists + stat: + path: /home/domjudge/.netrc + get_attributes: false + get_checksum: false + get_mime: false + register: netrc_file + check_mode: no - name: Install netrc file template: @@ -115,3 +97,75 @@ owner: domjudge group: domjudge mode: 0600 + when: not netrc_file.stat.exists + + - name: Install .my.cnf file + template: + src: files/admin.my.cnf.j2 + dest: /home/domjudge/.my.cnf + owner: domjudge + group: domjudge + mode: 0600 + + - name: Create local python directory + become: true + become_user: domjudge + file: + path: "/home/domjudge/.local/lib/python3.8" + state: directory + mode: '0755' + owner: domjudge + group: domjudge + + + - name: Install local python packages + synchronize: + src: files/admin-python/ + dest: "/home/domjudge/.local/lib/python3.8" + owner: false + use_ssh_args: true + + - name: Fix ownership of local python packages + file: + path: "/home/domjudge/.local/lib/python3.8" + recurse: true + owner: domjudge + group: domjudge + + - name: Grant www-data permissions to user homedir (needed for web browser) + acl: + path: "/home/domjudge" + entity: www-data + etype: user + permissions: rwx + state: present + + - name: Download offline repos + when: WF_RESTRICTED_NETWORK + become: true + become_user: domjudge + block: + - name: Add ccsadmin remote + git_config: + name: "remote.{{ item.remote }}.url" + value: "{{ item.url }}" + scope: local + repo: /home/domjudge/domjudge-checkout + loop: + - remote: origin + url: ansible@10.3.3.224:domjudge + - remote: github + url: git@github.com:DOMjudge/domjudge.git + - remote: sysops + url: domjudge@packages:domjudge.git + - remote: initial + url: ansible@domjudge-ccsadmin1:domjudge.git + + - name: Create working copy of the domjudge-scripts repo + # We use a different directory here to have one single 'upstream' and not have issues with it + git: repo={{ dj_git_repo_scripts }} dest=/home/domjudge/domjudge-scripts-checkout version=main accept_hostkey=yes update=no + + - name: Create working copy of the contest repo + ignore_errors: true + git: repo=git@packages:{{ CONTEST_REPO }} dest=/home/domjudge/{{ CONTEST_REPO }} version=master accept_hostkey=yes update=no + diff --git a/provision-contest/ansible/ansible.cfg b/provision-contest/ansible/ansible.cfg new file mode 100644 index 00000000..ee6bd190 --- /dev/null +++ b/provision-contest/ansible/ansible.cfg @@ -0,0 +1,11 @@ +[defaults] +forks = 10 +log_path = /tmp/ansible.log +system_warnings = True +strategy = free +retry_files_enabled = True +retry_files_save_path = ~/.ansible-retry + +[connection] +ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s +pipelining = True diff --git a/provision-contest/ansible/autoanalyst.yml b/provision-contest/ansible/autoanalyst.yml new file mode 100644 index 00000000..1a411419 --- /dev/null +++ b/provision-contest/ansible/autoanalyst.yml @@ -0,0 +1,9 @@ +--- +# This playbook installs the ICPC AutoAnalyst + +- name: Setup autoanalyst + hosts: autoanalyst + become: true + roles: + - role: autoanalyst + tags: autoanalyst diff --git a/provision-contest/ansible/cds.yml b/provision-contest/ansible/cds.yml index fa9b45cd..ae4cb730 100644 --- a/provision-contest/ansible/cds.yml +++ b/provision-contest/ansible/cds.yml @@ -1,11 +1,13 @@ --- # This playbook installs the CDS -- name: setup CDS +- name: Setup CDS hosts: cds vars: host_type: cds become: true + handlers: + - import_tasks: handlers.yml roles: - role: base_packages tags: base_packages diff --git a/provision-contest/ansible/domserver.yml b/provision-contest/ansible/domserver.yml index e1c5b75d..c688690c 100644 --- a/provision-contest/ansible/domserver.yml +++ b/provision-contest/ansible/domserver.yml @@ -1,8 +1,19 @@ --- # This playbook installs the DOMjudge server(s) -- name: setup domserver - hosts: domserver +- name: Install master dependency for ipaddr jinja filter + hosts: localhost + tasks: + - name: Install for debian family + when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' + tags: install_master,never + apt: + state: present + pkg: + - python3-netaddr + +- name: Setup domserver + hosts: domserver,online-domserver vars: host_type: domserver become: true @@ -48,9 +59,9 @@ tags: prometheus_target_all when: GRAFANA_MONITORING handlers: - - include: handlers.yml + - import_tasks: handlers.yml tasks: - - name: install domserver required packages + - name: Install domserver required packages apt: state: present pkg: @@ -58,19 +69,32 @@ - macchanger - molly-guard - - name: disable developer mode + - name: Disable developer mode lineinfile: regexp: '^APP_ENV=dev' state: absent dest: "{{ DJ_DIR }}/webapp/.env.local" - - name: install contest images + - name: Install contest images synchronize: src: files/domjudge-public/ dest: "{{ DJ_DIR }}/webapp/public" owner: false use_ssh_args: true - notify: clear application cache + notify: Clear application cache + + - name: Grant www-data permissions to image folders + acl: + path: "{{ DJ_DIR }}/webapp/public/images/{{ item }}" + entity: www-data + etype: user + permissions: rwx + state: present + recursive: true + loop: + - teams + - affiliations + - banners - name: Add documentation in DOMjudge team interface copy: @@ -79,9 +103,17 @@ mode: 0644 group: root owner: root - notify: clear application cache + notify: Clear application cache + + - name: Add log viewing helper script + copy: + src: files/nicelog + dest: /home/domjudge/bin + owner: domjudge + group: domjudge + mode: 0755 - - name: add autostart shortcuts + - name: Add autostart shortcuts copy: src: files/{{ item }}.desktop dest: /home/domjudge/.config/autostart/ @@ -91,8 +123,9 @@ loop: - htop - taillog-domserver-nginx-error + when: GRAPHICAL - - name: add autostart shortcuts from template + - name: Add autostart shortcuts from template template: src: files/{{ item }}.desktop.j2 dest: /home/domjudge/.config/autostart/{{ item }}.desktop @@ -101,3 +134,4 @@ mode: 0755 loop: - taillog-domserver-symfony-error + when: GRAPHICAL diff --git a/provision-contest/ansible/emergency.yml b/provision-contest/ansible/emergency.yml new file mode 100644 index 00000000..e2b7b28f --- /dev/null +++ b/provision-contest/ansible/emergency.yml @@ -0,0 +1,60 @@ +--- +- hosts: domserver + tasks: + - name: Disable webserver to stop submissions + tags: powerloss + service: + name: nginx + state: stopped + +- hosts: all + tasks: + - name: Disable GDM autologin + tags: full_lockdown + lineinfile: + path: /etc/gdm3/custom.conf + regexp: 'AutomaticLoginEnable' + line: 'AutomaticLoginEnable=false' + create: true + mode: 0644 + when: GRAPHICAL + +- hosts: judgehost + tasks: + - name: Disable all running judgedaemons + tags: full_lockdown + service: + name: "{{ item }}" + state: stopped + with_sequence: start=0 end={{ ansible_processor_vcpus }} format=domjudge-judgedaemon@%1x + +- hosts: domserver:!emergency + tasks: + - name: Disable our domserver instance + # We also disable the startup as we're now in most likely a bad state + # make sure that replication can only start when someone is looking at it + tags: full_lockdown + service: + name: "{{ item }}" + state: stopped + enabled: false + loop: + - nginx + - php7.4-fpm + - mysql + +- hosts: all:!admin + tasks: + - name: Shutdown all non-needed computers + tags: full_lockdown + community.general.shutdown: + +# Get current running machine +- hosts: localhost + +- hosts: all + tasks: + - name: Shutdown all machines except for us + tags: force_lockdown + community.general.shutdown: + when: hostvars.localhost.ansible_hostname != ansible_hostname diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/INSTALLER b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/LICENSE b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/LICENSE new file mode 100644 index 00000000..9b114e03 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Manraj Singh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/METADATA b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/METADATA new file mode 100644 index 00000000..9c272bff --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/METADATA @@ -0,0 +1,235 @@ +Metadata-Version: 2.1 +Name: halo +Version: 0.0.31 +Summary: Beautiful terminal spinners in Python +Home-page: https://github.com/manrajgrover/halo +Author: Manraj Singh +Author-email: manrajsinghgrover@gmail.com +License: MIT +Keywords: console,loading,indicator,progress,cli,spinner,spinners,terminal,term,busy,wait,idle +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3 :: Only +Requires-Python: >=3.4 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: colorama (>=0.3.9) +Requires-Dist: log-symbols (>=0.0.14) +Requires-Dist: six (>=1.12.0) +Requires-Dist: spinners (>=0.0.24) +Requires-Dist: termcolor (>=1.1.0) +Requires-Dist: backports.shutil-get-terminal-size (>=1.0.0) ; python_version < "3.3" +Provides-Extra: ipython +Requires-Dist: IPython (==5.7.0) ; extra == 'ipython' +Requires-Dist: ipywidgets (==7.1.0) ; extra == 'ipython' + +

+ +
+ halo +

+ +[![Build Status](https://travis-ci.com/manrajgrover/halo.svg?branch=master)](https://travis-ci.com/manrajgrover/halo) [![Build status](https://ci.appveyor.com/api/projects/status/wa6t414gltr403ff?svg=true)](https://ci.appveyor.com/project/manrajgrover/halo) [![Coverage Status](https://coveralls.io/repos/github/manrajgrover/halo/badge.svg?branch=master)](https://coveralls.io/github/manrajgrover/halo?branch=master) + [![PyPI](https://img.shields.io/pypi/v/halo.svg)](https://github.com/manrajgrover/halo) ![awesome](https://img.shields.io/badge/awesome-yes-green.svg) [![Downloads](https://pepy.tech/badge/halo)](https://pepy.tech/project/halo) [![Downloads](https://pepy.tech/badge/halo/month)](https://pepy.tech/project/halo/month) +> Beautiful spinners for terminal, IPython and Jupyter + +![halo](https://raw.github.com/manrajgrover/halo/master/art/doge_spin.svg?sanitize=true) + +## Install + +```shell +$ pip install halo +``` + +## Usage + +```py +from halo import Halo + +spinner = Halo(text='Loading', spinner='dots') +spinner.start() + +# Run time consuming work here +# You can also change properties for spinner as and when you want + +spinner.stop() +``` + +Alternatively, you can use halo with Python's `with` statement: + +```py +from halo import Halo + +with Halo(text='Loading', spinner='dots'): + # Run time consuming work here +``` + +Finally, you can use halo as a decorator: + +```py +from halo import Halo + +@Halo(text='Loading', spinner='dots') +def long_running_function(): + # Run time consuming work here + pass + +long_running_function() +``` + +## API + +#### `Halo([text|text_color|spinner|animation|placement|color|interval|stream|enabled])` + +##### `text` +*Type*: `str` + +Text shown along with spinner. + +##### `text_color` +*Type*: `str` +*Values*: `grey`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white` + +Color of the spinner text. Defaults to `None`. + +##### `spinner` +*Type*: `str|dict` + +If string, it should be one of the spinners listed in the given [json](https://github.com/sindresorhus/cli-spinners/blob/dac4fc6571059bb9e9bc204711e9dfe8f72e5c6f/spinners.json) file. If a dict is passed, it should define `interval` and `frames`. Something like: + +```py +{ + 'interval': 100, + 'frames': ['-', '+', '*', '+', '-'] +} +``` + +Defaults to `dots` spinner. For Windows users, it defaults to `line` spinner. + +##### `animation` +*Type*: `str` +*Values*: `bounce`, `marquee` + +Animation to apply to the text if it's too large and doesn't fit in the terminal. If no animation is defined, the text will be ellipsed. + +##### `placement` +*Type*: `str` +*Values*: `left`, `right` + +Which side of the text the spinner should be displayed. Defaults to `left` + +##### `color` +*Type*: `str` +*Values*: `grey`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white` + +Color of the spinner. Defaults to `cyan`. + +##### `interval` +*Type*: `float` + +Interval between each frame. Defaults to spinner interval (recommended). + +##### `stream` +*Type*: `file` + +Stream to write the output. Defaults to `sys.stdout`. + +##### `enabled` +*Type*: `bool` + +Enable or disable the spinner. Defaults to `True`. + +### Methods + +Following are the methods available: + +#### `spinner.start([text])` + +Starts the spinner. If `text` is passed, it is set as spinner text. Returns the instance. + +#### `spinner.stop()` + +Stops and clears the spinner. Returns the instance. + +#### `spinner.clear()` + +Clears the spinner. Returns the instance. + +#### `spinner.render()` + +Manually renders a new frame. Returns the instance. + +#### `spinner.frame()` + +Returns next frame to be rendered. + +#### `spinner.succeed([text])` +##### `text`: *Type*: `str` + +Stops the spinner and changes symbol to `✔`. If text is provided, it is persisted else current text is persisted. Returns the instance. + +#### `spinner.fail([text])` +##### `text`: *Type*: `str` + +Stops the spinner and changes symbol to `✖`. If text is provided, it is persisted else current text is persisted. Returns the instance. + +#### `spinner.warn([text])` +##### `text`: *Type*: `str` + +Stops the spinner and changes symbol to `⚠`. If text is provided, it is persisted else current text is persisted. Returns the instance. + +#### `spinner.info([text])` +##### `text`: *Type*: `str` + +Stops the spinner and changes symbol to `ℹ`. If text is provided, it is persisted else current text is persisted. Returns the instance. + +#### `spinner.stop_and_persist([symbol|text])` +Stops the spinner and changes symbol and text. Returns the instance. + +##### `symbol` +*Type*: `str` + +Symbol to replace the spinner with. Defaults to `' '`. + +##### `text` +*Type*: `str` + +Text to be persisted. Defaults to instance text. + +![Persist spin](https://raw.github.com/manrajgrover/halo/master/art/persist_spin.svg?sanitize=true) + +#### `spinner.text` +Change the text of spinner. + +#### `spinner.color` +Change the color of spinner + +#### `spinner.spinner` +Change the spinner itself. + +#### `spinner.enabled` +Enable or disable the spinner. + +## How to contribute? + +Please see [Contributing guidelines](https://github.com/manrajgrover/halo/blob/master/.github/CONTRIBUTING.md) for more information. + +## Like it? + +🌟 this repo to show support. Let me know you liked it on [Twitter](https://twitter.com/manrajsgrover). +Also, share the [project](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.amrom.workers.dev%2Fmanrajgrover%2Fhalo&via=manrajsgrover&text=Checkout%20%23halo%20-%20a%20beautiful%20%23terminal%20%23spinners%20library%20for%20%23python&hashtags=github%2C%20pypi). + +## Related + +* [py-spinners](https://github.com/manrajgrover/py-spinners) - Spinners in Python +* [py-log-symbols](https://github.com/manrajgrover/py-log-symbols) - Log Symbols in Python +* [ora](https://github.com/sindresorhus/ora) - Elegant terminal spinners in JavaScript (inspiration behind this project) + +## License +[MIT](https://github.com/manrajgrover/halo/blob/master/LICENSE) © Manraj Singh + + diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/RECORD b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/RECORD new file mode 100644 index 00000000..022d65b4 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/RECORD @@ -0,0 +1,17 @@ +halo-0.0.31.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +halo-0.0.31.dist-info/LICENSE,sha256=R2t7cIDZ6-VroHO5M_FWET8adqmdI-PGv0AnhyvCo4A,1069 +halo-0.0.31.dist-info/METADATA,sha256=azLsRMInovCX1dGE0E2InFMbyQJUqFm2YcqfewD93lM,7088 +halo-0.0.31.dist-info/RECORD,, +halo-0.0.31.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +halo-0.0.31.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +halo-0.0.31.dist-info/top_level.txt,sha256=LEvREToTCAduUBWpz2ktV7qe8OlPO63JGUYbX4wQVXI,5 +halo/__init__.py,sha256=_a2qkLkcB4Lw42TZau7lmdvBZeGGum-Spoz4GmQ9qm4,237 +halo/__pycache__/__init__.cpython-310.pyc,, +halo/__pycache__/_utils.cpython-310.pyc,, +halo/__pycache__/cursor.cpython-310.pyc,, +halo/__pycache__/halo.cpython-310.pyc,, +halo/__pycache__/halo_notebook.cpython-310.pyc,, +halo/_utils.py,sha256=t7wEz0Gr17uyO-NDT3qx2foyjYKG8_G_8i9xGTFvCx0,2995 +halo/cursor.py,sha256=nmUO1ouWBOUBgpGBzpQ5pqcRe802Z5GJ-f8ghv0r0ns,1321 +halo/halo.py,sha256=okUK8a2RS1VTQlAvGBQhx6NqpRouVzFaHuQvyNBpyic,16610 +halo/halo_notebook.py,sha256=c-nrJZVHcJg868v9wXQmg44SAO8Uu9hxuc92K1fiqL8,3183 diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/REQUESTED b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/WHEEL b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/WHEEL new file mode 100644 index 00000000..becc9a66 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/top_level.txt b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/top_level.txt new file mode 100644 index 00000000..1185b465 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo-0.0.31.dist-info/top_level.txt @@ -0,0 +1 @@ +halo diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__init__.py b/provision-contest/ansible/files/admin-python/site-packages/halo/__init__.py new file mode 100644 index 00000000..e9d67d92 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +__author__ = 'Manraj Singh' +__email__ = 'manrajsinghgrover@gmail.com' + +import logging + +from .halo import Halo +from .halo_notebook import HaloNotebook + +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/__init__.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 00000000..f41907ef Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/__init__.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/__init__.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 00000000..e5778744 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/__init__.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/_utils.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/_utils.cpython-310.pyc new file mode 100644 index 00000000..34c47c90 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/_utils.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/_utils.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/_utils.cpython-38.pyc new file mode 100644 index 00000000..164badc8 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/_utils.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/cursor.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/cursor.cpython-310.pyc new file mode 100644 index 00000000..44a03439 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/cursor.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/cursor.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/cursor.cpython-38.pyc new file mode 100644 index 00000000..cbc9ef16 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/cursor.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo.cpython-310.pyc new file mode 100644 index 00000000..e41b54d0 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo.cpython-38.pyc new file mode 100644 index 00000000..0fde7e24 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo_notebook.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo_notebook.cpython-310.pyc new file mode 100644 index 00000000..2c65e730 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo_notebook.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo_notebook.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo_notebook.cpython-38.pyc new file mode 100644 index 00000000..5b16da39 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/halo/__pycache__/halo_notebook.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/_utils.py b/provision-contest/ansible/files/admin-python/site-packages/halo/_utils.py new file mode 100644 index 00000000..999a95b8 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo/_utils.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +"""Utilities for Halo library. +""" +import codecs +import platform +import six +try: + from shutil import get_terminal_size +except ImportError: + from backports.shutil_get_terminal_size import get_terminal_size + +from colorama import init +from termcolor import colored + +init(autoreset=True) + + +def is_supported(): + """Check whether operating system supports main symbols or not. + + Returns + ------- + boolean + Whether operating system supports main symbols or not + """ + + os_arch = platform.system() + + if os_arch != 'Windows': + return True + + return False + + +def get_environment(): + """Get the environment in which halo is running + + Returns + ------- + str + Environment name + """ + try: + from IPython import get_ipython + except ImportError: + return 'terminal' + + try: + shell = get_ipython().__class__.__name__ + + if shell == 'ZMQInteractiveShell': # Jupyter notebook or qtconsole + return 'jupyter' + elif shell == 'TerminalInteractiveShell': # Terminal running IPython + return 'ipython' + else: + return 'terminal' # Other type (?) + + except NameError: + return 'terminal' + + +def colored_frame(frame, color): + """Color the frame with given color and returns. + + Parameters + ---------- + frame : str + Frame to be colored + color : str + Color to be applied + + Returns + ------- + str + Colored frame + """ + return colored(frame, color, attrs=['bold']) + + +def is_text_type(text): + """Check if given parameter is a string or not + + Parameters + ---------- + text : * + Parameter to be checked for text type + + Returns + ------- + bool + Whether parameter is a string or not + """ + if isinstance(text, six.text_type) or isinstance(text, six.string_types): + return True + + return False + + +def decode_utf_8_text(text): + """Decode the text from utf-8 format + + Parameters + ---------- + text : str + String to be decoded + + Returns + ------- + str + Decoded string + """ + try: + return codecs.decode(text, 'utf-8') + except (TypeError, ValueError): + return text + + +def encode_utf_8_text(text): + """Encodes the text to utf-8 format + + Parameters + ---------- + text : str + String to be encoded + + Returns + ------- + str + Encoded string + """ + try: + return codecs.encode(text, 'utf-8', 'ignore') + except (TypeError, ValueError): + return text + + +def get_terminal_columns(): + """Determine the amount of available columns in the terminal + + Returns + ------- + int + Terminal width + """ + terminal_size = get_terminal_size() + + # If column size is 0 either we are not connected + # to a terminal or something else went wrong. Fallback to 80. + if terminal_size.columns == 0: + return 80 + else: + return terminal_size.columns diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/cursor.py b/provision-contest/ansible/files/admin-python/site-packages/halo/cursor.py new file mode 100644 index 00000000..b0e54c9f --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo/cursor.py @@ -0,0 +1,48 @@ +""" +Source: https://stackoverflow.com/a/10455937/2692667 +""" + +import sys +import os + +if os.name == "nt": + import ctypes + + class _CursorInfo(ctypes.Structure): + _fields_ = [("size", ctypes.c_int), ("visible", ctypes.c_byte)] + + +def hide(stream=sys.stdout): + """Hide cursor. + Parameters + ---------- + stream: sys.stdout, Optional + Defines stream to write output to. + """ + if os.name == "nt": + ci = _CursorInfo() + handle = ctypes.windll.kernel32.GetStdHandle(-11) + ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) + ci.visible = False + ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) + elif os.name == "posix": + stream.write("\033[?25l") + stream.flush() + + +def show(stream=sys.stdout): + """Show cursor. + Parameters + ---------- + stream: sys.stdout, Optional + Defines stream to write output to. + """ + if os.name == "nt": + ci = _CursorInfo() + handle = ctypes.windll.kernel32.GetStdHandle(-11) + ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) + ci.visible = True + ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) + elif os.name == "posix": + stream.write("\033[?25h") + stream.flush() diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/halo.py b/provision-contest/ansible/files/admin-python/site-packages/halo/halo.py new file mode 100644 index 00000000..9e10b66b --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo/halo.py @@ -0,0 +1,609 @@ +# -*- coding: utf-8 -*- +# pylint: disable=unsubscriptable-object +"""Beautiful terminal spinners in Python. +""" +from __future__ import absolute_import, unicode_literals + +import atexit +import functools +import sys +import threading +import time + +import halo.cursor as cursor + +from log_symbols.symbols import LogSymbols +from spinners.spinners import Spinners + +from halo._utils import ( + colored_frame, + decode_utf_8_text, + get_environment, + get_terminal_columns, + is_supported, + is_text_type, + encode_utf_8_text, +) + + +class Halo(object): + """Halo library. + Attributes + ---------- + CLEAR_LINE : str + Code to clear the line + """ + + CLEAR_LINE = "\033[K" + SPINNER_PLACEMENTS = ( + "left", + "right", + ) + + def __init__( + self, + text="", + color="cyan", + text_color=None, + spinner=None, + animation=None, + placement="left", + interval=-1, + enabled=True, + stream=sys.stdout, + ): + """Constructs the Halo object. + Parameters + ---------- + text : str, optional + Text to display. + text_color : str, optional + Color of the text. + color : str, optional + Color of the text to display. + spinner : str|dict, optional + String or dictionary representing spinner. String can be one of 60+ spinners + supported. + animation: str, optional + Animation to apply if text is too large. Can be one of `bounce`, `marquee`. + Defaults to ellipses. + placement: str, optional + Side of the text to place the spinner on. Can be `left` or `right`. + Defaults to `left`. + interval : integer, optional + Interval between each frame of the spinner in milliseconds. + enabled : boolean, optional + Spinner enabled or not. + stream : io, optional + Output. + """ + self._color = color + self._animation = animation + + self.spinner = spinner + self.text = text + self._text_color = text_color + + self._interval = ( + int(interval) if int(interval) > 0 else self._spinner["interval"] + ) + self._stream = stream + + self.placement = placement + self._frame_index = 0 + self._text_index = 0 + self._spinner_thread = None + self._stop_spinner = None + self._spinner_id = None + self.enabled = enabled + + environment = get_environment() + + def clean_up(): + """Handle cell execution""" + self.stop() + + if environment in ("ipython", "jupyter"): + from IPython import get_ipython + + ip = get_ipython() + ip.events.register("post_run_cell", clean_up) + else: # default terminal + atexit.register(clean_up) + + def __enter__(self): + """Starts the spinner on a separate thread. For use in context managers. + Returns + ------- + self + """ + return self.start() + + def __exit__(self, type, value, traceback): + """Stops the spinner. For use in context managers.""" + self.stop() + + def __call__(self, f): + """Allow the Halo object to be used as a regular function decorator.""" + + @functools.wraps(f) + def wrapped(*args, **kwargs): + with self: + return f(*args, **kwargs) + + return wrapped + + @property + def spinner(self): + """Getter for spinner property. + Returns + ------- + dict + spinner value + """ + return self._spinner + + @spinner.setter + def spinner(self, spinner=None): + """Setter for spinner property. + Parameters + ---------- + spinner : dict, str + Defines the spinner value with frame and interval + """ + + self._spinner = self._get_spinner(spinner) + self._frame_index = 0 + self._text_index = 0 + + @property + def text(self): + """Getter for text property. + Returns + ------- + str + text value + """ + return self._text["original"] + + @text.setter + def text(self, text): + """Setter for text property. + Parameters + ---------- + text : str + Defines the text value for spinner + """ + self._text = self._get_text(text) + + @property + def text_color(self): + """Getter for text color property. + Returns + ------- + str + text color value + """ + return self._text_color + + @text_color.setter + def text_color(self, text_color): + """Setter for text color property. + Parameters + ---------- + text_color : str + Defines the text color value for spinner + """ + self._text_color = text_color + + @property + def color(self): + """Getter for color property. + Returns + ------- + str + color value + """ + return self._color + + @color.setter + def color(self, color): + """Setter for color property. + Parameters + ---------- + color : str + Defines the color value for spinner + """ + self._color = color + + @property + def placement(self): + """Getter for placement property. + Returns + ------- + str + spinner placement + """ + return self._placement + + @placement.setter + def placement(self, placement): + """Setter for placement property. + Parameters + ---------- + placement: str + Defines the placement of the spinner + """ + if placement not in self.SPINNER_PLACEMENTS: + raise ValueError( + "Unknown spinner placement '{0}', available are {1}".format( + placement, self.SPINNER_PLACEMENTS + ) + ) + self._placement = placement + + @property + def spinner_id(self): + """Getter for spinner id + Returns + ------- + str + Spinner id value + """ + return self._spinner_id + + @property + def animation(self): + """Getter for animation property. + Returns + ------- + str + Spinner animation + """ + return self._animation + + @animation.setter + def animation(self, animation): + """Setter for animation property. + Parameters + ---------- + animation: str + Defines the animation of the spinner + """ + self._animation = animation + self._text = self._get_text(self._text["original"]) + + def _check_stream(self): + """Returns whether the stream is open, and if applicable, writable + Returns + ------- + bool + Whether the stream is open + """ + if self._stream.closed: + return False + + try: + # Attribute access kept separate from invocation, to avoid + # swallowing AttributeErrors from the call which should bubble up. + check_stream_writable = self._stream.writable + except AttributeError: + pass + else: + return check_stream_writable() + + return True + + def _write(self, s): + """Write to the stream, if writable + Parameters + ---------- + s : str + Characters to write to the stream + """ + if self._check_stream(): + self._stream.write(s) + + def _hide_cursor(self): + """Disable the user's blinking cursor + """ + if self._check_stream() and self._stream.isatty(): + cursor.hide(stream=self._stream) + + def _show_cursor(self): + """Re-enable the user's blinking cursor + """ + if self._check_stream() and self._stream.isatty(): + cursor.show(stream=self._stream) + + def _get_spinner(self, spinner): + """Extracts spinner value from options and returns value + containing spinner frames and interval, defaults to 'dots' spinner. + Parameters + ---------- + spinner : dict, str + Contains spinner value or type of spinner to be used + Returns + ------- + dict + Contains frames and interval defining spinner + """ + default_spinner = Spinners["dots"].value + + if spinner and type(spinner) == dict: + return spinner + + if is_supported(): + if all([is_text_type(spinner), spinner in Spinners.__members__]): + return Spinners[spinner].value + else: + return default_spinner + else: + return Spinners["line"].value + + def _get_text(self, text): + """Creates frames based on the selected animation + Returns + ------- + self + """ + animation = self._animation + stripped_text = text.strip() + + # Check which frame of the animation is the widest + max_spinner_length = max([len(i) for i in self._spinner["frames"]]) + + # Subtract to the current terminal size the max spinner length + # (-1 to leave room for the extra space between spinner and text) + terminal_width = get_terminal_columns() - max_spinner_length - 1 + text_length = len(stripped_text) + + frames = [] + + if terminal_width < text_length and animation: + if animation == "bounce": + """ + Make the text bounce back and forth + """ + for x in range(0, text_length - terminal_width + 1): + frames.append(stripped_text[x : terminal_width + x]) + frames.extend(list(reversed(frames))) + elif "marquee": + """ + Make the text scroll like a marquee + """ + stripped_text = stripped_text + " " + stripped_text[:terminal_width] + for x in range(0, text_length + 1): + frames.append(stripped_text[x : terminal_width + x]) + elif terminal_width < text_length and not animation: + # Add ellipsis if text is larger than terminal width and no animation was specified + frames = [stripped_text[: terminal_width - 6] + " (...)"] + else: + frames = [stripped_text] + + return {"original": text, "frames": frames} + + def clear(self): + """Clears the line and returns cursor to the start. + of line + Returns + ------- + self + """ + self._write("\r") + self._write(self.CLEAR_LINE) + return self + + def _render_frame(self): + """Renders the frame on the line after clearing it. + """ + if not self.enabled: + # in case we're disabled or stream is closed while still rendering, + # we render the frame and increment the frame index, so the proper + # frame is rendered if we're reenabled or the stream opens again. + return + + self.clear() + frame = self.frame() + output = "\r{}".format(frame) + try: + self._write(output) + except UnicodeEncodeError: + self._write(encode_utf_8_text(output)) + + def render(self): + """Runs the render until thread flag is set. + Returns + ------- + self + """ + while not self._stop_spinner.is_set(): + self._render_frame() + time.sleep(0.001 * self._interval) + + return self + + def frame(self): + """Builds and returns the frame to be rendered + Returns + ------- + self + """ + frames = self._spinner["frames"] + frame = frames[self._frame_index] + + if self._color: + frame = colored_frame(frame, self._color) + + self._frame_index += 1 + self._frame_index = self._frame_index % len(frames) + + text_frame = self.text_frame() + return "{0} {1}".format( + *[ + (text_frame, frame) + if self._placement == "right" + else (frame, text_frame) + ][0] + ) + + def text_frame(self): + """Builds and returns the text frame to be rendered + Returns + ------- + self + """ + if len(self._text["frames"]) == 1: + if self._text_color: + return colored_frame(self._text["frames"][0], self._text_color) + + # Return first frame (can't return original text because at this point it might be ellipsed) + return self._text["frames"][0] + + frames = self._text["frames"] + frame = frames[self._text_index] + + self._text_index += 1 + self._text_index = self._text_index % len(frames) + + if self._text_color: + return colored_frame(frame, self._text_color) + + return frame + + def start(self, text=None): + """Starts the spinner on a separate thread. + Parameters + ---------- + text : None, optional + Text to be used alongside spinner + Returns + ------- + self + """ + if text is not None: + self.text = text + + if self._spinner_id is not None: + return self + + if not (self.enabled and self._check_stream()): + return self + + self._hide_cursor() + + self._stop_spinner = threading.Event() + self._spinner_thread = threading.Thread(target=self.render) + self._spinner_thread.setDaemon(True) + self._render_frame() + self._spinner_id = self._spinner_thread.name + self._spinner_thread.start() + + return self + + def stop(self): + """Stops the spinner and clears the line. + Returns + ------- + self + """ + if self._spinner_thread and self._spinner_thread.is_alive(): + self._stop_spinner.set() + self._spinner_thread.join() + + if self.enabled: + self.clear() + + self._frame_index = 0 + self._spinner_id = None + self._show_cursor() + return self + + def succeed(self, text=None): + """Shows and persists success symbol and text and exits. + Parameters + ---------- + text : None, optional + Text to be shown alongside success symbol. + Returns + ------- + self + """ + return self.stop_and_persist(symbol=LogSymbols.SUCCESS.value, text=text) + + def fail(self, text=None): + """Shows and persists fail symbol and text and exits. + Parameters + ---------- + text : None, optional + Text to be shown alongside fail symbol. + Returns + ------- + self + """ + return self.stop_and_persist(symbol=LogSymbols.ERROR.value, text=text) + + def warn(self, text=None): + """Shows and persists warn symbol and text and exits. + Parameters + ---------- + text : None, optional + Text to be shown alongside warn symbol. + Returns + ------- + self + """ + return self.stop_and_persist(symbol=LogSymbols.WARNING.value, text=text) + + def info(self, text=None): + """Shows and persists info symbol and text and exits. + Parameters + ---------- + text : None, optional + Text to be shown alongside info symbol. + Returns + ------- + self + """ + return self.stop_and_persist(symbol=LogSymbols.INFO.value, text=text) + + def stop_and_persist(self, symbol=" ", text=None): + """Stops the spinner and persists the final frame to be shown. + Parameters + ---------- + symbol : str, optional + Symbol to be shown in final frame + text: str, optional + Text to be shown in final frame + + Returns + ------- + self + """ + if not self.enabled: + return self + + symbol = decode_utf_8_text(symbol) + + if text is not None: + text = decode_utf_8_text(text) + else: + text = self._text["original"] + + text = text.strip() + + if self._text_color: + text = colored_frame(text, self._text_color) + + self.stop() + + output = "{0} {1}\n".format( + *[(text, symbol) if self._placement == "right" else (symbol, text)][0] + ) + + try: + self._write(output) + except UnicodeEncodeError: + self._write(encode_utf_8_text(output)) + + return self diff --git a/provision-contest/ansible/files/admin-python/site-packages/halo/halo_notebook.py b/provision-contest/ansible/files/admin-python/site-packages/halo/halo_notebook.py new file mode 100644 index 00000000..753fd7ac --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/halo/halo_notebook.py @@ -0,0 +1,122 @@ +from __future__ import absolute_import, print_function, unicode_literals + +import sys +import threading + +import halo.cursor as cursor + +from halo import Halo +from halo._utils import colored_frame, decode_utf_8_text + + +class HaloNotebook(Halo): + def __init__( + self, + text="", + color="cyan", + text_color=None, + spinner=None, + placement="left", + animation=None, + interval=-1, + enabled=True, + stream=sys.stdout, + ): + super(HaloNotebook, self).__init__( + text=text, + color=color, + text_color=text_color, + spinner=spinner, + placement=placement, + animation=animation, + interval=interval, + enabled=enabled, + stream=stream, + ) + self.output = self._make_output_widget() + + def _make_output_widget(self): + from ipywidgets.widgets import Output + + return Output() + + # TODO: using property and setter + def _output(self, text=""): + return ({"name": "stdout", "output_type": "stream", "text": text},) + + def clear(self): + if not self.enabled: + return self + + with self.output: + self.output.outputs += self._output("\r") + self.output.outputs += self._output(self.CLEAR_LINE) + + self.output.outputs = self._output() + return self + + def _render_frame(self): + frame = self.frame() + output = "\r{}".format(frame) + with self.output: + self.output.outputs += self._output(output) + + def start(self, text=None): + if text is not None: + self.text = text + + if not self.enabled or self._spinner_id is not None: + return self + + if self._stream.isatty(): + cursor.hide() + + self.output = self._make_output_widget() + from IPython.display import display + + display(self.output) + self._stop_spinner = threading.Event() + self._spinner_thread = threading.Thread(target=self.render) + self._spinner_thread.setDaemon(True) + self._render_frame() + self._spinner_id = self._spinner_thread.name + self._spinner_thread.start() + + return self + + def stop_and_persist(self, symbol=" ", text=None): + """Stops the spinner and persists the final frame to be shown. + Parameters + ---------- + symbol : str, optional + Symbol to be shown in final frame + text: str, optional + Text to be shown in final frame + + Returns + ------- + self + """ + if not self.enabled: + return self + + symbol = decode_utf_8_text(symbol) + + if text is not None: + text = decode_utf_8_text(text) + else: + text = self._text["original"] + + text = text.strip() + + if self._text_color: + text = colored_frame(text, self._text_color) + + self.stop() + + output = "\r{} {}\n".format( + *[(text, symbol) if self._placement == "right" else (symbol, text)][0] + ) + + with self.output: + self.output.outputs = self._output(output) diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/INSTALLER b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/LICENSE b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/LICENSE new file mode 100644 index 00000000..9b114e03 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Manraj Singh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/METADATA b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/METADATA new file mode 100644 index 00000000..e67b1ad1 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/METADATA @@ -0,0 +1,16 @@ +Metadata-Version: 2.1 +Name: log-symbols +Version: 0.0.14 +Summary: Colored symbols for various log levels for Python +Home-page: https://github.com/manrajgrover/py-log-symbols +Author: Manraj Singh +Author-email: manrajsinghgrover@gmail.com +License: MIT +Keywords: log symbols,symbols,log +Platform: UNKNOWN +Requires-Dist: colorama (>=0.3.9) +Requires-Dist: enum34 (==1.1.6) ; python_version < "3.4" + +Colored symbols for various log levels for Python. Find the documentation here: https://github.com/manrajgrover/py-log-symbols. + + diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/RECORD b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/RECORD new file mode 100644 index 00000000..d5bba0a3 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/RECORD @@ -0,0 +1,10 @@ +log_symbols-0.0.14.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +log_symbols-0.0.14.dist-info/LICENSE,sha256=R2t7cIDZ6-VroHO5M_FWET8adqmdI-PGv0AnhyvCo4A,1069 +log_symbols-0.0.14.dist-info/METADATA,sha256=cHv9QxJp7AaSrrnDMYjD1Q0ffgKVtD1n7YsF9qLHyC8,523 +log_symbols-0.0.14.dist-info/RECORD,, +log_symbols-0.0.14.dist-info/WHEEL,sha256=S8S5VL-stOTSZDYxHyf0KP7eds0J72qrK0Evu3TfyAY,92 +log_symbols-0.0.14.dist-info/top_level.txt,sha256=Mq8LH58W68d6O7zejMld8iZaLebMYm6ZK4RS-A52peI,12 +log_symbols/__init__.py,sha256=ULFhsDEyAEzfJYo3f-0rnMUQxO0FzQ1povrEPyjMGjI,127 +log_symbols/__pycache__/__init__.cpython-310.pyc,, +log_symbols/__pycache__/symbols.cpython-310.pyc,, +log_symbols/symbols.py,sha256=1cSjjIzw4_nGGUQB5Mj-TwZFYmx2m6rXjiV-CVNfGts,1276 diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/WHEEL b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/WHEEL new file mode 100644 index 00000000..c57a5970 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/top_level.txt b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/top_level.txt new file mode 100644 index 00000000..42af2dd2 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols-0.0.14.dist-info/top_level.txt @@ -0,0 +1 @@ +log_symbols diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__init__.py b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__init__.py new file mode 100644 index 00000000..9e63b021 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +__author__ = 'Manraj Singh' +__email__ = 'manrajsinghgrover@gmail.com' + +from .symbols import LogSymbols diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/__init__.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 00000000..075e9c1b Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/__init__.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/__init__.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 00000000..c00e5265 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/__init__.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/symbols.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/symbols.cpython-310.pyc new file mode 100644 index 00000000..e3d1a7be Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/symbols.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/symbols.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/symbols.cpython-38.pyc new file mode 100644 index 00000000..9e25222b Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/__pycache__/symbols.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/log_symbols/symbols.py b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/symbols.py new file mode 100644 index 00000000..e963d31d --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/log_symbols/symbols.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +"""Provide log symbols for various log levels.""" +import platform + +from enum import Enum +from colorama import init, deinit, Fore + +init(autoreset=True) + +_MAIN = { + 'info': 'ℹ', + 'success': '✔', + 'warning': '⚠', + 'error': '✖' +} + +_FALLBACKS = { + 'info': '¡', + 'success': 'v', + 'warning': '!!', + 'error': '×' +} + + +def is_supported(): + """Check whether operating system supports main symbols or not. + + Returns + ------- + boolean + Whether operating system supports main symbols or not + """ + + os_arch = platform.system() + + if os_arch != 'Windows': + return True + + return False + + +_SYMBOLS = _MAIN if is_supported() else _FALLBACKS + + +class LogSymbols(Enum): # pylint: disable=too-few-public-methods + """LogSymbol enum class. + + Attributes + ---------- + ERROR : str + Colored error symbol + INFO : str + Colored info symbol + SUCCESS : str + Colored success symbol + WARNING : str + Colored warning symbol + """ + + INFO = Fore.BLUE + _SYMBOLS['info'] + Fore.RESET + SUCCESS = Fore.GREEN + _SYMBOLS['success'] + Fore.RESET + WARNING = Fore.YELLOW + _SYMBOLS['warning'] + Fore.RESET + ERROR = Fore.RED + _SYMBOLS['error'] + Fore.RESET + +deinit() diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/INSTALLER b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/LICENSE b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/LICENSE new file mode 100644 index 00000000..9b114e03 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Manraj Singh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/METADATA b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/METADATA new file mode 100644 index 00000000..f8618054 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/METADATA @@ -0,0 +1,15 @@ +Metadata-Version: 2.1 +Name: spinners +Version: 0.0.24 +Summary: Spinners for terminals +Home-page: https://github.com/manrajgrover/py-spinners +Author: Manraj Singh +Author-email: manrajsinghgrover@gmail.com +License: MIT +Keywords: cli,spinner,spinners,terminal,term,console,ascii,unicode,loading,indicator,progress,busy,wait,idle,json +Platform: UNKNOWN +Requires-Dist: enum34 (==1.1.6) ; python_version < "3.4" + +More than 60 spinners for terminal, this is python port of amazing node library cli-spinners. Find the documentation here: https://github.com/manrajgrover/py-spinners. + + diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/RECORD b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/RECORD new file mode 100644 index 00000000..51d0fad7 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/RECORD @@ -0,0 +1,10 @@ +spinners-0.0.24.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +spinners-0.0.24.dist-info/LICENSE,sha256=R2t7cIDZ6-VroHO5M_FWET8adqmdI-PGv0AnhyvCo4A,1069 +spinners-0.0.24.dist-info/METADATA,sha256=cJ34-hmKWJZL898eLFz0BhL6WBykUsFtzOMKebuTWMA,576 +spinners-0.0.24.dist-info/RECORD,, +spinners-0.0.24.dist-info/WHEEL,sha256=p46_5Uhzqz6AzeSosiOnxK-zmFja1i22CrQCjmYe8ec,92 +spinners-0.0.24.dist-info/top_level.txt,sha256=P6azeuTfm-k56cPZr6jTxI0gHW-YM6H9nn3t-s7760o,9 +spinners/__init__.py,sha256=33bmNOuNt5tMmHsNge8cANVYXMAtNm_GAA2zDvvksuY,102 +spinners/__pycache__/__init__.cpython-310.pyc,, +spinners/__pycache__/spinners.cpython-310.pyc,, +spinners/spinners.py,sha256=UV7oVBbWizTWek87ExGSFhoP8fi5hIDYIYLAGhIL5J8,18813 diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/WHEEL b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/WHEEL new file mode 100644 index 00000000..3b5c4038 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.6) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/top_level.txt b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/top_level.txt new file mode 100644 index 00000000..e67b0824 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners-0.0.24.dist-info/top_level.txt @@ -0,0 +1 @@ +spinners diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners/__init__.py b/provision-contest/ansible/files/admin-python/site-packages/spinners/__init__.py new file mode 100644 index 00000000..969320e1 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners/__init__.py @@ -0,0 +1,4 @@ +__author__ = 'Manraj Singh' +__email__ = 'manrajsinghgrover@gmail.com' + +from .spinners import Spinners diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/__init__.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 00000000..cb45193b Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/__init__.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/__init__.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 00000000..740d217b Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/__init__.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/spinners.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/spinners.cpython-310.pyc new file mode 100644 index 00000000..5e90368f Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/spinners.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/spinners.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/spinners.cpython-38.pyc new file mode 100644 index 00000000..cea54fbf Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/spinners/__pycache__/spinners.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/spinners/spinners.py b/provision-contest/ansible/files/admin-python/site-packages/spinners/spinners.py new file mode 100644 index 00000000..7eee4f1a --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/spinners/spinners.py @@ -0,0 +1,943 @@ +# -*- coding: utf-8 -*- +""" +Python wrapper for beautiful terminal spinner library. + +Spinners are from: +* cli-spinners: + MIT License + + Copyright (c) Sindre Sorhus (sindresorhus.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +""" +from __future__ import unicode_literals + +from enum import Enum + +Spinners = Enum('Spinners', { + "dots": { + "interval": 80, + "frames": [ + "⠋", + "⠙", + "⠹", + "⠸", + "⠼", + "⠴", + "⠦", + "⠧", + "⠇", + "⠏" + ] + }, + "dots2": { + "interval": 80, + "frames": [ + "⣾", + "⣽", + "⣻", + "⢿", + "⡿", + "⣟", + "⣯", + "⣷" + ] + }, + "dots3": { + "interval": 80, + "frames": [ + "⠋", + "⠙", + "⠚", + "⠞", + "⠖", + "⠦", + "⠴", + "⠲", + "⠳", + "⠓" + ] + }, + "dots4": { + "interval": 80, + "frames": [ + "⠄", + "⠆", + "⠇", + "⠋", + "⠙", + "⠸", + "⠰", + "⠠", + "⠰", + "⠸", + "⠙", + "⠋", + "⠇", + "⠆" + ] + }, + "dots5": { + "interval": 80, + "frames": [ + "⠋", + "⠙", + "⠚", + "⠒", + "⠂", + "⠂", + "⠒", + "⠲", + "⠴", + "⠦", + "⠖", + "⠒", + "⠐", + "⠐", + "⠒", + "⠓", + "⠋" + ] + }, + "dots6": { + "interval": 80, + "frames": [ + "⠁", + "⠉", + "⠙", + "⠚", + "⠒", + "⠂", + "⠂", + "⠒", + "⠲", + "⠴", + "⠤", + "⠄", + "⠄", + "⠤", + "⠴", + "⠲", + "⠒", + "⠂", + "⠂", + "⠒", + "⠚", + "⠙", + "⠉", + "⠁" + ] + }, + "dots7": { + "interval": 80, + "frames": [ + "⠈", + "⠉", + "⠋", + "⠓", + "⠒", + "⠐", + "⠐", + "⠒", + "⠖", + "⠦", + "⠤", + "⠠", + "⠠", + "⠤", + "⠦", + "⠖", + "⠒", + "⠐", + "⠐", + "⠒", + "⠓", + "⠋", + "⠉", + "⠈" + ] + }, + "dots8": { + "interval": 80, + "frames": [ + "⠁", + "⠁", + "⠉", + "⠙", + "⠚", + "⠒", + "⠂", + "⠂", + "⠒", + "⠲", + "⠴", + "⠤", + "⠄", + "⠄", + "⠤", + "⠠", + "⠠", + "⠤", + "⠦", + "⠖", + "⠒", + "⠐", + "⠐", + "⠒", + "⠓", + "⠋", + "⠉", + "⠈", + "⠈" + ] + }, + "dots9": { + "interval": 80, + "frames": [ + "⢹", + "⢺", + "⢼", + "⣸", + "⣇", + "⡧", + "⡗", + "⡏" + ] + }, + "dots10": { + "interval": 80, + "frames": [ + "⢄", + "⢂", + "⢁", + "⡁", + "⡈", + "⡐", + "⡠" + ] + }, + "dots11": { + "interval": 100, + "frames": [ + "⠁", + "⠂", + "⠄", + "⡀", + "⢀", + "⠠", + "⠐", + "⠈" + ] + }, + "dots12": { + "interval": 80, + "frames": [ + "⢀⠀", + "⡀⠀", + "⠄⠀", + "⢂⠀", + "⡂⠀", + "⠅⠀", + "⢃⠀", + "⡃⠀", + "⠍⠀", + "⢋⠀", + "⡋⠀", + "⠍⠁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⢈⠩", + "⡀⢙", + "⠄⡙", + "⢂⠩", + "⡂⢘", + "⠅⡘", + "⢃⠨", + "⡃⢐", + "⠍⡐", + "⢋⠠", + "⡋⢀", + "⠍⡁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⠈⠩", + "⠀⢙", + "⠀⡙", + "⠀⠩", + "⠀⢘", + "⠀⡘", + "⠀⠨", + "⠀⢐", + "⠀⡐", + "⠀⠠", + "⠀⢀", + "⠀⡀" + ] + }, + "line": { + "interval": 130, + "frames": [ + "-", + "\\", + "|", + "/" + ] + }, + "line2": { + "interval": 100, + "frames": [ + "⠂", + "-", + "–", + "—", + "–", + "-" + ] + }, + "pipe": { + "interval": 100, + "frames": [ + "┤", + "┘", + "┴", + "└", + "├", + "┌", + "┬", + "┐" + ] + }, + "simpleDots": { + "interval": 400, + "frames": [ + ". ", + ".. ", + "...", + " " + ] + }, + "simpleDotsScrolling": { + "interval": 200, + "frames": [ + ". ", + ".. ", + "...", + " ..", + " .", + " " + ] + }, + "star": { + "interval": 70, + "frames": [ + "✶", + "✸", + "✹", + "✺", + "✹", + "✷" + ] + }, + "star2": { + "interval": 80, + "frames": [ + "+", + "x", + "*" + ] + }, + "flip": { + "interval": 70, + "frames": [ + "_", + "_", + "_", + "-", + "`", + "`", + "'", + "´", + "-", + "_", + "_", + "_" + ] + }, + "hamburger": { + "interval": 100, + "frames": [ + "☱", + "☲", + "☴" + ] + }, + "growVertical": { + "interval": 120, + "frames": [ + "▁", + "▃", + "▄", + "▅", + "▆", + "▇", + "▆", + "▅", + "▄", + "▃" + ] + }, + "growHorizontal": { + "interval": 120, + "frames": [ + "▏", + "▎", + "▍", + "▌", + "▋", + "▊", + "▉", + "▊", + "▋", + "▌", + "▍", + "▎" + ] + }, + "balloon": { + "interval": 140, + "frames": [ + " ", + ".", + "o", + "O", + "@", + "*", + " " + ] + }, + "balloon2": { + "interval": 120, + "frames": [ + ".", + "o", + "O", + "°", + "O", + "o", + "." + ] + }, + "noise": { + "interval": 100, + "frames": [ + "▓", + "▒", + "░" + ] + }, + "bounce": { + "interval": 120, + "frames": [ + "⠁", + "⠂", + "⠄", + "⠂" + ] + }, + "boxBounce": { + "interval": 120, + "frames": [ + "▖", + "▘", + "▝", + "▗" + ] + }, + "boxBounce2": { + "interval": 100, + "frames": [ + "▌", + "▀", + "▐", + "▄" + ] + }, + "triangle": { + "interval": 50, + "frames": [ + "◢", + "◣", + "◤", + "◥" + ] + }, + "arc": { + "interval": 100, + "frames": [ + "◜", + "◠", + "◝", + "◞", + "◡", + "◟" + ] + }, + "circle": { + "interval": 120, + "frames": [ + "◡", + "⊙", + "◠" + ] + }, + "squareCorners": { + "interval": 180, + "frames": [ + "◰", + "◳", + "◲", + "◱" + ] + }, + "circleQuarters": { + "interval": 120, + "frames": [ + "◴", + "◷", + "◶", + "◵" + ] + }, + "circleHalves": { + "interval": 50, + "frames": [ + "◐", + "◓", + "◑", + "◒" + ] + }, + "squish": { + "interval": 100, + "frames": [ + "╫", + "╪" + ] + }, + "toggle": { + "interval": 250, + "frames": [ + "⊶", + "⊷" + ] + }, + "toggle2": { + "interval": 80, + "frames": [ + "▫", + "▪" + ] + }, + "toggle3": { + "interval": 120, + "frames": [ + "□", + "■" + ] + }, + "toggle4": { + "interval": 100, + "frames": [ + "■", + "□", + "▪", + "▫" + ] + }, + "toggle5": { + "interval": 100, + "frames": [ + "▮", + "▯" + ] + }, + "toggle6": { + "interval": 300, + "frames": [ + "ဝ", + "၀" + ] + }, + "toggle7": { + "interval": 80, + "frames": [ + "⦾", + "⦿" + ] + }, + "toggle8": { + "interval": 100, + "frames": [ + "◍", + "◌" + ] + }, + "toggle9": { + "interval": 100, + "frames": [ + "◉", + "◎" + ] + }, + "toggle10": { + "interval": 100, + "frames": [ + "㊂", + "㊀", + "㊁" + ] + }, + "toggle11": { + "interval": 50, + "frames": [ + "⧇", + "⧆" + ] + }, + "toggle12": { + "interval": 120, + "frames": [ + "☗", + "☖" + ] + }, + "toggle13": { + "interval": 80, + "frames": [ + "=", + "*", + "-" + ] + }, + "arrow": { + "interval": 100, + "frames": [ + "←", + "↖", + "↑", + "↗", + "→", + "↘", + "↓", + "↙" + ] + }, + "arrow2": { + "interval": 80, + "frames": [ + "⬆️ ", + "↗️ ", + "➡️ ", + "↘️ ", + "⬇️ ", + "↙️ ", + "⬅️ ", + "↖️ " + ] + }, + "arrow3": { + "interval": 120, + "frames": [ + "▹▹▹▹▹", + "▸▹▹▹▹", + "▹▸▹▹▹", + "▹▹▸▹▹", + "▹▹▹▸▹", + "▹▹▹▹▸" + ] + }, + "bouncingBar": { + "interval": 80, + "frames": [ + "[ ]", + "[= ]", + "[== ]", + "[=== ]", + "[ ===]", + "[ ==]", + "[ =]", + "[ ]", + "[ =]", + "[ ==]", + "[ ===]", + "[====]", + "[=== ]", + "[== ]", + "[= ]" + ] + }, + "bouncingBall": { + "interval": 80, + "frames": [ + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "( ●)", + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "(● )" + ] + }, + "smiley": { + "interval": 200, + "frames": [ + "😄 ", + "😝 " + ] + }, + "monkey": { + "interval": 300, + "frames": [ + "🙈 ", + "🙈 ", + "🙉 ", + "🙊 " + ] + }, + "hearts": { + "interval": 100, + "frames": [ + "💛 ", + "💙 ", + "💜 ", + "💚 ", + "❤️ " + ] + }, + "clock": { + "interval": 100, + "frames": [ + "🕛 ", + "🕐 ", + "🕑 ", + "🕒 ", + "🕓 ", + "🕔 ", + "🕕 ", + "🕖 ", + "🕗 ", + "🕘 ", + "🕙 ", + "🕚 " + ] + }, + "earth": { + "interval": 180, + "frames": [ + "🌍 ", + "🌎 ", + "🌏 " + ] + }, + "moon": { + "interval": 80, + "frames": [ + "🌑 ", + "🌒 ", + "🌓 ", + "🌔 ", + "🌕 ", + "🌖 ", + "🌗 ", + "🌘 " + ] + }, + "runner": { + "interval": 140, + "frames": [ + "🚶 ", + "🏃 " + ] + }, + "pong": { + "interval": 80, + "frames": [ + "▐⠂ ▌", + "▐⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂▌", + "▐ ⠠▌", + "▐ ⡀▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐⠠ ▌" + ] + }, + "shark": { + "interval": 120, + "frames": [ + "▐|\\____________▌", + "▐_|\\___________▌", + "▐__|\\__________▌", + "▐___|\\_________▌", + "▐____|\\________▌", + "▐_____|\\_______▌", + "▐______|\\______▌", + "▐_______|\\_____▌", + "▐________|\\____▌", + "▐_________|\\___▌", + "▐__________|\\__▌", + "▐___________|\\_▌", + "▐____________|\\▌", + "▐____________/|▌", + "▐___________/|_▌", + "▐__________/|__▌", + "▐_________/|___▌", + "▐________/|____▌", + "▐_______/|_____▌", + "▐______/|______▌", + "▐_____/|_______▌", + "▐____/|________▌", + "▐___/|_________▌", + "▐__/|__________▌", + "▐_/|___________▌", + "▐/|____________▌" + ] + }, + "dqpb": { + "interval": 100, + "frames": [ + "d", + "q", + "p", + "b" + ] + }, + "weather": { + "interval": 100, + "frames": [ + "☀️ ", + "☀️ ", + "☀️ ", + "🌤 ", + "⛅️ ", + "🌥 ", + "☁️ ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "⛈ ", + "🌨 ", + "🌧 ", + "🌨 ", + "☁️ ", + "🌥 ", + "⛅️ ", + "🌤 ", + "☀️ ", + "☀️ " + ] + }, + "christmas": { + "interval": 400, + "frames": [ + "🌲", + "🎄" + ] + }, + "grenade": { + "interval": 80, + "frames": [ + "، ", + "′ ", + " ´ ", + " ‾ ", + " ⸌", + " ⸊", + " |", + " ⁎", + " ⁕", + " ෴ ", + " ⁓", + " ", + " ", + " " + ] + }, + "point": { + "interval": 125, + "frames": [ + "∙∙∙", + "●∙∙", + "∙●∙", + "∙∙●", + "∙∙∙" + ] + }, + "layer": { + "interval": 150, + "frames": [ + "-", + "=", + "≡" + ] + } +}) diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/INSTALLER b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/METADATA b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/METADATA new file mode 100644 index 00000000..20070a73 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/METADATA @@ -0,0 +1,112 @@ +Metadata-Version: 2.1 +Name: termcolor +Version: 2.0.1 +Summary: ANSI color formatting for output in terminal +Project-URL: Changelog, https://github.com/termcolor/termcolor/releases +Project-URL: Homepage, https://github.com/termcolor/termcolor +Project-URL: Source, https://github.com/termcolor/termcolor +Author-email: Konstantin Lepa +Maintainer: Hugo van Kemenade +License: MIT +License-File: COPYING.txt +Keywords: ANSI,ANSI color,ANSI colour,color,colour,formatting,termcolor,terminal +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Terminals +Requires-Python: >=3.7 +Provides-Extra: tests +Requires-Dist: pytest; extra == 'tests' +Requires-Dist: pytest-cov; extra == 'tests' +Description-Content-Type: text/markdown + +# termcolor + +[![PyPI version](https://img.shields.io/pypi/v/termcolor.svg?logo=pypi&logoColor=FFE873)](https://pypi.org/project/termcolor) +[![Supported Python versions](https://img.shields.io/pypi/pyversions/termcolor.svg?logo=python&logoColor=FFE873)](https://pypi.org/project/termcolor) +[![PyPI downloads](https://img.shields.io/pypi/dm/termcolor.svg)](https://pypistats.org/packages/termcolor) +[![GitHub Actions status](https://github.com/termcolor/termcolor/workflows/Test/badge.svg)](https://github.com/termcolor/termcolor/actions) +[![Codecov](https://codecov.io/gh/termcolor/termcolor/branch/main/graph/badge.svg)](https://codecov.io/gh/termcolor/termcolor) +[![Licence](https://img.shields.io/github/license/termcolor/termcolor.svg)](COPYING.txt) +[![Code style: Black](https://img.shields.io/badge/code%20style-Black-000000.svg)](https://github.com/psf/black) + +## Example + +```python +import sys + +from termcolor import colored, cprint + +text = colored("Hello, World!", "red", attrs=["reverse", "blink"]) +print(text) +cprint("Hello, World!", "green", "on_red") + +print_red_on_cyan = lambda x: cprint(x, "red", "on_cyan") +print_red_on_cyan("Hello, World!") +print_red_on_cyan("Hello, Universe!") + +for i in range(10): + cprint(i, "magenta", end=" ") + +cprint("Attention!", "red", attrs=["bold"], file=sys.stderr) +``` + +## Text properties + +Text colors: + +- grey +- red +- green +- yellow +- blue +- magenta +- cyan +- white + +Text highlights: + +- on_grey +- on_red +- on_green +- on_yellow +- on_blue +- on_magenta +- on_cyan +- on_white + +Attributes: + +- bold +- dark +- underline +- blink +- reverse +- concealed + +## Terminal properties + +| Terminal | bold | dark | underline | blink | reverse | concealed | +| ------------ | ------- | ---- | --------- | ---------- | ------- | --------- | +| xterm | yes | no | yes | bold | yes | yes | +| linux | yes | yes | bold | yes | yes | no | +| rxvt | yes | no | yes | bold/black | yes | no | +| dtterm | yes | yes | yes | reverse | yes | yes | +| teraterm | reverse | no | yes | rev/red | yes | no | +| aixterm | normal | no | yes | no | yes | yes | +| PuTTY | color | no | yes | no | yes | no | +| Windows | no | no | no | no | yes | no | +| Cygwin SSH | yes | no | color | color | color | yes | +| Mac Terminal | yes | no | yes | yes | yes | yes | diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/RECORD b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/RECORD new file mode 100644 index 00000000..69b522b6 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/RECORD @@ -0,0 +1,10 @@ +termcolor-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +termcolor-2.0.1.dist-info/METADATA,sha256=ICPL4HYUz0sP9Mu9VfkPkFySyaM1qP7F7jfB9hzuG3g,4067 +termcolor-2.0.1.dist-info/RECORD,, +termcolor-2.0.1.dist-info/WHEEL,sha256=Y57InxZzFBC6JOUn3gj3DWHnWQvSjh7u7UaNLmWRXIQ,86 +termcolor-2.0.1.dist-info/licenses/COPYING.txt,sha256=55tr2CliwTMMqqfEInhWewhmd3dnP44jcaYk1XFdTA4,1072 +termcolor/__init__.py,sha256=Ko_lcHUBKQUPMSCzcfhNwN3kd-AX8fAxajxPbODlG7Q,230 +termcolor/__pycache__/__init__.cpython-310.pyc,, +termcolor/__pycache__/termcolor.cpython-310.pyc,, +termcolor/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +termcolor/termcolor.py,sha256=Xn2UxIp3sJbv8FThBHphin6vw5Do16A3JEXt_iCS-7s,5378 diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/WHEEL b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/WHEEL new file mode 100644 index 00000000..8fe858da --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/licenses/COPYING.txt b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/licenses/COPYING.txt new file mode 100644 index 00000000..d0b79705 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor-2.0.1.dist-info/licenses/COPYING.txt @@ -0,0 +1,19 @@ +Copyright (c) 2008-2011 Volvox Development Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/__init__.py b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__init__.py new file mode 100644 index 00000000..9dceed79 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__init__.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from termcolor.termcolor import ATTRIBUTES, COLORS, HIGHLIGHTS, RESET, colored, cprint + +__all__ = [ + "ATTRIBUTES", + "COLORS", + "HIGHLIGHTS", + "RESET", + "colored", + "cprint", +] diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/__init__.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 00000000..d48fe816 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/__init__.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/__init__.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 00000000..436a5d0f Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/__init__.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/termcolor.cpython-310.pyc b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/termcolor.cpython-310.pyc new file mode 100644 index 00000000..82a4b3d7 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/termcolor.cpython-310.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/termcolor.cpython-38.pyc b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/termcolor.cpython-38.pyc new file mode 100644 index 00000000..74016674 Binary files /dev/null and b/provision-contest/ansible/files/admin-python/site-packages/termcolor/__pycache__/termcolor.cpython-38.pyc differ diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/py.typed b/provision-contest/ansible/files/admin-python/site-packages/termcolor/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/provision-contest/ansible/files/admin-python/site-packages/termcolor/termcolor.py b/provision-contest/ansible/files/admin-python/site-packages/termcolor/termcolor.py new file mode 100644 index 00000000..82dae308 --- /dev/null +++ b/provision-contest/ansible/files/admin-python/site-packages/termcolor/termcolor.py @@ -0,0 +1,191 @@ +# Copyright (c) 2008-2011 Volvox Development Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# Author: Konstantin Lepa + +"""ANSI color formatting for output in terminal.""" + +from __future__ import annotations + +import os +from typing import Any, Iterable + +__ALL__ = ["colored", "cprint"] + +ATTRIBUTES = dict( + list( + zip( + [ + "bold", + "dark", + "", + "underline", + "blink", + "", + "reverse", + "concealed", + ], + list(range(1, 9)), + ) + ) +) +del ATTRIBUTES[""] + + +HIGHLIGHTS = dict( + list( + zip( + [ + "on_grey", + "on_red", + "on_green", + "on_yellow", + "on_blue", + "on_magenta", + "on_cyan", + "on_white", + ], + list(range(40, 48)), + ) + ) +) + + +COLORS = dict( + list( + zip( + [ + "grey", + "red", + "green", + "yellow", + "blue", + "magenta", + "cyan", + "white", + ], + list(range(30, 38)), + ) + ) +) + + +RESET = "\033[0m" + + +def colored( + text: str, + color: str | None = None, + on_color: str | None = None, + attrs: Iterable[str] | None = None, +) -> str: + """Colorize text. + + Available text colors: + grey, red, green, yellow, blue, magenta, cyan, white. + + Available text highlights: + on_grey, on_red, on_green, on_yellow, on_blue, on_magenta, on_cyan, on_white. + + Available attributes: + bold, dark, underline, blink, reverse, concealed. + + Example: + colored('Hello, World!', 'red', 'on_grey', ['bold', 'blink']) + colored('Hello, World!', 'green') + """ + if "NO_COLOR" in os.environ or "ANSI_COLORS_DISABLED" in os.environ: + return text + + fmt_str = "\033[%dm%s" + if color is not None: + text = fmt_str % (COLORS[color], text) + + if on_color is not None: + text = fmt_str % (HIGHLIGHTS[on_color], text) + + if attrs is not None: + for attr in attrs: + text = fmt_str % (ATTRIBUTES[attr], text) + + return text + RESET + + +def cprint( + text: str, + color: str | None = None, + on_color: str | None = None, + attrs: Iterable[str] | None = None, + **kwargs: Any, +) -> None: + """Print colorize text. + + It accepts arguments of print function. + """ + + print((colored(text, color, on_color, attrs)), **kwargs) + + +if __name__ == "__main__": + print("Current terminal type: %s" % os.getenv("TERM")) + print("Test basic colors:") + cprint("Grey color", "grey") + cprint("Red color", "red") + cprint("Green color", "green") + cprint("Yellow color", "yellow") + cprint("Blue color", "blue") + cprint("Magenta color", "magenta") + cprint("Cyan color", "cyan") + cprint("White color", "white") + print("-" * 78) + + print("Test highlights:") + cprint("On grey color", on_color="on_grey") + cprint("On red color", on_color="on_red") + cprint("On green color", on_color="on_green") + cprint("On yellow color", on_color="on_yellow") + cprint("On blue color", on_color="on_blue") + cprint("On magenta color", on_color="on_magenta") + cprint("On cyan color", on_color="on_cyan") + cprint("On white color", color="grey", on_color="on_white") + print("-" * 78) + + print("Test attributes:") + cprint("Bold grey color", "grey", attrs=["bold"]) + cprint("Dark red color", "red", attrs=["dark"]) + cprint("Underline green color", "green", attrs=["underline"]) + cprint("Blink yellow color", "yellow", attrs=["blink"]) + cprint("Reversed blue color", "blue", attrs=["reverse"]) + cprint("Concealed Magenta color", "magenta", attrs=["concealed"]) + cprint( + "Bold underline reverse cyan color", + "cyan", + attrs=["bold", "underline", "reverse"], + ) + cprint( + "Dark blink concealed white color", + "white", + attrs=["dark", "blink", "concealed"], + ) + print("-" * 78) + + print("Test mixing:") + cprint("Underline red on grey color", "red", "on_grey", ["underline"]) + cprint("Reversed green on red color", "green", "on_red", ["reverse"]) diff --git a/provision-contest/ansible/files/admin.my.cnf.j2 b/provision-contest/ansible/files/admin.my.cnf.j2 new file mode 100644 index 00000000..7a581112 --- /dev/null +++ b/provision-contest/ansible/files/admin.my.cnf.j2 @@ -0,0 +1,3 @@ +[mysql] +user=domjudge +password={{DB_PASSWORD}} diff --git a/provision-contest/ansible/files/docs.yaml b/provision-contest/ansible/files/docs.yaml index 32646e3b..dc4a8fe1 100644 --- a/provision-contest/ansible/files/docs.yaml +++ b/provision-contest/ansible/files/docs.yaml @@ -3,18 +3,13 @@ # This file should contain an array with items to show. + - name: DOMjudge Team Guide - link: ../docs/DOMJudge.Team-Manual.WF2021.pdf + link: ../docs/WFDhaka-domjudge-team-manual.pdf icon: fas fa-users - name: Judging Notes - link: ../docs/2020-21.JudgingNotes.A4.pdf + link: ../docs/2021Dhaka.JudgingNotes.A4.pdf icon: fas fa-gavel -- name: Judging Notes Addendum - link: ../docs/2020-21.JudgingNotes.Addendum.A4.pdf - icon: fas fa-plus - name: Tech Notes - link: ../docs/2020-21.TechNotes.A4.pdf + link: ../docs/2021Dhaka.TechNotes.A4.pdf icon: fas fa-cog -- name: Mumble Notes - link: ../docs/2020-21.MumbleNotes.A4.pdf - icon: fas fa-headset diff --git a/provision-contest/ansible/files/grafana/nginx.conf b/provision-contest/ansible/files/grafana/nginx.conf deleted file mode 100644 index 18b5d666..00000000 --- a/provision-contest/ansible/files/grafana/nginx.conf +++ /dev/null @@ -1,19 +0,0 @@ -server { - listen 8443 ssl http2; - location / { - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Expose grafana - proxy_pass http://127.0.0.1:3000/; - proxy_read_timeout 90; - } - access_log /var/log/nginx/prometheus.log; - error_log /var/log/nginx/prometheus.log; - - ssl_certificate /etc/ssl/certs/grafana.crt; - ssl_certificate_key /etc/ssl/private/grafana.key; - ssl_session_timeout 5m; - ssl_prefer_server_ciphers on; -} diff --git a/provision-contest/ansible/files/nicelog b/provision-contest/ansible/files/nicelog new file mode 100755 index 00000000..1c83db8a --- /dev/null +++ b/provision-contest/ansible/files/nicelog @@ -0,0 +1,6 @@ +awk ' +/(request|app)\.INFO/ {print "\033[36m" $0 "\033[39m"} +/(request|app)\.WARNING/ {print "\033[33m" $0 "\033[39m"} +/(request|app)\.ERROR/ {print "\033[31m" $0 "\033[39m"} +/(request|app)\.CRITICAL/ {print "\033[31m" $0 "\033[39m"} +' diff --git a/provision-contest/ansible/files/taillog-domserver-symfony-error.desktop.j2 b/provision-contest/ansible/files/taillog-domserver-symfony-error.desktop.j2 index e1bb4ee2..61325a00 100644 --- a/provision-contest/ansible/files/taillog-domserver-symfony-error.desktop.j2 +++ b/provision-contest/ansible/files/taillog-domserver-symfony-error.desktop.j2 @@ -1,4 +1,4 @@ [Desktop Entry] Name=taillog-domserver-symfony-error Type=Application -Exec=gnome-terminal --window --geometry 74x17+1000+400 -e 'bash -c "tail -F {{ DJ_DIR }}/webapp/var/log/prod.log"' +Exec=gnome-terminal --window --geometry 74x17+1000+400 -e 'bash -c "tail -n 1000 -F {{ DJ_DIR }}/webapp/var/log/prod.log | ~/bin/nicelog"' diff --git a/provision-contest/ansible/files/taillog.desktop.j2 b/provision-contest/ansible/files/taillog.desktop.j2 index 6b9b6f74..ec4cbf24 100644 --- a/provision-contest/ansible/files/taillog.desktop.j2 +++ b/provision-contest/ansible/files/taillog.desktop.j2 @@ -1,4 +1,4 @@ [Desktop Entry] Name=taillog Type=Application -Exec=gnome-terminal --window --full-screen -e 'bash -c "tail -f {{ DJ_DIR }}/output/log/*-0.log"' +Exec=gnome-terminal --window --full-screen -e 'bash -c "tail -f {{ DJ_DIR }}/output/log/*-*.log"' diff --git a/provision-contest/ansible/generate_passwords.py b/provision-contest/ansible/generate_passwords.py new file mode 100644 index 00000000..68451dad --- /dev/null +++ b/provision-contest/ansible/generate_passwords.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +# Script based on: https://pypi.org/project/xkcdpass/ +from xkcdpass import xkcd_password as xp +import re, os + +words = xp.generate_wordlist(wordfile=xp.locate_wordfile(), min_length=3, max_length=8) + +for root,_,files in os.walk('group_vars'): + out = '' + if '/' not in root: + continue + if 'secret.yml.example' not in files: + continue + if os.path.isfile(os.path.join(root, 'secret.yml')): + print("Secret file exists already, exiting.") + exit(-1) + with open(os.path.join(root, 'secret.yml.example'), 'r') as fi: + for line in fi: + if '{' in line: + parms = str(re.search('{.*}', line).group())[1:-1] + if '-' in parms: + if 'strong' in parms: + vls = {parms: xp.generate_xkcdpassword(words, numwords=5, delimiter='-')} + else: + vls = {parms: xp.generate_xkcdpassword(words, numwords=3, delimiter='-')} + out += line.format(**vls) + else: + out += line + with open(os.path.join(root, 'secret.yml'), 'w') as fo: + fo.write(out) diff --git a/provision-contest/ansible/glitchtip.yml b/provision-contest/ansible/glitchtip.yml new file mode 100644 index 00000000..a9673f7c --- /dev/null +++ b/provision-contest/ansible/glitchtip.yml @@ -0,0 +1,21 @@ +--- +- hosts: glitchtip + vars: + host_type: glitchtip + become: true + handlers: + - include_tasks: handlers.yml + roles: + - role: base_packages + tags: base_packages + - role: icpc_fixes + tags: icpc_fixes + when: ICPC_IMAGE + - role: system_fixes + tags: system_fixes + - role: hosts + tags: hosts + - role: docker + tags: docker + - role: glitchtip + tags: glitchtip diff --git a/provision-contest/ansible/grafana.yml b/provision-contest/ansible/grafana.yml index e7ab4b67..59d24f8d 100644 --- a/provision-contest/ansible/grafana.yml +++ b/provision-contest/ansible/grafana.yml @@ -4,6 +4,8 @@ vars: host_type: grafana become: true + handlers: + - import_tasks: handlers.yml roles: - role: base_packages tags: base_packages diff --git a/provision-contest/ansible/group_vars/admin.yml b/provision-contest/ansible/group_vars/admin.yml index ae8cc316..ca98b241 100644 --- a/provision-contest/ansible/group_vars/admin.yml +++ b/provision-contest/ansible/group_vars/admin.yml @@ -1,5 +1,7 @@ DJ_DIR: /home/domjudge/domjudge-checkout +DJ_FORCE_UPDATE: false + DOMSERVER_URL: https://localhost PHP_FPM_MAX_CHILDREN: 5 diff --git a/provision-contest/ansible/group_vars/all/secret.yml.example b/provision-contest/ansible/group_vars/all/secret.yml.example deleted file mode 100644 index b166b42b..00000000 --- a/provision-contest/ansible/group_vars/all/secret.yml.example +++ /dev/null @@ -1,60 +0,0 @@ -# Password for the MySQL replication user. -# Set this to enable master-master replication between two domservers. -#REPLICATION_PASSWORD: some-replication-password - -# Database user password. -DB_PASSWORD: some-database-password - -# Credentials for the REST API. -API_USER: judgehost -API_PASSWORD: some-judgehost-password - -# Username and password to be used in .netrc files on admin machines -ADMIN_USER: admin -ADMIN_PASSWORD: some-admin-password - -# Password for domjudge shell user -# Set this to enable a password on the 'domjudge' shell accounts -# created on the domserver and judgehosts. -#DJ_SHELL_USER_PW: some-hashed-password - -# Accounts to create when setting up the CDS -CDS_ACCOUNTS: - - username: admin - password: adm1n - type: admin - - username: presAdmin - password: padm1n - type: admin - - username: blue - password: blu3 - type: staff - - username: balloon - password: balloonPr1nter - type: balloon - - username: public - password: publ1c - type: public - - username: presentation - password: presentat1on - type: public - - username: myicpc - password: my1cpc - type: spectator - - username: live - password: l1ve - type: analyst - - username: team1 - password: t3am - type: team - team_id: 1 - -# Contest(s) to configure in the CDS -CDS_CONTESTS: - - path: nwerc18 # Path in the contest directory - ccs: - id: nwerc18 # ID of the contest if hosted at DOMJUDGE_URL - # Or provide a absolute URL - # url: https://www.domjudge.org/demoweb/api/contests/nwerc18 - username: admin - password: admin diff --git a/provision-contest/ansible/group_vars/domserver.yml b/provision-contest/ansible/group_vars/domserver.yml new file mode 100644 index 00000000..f71da846 --- /dev/null +++ b/provision-contest/ansible/group_vars/domserver.yml @@ -0,0 +1,2 @@ +# If set, configure the real_ip_from header in NGINX to trust a proxy +# FRONT_REVERSE_PROXY: 192.168.0.1 diff --git a/provision-contest/ansible/group_vars/all/.gitignore b/provision-contest/ansible/group_vars/online/.gitignore similarity index 57% rename from provision-contest/ansible/group_vars/all/.gitignore rename to provision-contest/ansible/group_vars/online/.gitignore index 769fbcc2..c35135b1 100644 --- a/provision-contest/ansible/group_vars/all/.gitignore +++ b/provision-contest/ansible/group_vars/online/.gitignore @@ -1 +1,2 @@ /secret.yml +/all.yml diff --git a/provision-contest/ansible/group_vars/online/all.yml.example b/provision-contest/ansible/group_vars/online/all.yml.example new file mode 120000 index 00000000..cb36e6fe --- /dev/null +++ b/provision-contest/ansible/group_vars/online/all.yml.example @@ -0,0 +1 @@ +../onprem/all.yml.example \ No newline at end of file diff --git a/provision-contest/ansible/group_vars/online/secret.yml.example b/provision-contest/ansible/group_vars/online/secret.yml.example new file mode 120000 index 00000000..66dd4b02 --- /dev/null +++ b/provision-contest/ansible/group_vars/online/secret.yml.example @@ -0,0 +1 @@ +../onprem/secret.yml.example \ No newline at end of file diff --git a/provision-contest/ansible/group_vars/onprem/.gitignore b/provision-contest/ansible/group_vars/onprem/.gitignore new file mode 100644 index 00000000..c35135b1 --- /dev/null +++ b/provision-contest/ansible/group_vars/onprem/.gitignore @@ -0,0 +1,2 @@ +/secret.yml +/all.yml diff --git a/provision-contest/ansible/group_vars/all/all.yml b/provision-contest/ansible/group_vars/onprem/all.yml.example similarity index 56% rename from provision-contest/ansible/group_vars/all/all.yml rename to provision-contest/ansible/group_vars/onprem/all.yml.example index 47de5730..790b227c 100644 --- a/provision-contest/ansible/group_vars/all/all.yml +++ b/provision-contest/ansible/group_vars/onprem/all.yml.example @@ -4,11 +4,14 @@ DJ_DIR: /opt/domjudge # Branch to checkout and use. DJ_BRANCH: main +# Whether to force-pull changes to the checkout, overwriting any local changes. +DJ_FORCE_UPDATE: true + # Server VLAN IP prefix. SERVER_IP_PREFIX: 10.3.3 -# IP address of the admin machine used as ansible and git server. -MAIN_ADMIN_IP: "{{SERVER_IP_PREFIX}}.223" +# IP address of the git server. +LOCAL_GIT_IP: "{{SERVER_IP_PREFIX}}.207" # URL and IP of domserver from judgehosts. A hostname 'domserver' with # DOMSERVER_IP will be added to the judgehost /etc/hosts file. @@ -21,35 +24,64 @@ DOMSERVER_SSL_KEY: /etc/ssl/private/domserver.key # Set this to true when you are using the ICPC World Finals Contest Image ICPC_IMAGE: false +# Set this to true when you are using a graphical desktop +GRAPHICAL: false + +# Set this to true when you use an (ICPC) AWS machine +AWS: true + # Set this when on the blue network at the World Finals where no # internet access is available and "packages" must be used as APT repo # server. WF_RESTRICTED_NETWORK: false +WF_GREEN: false -TIMEZONE: "Europe/Moscow" +# Static IP address configuration. Uses the ansible_host variable as the static +# IP address. Only configured if STATIC_IP_ENABLED is true. +STATIC_IP_ENABLED: false +STATIC_IP_NETMASK: 255.255.252.0 +STATIC_IP_GATEWAY: 172.29.0.1 +STATIC_IP_INTERFACE: enp1s0 + +# Additional entries for the /etc/hosts file. +HOSTS: + cds: 10.3.3.207 + packages: 10.3.3.209 + ntp1: 10.3.3.208 + ntp2: 10.3.3.209 + nisprint: 10.3.3.211 + nismaster: 10.3.3.211 + printsrv: 10.3.3.211 + domjudge-laptop: 10.3.3.200 + pc2: 10.3.3.241 + +TIMEZONE: "Europe/Amsterdam" PHP_FPM_MAX_CHILDREN: 400 PHP_FPM_MAX_REQUESTS: 500 PHP_MEMORY_LIMIT: 1024M -PHP_UPLOAD_MAX_FILESIZE: 256M -PHP_POST_MAX_SIZE: 256M +PHP_UPLOAD_MAX_FILESIZE: 512M +PHP_POST_MAX_SIZE: 512M PHP_MAX_FILE_UPLOADS: 101 # Git repo URL -DJ_GIT_HOST: "{{MAIN_ADMIN_IP}}" +DJ_GIT_HOST: "{{LOCAL_GIT_IP}}" DJ_GIT_REPO: "https://github.com/domjudge/domjudge.git" DJ_GIT_REPO_RESTRICTED: "domjudge@{{DJ_GIT_HOST}}:domjudge" DJ_GIT_REPO_SCRIPTS: "https://github.com/domjudge/domjudge-scripts.git" DJ_GIT_REPO_SCRIPTS_RESTRICTED: "domjudge@{{DJ_GIT_HOST}}:domjudge-scripts-bare" +CONTEST_REPO: "wf2021" -PHPSTORM_VERSION: 2021.2 -PHPSTORM_FULL_VERSION: 212.5284.49 +PHPSTORM_VERSION: 2022.2 +PHPSTORM_FULL_VERSION: 222.4345.15 GRAFANA_MONITORING: false # Hostname of the CDS. If set, will add an nginx in front of the CDS # If not set, will only expose CDS directly CDS_HOSTNAME: cds +CDS_PORT: 80 +CDS_PORT_SECURE: 443 # CDS SSL cert and key. Only needed when CDS_HOSTNAME is set CDS_SSL_CERT: /etc/ssl/certs/cds.crt @@ -60,3 +92,8 @@ STATIC_SCOREBOARD_HOSTNAME: scoreboard STATIC_SCOREBOARD_SSL_CERT: /etc/ssl/certs/scoreboard.crt STATIC_SCOREBOARD_SSL_KEY: /etc/ssl/private/scoreboard.key + +# Block access to the CDS for IPs other than these +#CDS_IP_FILTER: +# - 127.0.0.1-127.0.0.1 +# - 192.168.0.0-192.168.255.255 diff --git a/provision-contest/ansible/group_vars/onprem/secret.yml.example b/provision-contest/ansible/group_vars/onprem/secret.yml.example new file mode 100644 index 00000000..bdd179a5 --- /dev/null +++ b/provision-contest/ansible/group_vars/onprem/secret.yml.example @@ -0,0 +1,75 @@ +# Templated passwords as `{some-strong-password}` are written to make sure our +# script detects those, if you manually change those the `{}` are not required and +# would become part of the password. +# Adding `strong` in the template will create longer passwords and is used for the +# passwords which almost never need to be manually typed. + +# Password for the MySQL replication user. +# Set this to enable master-master replication between two domservers. +#REPLICATION_PASSWORD: {some-strong-replication-password} + +# Database user password. +DB_PASSWORD: {some-strong-database-password} + +# Credentials for the judgehost. +JUDGEHOST_USER: judgehost +JUDGEHOST_PASSWORD: {some-strong-judgehost-password} + +# Username and password to be used in .netrc files on admin machines +ADMIN_USER: admin +ADMIN_PASSWORD: {some-admin-password} + +# Password for domjudge shell user +# Set this to enable a password on the 'domjudge' shell accounts +# created on the domserver and judgehosts. +#DJ_SHELL_USER_PW: {some-hashed-password} + +# Accounts to create when setting up the CDS +CDS_ACCOUNTS: + - username: admin + password: {some-adm1n-password} + type: admin + - username: presAdmin + password: {some-presentation-adm1n-password} + type: admin + - username: presentation + password: {some-public-presentation-password} + type: public + #- username: blue + # password: blu3 + # type: staff + #- username: balloon + # password: balloonPr1nter + # type: balloon + #- username: public + # password: publ1c + # type: public + #- username: myicpc + # password: my1cpc + # type: spectator + #- username: live + # password: l1ve + # type: analyst + #- username: team1 + # password: t3am + # type: team + # team_id: 1 + +# Contest(s) to configure in the CDS +CDS_CONTESTS: + - path: nwerc18 # Path in the contest directory + ccs: + id: nwerc18 # ID of the contest if hosted at DOMJUDGE_URL + # Or provide a absolute URL + # url: https://www.domjudge.org/demoweb/api/contests/nwerc18 + username: admin + password: admin + +PRESCLIENT_CONTEST: nwerc18 + +# Sentry DSN URL +# SENTRY_DSN: + +# GlitchTip +# GLITCHTIP_SECRET: {some-strong-glitchtip-password} +# GLITCHTIP_PASSWORD: {some-strong-glitchtip-password} diff --git a/provision-contest/ansible/handlers.yml b/provision-contest/ansible/handlers.yml index e1378e82..3c456cc4 100644 --- a/provision-contest/ansible/handlers.yml +++ b/provision-contest/ansible/handlers.yml @@ -1,6 +1,20 @@ # Common definition of handlers --- -- name: clear application cache +- name: Clear application cache command: "{{ DJ_DIR }}/webapp/bin/console cache:clear" become: true become_user: domjudge + +- name: Rebuild domjudge + become: true + become_user: domjudge + make: + chdir: "{{ DJ_DIR }}" + target: inplace-install + notify: Fix permissions on domjudge inplace-install + +- name: Restart nginx + systemd: + name: nginx + enabled: true + state: restarted diff --git a/provision-contest/ansible/hosts b/provision-contest/ansible/hosts.example similarity index 59% rename from provision-contest/ansible/hosts rename to provision-contest/ansible/hosts.example index d9e25401..837eb59e 100644 --- a/provision-contest/ansible/hosts +++ b/provision-contest/ansible/hosts.example @@ -2,10 +2,35 @@ ansible_user=root ansible_python_interpreter=/usr/bin/python3 +# When moving clients for ad-hoc actions: +# move them to their own group to keep hosts files on deployed machines in sync. + +[onprem:children] +domserver +judgehost +cds +grafana +admin +presclient +presadmin +scoreboard +mgmt +autoanalyst + +[online:children] +online-domserver +online-judgehost + [domserver] domjudge-primary ansible_host=10.3.3.216 KEEPALIVED_PRIORITY=100 EFI_ORDER='0\,1\,3\,4' domjudge-backup ansible_host=10.3.3.217 KEEPALIVED_PRIORITY=99 EFI_ORDER='0\,1\,3\,4' +[domserver:children] +emergency + +[emergency] +domjudge-laptop ansible_host=10.3.3.218 + [judgehost] domjudge-judgehost1 ansible_host=10.2.2.192 domjudge-judgehost2 ansible_host=10.2.2.193 @@ -34,13 +59,34 @@ domjudge-ccsadmin4 ansible_host=10.3.3.226 domjudge-ccsadmin5 ansible_host=10.3.3.227 [grafana] -domjudge-prometheus ansible_host=10.3.3.223 +# During the WFs we use one of the ccsadmin machines +# Doesn't matter which (admin) machine but should not be 1 as that runs ansible +domjudge-ccsadmin2 ansible_host=10.3.3.224 + +[glitchtip] +# During the WFs we use one of the ccsadmin machines +# Doesn't matter which (admin) machine but should not be 1 as that runs ansible +domjudge-ccsadmin3 ansible_host=10.3.3.225 [cds] domjudge-cds ansible_host=10.2.2.228 +[presclient] +presclient1 ansible_host=10.2.2.230 + +[presadmin] +presadmin ansible_host=10.2.2.231 + [scoreboard] domjudge-scoreboard ansible_host=10.2.2.229 [mgmt] domjudge-mgmg ansible_host=10.3.3.223 + +[autoanalyst] + +[online-domserver] +online-domserver ansible_host=192.168.255.255 + +[online-judgehost] +online-judgehost1 ansible_host=192.168.255.255 diff --git a/provision-contest/ansible/judgehost.yml b/provision-contest/ansible/judgehost.yml index e2f51b07..a26462ab 100644 --- a/provision-contest/ansible/judgehost.yml +++ b/provision-contest/ansible/judgehost.yml @@ -1,13 +1,9 @@ --- # This playbook installs the DOMjudge judgehosts -- name: setup judgehost - hosts: judgehost - # We always leave 1/3 of the judges online - serial: - - 33% - - 33% - - 34% +- name: Setup judgehost + hosts: judgehost,online-judgehost + strategy: free vars: host_type: judgehost become: true @@ -28,45 +24,56 @@ tags: ssl - role: domjudge_user tags: domjudge_user + - role: ssh + tags: ssh - role: domjudge_checkout tags: domjudge_checkout - role: domjudge_build tags: domjudge_build - role: judgedaemon tags: judgedaemon - - role: ssh - tags: ssh - role: domlogo tags: domlogo + when: GRAPHICAL - role: prometheus_target_all tags: prometheus_target_all when: GRAFANA_MONITORING + handlers: + - import_tasks: handlers.yml pre_tasks: - - name: Get current services - service_facts: - - name: Disable all cores + - name: Check if the service is already available + stat: + path: /etc/systemd/system/domjudge-judgedaemon@.service + get_attributes: false + get_checksum: false + get_mime: false + register: service_file + check_mode: no + tags: pretask + + - name: Stop judgedaemon service(s) on every possible core service: - name="domjudge-judgedaemon@{{ item }}" - state=stopped + name: "{{ item }}" + state: stopped with_sequence: start=0 end={{ ansible_processor_vcpus }} format=domjudge-judgedaemon@%1x - when: '"domjudge-judgedaemon@0.service" in services' + when: service_file.stat.exists + tags: pretask tasks: - - name: add autostart shortcuts - template: - src: files/{{ item }}.desktop - dest: /home/domjudge/.config/autostart/ - owner: domjudge - group: domjudge - mode: 0755 - loop: - - rotate + - name: Create graphical autostart entries + when: GRAPHICAL + block: + - name: Add autostart shortcuts + template: + src: files/rotate.desktop + dest: /home/domjudge/.config/autostart/ + owner: domjudge + group: domjudge + mode: 0755 - - name: add autostart shortcuts from template - template: - src: files/{{ item }}.desktop.j2 - dest: /home/domjudge/.config/autostart/{{ item }}.desktop - owner: domjudge - group: domjudge - mode: 0755 - loop: - - taillog + - name: Add autostart shortcuts from template + template: + src: files/taillog.desktop.j2 + dest: /home/domjudge/.config/autostart/taillog.desktop + owner: domjudge + group: domjudge + mode: 0755 diff --git a/provision-contest/ansible/mgmt.yml b/provision-contest/ansible/mgmt.yml index dba3333e..62d76794 100644 --- a/provision-contest/ansible/mgmt.yml +++ b/provision-contest/ansible/mgmt.yml @@ -1,12 +1,16 @@ --- # This playbook installs the Management machine -- name: setup management +- name: Setup management hosts: mgmt vars: host_type: mgmt become: true + handlers: + - import_tasks: handlers.yml roles: + - role: base_packages + tags: base_packages - role: prometheus_target_all tags: prometheus_target_all when: GRAFANA_MONITORING diff --git a/provision-contest/ansible/presadmin.yml b/provision-contest/ansible/presadmin.yml new file mode 100644 index 00000000..3d8f0bba --- /dev/null +++ b/provision-contest/ansible/presadmin.yml @@ -0,0 +1,30 @@ +--- +# This playbook installs the presentation admin + +- name: Setup presentation admin + hosts: presadmin + vars: + host_type: presadmin + GRAPHICAL: true + become: true + handlers: + - import_tasks: handlers.yml + roles: + - role: base_packages + tags: base_packages + - role: icpc_fixes + tags: icpc_fixes + when: ICPC_IMAGE + - role: system_fixes + tags: system_fixes + - role: hosts + tags: hosts + - role: domjudge_user + tags: domjudge_user + - role: ssh + tags: ssh + - role: presadmin + tags: presadmin + - role: prometheus_target_all + tags: prometheus_target_all + when: GRAFANA_MONITORING diff --git a/provision-contest/ansible/presclient.yml b/provision-contest/ansible/presclient.yml new file mode 100644 index 00000000..a3334aff --- /dev/null +++ b/provision-contest/ansible/presclient.yml @@ -0,0 +1,30 @@ +--- +# This playbook installs the presentation client + +- name: Setup presentation client + hosts: presclient + vars: + host_type: presclient + GRAPHICAL: true + become: true + handlers: + - import_tasks: handlers.yml + roles: + - role: base_packages + tags: base_packages + - role: icpc_fixes + tags: icpc_fixes + when: ICPC_IMAGE + - role: system_fixes + tags: system_fixes + - role: hosts + tags: hosts + - role: domjudge_user + tags: domjudge_user + - role: ssh + tags: ssh + - role: presclient + tags: presclient + - role: prometheus_target_all + tags: prometheus_target_all + when: GRAFANA_MONITORING diff --git a/provision-contest/ansible/roles/autoanalyst/handlers/main.yml b/provision-contest/ansible/roles/autoanalyst/handlers/main.yml new file mode 100644 index 00000000..6dfcdd76 --- /dev/null +++ b/provision-contest/ansible/roles/autoanalyst/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Reload nginx + systemd: + name: nginx + state: reloaded diff --git a/provision-contest/ansible/roles/autoanalyst/tasks/main.yml b/provision-contest/ansible/roles/autoanalyst/tasks/main.yml new file mode 100644 index 00000000..7eaf142f --- /dev/null +++ b/provision-contest/ansible/roles/autoanalyst/tasks/main.yml @@ -0,0 +1,199 @@ +--- +- name: Create the analyst user + user: + name: analyst + groups: [sudo] + state: present + shell: /bin/bash + +- name: Check if autoanalyst repository exists + stat: + path: /home/analyst/autoanalyst + register: repo_directory + +- name: Clone the autoanalyst repository + become_user: analyst + git: + repo: 'https://github.com/icpc-live/autoanalyst.git' + dest: /home/analyst/autoanalyst + when: not repo_directory.stat.exists + +- name: Create directories + become_user: analyst + file: + path: /home/analyst//{{ item }} + state: directory + with_items: + - githomes + - backup + - history + - autoanalyst/output + +- name: Install necessary packages + apt: + name: + - git + - gitk + - gitweb + - apache2-utils + - mariadb-server + - mariadb-client + - php-cli + - ntp + - rsync + - make + - curl + - python-yaml + - openjdk-8-jdk + - openjdk-8-jre + - python-httplib2 + - python3-mysqldb + - maven + +- name: Check if configuration exists + stat: + path: /home/analyst/autoanalyst/config.yaml + register: config_file + +- name: Create initial configuration + become_user: analyst + copy: + src: /home/analyst/autoanalyst/config.yaml.template + dest: /home/analyst/autoanalyst/config.yaml + when: not config_file.stat.exists + +- name: Create initial configuration + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + regexp: /home/icpclive + replace: /home/analyst + when: not config_file.stat.exists + +- name: Configure CCS baseurl + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'CCS:' + regexp: 'baseurl:.*' + replace: 'baseurl: "https://domjudge/"' + before: 'CDS:' + when: not config_file.stat.exists + +- name: Configure CCS submissionurl + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'CCS:' + regexp: 'submissionurl:.*' + replace: 'submissionurl: "jury/submissions/by-external-id/@ID@"' + before: 'CDS:' + when: not config_file.stat.exists + +- name: Configure CDS baseurl + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'CDS:' + regexp: 'baseurl:.*' + replace: 'baseurl: "https://cds/api/contests"' + before: 'teambackup:' + when: not config_file.stat.exists + +- name: Configure CDS user + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'CDS:' + regexp: 'user:.*' + replace: 'user: analytics' + before: 'teambackup:' + when: not config_file.stat.exists + +- name: Configure CDS user + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'CDS:' + regexp: 'userfull:.*' + replace: 'userfull: analyticsplus' + before: 'teambackup:' + when: not config_file.stat.exists + +- name: Configure database user + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'database:' + regexp: 'user:.*' + replace: 'user: icat' + before: 'timezone:' + when: not config_file.stat.exists + +- name: Enable file publisher + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: '# File publisher' + regexp: 'enable:.*' + replace: 'enable: true' + before: 'targetDirectory:' + when: not config_file.stat.exists + +- name: Generate database password + set_fact: + database_pass: '{{ lookup("password", "/dev/null length=15 chars=ascii_letters") }}' + +- name: Configure database password + become_user: analyst + replace: + path: /home/analyst/autoanalyst/config.yaml + after: 'database:' + regexp: 'password:.*' + replace: 'password: {{ database_pass }}' + before: 'teambackup:' + when: not config_file.stat.exists + +- name: Configure timezone + become_user: analyst + lineinfile: + path: /home/analyst/autoanalyst/config.yaml + regexp: '^timezone:' + line: 'timezone: Asia/Dhaka' + when: not config_file.stat.exists + +- name: Create database user + pause: + prompt: "mysql> CREATE USER 'icat'@'localhost' IDENTIFIED BY '{{ database_pass }}';" + +- name: Create database + pause: + prompt: "mysql> CREATE DATABASE icat;" + +- name: Grant database user privileges + pause: + prompt: "mysql> GRANT ALL PRIVILEGES ON icat.* TO 'icat'@'localhost';" + +- name: Populate database + pause: + prompt: "mysql -u icat --password={{ database_pass }} -h 127.0.0.1 < /home/analyst/autoanalyst/create_icat_instance.sql" + +- name: Create the nginx configuration + template: + src: nginx.conf.j2 + dest: /etc/nginx/sites-available/autoanalyst.conf + notify: Reload nginx + +- name: Create the nginx configuration + file: + src: /etc/nginx/sites-available/autoanalyst.conf + dest: /etc/nginx/sites-enabled/autoanalyst.conf + state: link + notify: Reload nginx + +- name: Build the Katalyzer + become_user: analyst + command: mvn package + args: + chdir: /home/analyst/autoanalyst/katalyze + creates: /home/analyst/autoanalyst/katalyze/target diff --git a/provision-contest/ansible/roles/autoanalyst/templates/nginx.conf.j2 b/provision-contest/ansible/roles/autoanalyst/templates/nginx.conf.j2 new file mode 100644 index 00000000..b6d41232 --- /dev/null +++ b/provision-contest/ansible/roles/autoanalyst/templates/nginx.conf.j2 @@ -0,0 +1,34 @@ +server { + listen 80; + server_name icat; + return 308 https://$host$request_uri; # enforce https +} + +server { + listen 443 ssl; + server_name icat; + + ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + ssl_session_timeout 5m; + ssl_prefer_server_ciphers on; + + send_timeout 36000s; + root /home/analyst/autoanalyst/www; + + location ~ ^/icat/api/(EventFeed|scoreboard|teams)$ { + rewrite ^/icat/api/(EventFeed|scoreboard|teams)$ /$1 break; + proxy_pass http://localhost:8099; + proxy_redirect off; + proxy_set_header Host $host; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + + # With php-fpm (or other unix sockets): + fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; + # With php-cgi (or other tcp sockets): + #fastcgi_pass 127.0.0.1:9000; + } +} diff --git a/provision-contest/ansible/roles/base_packages/defaults/main.yml b/provision-contest/ansible/roles/base_packages/defaults/main.yml new file mode 100644 index 00000000..6a552323 --- /dev/null +++ b/provision-contest/ansible/roles/base_packages/defaults/main.yml @@ -0,0 +1,55 @@ +REMOVED_PACKAGES: + - apport + - ntp + +INSTALLED_PACKAGES: + - ack + - acl + - apache2-utils + - autoconf + - automake + - bat + - bats + - bsdmainutils + - composer + - curl + - debootstrap + - default-jdk-headless + - efibootmgr + - etckeeper + - fontconfig + - g++ + - gcc + - git + - git-gui + - gitk + - htop + - httpie + - jq + - latexmk + - libcgroup-dev + - libcurl4-gnutls-dev + - libjsoncpp-dev + - libmagic-dev + - make + - ncdu + - php-cli + - php-curl + - php-gd + - php-intl + - php-json + - php-mbstring + - php-mysql + - php-xml + - php-zip + - php-bcmath + - pv + - python3-sphinx + - python3-sphinx-rtd-theme + - python3-yaml + - rst2pdf + - screen + - tig + - tree + - unzip + - zip diff --git a/provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore b/provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore new file mode 100644 index 00000000..c00df136 --- /dev/null +++ b/provision-contest/ansible/roles/base_packages/files/install-docker/.gitignore @@ -0,0 +1 @@ +*.deb diff --git a/provision-contest/ansible/roles/base_packages/handlers/main.yml b/provision-contest/ansible/roles/base_packages/handlers/main.yml index 81c2578c..5230f6b8 100644 --- a/provision-contest/ansible/roles/base_packages/handlers/main.yml +++ b/provision-contest/ansible/roles/base_packages/handlers/main.yml @@ -1,5 +1,4 @@ --- -# Define here handlers associated to this role. - -- name: run apt update - command: apt update +- name: Run apt update + apt: + update_cache: true diff --git a/provision-contest/ansible/roles/base_packages/tasks/install-local-package.yml b/provision-contest/ansible/roles/base_packages/tasks/install-local-package.yml index 180b53a3..65a49aaa 100644 --- a/provision-contest/ansible/roles/base_packages/tasks/install-local-package.yml +++ b/provision-contest/ansible/roles/base_packages/tasks/install-local-package.yml @@ -1,6 +1,6 @@ # Install a single local DEB package. --- -- name: create temp package directory +- name: Create temp package directory file: path: /tmp/dj_ansible state: directory @@ -8,12 +8,12 @@ group: root mode: 0700 -- name: copy DEB package to remote +- name: Copy DEB package to remote synchronize: src: "{{ item }}" - dest: "/tmp/dj_ansible/{{ item|basename }}" + dest: "/tmp/dj_ansible/{{ item | basename }}" -- name: install DEB package {{ item|basename }} +- name: Install DEB package {{ item | basename }} apt: - deb: "/tmp/dj_ansible/{{ item|basename }}" + deb: "/tmp/dj_ansible/{{ item | basename }}" state: present diff --git a/provision-contest/ansible/roles/base_packages/tasks/main.yml b/provision-contest/ansible/roles/base_packages/tasks/main.yml index 2fede9ff..de4b0175 100644 --- a/provision-contest/ansible/roles/base_packages/tasks/main.yml +++ b/provision-contest/ansible/roles/base_packages/tasks/main.yml @@ -1,128 +1,63 @@ --- # This task configures packaging and installs various system utilities -- name: replace pc2.ecs.baylor.edu by packages in apt sources - replace: - dest: "/etc/apt/{{ item }}" - regexp: 'pc2\.ecs\.baylor\.edu' - replace: 'packages' - loop: - - sources.list - - sources.list.d/microsoft.list - - sources.list.d/mono.list - - sources.list.d/vscode.list - - sources.list.d/pypy-ubuntu-ppa-buster.list - notify: run apt update - when: WF_RESTRICTED_NETWORK - -- name: add packages to hosts file - lineinfile: - dest: /etc/hosts - regexp: '^10\.3\.3\.209' - line: "10.3.3.209 packages" - notify: run apt update - when: WF_RESTRICTED_NETWORK - -- name: check for dpkg architecture i386 +- name: World finals fixes for apt sources + block: + - name: Replace pc2.ecs.baylor.edu by packages in apt sources + replace: + dest: "/etc/apt/{{ item }}" + regexp: 'sysopspackages\.icpc\.global' + replace: 'packages' + loop: + - sources.list + - sources.list.d/mono.list + - sources.list.d/vscode.list + - sources.list.d/pypy-ubuntu-ppa-jammy.list + notify: Run apt update + + - name: Add packages to hosts file + lineinfile: + dest: /etc/hosts + line: "{{ HOSTS['packages'] }} packages" + notify: Run apt update + + - name: Remove pycharm repo + replace: + dest: /etc/apt/sources.list + regexp: '^([^#].*pycharm.*)$' + replace: '# \1' + notify: Run apt update + when: ICPC_IMAGE and WF_RESTRICTED_NETWORK + +- name: Check for dpkg architecture i386 command: dpkg --print-foreign-architectures register: dpkg_architectures changed_when: false + check_mode: no -- name: remove unused dpkg architecture i386 +- name: Remove unused dpkg architecture i386 command: dpkg --remove-architecture i386 - notify: run apt update + notify: Run apt update when: dpkg_architectures.stdout.find('i386') != -1 -- name: remove pycharm repo - replace: - dest: /etc/apt/sources.list - regexp: '^([^#].*pycharm.*)$' - replace: '# \1' - notify: run apt update +- name: Update cache if this is our first run + apt: + cache_valid_time: 3600 -- name: flush handlers +- name: Flush handlers meta: flush_handlers -- name: remove some packages +- name: Remove some packages apt: state: absent - pkg: - - apport - - ntp + pkg: "{{ REMOVED_PACKAGES }}" -- name: install common required/useful packages +- name: Install common required/useful packages apt: state: present - pkg: - - ack - - git - - htop - - httpie - - ncdu - - pv - - screen - - autoconf - - automake - - efibootmgr - - curl - - gcc - - g++ - - default-jdk-headless - - make - - zip - - unzip - - php-cli - - php-gd - - php-curl - - php-mysql - - php-json - - php-xml - - php-zip - - php-mbstring - - php-intl - - bsdmainutils - - libcgroup-dev - - libcurl4-gnutls-dev - - libjsoncpp-dev - - libmagic-dev - - debootstrap - - texlive-latex-recommended - - texlive-latex-extra - - apache2-utils - - tig - - bat - - jq - - python3-sphinx - - autoconf - - automake - - bats - - python3-sphinx - - python3-sphinx-rtd-theme - - rst2pdf - - fontconfig - - python3-yaml - - latexmk - - acl - -- name: Check if composer is installed - stat: - path: /usr/local/bin/composer - register: composer_file - -- name: Download Composer Installer - get_url: - url: https://getcomposer.org/installer - dest: /root/composer-setup.php - owner: root - group: root - mode: 0755 - when: not composer_file.stat.exists - -- name: Install composer - command: php /root/composer-setup.php --install-dir=/usr/local/bin --filename=composer - when: not composer_file.stat.exists + pkg: "{{ INSTALLED_PACKAGES }}" -- name: install local DEB packages - include: install-local-package.yml +- name: Install local DEB packages + include_tasks: install-local-package.yml with_fileglob: - install-{{ host_type }}/*.deb diff --git a/provision-contest/ansible/roles/base_packages/vars/.gitignore b/provision-contest/ansible/roles/base_packages/vars/.gitignore new file mode 100644 index 00000000..1cda54be --- /dev/null +++ b/provision-contest/ansible/roles/base_packages/vars/.gitignore @@ -0,0 +1 @@ +*.yml diff --git a/provision-contest/ansible/roles/cds/files/cds.service b/provision-contest/ansible/roles/cds/files/cds.service index a1e6e7b4..a1b24a1e 100644 --- a/provision-contest/ansible/roles/cds/files/cds.service +++ b/provision-contest/ansible/roles/cds/files/cds.service @@ -4,6 +4,7 @@ Description=CDS User=domjudge Restart=always ExecStart=/home/domjudge/cds/wlp/bin/server run cds +Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 TimeoutStopSec=20s [Install] WantedBy=multi-user.target diff --git a/provision-contest/ansible/roles/cds/handlers/main.yml b/provision-contest/ansible/roles/cds/handlers/main.yml index 71fde60f..dae2b91d 100644 --- a/provision-contest/ansible/roles/cds/handlers/main.yml +++ b/provision-contest/ansible/roles/cds/handlers/main.yml @@ -1,16 +1,13 @@ --- -# Define here handlers associated to this role. - -- name: restart cds +- name: Restart cds systemd: name: cds enabled: true state: restarted daemon_reload: true -- name: restart nginx +- name: Restart nginx systemd: name: nginx enabled: true state: restarted - daemon_reload: true diff --git a/provision-contest/ansible/roles/cds/tasks/main.yml b/provision-contest/ansible/roles/cds/tasks/main.yml index e55f112f..db155a0b 100644 --- a/provision-contest/ansible/roles/cds/tasks/main.yml +++ b/provision-contest/ansible/roles/cds/tasks/main.yml @@ -1,6 +1,11 @@ --- # These tasks configure the CDS +- name: Install OpenJDK17 + apt: + name: + - openjdk-17-jre + - name: Get the CDS release uri: url: https://api.github.com/repos/icpctools/icpctools/releases?per_page=1 @@ -9,6 +14,7 @@ status_code: 200 body_format: json register: latest_cds_release_array + check_mode: no - name: Set CDS latest version set_fact: @@ -22,8 +28,9 @@ stat: path: /home/domjudge/cds/wlp/usr/servers/cds/apps/CDS.war register: cds_war + check_mode: no -- name: create CDS directory +- name: Create CDS directory file: path: /home/domjudge/cds state: directory @@ -39,7 +46,7 @@ owner: domjudge group: domjudge when: not cds_war.stat.exists - notify: restart cds + notify: Restart cds - name: Download and unpack CDS WAR unarchive: @@ -57,7 +64,7 @@ group: domjudge mode: 0644 when: cds_war.stat.exists - notify: restart cds + notify: Restart cds - name: Populate CDS accounts.yaml template: @@ -66,7 +73,7 @@ owner: domjudge group: domjudge mode: 0600 - notify: restart cds + notify: Restart cds - name: Populate CDS cdsConfig.xml template: @@ -75,7 +82,7 @@ owner: domjudge group: domjudge mode: 0600 - notify: restart cds + notify: Restart cds - name: Create contests config directory file: @@ -94,41 +101,41 @@ mode: 0755 loop: "{{ CDS_CONTESTS }}" -- name: copy cds systemd unit file +- name: Copy cds systemd unit file copy: src: cds.service dest: /etc/systemd/system/ mode: 0644 notify: - - restart cds + - Restart cds - name: Setup nginx block: - - name: install nginx + - name: Install nginx apt: state: present pkg: - nginx - - name: add CDS nginx conf + - name: Add CDS nginx conf template: src: cds.conf.j2 dest: /etc/nginx/sites-available/cds.conf mode: 0644 group: root owner: root - notify: restart nginx + notify: Restart nginx - - name: enable nginx conf for CDS + - name: Enable nginx conf for CDS file: src: /etc/nginx/sites-available/cds.conf dest: /etc/nginx/sites-enabled/cds.conf state: link - notify: restart nginx + notify: Restart nginx - - name: disable default nginx site + - name: Disable default nginx site file: path: /etc/nginx/sites-enabled/default state: absent - notify: restart nginx + notify: Restart nginx when: CDS_HOSTNAME is defined diff --git a/provision-contest/ansible/roles/cds/templates/cds.conf.j2 b/provision-contest/ansible/roles/cds/templates/cds.conf.j2 index 13dd26fb..94abc183 100644 --- a/provision-contest/ansible/roles/cds/templates/cds.conf.j2 +++ b/provision-contest/ansible/roles/cds/templates/cds.conf.j2 @@ -1,16 +1,27 @@ # nginx configuration for the CDS + +{% if CDS_IP_FILTER is defined %} +geo $network { + ranges; + default BLOCK; +{% for range in CDS_IP_FILTER %} + {{ range }} ALLOW; +{% endfor %} +} +{% endif %} + server { - listen 80; - listen [::]:80; + listen {{ CDS_PORT }}; + listen [::]:{{ CDS_PORT }}; server_name {{CDS_HOSTNAME}}; - return 301 https://$host$request_uri; + return 301 https://$host$request_uri:{{ CDS_PORT_SECURE }}; } server { - listen 443 ssl http2; - listen [::]:443 ssl http2; + listen {{ CDS_PORT_SECURE }} ssl http2; + listen [::]:{{ CDS_PORT_SECURE }} ssl http2; server_name {{CDS_HOSTNAME}}; @@ -26,12 +37,20 @@ server { proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $host; - proxy_pass https://localhost:8443; + {% if CDS_IP_FILTER is defined %} + if ( $network = ALLOW) { + proxy_pass https://localhost:8443; + } + {% else %} + proxy_pass https://localhost:8443; + {% endif %} proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_request_buffering off; proxy_buffering off; + # CDS event feed sends a heartbeat every 120s, so make sure our timeout is longer + proxy_read_timeout 130s; } } diff --git a/provision-contest/ansible/roles/clusterssh/tasks/main.yml b/provision-contest/ansible/roles/clusterssh/tasks/main.yml index b9d7f783..585432da 100644 --- a/provision-contest/ansible/roles/clusterssh/tasks/main.yml +++ b/provision-contest/ansible/roles/clusterssh/tasks/main.yml @@ -1,32 +1,24 @@ --- # These tasks configure clusterssh -- name: install clusterssh package +- name: Install clusterssh package apt: state: present pkg: - clusterssh -- name: create clusterssh 'all' config group - become: true - become_user: domjudge - lineinfile: - dest: /home/domjudge/.clusterssh/clusters - regexp: '^all' - line: "all {{ groups['all'] | join(' ') }}" - create: true - mode: 0644 +- name: Create clusterssh clusters file + file: + state: directory + owner: domjudge + group: domjudge + path: /home/domjudge/.clusterssh + mode: 0755 -- name: create clusterssh config groups +- name: Create clusterssh clusters file become: true become_user: domjudge - lineinfile: + template: + src: clusters.j2 dest: /home/domjudge/.clusterssh/clusters - regexp: '^{{ item }}s' - line: "{{ item }}s {{ groups[item] | join(' ') }}" - create: true mode: 0644 - loop: - - domserver - - judgehost - - admin diff --git a/provision-contest/ansible/roles/clusterssh/templates/clusters.j2 b/provision-contest/ansible/roles/clusterssh/templates/clusters.j2 new file mode 100644 index 00000000..0dfcf597 --- /dev/null +++ b/provision-contest/ansible/roles/clusterssh/templates/clusters.j2 @@ -0,0 +1,5 @@ +{% for group in groups %} +{% if groups[group] %} +{{ group | replace('emergency', 'emergencie') }}{% if group != 'all' %}s{% endif %} {{ groups[group] | join(' ') }} +{% endif %} +{% endfor %} diff --git a/provision-contest/ansible/roles/dj_notify/defaults/main.yml b/provision-contest/ansible/roles/dj_notify/defaults/main.yml new file mode 100644 index 00000000..1becf1d3 --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/defaults/main.yml @@ -0,0 +1,3 @@ +ALSA_DEVICE: alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__hw_sofhdadsp__sink +NOTIFICATION_SOUND: /usr/share/sounds/sound-icons/trumpet-12.wav +NOTIFICATION_SOUND_VOLUME: 35536 diff --git a/provision-contest/ansible/roles/dj_notify/files/dj_notify.py b/provision-contest/ansible/roles/dj_notify/files/dj_notify.py new file mode 100644 index 00000000..74988427 --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/files/dj_notify.py @@ -0,0 +1,87 @@ +from http.server import BaseHTTPRequestHandler, HTTPServer +import json +import subprocess +import gi +import os +import webbrowser +import subprocess +import traceback +gi.require_version('Notify', '0.7') +from gi.repository import Notify + +HOSTNAME = "0.0.0.0" +PORT = 9999 +ALSA_DEVICE = os.environ['ALSA_DEVICE'] +NOTIFICATION_SOUND = os.environ['NOTIFICATION_SOUND'] +NOTIFICATION_SOUND_VOLUME = int(os.environ['NOTIFICATION_SOUND_VOLUME']) + + +def on_notification_closed(notification): + print(f"Notification {notification.id} closed.") + + +def on_link_click(notification, action, link): + webbrowser.open(link) + + +def filter_notification(title, body, link): + return not title.startswith("Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException") + + +class NotifyServer(BaseHTTPRequestHandler): + def create_notification(self, title, body, link): + notification = Notify.Notification.new(title, body) + notification.connect("closed", on_notification_closed) + notification.add_action( + "action_click", + "View in browser", + on_link_click, + link + ) + notification.show() + + + def notification_sound(self, sound): + # Use Popen to launch a non-blocking background process + subprocess.Popen(["paplay", "--volume", str(NOTIFICATION_SOUND_VOLUME), "--device", ALSA_DEVICE, sound]) + + + def do_POST(self): + length = int(self.headers.get('Content-Length')) + body = self.rfile.read(length) + content = json.loads(body) + print(json.dumps(content, indent=2)) + + att = content['attachments'][0] + title = att['title'] + link = att['title_link'] + body = att['text'] + + if filter_notification(title, body, link): + try: + self.create_notification(title, body, link) + except Exception: + print(traceback.format_exc()) + try: + self.notification_sound(NOTIFICATION_SOUND) + except Exception: + print(traceback.format_exc()) + + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.end_headers() + + self.wfile.write(bytes("ok", "utf-8")) + + +Notify.init("DOMjudge notifications") +server = HTTPServer((HOSTNAME, PORT), NotifyServer) + +try: + server.serve_forever() +except KeyboardInterrupt: + pass + +# Clean up +server.server_close() +Notify.uninit() diff --git a/provision-contest/ansible/roles/dj_notify/handlers/main.yml b/provision-contest/ansible/roles/dj_notify/handlers/main.yml new file mode 100644 index 00000000..b7ce19cb --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart dj_notify + systemd: + name: dj_notify + enabled: true + state: restarted + daemon_reload: true diff --git a/provision-contest/ansible/roles/dj_notify/tasks/main.yml b/provision-contest/ansible/roles/dj_notify/tasks/main.yml new file mode 100644 index 00000000..c6391e3d --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/tasks/main.yml @@ -0,0 +1,17 @@ +--- +# These tasks install the DOMjudge Notify script + +- name: Install dj_notify + copy: + src: "dj_notify.py" + dest: "/home/domjudge/bin/dj_notify.py" + owner: domjudge + group: domjudge + mode: 0755 + notify: Restart dj_notify + +- name: Copy dj_notify systemd unit file + template: + src: "dj_notify.service.j2" + dest: "/etc/systemd/system/dj_notify.service" + notify: Restart dj_notify diff --git a/provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 b/provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 new file mode 100644 index 00000000..fb4737e2 --- /dev/null +++ b/provision-contest/ansible/roles/dj_notify/templates/dj_notify.service.j2 @@ -0,0 +1,21 @@ +[Unit] +Description="DOMjudge Notify" +After=network.target + +[Service] +Type=simple + +Environment=ALSA_DEVICE={{ ALSA_DEVICE }} +Environment=NOTIFICATION_SOUND={{ NOTIFICATION_SOUND }} +Environment=DISPLAY=:0 +Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus +Environment=PULSE_SERVER=/run/user/1001/pulse/native +WorkingDirectory=/home/domjudge +ExecStart=/usr/bin/python3 -u /home/domjudge/bin/dj_notify.py +User=domjudge + +Restart=always +RestartSec=3 + +[Install] +WantedBy=graphical.target diff --git a/provision-contest/ansible/roles/docker/files/README.md b/provision-contest/ansible/roles/docker/files/README.md new file mode 100644 index 00000000..2eafe9e8 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/README.md @@ -0,0 +1,5 @@ +# Loading containers from archives +Any container `.tar` files placed in a `containers-` directory will be loaded as a container for the said host type. +The container will be tagged with the relative path starting from `containers-`, without the `.tar` file extension. +For example, the file `containers-glitchtip/glitchtip/glitchtip:v4.1.3.tar` will be loaded as a container tagged `glitchtip/glitchtip:v4.1.3` for the `glitchtip` host type. +Note that a nested directory structure is needed to tag containers which have both an organization and a container name. diff --git a/provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore b/provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore new file mode 100644 index 00000000..d874ad67 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/containers-glitchtip/.gitignore @@ -0,0 +1 @@ +*.tar diff --git a/provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore b/provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/containers-glitchtip/glitchtip/.gitignore @@ -0,0 +1 @@ +* diff --git a/provision-contest/ansible/roles/docker/files/docker.asc b/provision-contest/ansible/roles/docker/files/docker.asc new file mode 100644 index 00000000..ee7872e5 --- /dev/null +++ b/provision-contest/ansible/roles/docker/files/docker.asc @@ -0,0 +1,62 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFit2ioBEADhWpZ8/wvZ6hUTiXOwQHXMAlaFHcPH9hAtr4F1y2+OYdbtMuth +lqqwp028AqyY+PRfVMtSYMbjuQuu5byyKR01BbqYhuS3jtqQmljZ/bJvXqnmiVXh +38UuLa+z077PxyxQhu5BbqntTPQMfiyqEiU+BKbq2WmANUKQf+1AmZY/IruOXbnq +L4C1+gJ8vfmXQt99npCaxEjaNRVYfOS8QcixNzHUYnb6emjlANyEVlZzeqo7XKl7 +UrwV5inawTSzWNvtjEjj4nJL8NsLwscpLPQUhTQ+7BbQXAwAmeHCUTQIvvWXqw0N +cmhh4HgeQscQHYgOJjjDVfoY5MucvglbIgCqfzAHW9jxmRL4qbMZj+b1XoePEtht +ku4bIQN1X5P07fNWzlgaRL5Z4POXDDZTlIQ/El58j9kp4bnWRCJW0lya+f8ocodo +vZZ+Doi+fy4D5ZGrL4XEcIQP/Lv5uFyf+kQtl/94VFYVJOleAv8W92KdgDkhTcTD +G7c0tIkVEKNUq48b3aQ64NOZQW7fVjfoKwEZdOqPE72Pa45jrZzvUFxSpdiNk2tZ +XYukHjlxxEgBdC/J3cMMNRE1F4NCA3ApfV1Y7/hTeOnmDuDYwr9/obA8t016Yljj +q5rdkywPf4JF8mXUW5eCN1vAFHxeg9ZWemhBtQmGxXnw9M+z6hWwc6ahmwARAQAB +tCtEb2NrZXIgUmVsZWFzZSAoQ0UgZGViKSA8ZG9ja2VyQGRvY2tlci5jb20+iQI3 +BBMBCgAhBQJYrefAAhsvBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEI2BgDwO +v82IsskP/iQZo68flDQmNvn8X5XTd6RRaUH33kXYXquT6NkHJciS7E2gTJmqvMqd +tI4mNYHCSEYxI5qrcYV5YqX9P6+Ko+vozo4nseUQLPH/ATQ4qL0Zok+1jkag3Lgk +jonyUf9bwtWxFp05HC3GMHPhhcUSexCxQLQvnFWXD2sWLKivHp2fT8QbRGeZ+d3m +6fqcd5Fu7pxsqm0EUDK5NL+nPIgYhN+auTrhgzhK1CShfGccM/wfRlei9Utz6p9P +XRKIlWnXtT4qNGZNTN0tR+NLG/6Bqd8OYBaFAUcue/w1VW6JQ2VGYZHnZu9S8LMc +FYBa5Ig9PxwGQOgq6RDKDbV+PqTQT5EFMeR1mrjckk4DQJjbxeMZbiNMG5kGECA8 +g383P3elhn03WGbEEa4MNc3Z4+7c236QI3xWJfNPdUbXRaAwhy/6rTSFbzwKB0Jm +ebwzQfwjQY6f55MiI/RqDCyuPj3r3jyVRkK86pQKBAJwFHyqj9KaKXMZjfVnowLh +9svIGfNbGHpucATqREvUHuQbNnqkCx8VVhtYkhDb9fEP2xBu5VvHbR+3nfVhMut5 +G34Ct5RS7Jt6LIfFdtcn8CaSas/l1HbiGeRgc70X/9aYx/V/CEJv0lIe8gP6uDoW +FPIZ7d6vH+Vro6xuWEGiuMaiznap2KhZmpkgfupyFmplh0s6knymuQINBFit2ioB +EADneL9S9m4vhU3blaRjVUUyJ7b/qTjcSylvCH5XUE6R2k+ckEZjfAMZPLpO+/tF +M2JIJMD4SifKuS3xck9KtZGCufGmcwiLQRzeHF7vJUKrLD5RTkNi23ydvWZgPjtx +Q+DTT1Zcn7BrQFY6FgnRoUVIxwtdw1bMY/89rsFgS5wwuMESd3Q2RYgb7EOFOpnu +w6da7WakWf4IhnF5nsNYGDVaIHzpiqCl+uTbf1epCjrOlIzkZ3Z3Yk5CM/TiFzPk +z2lLz89cpD8U+NtCsfagWWfjd2U3jDapgH+7nQnCEWpROtzaKHG6lA3pXdix5zG8 +eRc6/0IbUSWvfjKxLLPfNeCS2pCL3IeEI5nothEEYdQH6szpLog79xB9dVnJyKJb +VfxXnseoYqVrRz2VVbUI5Blwm6B40E3eGVfUQWiux54DspyVMMk41Mx7QJ3iynIa +1N4ZAqVMAEruyXTRTxc9XW0tYhDMA/1GYvz0EmFpm8LzTHA6sFVtPm/ZlNCX6P1X +zJwrv7DSQKD6GGlBQUX+OeEJ8tTkkf8QTJSPUdh8P8YxDFS5EOGAvhhpMBYD42kQ +pqXjEC+XcycTvGI7impgv9PDY1RCC1zkBjKPa120rNhv/hkVk/YhuGoajoHyy4h7 +ZQopdcMtpN2dgmhEegny9JCSwxfQmQ0zK0g7m6SHiKMwjwARAQABiQQ+BBgBCAAJ +BQJYrdoqAhsCAikJEI2BgDwOv82IwV0gBBkBCAAGBQJYrdoqAAoJEH6gqcPyc/zY +1WAP/2wJ+R0gE6qsce3rjaIz58PJmc8goKrir5hnElWhPgbq7cYIsW5qiFyLhkdp +YcMmhD9mRiPpQn6Ya2w3e3B8zfIVKipbMBnke/ytZ9M7qHmDCcjoiSmwEXN3wKYI +mD9VHONsl/CG1rU9Isw1jtB5g1YxuBA7M/m36XN6x2u+NtNMDB9P56yc4gfsZVES +KA9v+yY2/l45L8d/WUkUi0YXomn6hyBGI7JrBLq0CX37GEYP6O9rrKipfz73XfO7 +JIGzOKZlljb/D9RX/g7nRbCn+3EtH7xnk+TK/50euEKw8SMUg147sJTcpQmv6UzZ +cM4JgL0HbHVCojV4C/plELwMddALOFeYQzTif6sMRPf+3DSj8frbInjChC3yOLy0 +6br92KFom17EIj2CAcoeq7UPhi2oouYBwPxh5ytdehJkoo+sN7RIWua6P2WSmon5 +U888cSylXC0+ADFdgLX9K2zrDVYUG1vo8CX0vzxFBaHwN6Px26fhIT1/hYUHQR1z +VfNDcyQmXqkOnZvvoMfz/Q0s9BhFJ/zU6AgQbIZE/hm1spsfgvtsD1frZfygXJ9f +irP+MSAI80xHSf91qSRZOj4Pl3ZJNbq4yYxv0b1pkMqeGdjdCYhLU+LZ4wbQmpCk +SVe2prlLureigXtmZfkqevRz7FrIZiu9ky8wnCAPwC7/zmS18rgP/17bOtL4/iIz +QhxAAoAMWVrGyJivSkjhSGx1uCojsWfsTAm11P7jsruIL61ZzMUVE2aM3Pmj5G+W +9AcZ58Em+1WsVnAXdUR//bMmhyr8wL/G1YO1V3JEJTRdxsSxdYa4deGBBY/Adpsw +24jxhOJR+lsJpqIUeb999+R8euDhRHG9eFO7DRu6weatUJ6suupoDTRWtr/4yGqe +dKxV3qQhNLSnaAzqW/1nA3iUB4k7kCaKZxhdhDbClf9P37qaRW467BLCVO/coL3y +Vm50dwdrNtKpMBh3ZpbB1uJvgi9mXtyBOMJ3v8RZeDzFiG8HdCtg9RvIt/AIFoHR +H3S+U79NT6i0KPzLImDfs8T7RlpyuMc4Ufs8ggyg9v3Ae6cN3eQyxcK3w0cbBwsh +/nQNfsA6uu+9H7NhbehBMhYnpNZyrHzCmzyXkauwRAqoCbGCNykTRwsur9gS41TQ +M8ssD1jFheOJf3hODnkKU+HKjvMROl1DK7zdmLdNzA1cvtZH/nCC9KPj1z8QC47S +xx+dTZSx4ONAhwbS/LN3PoKtn8LPjY9NP9uDWI+TWYquS2U+KHDrBDlsgozDbs/O +jCxcpDzNmXpWQHEtHU7649OXHP7UeNST1mCUCH5qdank0V1iejF6/CfTFU4MfcrG +YT90qFF93M3v01BbxP+EIY2/9tiIPbrd +=0YYh +-----END PGP PUBLIC KEY BLOCK----- diff --git a/provision-contest/ansible/roles/docker/tasks/load-container.yml b/provision-contest/ansible/roles/docker/tasks/load-container.yml new file mode 100644 index 00000000..1f4c6df2 --- /dev/null +++ b/provision-contest/ansible/roles/docker/tasks/load-container.yml @@ -0,0 +1,35 @@ +--- +- name: Load the container from archive if needed + block: + - name: Check for existing container + community.docker.docker_image_info: + name: "{{ img_name }}" + register: result + + - name: Transfer and load the container + block: + - name: Create temp container directory + file: + path: /tmp/dj_ansible + state: directory + owner: root + group: root + mode: 0700 + + - name: Transfer container archive + copy: + src: "{{ item.src }}" + dest: "{{ img_path }}" + owner: root + group: root + mode: 0700 + + - name: Import container from archive + community.docker.docker_image: + name: "{{ img_name }}" + load_path: "{{ img_path }}" + source: load + when: not result.images + vars: + img_name: "{{ item.path | splitext | first }}" + img_path: "/tmp/dj_ansible/{{ item.path | basename }}" diff --git a/provision-contest/ansible/roles/docker/tasks/main.yml b/provision-contest/ansible/roles/docker/tasks/main.yml new file mode 100644 index 00000000..537c7304 --- /dev/null +++ b/provision-contest/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Add Docker GPG apt Key + apt_key: + data: "{{ lookup('ansible.builtin.file', 'docker.asc') }}" + state: present + +- name: Add Docker Repository + when: not WF_RESTRICTED_NETWORK + apt_repository: + repo: deb https://download.docker.com/linux/ubuntu jammy stable + state: present + +- name: Install Docker and dependencies + apt: + state: present + install_recommends: false + pkg: + - containerd.io + - docker-buildx-plugin + - docker-ce + - docker-ce-cli + - docker-compose-plugin + - python3-pip + - python3-docker + +# The `runc` script interferes with running Docker containers. +# Mark it as not executable such that containers can run normally. +# If the machine does need the `runc` in the image, a custom fix has to be applied. +- name: Remove executable bit from "/usr/local/bin/runc" + when: ICPC_IMAGE + file: + dest: /usr/local/bin/runc + mode: -x + +- name: Load container archives + include_tasks: load-container.yml + with_filetree: + - files/containers-{{ host_type }}/ + when: item.state == 'file' and (item.path | splitext | last) == ".tar" diff --git a/provision-contest/ansible/roles/docker/vars/.gitignore b/provision-contest/ansible/roles/docker/vars/.gitignore new file mode 100644 index 00000000..1cda54be --- /dev/null +++ b/provision-contest/ansible/roles/docker/vars/.gitignore @@ -0,0 +1 @@ +*.yml diff --git a/provision-contest/ansible/roles/domjudge_build/handlers/main.yml b/provision-contest/ansible/roles/domjudge_build/handlers/main.yml index 8e0de116..b352492f 100644 --- a/provision-contest/ansible/roles/domjudge_build/handlers/main.yml +++ b/provision-contest/ansible/roles/domjudge_build/handlers/main.yml @@ -1,8 +1,14 @@ --- -# Define here handlers associated to this role. - -- name: fix permissions on domjudge inplace-install +- name: Fix permissions on domjudge inplace-install command: make -C {{ DJ_DIR }} inplace-postinstall-permissions -- name: restart rsyslog - service: name=rsyslog enabled=true state=restarted +- name: Fix composer autoload + command: + cmd: composer dump-autoload -o + chdir: "{{ DJ_DIR }}/webapp" + +- name: Restart rsyslog + service: + name: rsyslog + enabled: true + state: restarted diff --git a/provision-contest/ansible/roles/domjudge_build/meta/main.yml b/provision-contest/ansible/roles/domjudge_build/meta/main.yml index 551c866e..10cf7c8e 100644 --- a/provision-contest/ansible/roles/domjudge_build/meta/main.yml +++ b/provision-contest/ansible/roles/domjudge_build/meta/main.yml @@ -2,4 +2,6 @@ --- dependencies: - role: domjudge_user + tags: domjudge_user - role: domjudge_checkout + tags: domjudge_checkout diff --git a/provision-contest/ansible/roles/domjudge_build/tasks/main.yml b/provision-contest/ansible/roles/domjudge_build/tasks/main.yml index 5737e455..4add2702 100644 --- a/provision-contest/ansible/roles/domjudge_build/tasks/main.yml +++ b/provision-contest/ansible/roles/domjudge_build/tasks/main.yml @@ -1,7 +1,7 @@ --- # Tasks to build DOMjudge -- name: add the restapi secret file +- name: Add the restapi secret file template: src: restapi.secret.j2 dest: "{{ DJ_DIR }}/etc/restapi.secret" @@ -9,7 +9,7 @@ group: domjudge mode: 0600 -- name: run inplace-conf +- name: Run inplace-conf become: true become_user: domjudge command: make inplace-conf CONFIGURE_FLAGS='--disable-doc-build --with-baseurl={{ DOMSERVER }}/' @@ -18,20 +18,23 @@ chdir: "{{ DJ_DIR }}" creates: "{{ DJ_DIR }}/paths.mk" -- name: check if domjudge is built +- name: Check if domjudge is built stat: path="{{ DJ_DIR }}/judge/judgedaemon" register: judgedaemon_binary + check_mode: no -- name: build domjudge +- name: Build domjudge become: true become_user: domjudge command: make inplace-install args: chdir: "{{ DJ_DIR }}" - notify: fix permissions on domjudge inplace-install + notify: + - Fix permissions on domjudge inplace-install + - Fix composer autoload when: (git_working_copy is defined and git_working_copy.changed) or dj_configured.changed or not judgedaemon_binary.stat.exists -- name: copy domjudge-sudoers file +- name: Copy domjudge-sudoers file copy: remote_src: true src: "{{ DJ_DIR }}/etc/sudoers-domjudge" @@ -40,16 +43,16 @@ owner: root group: root -- name: configure domjudge logging +- name: Configure domjudge logging copy: src: rsyslog.domjudge.conf dest: /etc/rsyslog.d/domjudge.conf mode: 0644 group: root owner: root - notify: restart rsyslog + notify: Restart rsyslog -- name: configure domjudge logrotate +- name: Configure domjudge logrotate copy: src: logrotate.domjudge dest: /etc/logrotate.d/domjudge diff --git a/provision-contest/ansible/roles/domjudge_build/templates/restapi.secret.j2 b/provision-contest/ansible/roles/domjudge_build/templates/restapi.secret.j2 index b5adfce3..b086f548 100644 --- a/provision-contest/ansible/roles/domjudge_build/templates/restapi.secret.j2 +++ b/provision-contest/ansible/roles/domjudge_build/templates/restapi.secret.j2 @@ -1,3 +1,3 @@ # Randomly generated on host domjudge-judgehost3, Thu Apr 6 23:36:19 MDT 2017 # Format: ' ' -default {{DOMSERVER_URL}}/api {{API_USER}} {{API_PASSWORD}} +default {{DOMSERVER_URL}}/api {{JUDGEHOST_USER}} {{JUDGEHOST_PASSWORD}} diff --git a/provision-contest/ansible/roles/domjudge_checkout/files/.gitignore b/provision-contest/ansible/roles/domjudge_checkout/files/.gitignore index 502167fa..13ecffc5 100644 --- a/provision-contest/ansible/roles/domjudge_checkout/files/.gitignore +++ b/provision-contest/ansible/roles/domjudge_checkout/files/.gitignore @@ -1 +1,3 @@ /lib +/lib-vendor.tgz +/vendor.tgz diff --git a/provision-contest/ansible/roles/domjudge_checkout/files/lib-vendor.tgz b/provision-contest/ansible/roles/domjudge_checkout/files/lib-vendor.tgz deleted file mode 100644 index 21e31971..00000000 Binary files a/provision-contest/ansible/roles/domjudge_checkout/files/lib-vendor.tgz and /dev/null differ diff --git a/provision-contest/ansible/roles/domjudge_checkout/meta/main.yml b/provision-contest/ansible/roles/domjudge_checkout/meta/main.yml index 4a4f743a..a480e52b 100644 --- a/provision-contest/ansible/roles/domjudge_checkout/meta/main.yml +++ b/provision-contest/ansible/roles/domjudge_checkout/meta/main.yml @@ -2,3 +2,4 @@ --- dependencies: - role: domjudge_user + tags: domjudge_user diff --git a/provision-contest/ansible/roles/domjudge_checkout/tasks/main.yml b/provision-contest/ansible/roles/domjudge_checkout/tasks/main.yml index 3fdd9aa7..96a7b68a 100644 --- a/provision-contest/ansible/roles/domjudge_checkout/tasks/main.yml +++ b/provision-contest/ansible/roles/domjudge_checkout/tasks/main.yml @@ -1,7 +1,7 @@ --- # These tasks create a checkout of the DOMjudge repo -- name: create working copy directory +- name: Create working copy directory file: path: "{{ DJ_DIR }}" state: directory @@ -9,6 +9,14 @@ group: domjudge mode: 0755 +- name: Create ansible tmp directory + file: + path: /home/domjudge/.ansible/tmp + state: directory + owner: domjudge + group: domjudge + mode: 0755 + - name: Update repo URL based on network set_fact: dj_git_repo: "{{ DJ_GIT_REPO_RESTRICTED if WF_RESTRICTED_NETWORK else DJ_GIT_REPO }}" @@ -16,27 +24,37 @@ - name: Create working copy of the domjudge repo become: true become_user: domjudge - git: repo={{ dj_git_repo }} dest={{ DJ_DIR }} version={{ DJ_BRANCH }} accept_hostkey=yes update=yes + git: + repo: "{{ dj_git_repo }}" + dest: "{{ DJ_DIR }}" + version: "{{ DJ_BRANCH }}" + accept_hostkey: true + update: true + force: "{{ DJ_FORCE_UPDATE }}" register: git_working_copy -- name: Check composer dependencies present - become: false - delegate_to: localhost - stat: - path: files/lib/vendor - register: libvendor +- name: Install composer dependencies + when: WF_RESTRICTED_NETWORK + block: + - name: Check composer dependencies present + become: false + delegate_to: localhost + stat: + path: roles/domjudge_checkout/files/webapp/vendor + register: vendor + check_mode: no -- name: Copy in composer dependencies (if they exist locally) - synchronize: - src: files/lib/vendor/ - dest: "{{ DJ_DIR }}/lib/vendor/" - owner: false - use_ssh_args: true - when: libvendor.stat.exists + - name: Copy in composer dependencies (if they exist locally) + synchronize: + src: files/webapp/vendor/ + dest: "{{ DJ_DIR }}/webapp/vendor/" + owner: false + use_ssh_args: true + when: vendor.stat.exists -- name: fix ownership of lib/vendor - file: - path: "{{ DJ_DIR }}/lib/vendor" - recurse: true - owner: domjudge - group: domjudge + - name: Fix ownership of webapp/vendor + file: + path: "{{ DJ_DIR }}/webapp/vendor" + recurse: true + owner: domjudge + group: domjudge diff --git a/provision-contest/ansible/roles/domjudge_user/handlers/main.yml b/provision-contest/ansible/roles/domjudge_user/handlers/main.yml index e905aa84..d1c7e282 100644 --- a/provision-contest/ansible/roles/domjudge_user/handlers/main.yml +++ b/provision-contest/ansible/roles/domjudge_user/handlers/main.yml @@ -1,5 +1,6 @@ --- -# Define here handlers associated to this role. - -- name: restart gdm - service: name=gdm3 enabled=true state=restarted +- name: Restart gdm + service: + name: gdm3 + enabled: true + state: restarted diff --git a/provision-contest/ansible/roles/domjudge_user/tasks/gitconfig.yml b/provision-contest/ansible/roles/domjudge_user/tasks/gitconfig.yml new file mode 100644 index 00000000..3bb97da2 --- /dev/null +++ b/provision-contest/ansible/roles/domjudge_user/tasks/gitconfig.yml @@ -0,0 +1,22 @@ +- name: "Configure git user {{ user_item.login }}" + ini_file: + dest: "{{ user_item.homedir }}/.gitconfig" + section: user + option: "{{ item.name }}" + value: "{{ item.value }}" + owner: "{{ user_item.login }}" + group: "{{ user_item.login }}" + mode: 0644 + loop: + - { name: 'email', value: 'team@domjudge.org' } + - { name: 'name', value: 'DOMjudge team' } + +- name: "Configure git to rebase on pull for {{ user_item.login }}" + ini_file: + dest: "{{ user_item.homedir }}/.gitconfig" + section: branch + option: "autosetuprebase" + value: "remote" + owner: "{{ user_item.login }}" + group: "{{ user_item.login }}" + mode: 0644 diff --git a/provision-contest/ansible/roles/domjudge_user/tasks/main.yml b/provision-contest/ansible/roles/domjudge_user/tasks/main.yml index d9f7efc4..53c86d76 100644 --- a/provision-contest/ansible/roles/domjudge_user/tasks/main.yml +++ b/provision-contest/ansible/roles/domjudge_user/tasks/main.yml @@ -1,7 +1,7 @@ --- # These tasks create a domjudge system account and fix sudo/autologin -- name: create domjudge user +- name: Create domjudge user user: name: domjudge shell: /bin/bash @@ -15,49 +15,36 @@ regexp: '^%sudo' line: '%sudo ALL=(ALL) NOPASSWD: ALL' -- name: configure git user - ini_file: - dest: /home/domjudge/.gitconfig - section: user - option: "{{ item.name }}" - value: "{{ item.value }}" - owner: domjudge - group: domjudge - mode: 0644 +- include_tasks: gitconfig.yml loop: - - { name: 'email', value: 'team@domjudge.org' } - - { name: 'name', value: 'DOMjudge team' } + - { login: 'domjudge', homedir: '/home/domjudge' } + - { login: 'root', homedir: '/root' } + loop_control: + loop_var: user_item -- name: configure git to rebase on pull - ini_file: - dest: /home/domjudge/.gitconfig - section: branch - option: "autosetuprebase" - value: "remote" - owner: domjudge - group: domjudge - mode: 0644 +- name: Setup autologin on desktop installs + block: + - name: Enable GDM autologin + lineinfile: + path: /etc/gdm3/custom.conf + regexp: 'AutomaticLoginEnable' + line: 'AutomaticLoginEnable=true' + create: true + mode: 0644 + notify: Restart gdm -- name: enable GDM autologin - lineinfile: - path: /etc/gdm3/custom.conf - regexp: 'AutomaticLoginEnable' - line: 'AutomaticLoginEnable=true' - create: true - mode: 0644 - notify: restart gdm - -- name: Automatically login domjudge user - lineinfile: - path: /etc/gdm3/custom.conf - regexp: 'AutomaticLogin=' - line: 'AutomaticLogin=domjudge' - notify: restart gdm + - name: Automatically login domjudge user + lineinfile: + path: /etc/gdm3/custom.conf + regexp: 'AutomaticLogin\s*=' + line: 'AutomaticLogin=domjudge' + notify: Restart gdm -- name: make sure autostart directory exists - file: - dest: /home/domjudge/.config/autostart - state: directory - owner: domjudge - group: domjudge - mode: 0755 + - name: Make sure autostart directory exists + file: + dest: /home/domjudge/.config/autostart + state: directory + owner: domjudge + group: domjudge + mode: 0755 + when: GRAPHICAL diff --git a/provision-contest/ansible/roles/domlogo/files/README.md b/provision-contest/ansible/roles/domlogo/files/README.md new file mode 100644 index 00000000..2d1d2ac3 --- /dev/null +++ b/provision-contest/ansible/roles/domlogo/files/README.md @@ -0,0 +1,17 @@ +# Preparing DOMlogo + +First, create the following files: +- `images/logos/DOMjudge.png`, a 64x64 DOMjudge logo with transparent background. +- `images/photos/crew.png`, an image with a width of 1024 (and any height) to show for teams without a photo. +- `images/photos/idle.png`, an image with a width of 1024 (and any height) to show when the judgedaemon is idle. + +Next, add the needed Python dependencies to the `lib` folder, within a folder called `python3.10`. You can copy this +folder from a local machine and it should contain the `FreeSimpleGUI` and `requests` Python packages. + +Optionally you can create a file `images/config.yaml` with something like: + +```yaml +host-bg-color: '#013370' +``` + +DOMlogo will use the DOMjudge API to download logos and photos for all teams, so no further configuration should be needed. diff --git a/provision-contest/ansible/roles/domlogo/files/lib/.gitignore b/provision-contest/ansible/roles/domlogo/files/lib/.gitignore index b960de0d..3c8ed4fa 100644 --- a/provision-contest/ansible/roles/domlogo/files/lib/.gitignore +++ b/provision-contest/ansible/roles/domlogo/files/lib/.gitignore @@ -1 +1 @@ -python3.8 +python3.* diff --git a/provision-contest/ansible/roles/domlogo/tasks/main.yml b/provision-contest/ansible/roles/domlogo/tasks/main.yml index 7bd14cf8..f5811239 100644 --- a/provision-contest/ansible/roles/domlogo/tasks/main.yml +++ b/provision-contest/ansible/roles/domlogo/tasks/main.yml @@ -1,13 +1,14 @@ --- # These tasks install domlogo -- name: install python3 modules for domlogo +- name: Install python3 modules and imagemagick for domlogo apt: state: present pkg: - python3-tk + - imagemagick -- name: install domlogo +- name: Install domlogo copy: src: domlogo.py dest: "{{ DJ_DIR }}" @@ -15,24 +16,41 @@ group: domjudge mode: 0755 -- name: install domlogo env +- name: Install domlogo Python libraries synchronize: src: lib dest: /home/domjudge/.local/ owner: false use_ssh_args: true -- name: install domlogo images +- name: Fix ownership of domlogo env + file: + path: "/home/domjudge/.local/lib/python3.10" + recurse: true + owner: domjudge + group: domjudge + +- name: Install domlogo images synchronize: - src: images - dest: "{{ DJ_DIR }}" + src: images/ + dest: "{{ DJ_DIR }}/domlogo-files/" owner: false use_ssh_args: true -- name: add autostart shortcuts - template: - src: domjudgelogo.desktop.j2 - dest: /home/domjudge/.config/autostart/domjudgelogo.desktop +- name: Fix ownership of domlogo images + file: + path: "{{ DJ_DIR }}/domlogo-files" + recurse: true owner: domjudge group: domjudge - mode: 0755 + +- name: Copy domlogo systemd unit file + template: + src: "domlogo.service.j2" + dest: "/etc/systemd/system/domlogo.service" + +- name: Enable and start domlogo + systemd: + name: domlogo + enabled: true + state: started \ No newline at end of file diff --git a/provision-contest/ansible/roles/domlogo/templates/domjudgelogo.desktop.j2 b/provision-contest/ansible/roles/domlogo/templates/domjudgelogo.desktop.j2 deleted file mode 100644 index 7cb14e98..00000000 --- a/provision-contest/ansible/roles/domlogo/templates/domjudgelogo.desktop.j2 +++ /dev/null @@ -1,4 +0,0 @@ -[Desktop Entry] -Name=domjudgelogo -Type=Application -Exec=bash -c "sleep 10 && cd {{ DJ_DIR }} && python3 ./domlogo.py 1>/home/domjudge/logo.out 2>/home/domjudge/logo.err" diff --git a/provision-contest/ansible/roles/domlogo/templates/domlogo.service.j2 b/provision-contest/ansible/roles/domlogo/templates/domlogo.service.j2 new file mode 100644 index 00000000..71c899e3 --- /dev/null +++ b/provision-contest/ansible/roles/domlogo/templates/domlogo.service.j2 @@ -0,0 +1,18 @@ +[Unit] +Description="DOMlogo" +After=network.target + +[Service] +Type=simple + +Environment=REQUESTS_CA_BUNDLE=/usr/local/share/ca-certificates/domserver.crt +Environment=DISPLAY=:0 +WorkingDirectory={{ DJ_DIR }} +ExecStart=domlogo.py +User=domjudge + +Restart=always +RestartSec=3 + +[Install] +WantedBy=graphical.target diff --git a/provision-contest/ansible/roles/domserver/defaults/main.yml b/provision-contest/ansible/roles/domserver/defaults/main.yml new file mode 100644 index 00000000..e2a3a2cf --- /dev/null +++ b/provision-contest/ansible/roles/domserver/defaults/main.yml @@ -0,0 +1,6 @@ +DOMSERVER_PACKAGES: + - nginx + - php-fpm + - php-intl + - texlive-latex-recommended + - texlive-latex-extra diff --git a/provision-contest/ansible/roles/domserver/handlers/main.yml b/provision-contest/ansible/roles/domserver/handlers/main.yml index 6fe95874..fae61e35 100644 --- a/provision-contest/ansible/roles/domserver/handlers/main.yml +++ b/provision-contest/ansible/roles/domserver/handlers/main.yml @@ -1,8 +1,6 @@ --- -# Define here handlers associated to this role. - -- name: restart nginx - service: name=nginx enabled=true state=restarted - -- name: restart PHP FPM - service: name=php7.4-fpm enabled=true state=restarted +- name: Restart PHP FPM + service: + name: php{{ php_version.stdout }}-fpm + enabled: true + state: restarted diff --git a/provision-contest/ansible/roles/domserver/meta/main.yml b/provision-contest/ansible/roles/domserver/meta/main.yml index 8daa8904..f073195b 100644 --- a/provision-contest/ansible/roles/domserver/meta/main.yml +++ b/provision-contest/ansible/roles/domserver/meta/main.yml @@ -2,4 +2,6 @@ --- dependencies: - role: mysql_server + tags: mysql_server - role: domjudge_build + tags: domjudge_build diff --git a/provision-contest/ansible/roles/domserver/tasks/main.yml b/provision-contest/ansible/roles/domserver/tasks/main.yml index db44a25e..28bb5df3 100644 --- a/provision-contest/ansible/roles/domserver/tasks/main.yml +++ b/provision-contest/ansible/roles/domserver/tasks/main.yml @@ -1,100 +1,98 @@ --- # These tasks configure DOMjudge as domserver -- name: install dbpasswords.secret file +- name: Install dbpasswords.secret file template: src: dbpasswords.secret.j2 dest: "{{ DJ_DIR }}/etc/dbpasswords.secret" mode: 0644 group: root owner: root - notify: fix permissions on domjudge inplace-install + notify: Fix permissions on domjudge inplace-install -- name: install initial_admin_password.secret file +- name: Install initial_admin_password.secret file template: src: initial_admin_password.secret.j2 dest: "{{ DJ_DIR }}/etc/initial_admin_password.secret" mode: 0644 group: domjudge owner: domjudge - notify: fix permissions on domjudge inplace-install + notify: Fix permissions on domjudge inplace-install # When using replication, the DB will be dropped and recreated on the slave later. -- name: check if the database is configured - command: "{{ DJ_DIR }}/bin/dj_setup_database -u root status" +- name: Check if the database is configured + command: "{{ DJ_DIR }}/bin/dj_setup_database -s -u root status" register: db_status ignore_errors: true changed_when: false + check_mode: no -- name: make sure the database is configured - command: "{{ DJ_DIR }}/bin/dj_setup_database -u root bare-install" +- name: Make sure the database is configured + command: "{{ DJ_DIR }}/bin/dj_setup_database -s -u root bare-install" when: "'failed' in db_status.stdout" -- name: install required packages +- name: Install required packages apt: state: present - pkg: - - nginx - - php-fpm - - php-intl + pkg: "{{ DOMSERVER_PACKAGES }}" -- name: set PHP timezone for FPM +- name: Set PHP timezone for FPM lineinfile: - dest: /etc/php/7.4/fpm/php.ini + dest: "/etc/php/{{ php_version.stdout }}/fpm/php.ini" state: present regexp: 'date\.timezone\s*=' line: 'date.timezone = {{ TIMEZONE }}' + notify: Restart PHP FPM -- name: enable PHP modules +- name: Enable PHP modules command: phpenmod {{ item }} args: - creates: /etc/php/7.4/fpm/conf.d/20-{{ item }}.ini - loop: - - zip - - intl + creates: "/etc/php/{{ php_version.stdout }}/fpm/conf.d/20-{{ item }}.ini" + loop: "{{ DOMSERVER_PACKAGES | map('regex_search', 'php-') | select() }}" + notify: Restart PHP FPM -- name: add domjudge nginx conf +- name: Add domjudge nginx conf template: src: nginx-domjudge.conf.j2 dest: /etc/nginx/sites-available/domjudge.conf mode: 0644 group: root owner: root - notify: restart nginx + notify: Restart nginx -- name: add domjudge inner nginx conf +- name: Add domjudge inner nginx conf template: src: nginx-domjudge-inner.j2 dest: /etc/nginx/snippets/domjudge-inner mode: 0644 group: root owner: root - notify: restart nginx + notify: Restart nginx -- name: enable nginx conf for domjudge +- name: Enable nginx conf for domjudge file: src: /etc/nginx/sites-available/domjudge.conf dest: /etc/nginx/sites-enabled/domjudge.conf state: link - notify: restart nginx + notify: Restart nginx -- name: disable default nginx site +- name: Disable default nginx site file: path: /etc/nginx/sites-enabled/default state: absent - notify: restart nginx + notify: Restart nginx -- name: symlink domjudge FPM conf +- name: Symlink domjudge FPM conf file: - path: /etc/php/7.4/fpm/pool.d/domjudge.conf + path: "/etc/php/{{ php_version.stdout }}/fpm/pool.d/domjudge.conf" src: "{{ DJ_DIR }}/etc/domjudge-fpm.conf" state: link force: true - notify: restart PHP FPM + notify: Restart PHP FPM -- name: set PHP settings +- name: Set PHP settings lineinfile: - dest: /etc/php/7.4/fpm/pool.d/domjudge.conf + dest: "/etc/php/{{ php_version.stdout }}/fpm/pool.d/domjudge.conf" regexp: "{{ item.regexp }}" line: "{{ item.key }} = {{ item.value }}" loop: @@ -104,4 +102,24 @@ - { key: 'php_admin_value[upload_max_filesize]', regexp: '^php_admin_value\[upload_max_filesize\]', value: '{{ PHP_UPLOAD_MAX_FILESIZE }}' } - { key: 'php_admin_value[post_max_size]', regexp: '^php_admin_value\[post_max_size\]', value: '{{ PHP_POST_MAX_SIZE }}' } - { key: 'php_admin_value[max_file_uploads]', regexp: '^php_admin_value\[max_file_uploads\]', value: '{{ PHP_MAX_FILE_UPLOADS }}' } - notify: restart PHP FPM + notify: Restart PHP FPM + +- name: Start domserver webservices + systemd: + name: "{{ item }}" + enabled: true + state: started + loop: + - nginx + - php{{ php_version.stdout }}-fpm + +- name: Send errors to Sentry + lineinfile: + regexp: '^SENTRY_DSN=' + state: present + line: "SENTRY_DSN={{ SENTRY_DSN | default('') }}" + dest: "{{ DJ_DIR }}/webapp/.env.local" + create: true + mode: 0664 + group: domjudge + owner: domjudge diff --git a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 index 2d19afe6..cf411ae1 100644 --- a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 +++ b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge-inner.j2 @@ -13,9 +13,33 @@ set $prefix ''; location / { root $domjudgeRoot; try_files $uri @domjudgeFront; + + # Handle API requests separately to be able to split the log + location /api/ { + try_files $uri @domjudgeFrontApi; + error_log /var/log/nginx/domjudge-api.log; + access_log /var/log/nginx/domjudge-api.log dj_access; + } } location @domjudgeFront { + fastcgi_split_path_info ^(.+\.php)(/.*)$; + fastcgi_pass domjudge; + include fastcgi_params; + fastcgi_param SERVER_NAME $host; + fastcgi_read_timeout 600; + fastcgi_send_timeout 600; + + fastcgi_param SCRIPT_FILENAME $domjudgeRoot/index.php; + fastcgi_param SCRIPT_NAME $prefix/index.php; + fastcgi_param REQUEST_URI $prefix$uri?$args; + fastcgi_param DOCUMENT_ROOT $domjudgeRoot; + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/app_dev.php/some-path + internal; +} + +location @domjudgeFrontApi { fastcgi_split_path_info ^(.+\.php)(/.*)$; fastcgi_pass domjudge; include fastcgi_params; @@ -24,9 +48,14 @@ location @domjudgeFront { fastcgi_param SCRIPT_NAME $prefix/index.php; fastcgi_param REQUEST_URI $prefix$uri?$args; fastcgi_param DOCUMENT_ROOT $domjudgeRoot; + fastcgi_param HTTPS $http_x_forwarded_proto if_not_empty; # Prevents URIs that include the front controller. This will 404: # http://domain.tld/app_dev.php/some-path internal; + + # Use a separate log file for the API + error_log /var/log/nginx/domjudge-api.log; + access_log /var/log/nginx/domjudge-api.log dj_access; } # The X-Frame-Options header defends against so-called 'clickjacking' attacks. @@ -38,6 +67,12 @@ add_header X-Frame-Options "DENY"; add_header Referrer-Policy "same-origin"; add_header X-Content-Type-Options "nosniff"; add_header X-XSS-Protection "1; mode=block"; +add_header X-Robots-Tag "none" always; + +{% if FRONT_REVERSE_PROXY is defined %} +set_real_ip_from {{ FRONT_REVERSE_PROXY }}; +real_ip_header X-Forwarded-For; +{% endif %} error_log /var/log/nginx/domjudge.log; -access_log /var/log/nginx/domjudge.log; +access_log /var/log/nginx/domjudge.log dj_access; diff --git a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 index b80a9bd0..9fc5e3de 100644 --- a/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 +++ b/provision-contest/ansible/roles/domserver/templates/nginx-domjudge.conf.j2 @@ -7,16 +7,17 @@ upstream domjudge { server unix:/var/run/php-fpm-domjudge.sock; # if using with etc/domjudge-fpm.conf } +# Custom log format that adds the request processing time. +log_format dj_access '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent $request_time "$http_referer" "$http_user_agent"'; + server { - listen 80; - listen [::]:80; + listen 80 default; server_name _default_; return 308 https://$host$request_uri; # enforce https } server { - listen 443 ssl http2; - listen [::]:443 ssl http2; + listen 443 ssl http2 default; ssl_certificate {{DOMSERVER_SSL_CERT}}; ssl_certificate_key {{DOMSERVER_SSL_KEY}}; @@ -25,5 +26,6 @@ server { add_header Strict-Transport-Security max-age=31556952; + send_timeout 36000s; include /etc/nginx/snippets/domjudge-inner; } diff --git a/provision-contest/ansible/roles/glitchtip/defaults/main.yml b/provision-contest/ansible/roles/glitchtip/defaults/main.yml new file mode 100644 index 00000000..df4a7c22 --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/defaults/main.yml @@ -0,0 +1,3 @@ +GLITCHTIP_PORT: 8000 +GLITCHTIP_TOKEN: +GLITCHTIP_WEBHOOK_DOMAIN: domjudge-ccsadmin2 diff --git a/provision-contest/ansible/roles/glitchtip/handlers/main.yml b/provision-contest/ansible/roles/glitchtip/handlers/main.yml new file mode 100644 index 00000000..61571dfd --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart GlitchTip + community.docker.docker_compose_v2: + project_src: /opt/glitchtip + files: + - docker-compose.yaml + state: restarted diff --git a/provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml b/provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml new file mode 100644 index 00000000..c8830a5a --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/tasks/create-monitor.yml @@ -0,0 +1,30 @@ +--- +- name: Fetch the project id + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/teams/domjudge/DOMjudge/projects/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_proj +- name: Store JSON query in a fact due to escaping problems in the string below + set_fact: + TEMP_QUERY0: "[?name=='{{ item }}'].id" +- name: Create an DOMjudge uptime monitor for the project + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/monitors/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + expectedBody: "" + expectedStatus: 200 + timeout: 5 + interval: 2 + monitorType: "GET" + url: "https://{{ hostvars[item].ansible_host }}/public" + name: "{{ item }}" + project_id: "{{ glitchtip_proj.json | community.general.json_query(TEMP_QUERY0) | first }}" + body_format: json diff --git a/provision-contest/ansible/roles/glitchtip/tasks/main.yml b/provision-contest/ansible/roles/glitchtip/tasks/main.yml new file mode 100644 index 00000000..dbe16dc2 --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/tasks/main.yml @@ -0,0 +1,185 @@ +--- +- name: Create directories + file: + path: /opt/glitchtip + state: directory + +- name: Create compose file + template: + src: templates/docker-compose.yaml.j2 + dest: /opt/glitchtip/docker-compose.yaml + +- name: Deploy GlitchTip compose stack + community.docker.docker_compose_v2: + project_src: /opt/glitchtip + files: + - docker-compose.yaml + +- name: Assume we don't have an account if we didn't specify the token + when: not GLITCHTIP_TOKEN + block: + - name: Wait for stable GlitchTip migrations + ansible.builtin.wait_for: + timeout: 10 + + - name: Fetch CSRF from login page + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/docs" + register: glitchtip_csrf + + - name: Register DOMjudge account + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/_allauth/browser/v1/auth/signup" + status_code: 200 + headers: + Cookie: "{{ glitchtip_csrf.cookies_string }}" + X-CSRFTOKEN: "{{ glitchtip_csrf.cookies_string | regex_search('csrftoken=([a-zA-Z0-9]+)', '\\1') | first }}" + body: + email: "team@domjudge.org" + password: "{{ GLITCHTIP_PASSWORD }}" + body_format: json + register: glitchtip_register + + - name: Create API token + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/api-tokens/" + status_code: 201 + headers: + Cookie: "{{ glitchtip_register.cookies_string }}" + X-CSRFTOKEN: "{{ glitchtip_register.cookies_string | regex_search('csrftoken=([a-zA-Z0-9]+)', '\\1') | first }}" + body: + label: "ansible" + scopes: [ + "project:read", + "project:write", + "project:admin", + "project:releases", + "team:read", + "team:write", + "team:admin", + "event:read", + "event:write", + "event:admin", + "org:read", + "org:write", + "org:admin", + "member:read", + "member:write", + "member:admin" + ] + body_format: json + register: glitchtip_token + + - name: Set API token + ansible.builtin.set_fact: + GLITCHTIP_TOKEN: "{{ glitchtip_token.json.token }}" + +- name: Check for existing organizations + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_org + +- name: Create DOMjudge organization + when: glitchtip_org.json | community.general.json_query("[?name=='DOMjudge']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + name: "DOMjudge" + body_format: json + register: glitchtip_org + +- name: Check for existing teams + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/teams/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_team + +- name: Create DOMjudge team + when: glitchtip_team.json | community.general.json_query("[?slug=='DOMjudge']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/teams/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + slug: "DOMjudge" + body_format: json + register: glitchtip_team + +- name: Check for existing projects + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/teams/domjudge/DOMjudge/projects/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_proj + +- name: Create DOMjudge projects + when: glitchtip_proj.json | community.general.json_query("[?name=='{{ item }}']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/teams/domjudge/DOMjudge/projects/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + name: "{{ item }}" + platform: "php-symfony" + body_format: json + loop: "{{ ['setup-phase'] + groups['domserver'] }}" + +- name: Create project dj_notify webhook + when: glitchtip_proj.json | community.general.json_query("[?name=='{{ item }}']") == [] + ansible.builtin.uri: + method: POST + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/projects/domjudge/{{ item }}/alerts/" + status_code: 201 + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + body: + name: "dj_notify" + alertRecipients: + - recipientType: "webhook" + url: "http://{{ GLITCHTIP_WEBHOOK_DOMAIN }}:9999/" + timespanMinutes: 1 + quantity: 1 + uptime: true + body_format: json + loop: "{{ ['setup-phase'] + groups['domserver'] }}" + +- name: Check for existing monitors + ansible.builtin.uri: + method: GET + return_content: yes + url: "http://localhost:{{ GLITCHTIP_PORT }}/api/0/organizations/domjudge/monitors/" + headers: + Authorization: "Bearer {{ GLITCHTIP_TOKEN }}" + register: glitchtip_mon + +- name: Create DOMjudge monitors + when: glitchtip_mon.json | community.general.json_query("[?name=='{{ item }}']") == [] + loop: "{{ groups['domserver'] }}" + include_tasks: create-monitor.yml diff --git a/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 b/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 new file mode 100644 index 00000000..64e0898b --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/templates/docker-compose.yaml.j2 @@ -0,0 +1,57 @@ +# Uncomment version if using an older version of docker compose +# version: "3.8" +x-environment: + &default-environment + DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres + SECRET_KEY: {{ GLITCHTIP_SECRET }} + PORT: {{ GLITCHTIP_PORT }} + EMAIL_URL: consolemail:// + GLITCHTIP_DOMAIN: http://glitchtip:{{ GLITCHTIP_PORT }} + DEFAULT_FROM_EMAIL: email@glitchtip + CELERY_WORKER_AUTOSCALE: "1,1" + CELERY_WORKER_MAX_TASKS_PER_CHILD: "1000" + REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt + +x-depends_on: + &default-depends_on + - postgres + - redis + +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_HOST_AUTH_METHOD: "trust" # Consider removing this and setting a password + restart: unless-stopped + volumes: + - pg-data:/var/lib/postgresql/data + redis: + image: redis:7-alpine + restart: unless-stopped + web: + image: glitchtip/glitchtip:v4.1.3 + depends_on: *default-depends_on + ports: + - {{ GLITCHTIP_PORT }}:{{ GLITCHTIP_PORT }} + environment: *default-environment + restart: unless-stopped + volumes: + - uploads:/code/uploads + worker: + image: glitchtip/glitchtip:v4.1.3 + command: ./bin/run-celery-with-beat.sh + depends_on: *default-depends_on + environment: *default-environment + restart: unless-stopped + volumes: + - uploads:/code/uploads + - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro + migrate: + image: glitchtip/glitchtip:v4.1.3 + depends_on: *default-depends_on + command: ./bin/run-migrate.sh + environment: *default-environment + +volumes: + pg-data: + uploads: diff --git a/provision-contest/ansible/roles/glitchtip/vars/.gitignore b/provision-contest/ansible/roles/glitchtip/vars/.gitignore new file mode 100644 index 00000000..1cda54be --- /dev/null +++ b/provision-contest/ansible/roles/glitchtip/vars/.gitignore @@ -0,0 +1 @@ +*.yml diff --git a/provision-contest/ansible/roles/grafana/defaults/main.yml b/provision-contest/ansible/roles/grafana/defaults/main.yml index 804c3e1b..7d90ce04 100644 --- a/provision-contest/ansible/roles/grafana/defaults/main.yml +++ b/provision-contest/ansible/roles/grafana/defaults/main.yml @@ -1,2 +1,2 @@ -grafana_port: 8443 +grafana_port: 9443 loki: false diff --git a/provision-contest/ansible/roles/grafana/files/.gitignore b/provision-contest/ansible/roles/grafana/files/.gitignore index 87ce969a..bc581906 100644 --- a/provision-contest/ansible/roles/grafana/files/.gitignore +++ b/provision-contest/ansible/roles/grafana/files/.gitignore @@ -1 +1,3 @@ /ssl.* +/grafana.deb +/loki-linux-amd64.zip diff --git a/provision-contest/ansible/roles/grafana/files/loki-local-config.yaml b/provision-contest/ansible/roles/grafana/files/loki-local-config.yaml index b52f9864..b714edb6 100644 --- a/provision-contest/ansible/roles/grafana/files/loki-local-config.yaml +++ b/provision-contest/ansible/roles/grafana/files/loki-local-config.yaml @@ -2,45 +2,26 @@ auth_enabled: false server: http_listen_port: 3100 - grpc_listen_port: 9096 + grpc_listen_port: 13100 -ingester: - lifecycler: - address: 127.0.0.1 - ring: - kvstore: - store: inmemory - replication_factor: 1 - final_sleep: 0s - chunk_idle_period: 5m - chunk_retain_period: 30s - max_transfer_retries: 0 +common: + ring: + instance_addr: 127.0.0.1 + kvstore: + store: inmemory + replication_factor: 1 + path_prefix: /tmp/loki schema_config: configs: - - from: 2018-04-15 - store: boltdb + - from: 2020-05-15 + store: tsdb object_store: filesystem - schema: v11 + schema: v13 index: prefix: index_ - period: 168h + period: 24h storage_config: - boltdb: - directory: /data/loki/index - filesystem: - directory: /data/loki/chunks - -limits_config: - enforce_metric_name: false - reject_old_samples: true - reject_old_samples_max_age: 168h - -chunk_store_config: - max_look_back_period: 0s - -table_manager: - retention_deletes_enabled: false - retention_period: 0s + directory: /tmp/loki/chunks diff --git a/provision-contest/ansible/roles/grafana/handlers/main.yml b/provision-contest/ansible/roles/grafana/handlers/main.yml index d2a23bee..b3fe0a7f 100644 --- a/provision-contest/ansible/roles/grafana/handlers/main.yml +++ b/provision-contest/ansible/roles/grafana/handlers/main.yml @@ -1,31 +1,29 @@ --- -# Define here handlers associated to this role. - -- name: restart nginx-exporter +- name: Restart nginx-exporter service: name: prometheus-nginx-exporter enabled: true state: restarted -- name: restart php-exporter +- name: Restart php-exporter service: name: php-fpm-exporter enabled: true state: restarted -- name: restart nginx +- name: Restart prometheus service: - name: nginx + name: prometheus enabled: true state: restarted -- name: restart grafana +- name: Restart grafana service: name: grafana-server enabled: true state: restarted -- name: restart loki +- name: Restart loki service: name: loki enabled: true diff --git a/provision-contest/ansible/roles/grafana/tasks/main.yml b/provision-contest/ansible/roles/grafana/tasks/main.yml index ea84629a..b7453422 100644 --- a/provision-contest/ansible/roles/grafana/tasks/main.yml +++ b/provision-contest/ansible/roles/grafana/tasks/main.yml @@ -1,16 +1,5 @@ --- -- name: Add upstream prometheus apt key - apt_key: - url: https://packagecloud.io/the_asten/prometheus/gpgkey - state: present - -- name: Add upstream prometheus apt repo - apt_repository: - repo: deb https://packagecloud.io/the_asten/prometheus/ubuntu/ focal main - state: present - register: prometheus_repo - -- name: install dependencies +- name: Install dependencies apt: state: present install_recommends: false @@ -20,108 +9,131 @@ # Setup Prometheus, which scrapes all metrics from the monitored machines # The client exposes the metrics (on different ports/URIs) and we collect those -- name: set up prometheus scrapes +- name: Set up prometheus scrapes template: src: prometheus.yml.j2 dest: /etc/prometheus/prometheus.yml mode: 0644 owner: root group: root - notify: restart prometheus + notify: Restart prometheus # Setup loki which gathers our logs -- name: Install loki - unarchive: - src: https://github.com/grafana/loki/releases/download/v2.5.0/loki-linux-amd64.zip - dest: /usr/bin/ - remote_src: true - owner: domjudge - group: domjudge - when: loki - -- name: Dir for loki settings - file: - state: directory - path: /etc/grafana/loki/ - owner: root - group: root - mode: 0755 - when: loki - -- name: Set loki settings - copy: - src: loki-local-config.yaml - dest: /etc/grafana/loki/ - owner: root - group: root - mode: 0644 - when: loki - notify: restart loki - -- name: Setup loki systemd - copy: - src: loki.service - dest: /etc/systemd/system/ - mode: 0655 - when: loki - notify: restart loki - -- name: Start loki service - service: - name: loki - state: started - enabled: true +- name: Setup loki when: loki + block: + - name: Install loki + unarchive: + src: loki-linux-amd64.zip + dest: /usr/bin/ + remote_src: false + owner: domjudge + group: domjudge + when: ICPC_IMAGE + + - name: Install loki + unarchive: + src: https://github.com/grafana/loki/releases/download/v2.5.0/loki-linux-amd64.zip + dest: /usr/bin/ + remote_src: true + owner: domjudge + group: domjudge + when: not ICPC_IMAGE + + - name: Dir for loki settings + file: + state: directory + path: /etc/grafana/loki/ + owner: root + group: root + mode: 0755 + + - name: Set loki settings + copy: + src: loki-local-config.yaml + dest: /etc/grafana/loki/ + owner: root + group: root + mode: 0644 + notify: Restart loki + + - name: Setup loki systemd + copy: + src: loki.service + dest: /etc/systemd/system/ + mode: 0655 + notify: Restart loki + + - name: Start loki service + service: + name: loki + state: started + enabled: true ## Setup grafana - name: Install grafana + when: not WF_RESTRICTED_NETWORK apt: - deb: https://dl.grafana.com/enterprise/release/grafana-enterprise_8.4.6_amd64.deb + deb: https://dl.grafana.com/oss/release/grafana_11.1.3_amd64.deb state: present - notify: restart grafana - -- name: configure grafana + notify: Restart grafana + +- name: Install grafana from local deb + when: WF_RESTRICTED_NETWORK + block: + - name: Copy local grafana deb to monitoring host + copy: + src: grafana.deb + dest: /srv/grafana.deb + + - name: Install local archive on remote + apt: + deb: /srv/grafana.deb + state: present + notify: Restart grafana + +- name: Configure grafana synchronize: src: files/grafana/environment dest: /etc/default/grafana-server - notify: restart grafana + notify: Restart grafana -- name: set up grafana datasources - synchronize: - src: files/grafana/datasources.yml +- name: Set up grafana datasources + template: + src: datasources.yml.j2 dest: /etc/grafana/provisioning/datasources/default.yml - notify: restart grafana + notify: Restart grafana -- name: set up grafana dashboards +- name: Set up grafana dashboards synchronize: src: files/grafana/dashboards.yml dest: /etc/grafana/provisioning/dashboards/default.yml - notify: restart grafana + notify: Restart grafana -- name: copy grafana dashboards +- name: Copy grafana dashboards synchronize: src: dashboards/ dest: /etc/grafana/dashboards/ - notify: restart grafana + notify: Restart grafana # Setup nginx with selfsigned certificate -- name: copy ssl cert +- name: Copy ssl cert synchronize: src: ssl.crt dest: /etc/ssl/certs/grafana.crt - notify: restart nginx + notify: Restart nginx -- name: copy ssl key +- name: Copy ssl key synchronize: src: ssl.key dest: /etc/ssl/private/grafana.key - notify: restart nginx + notify: Restart nginx -- name: copy default nginx config +- name: Copy default nginx config template: src: nginx.conf.j2 dest: /etc/nginx/sites-enabled/grafana.conf owner: root group: root mode: 0644 - notify: restart nginx + notify: Restart nginx diff --git a/provision-contest/ansible/roles/grafana/files/datasources.yml b/provision-contest/ansible/roles/grafana/templates/datasources.yml.j2 similarity index 86% rename from provision-contest/ansible/roles/grafana/files/datasources.yml rename to provision-contest/ansible/roles/grafana/templates/datasources.yml.j2 index 36088aff..fe32e974 100644 --- a/provision-contest/ansible/roles/grafana/files/datasources.yml +++ b/provision-contest/ansible/roles/grafana/templates/datasources.yml.j2 @@ -48,3 +48,14 @@ datasources: version: 1 # allow users to edit datasources from the UI. editable: true +{% if loki is defined and loki %} + - name: Loki + type: loki + access: proxy + url: http://localhost:3100 + jsonData: + timeout: 60 + maxLines: 1000 + httpHeaderName1: Connection + httpHeaderName2: Upgrade +{% endif %} diff --git a/provision-contest/ansible/roles/grafana/templates/nginx.conf.j2 b/provision-contest/ansible/roles/grafana/templates/nginx.conf.j2 index 3be8301b..8550b6a5 100644 --- a/provision-contest/ansible/roles/grafana/templates/nginx.conf.j2 +++ b/provision-contest/ansible/roles/grafana/templates/nginx.conf.j2 @@ -1,5 +1,5 @@ server { - listen 8443 ssl http2; + listen {{ grafana_port }} ssl http2; location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -21,7 +21,7 @@ server { proxy_redirect off; proxy_set_header Host $host; } -{% endif $} +{% endif %} access_log /var/log/nginx/prometheus.log; error_log /var/log/nginx/prometheus.log; diff --git a/provision-contest/ansible/roles/grafana/templates/prometheus.yml.j2 b/provision-contest/ansible/roles/grafana/templates/prometheus.yml.j2 index 33e22df8..8352c07c 100644 --- a/provision-contest/ansible/roles/grafana/templates/prometheus.yml.j2 +++ b/provision-contest/ansible/roles/grafana/templates/prometheus.yml.j2 @@ -5,86 +5,158 @@ global: scrape_configs: - job_name: 'grafana' static_configs: - - targets: ['localhost:443'] + - targets: ['localhost:3000'] - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - - job_name: 'db' +{% for group_prefix in GROUP_PREFIXES %} +{% if groups[group_prefix+"judgehost"][0] is defined %} + - job_name: node_judgehost + basic_auth: + username: "prometheus" + password: "{{ hostvars[groups[group_prefix+"judgehost"][0]]['PROMETHEUS_PASS'] }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: -{% for host in groups["domserver"] %} +{% for host in groups[group_prefix+"judgehost"] %} + - {{ hostvars[host].ansible_host }}:9100 +{% endfor %} +{% endif %} +{% if groups["domserver"][0] is defined %} + - job_name: {{ group_prefix ~ domjudge }} + basic_auth: + username: admin + password: {{ ADMIN_PASSWORD }} + metrics_path: /api/v4/metrics/prometheus + scheme: https + tls_config: + insecure_skip_verify: true + static_configs: + - targets: +{% for host in groups[group_prefix+"domserver"] %} + - {{ hostvars[host].ansible_host }} +{% endfor %} + - job_name: {{ group_prefix ~ 'db' }} + basic_auth: + username: "prometheus" + password: "{{ hostvars[groups[group_prefix+"domserver"][0]]['PROMETHEUS_PASS'] }}" + tls_config: + insecure_skip_verify: true + scheme: https + static_configs: + - targets: +{% for host in groups[group_prefix+"domserver"] %} - {{ hostvars[host].ansible_host }}:9104 {% endfor %} - - job_name: node_domserver + - job_name: {{ group_prefix ~ 'node_domserver' }} + basic_auth: + username: "prometheus" + password: "{{ hostvars[groups[group_prefix+"domserver"][0]]['PROMETHEUS_PASS'] }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: {% for host in groups["domserver"] %} - {{ hostvars[host].ansible_host }}:9100 {% endfor %} - - job_name: node_judgehost + - job_name: {{ group_prefix ~ 'web_nginx_domserver' }} + basic_auth: + username: "prometheus" + password: "{{ hostvars[groups[group_prefix+"domserver"][0]]['PROMETHEUS_PASS'] }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: -{% for host in groups["judgehost"] %} - - {{ hostvars[host].ansible_host }}:9100 +{% for host in groups[group_prefix+"domserver"] %} + - {{ hostvars[host].ansible_host }}:9113 +{% endfor %} + - job_name: {{ group_prefix ~ 'web_fpm_domserver' }} + basic_auth: + username: "prometheus" + password: "{{ hostvars[groups[group_prefix+"domserver"][0]]['PROMETHEUS_PASS'] }}" + tls_config: + insecure_skip_verify: true + scheme: https + static_configs: + - targets: +{% for host in groups[group_prefix+"domserver"] %} + - {{ hostvars[host].ansible_host }}:9253 +{% endfor %} +{% endif %} {% endfor %} - job_name: node_grafana + basic_auth: + username: "prometheus" + password: "{{ PROMETHEUS_PASS }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: {% for host in groups["grafana"] %} - {{ hostvars[host].ansible_host }}:9100 {% endfor %} - job_name: node_mgmt + basic_auth: + username: "prometheus" + password: "{{ PROMETHEUS_PASS }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: {% for host in groups["mgmt"] %} - {{ hostvars[host].ansible_host }}:9100 {% endfor %} - job_name: node_scoreboard + basic_auth: + username: "prometheus" + password: "{{ PROMETHEUS_PASS }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: {% for host in groups["scoreboard"] %} - {{ hostvars[host].ansible_host }}:9100 {% endfor %} - job_name: node_cds + basic_auth: + username: "prometheus" + password: "{{ PROMETHEUS_PASS }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: {% for host in groups["cds"] %} - {{ hostvars[host].ansible_host }}:9100 {% endfor %} - - job_name: domjudge + - job_name: 'web_nginx_cds' basic_auth: - username: admin - password: {{ ADMIN_PASSWORD }} - metrics_path: /api/v4/metrics/prometheus - scheme: https + username: "prometheus" + password: "{{ PROMETHEUS_PASS }}" tls_config: insecure_skip_verify: true - static_configs: - - targets: -{% for host in groups["domserver"] %} - - {{ hostvars[host].ansible_host }} -{% endfor %} - - job_name: 'web_nginx_cds' + scheme: https static_configs: - targets: {% for host in groups["cds"] %} - {{ hostvars[host].ansible_host }}:9113 {% endfor %} - job_name: 'web_nginx_scoreboard' + basic_auth: + username: "prometheus" + password: "{{ PROMETHEUS_PASS }}" + tls_config: + insecure_skip_verify: true + scheme: https static_configs: - targets: {% for host in groups["scoreboard"] %} - {{ hostvars[host].ansible_host }}:9113 -{% endfor %} - - job_name: 'web_nginx_domserver' - static_configs: - - targets: -{% for host in groups["domserver"] %} - - {{ hostvars[host].ansible_host }}:9113 -{% endfor %} - - job_name: 'web_fpm_domserver' - static_configs: - - targets: -{% for host in groups["domserver"] %} - - {{ hostvars[host].ansible_host }}:9253 {% endfor %} diff --git a/provision-contest/ansible/roles/hosts/tasks/main.yml b/provision-contest/ansible/roles/hosts/tasks/main.yml index 825265ed..3193aa7e 100644 --- a/provision-contest/ansible/roles/hosts/tasks/main.yml +++ b/provision-contest/ansible/roles/hosts/tasks/main.yml @@ -1,19 +1,14 @@ --- # These tasks add entries to /etc/hosts -# FIXME: this needs to be cleaned up and done in a way such that the -# tasks are not marked as "changed" every time they are run, and also -# work better with ad-hoc editing of the inventory hosts file. +- name: Set the hostname based on inventory + hostname: + name: "{{ inventory_hostname }}" -- name: add all hosts to hosts file - lineinfile: +- name: Set new hosts file + template: + src: hosts.j2 dest: /etc/hosts - regexp: '{{ item }}$' - line: "{{ hostvars[item].ansible_host }} {{ item }}" - loop: "{{ groups['all'] }}" - -- name: add domjudge in hosts file - lineinfile: - dest: /etc/hosts - regexp: 'domserver' - line: "{{ DOMSERVER_IP }} domjudge domserver" + group: root + owner: root + mode: 0644 diff --git a/provision-contest/ansible/roles/hosts/templates/hosts.j2 b/provision-contest/ansible/roles/hosts/templates/hosts.j2 new file mode 100644 index 00000000..d1218d9e --- /dev/null +++ b/provision-contest/ansible/roles/hosts/templates/hosts.j2 @@ -0,0 +1,24 @@ +127.0.0.1 localhost +127.0.1.1 {{ inventory_hostname }} + +# The following lines are desirable for IPv6 capable hosts +::1 ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters + +{% if DOMSERVER_IP is defined %} +{{ DOMSERVER_IP }} domjudge domserver +{% endif %} +{% for item in groups['all'] %} +{% if hostvars[item].ansible_host is defined %} +{{ hostvars[item].ansible_host }} {{ item }} +{% endif %} +{% endfor %} + +{% if WF_RESTRICTED_NETWORK %} +{% for host, ip_address in HOSTS.items() %} +{{ ip_address }} {{ host }} +{% endfor %} +{% endif %} diff --git a/provision-contest/ansible/roles/icpc_fixes/handlers/main.yml b/provision-contest/ansible/roles/icpc_fixes/handlers/main.yml index d3c5d9d3..3e5568d5 100644 --- a/provision-contest/ansible/roles/icpc_fixes/handlers/main.yml +++ b/provision-contest/ansible/roles/icpc_fixes/handlers/main.yml @@ -1,7 +1,5 @@ --- -# Define here handlers associated to this role. - -- name: restart systemd timesyncd +- name: Restart systemd timesyncd systemd: name: systemd-timesyncd state: restarted diff --git a/provision-contest/ansible/roles/icpc_fixes/tasks/main.yml b/provision-contest/ansible/roles/icpc_fixes/tasks/main.yml index 862eed55..28a9d7b6 100644 --- a/provision-contest/ansible/roles/icpc_fixes/tasks/main.yml +++ b/provision-contest/ansible/roles/icpc_fixes/tasks/main.yml @@ -1,29 +1,35 @@ --- # These tasks perform fixes used on the ICPC image to the base system. -- name: add NTP servers +- name: Add NTP servers lineinfile: dest: /etc/systemd/timesyncd.conf regexp: '^#?NTP=' - line: "NTP=10.3.3.208 10.3.3.209" - notify: restart systemd timesyncd + line: "NTP=ntp1 ntp2" + notify: Restart systemd timesyncd when: WF_RESTRICTED_NETWORK -- name: remove source line from interfaces - lineinfile: - dest: /etc/network/interfaces - regexp: '^source-' - state: absent - - name: Re-order PXEboot command: efibootmgr -o {{ EFI_ORDER }} when: EFI_ORDER is defined -- name: disable mumble,selfie services - service: - name: "{{ item }}" - state: stopped - enabled: false - loop: - - mumble-server - - selfie +- name: IPv6 nginx fixes + block: + - name: Create nginx directory before installation + file: + path: /etc/{{ item }} + state: directory + mode: '0755' + group: root + owner: root + loop: + - nginx + - nginx/sites-enabled + + - name: Create nginx default without IPv6 listener + copy: + dest: /etc/nginx/sites-enabled/default + mode: '0644' + group: root + owner: root + content: "" diff --git a/provision-contest/ansible/roles/judgedaemon/vars/main.yml b/provision-contest/ansible/roles/judgedaemon/defaults/main.yml similarity index 100% rename from provision-contest/ansible/roles/judgedaemon/vars/main.yml rename to provision-contest/ansible/roles/judgedaemon/defaults/main.yml diff --git a/provision-contest/ansible/roles/judgedaemon/files/chroot-list/.gitignore b/provision-contest/ansible/roles/judgedaemon/files/chroot-list/.gitignore new file mode 100644 index 00000000..970eaaab --- /dev/null +++ b/provision-contest/ansible/roles/judgedaemon/files/chroot-list/.gitignore @@ -0,0 +1,4 @@ +*.list +*.list.gpg +*.list.asc +*.list.arm diff --git a/provision-contest/ansible/roles/judgedaemon/files/tune_cpu.service b/provision-contest/ansible/roles/judgedaemon/files/tune_cpu.service index 02e35a61..354d479e 100644 --- a/provision-contest/ansible/roles/judgedaemon/files/tune_cpu.service +++ b/provision-contest/ansible/roles/judgedaemon/files/tune_cpu.service @@ -1,12 +1,12 @@ [Unit] Description=Tune CPU cores: disable turboboost, downclocking, etc. +Before=domjudge-judgedaemon.target [Service] Type=oneshot ExecStart=/usr/local/sbin/disable-turboboost_ht RemainAfterExit=true ExecStartPre=/bin/sleep 10 -Before=domjudge-judgedaemon.target [Install] WantedBy=multi-user.target diff --git a/provision-contest/ansible/roles/judgedaemon/handlers/main.yml b/provision-contest/ansible/roles/judgedaemon/handlers/main.yml index 5676e7b2..6973da29 100644 --- a/provision-contest/ansible/roles/judgedaemon/handlers/main.yml +++ b/provision-contest/ansible/roles/judgedaemon/handlers/main.yml @@ -1,29 +1,28 @@ --- -# Define here handlers associated to this role. - -- name: enable and restart tune_cpu +- name: Enable and restart tune_cpu systemd: name: tune_cpu enabled: true state: restarted daemon_reload: true -- name: enable and restart create-cgroups +- name: Enable and restart create-cgroups service: name: create-cgroups enabled: true state: restarted daemon_reload: true -- name: enable and restart judgedaemon +- name: Enable and restart judgedaemon service: name: "domjudge-judgehost.target" enabled: true state: restarted daemon_reload: true -- name: update grub +- name: Update grub command: update-grub -- name: reboot +- name: Reboot reboot: + search_paths: ['/lib/molly-guard', '/sbin'] diff --git a/provision-contest/ansible/roles/judgedaemon/meta/main.yml b/provision-contest/ansible/roles/judgedaemon/meta/main.yml index 8a41d13b..15c87ace 100644 --- a/provision-contest/ansible/roles/judgedaemon/meta/main.yml +++ b/provision-contest/ansible/roles/judgedaemon/meta/main.yml @@ -2,3 +2,4 @@ --- dependencies: - role: domjudge_build + tags: domjudge_build diff --git a/provision-contest/ansible/roles/judgedaemon/tasks/main.yml b/provision-contest/ansible/roles/judgedaemon/tasks/main.yml index 8906e2a2..e585b7e2 100644 --- a/provision-contest/ansible/roles/judgedaemon/tasks/main.yml +++ b/provision-contest/ansible/roles/judgedaemon/tasks/main.yml @@ -1,14 +1,21 @@ --- # These tasks configure the DOMjudge judgedaemon with chroot -- name: create domjudge-run users - user: name=domjudge-run-{{ item }} createhome=no home=/nonexistent group=nogroup shell=/bin/false +- name: Create domjudge-run users + user: + name: "domjudge-run-{{ item }}" + createhome: false + home: /nonexistent + group: nogroup + shell: /bin/false loop: "{{ cpucore }}" -- name: create domjudge-run group - group: name=domjudge-run state=present +- name: Create domjudge-run group + group: + name: domjudge-run + state: present -- name: create temp package directory +- name: Create temp package directory file: path: /tmp/dj_ansible state: directory @@ -16,46 +23,76 @@ group: root mode: 0700 -- name: copy chroot DEB packages to install +- name: Copy chroot DEB packages to install synchronize: src: install-chroot dest: /tmp/dj_ansible/ -- name: create chroot - shell: "set -o pipefail && {{ DJ_DIR }}/misc-tools/dj_make_chroot -y -i openjdk-11-jdk-headless -l \"$(ls /tmp/dj_ansible/install-chroot/*.deb 2>/dev/null | tr '\n' ',')\" 2>&1 | tee /tmp/dj_make_chroot.log; grep '^Done building chroot in' /tmp/dj_make_chroot.log" +- name: Copy chroot-list to install + synchronize: + src: chroot-list + dest: /tmp/dj_ansible/ + +- name: Create chroot + shell: "set -o pipefail && + {{ DJ_DIR }}/misc-tools/dj_make_chroot -y -H + -i icpc-kotlinc,openjdk-17-jdk-headless + -l \"$(ls /tmp/dj_ansible/install-chroot/*.deb 2>/dev/null | tr '\n' ',')\" + -s \"$(ls /tmp/dj_ansible/chroot-list/*.list 2>/dev/null | tr '\n' ',')\" + 2>&1 | tee /tmp/dj_make_chroot.log; + grep '^Done building chroot in' /tmp/dj_make_chroot.log" environment: - DEBMIRROR: "{{ 'https://packages/ubuntu' if WF_RESTRICTED_NETWORK else '' }}" + DEBMIRROR: "{%- if WF_RESTRICTED_NETWORK and ICPC_IMAGE -%}https://packages/ubuntu + {%- elif ICPC_IMAGE -%}https://sysopspackages.icpc.global/ubuntu + {%- else -%} + {%- endif -%}" args: + executable: /bin/bash creates: "/chroot/domjudge" - name: Pre-generate the kernel flags for ansible usage set_fact: - procline: "cgroup_enable=memory swapaccount=1 isolcpus={{ cpucore|join(',') }}" + procline: "apparmor=0 systemd.unified_cgroup_hierarchy=0 cgroup_enable=memory swapaccount=1 isolcpus={{ cpucore | join(',') }}" -- name: add cgroup kernel parameters +- name: Add cgroup kernel parameters lineinfile: dest: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX_DEFAULT=' line: "GRUB_CMDLINE_LINUX_DEFAULT=\"{{ procline }}\"" -- name: check cgroup kernel parameters +- name: Pre-generate the kernel flags for ansible usage (specific AWS) + # AWS machines overwrite the variable from the last step in another file + # console: https://tldp.org/HOWTO/Remote-Serial-Console-HOWTO/configure-kernel.html + # nvme_core: https://docs.aws.amazon.com/ebs/latest/userguide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes + when: AWS + set_fact: + procline_aws: "console=tty1 console=ttyS0 nvme_core.io_timeout=4294967295 {{ procline }}" + +- name: Add cgroup kernel parameters for AWS machines + when: AWS + lineinfile: + dest: /etc/default/grub.d/50-cloudimg-settings.cfg + regexp: '^GRUB_CMDLINE_LINUX_DEFAULT=' + line: "GRUB_CMDLINE_LINUX_DEFAULT=\"{{ procline_aws }}\"" + +- name: Check cgroup kernel parameters command: cat /proc/cmdline register: kernel_cmdline changed_when: procline not in kernel_cmdline.stdout notify: - - update grub - - reboot + - Update grub + - Reboot -- name: reboot to activate kernel parameters +- name: Reboot to activate kernel parameters meta: flush_handlers -- name: copy script to disable turboboost and hyperthreading +- name: Copy script to disable turboboost and hyperthreading copy: src: disable-turboboost_ht dest: /usr/local/sbin/ mode: 0755 -- name: copy tune_cpu systemd unit file +- name: Copy tune_cpu systemd unit file copy: src: tune_cpu.service dest: /etc/systemd/system/ @@ -63,9 +100,13 @@ group: root owner: root notify: - - enable and restart tune_cpu + - Enable and restart tune_cpu -- name: copy judgedaemon related systemd unit files +- name: Enable and restart tune_cpu (flushing as this should always happen even if we fail further) + # We need a daemon-reload, so we loose idempotency if we start the service in the next block + ansible.builtin.meta: flush_handlers + +- name: Copy judgedaemon related systemd unit files copy: remote_src: true src: "{{ DJ_DIR }}/lib/judge/{{ item }}.service" @@ -77,9 +118,9 @@ - create-cgroups - domjudge-judgedaemon@ notify: - - enable and restart create-cgroups + - Enable and restart create-cgroups -- name: template judgedaemon template systemd unit file +- name: Template judgedaemon template systemd unit file template: src: domjudge-judgehost.target.j2 dest: /etc/systemd/system/domjudge-judgehost.target @@ -87,9 +128,9 @@ group: root owner: root notify: - - enable and restart judgedaemon + - Enable and restart judgedaemon -- name: disable systemd timers +- name: Disable systemd timers systemd: name: "{{ item }}" masked: true @@ -97,3 +138,10 @@ - apt-daily-upgrade.timer - apt-daily.timer - systemd-tmpfiles-clean.timer + +- name: Enable and start judgedaemon + service: + name: "domjudge-judgehost.target" + enabled: true + state: started + daemon_reload: true diff --git a/provision-contest/ansible/roles/judgedaemon/templates/domjudge-judgehost.target.j2 b/provision-contest/ansible/roles/judgedaemon/templates/domjudge-judgehost.target.j2 index d39dc784..490dc602 100644 --- a/provision-contest/ansible/roles/judgedaemon/templates/domjudge-judgehost.target.j2 +++ b/provision-contest/ansible/roles/judgedaemon/templates/domjudge-judgehost.target.j2 @@ -1,6 +1,6 @@ [Unit] Description=DOMjudge judgehost running one or more judgedaemons -Requires={% for core in CPUCORE %}domjudge-judgedaemon@{{ core }}.service {% endfor %} +Requires={% for core in cpucore %}domjudge-judgedaemon@{{ core }}.service {% endfor %} [Install] WantedBy=multi-user.target diff --git a/provision-contest/ansible/roles/keepalived/files/alerting/bin/alert_listener.sh b/provision-contest/ansible/roles/keepalived/files/alerting/bin/alert_listener.sh index 2afbf4e7..08da6a8b 100755 --- a/provision-contest/ansible/roles/keepalived/files/alerting/bin/alert_listener.sh +++ b/provision-contest/ansible/roles/keepalived/files/alerting/bin/alert_listener.sh @@ -3,4 +3,5 @@ while true; do if [ -f ~/alerting.sh ]; then ~/alerting.sh fi + sleep 0.1s done diff --git a/provision-contest/ansible/roles/keepalived/files/alerting/bin/alerting.sh.template b/provision-contest/ansible/roles/keepalived/files/alerting/bin/alerting.sh.template index a44e77a1..43b58760 100755 --- a/provision-contest/ansible/roles/keepalived/files/alerting/bin/alerting.sh.template +++ b/provision-contest/ansible/roles/keepalived/files/alerting/bin/alerting.sh.template @@ -1,7 +1,16 @@ -#!/bin/sh +#!/bin/bash +# Try a couple of different notification methods. + +notify-send -u critical -t 60000 "Alert: keepalived status changed at $(date '+%T') !" + +amixer -c 0 set Master 100% amixer -c 1 set Master 100% for i in 1 2 3 4 5 6 7 8 9 10 ; do aplay /usr/share/sounds/sound-icons/pipe.wav done + +beep -f 500 -l 400 -d 100 -r 4 +beep -f 1000 -l 400 -d 100 -r 4 +beep -f 500 -l 200 -d 100 -r 6 diff --git a/provision-contest/ansible/roles/keepalived/files/alerting/bin/trigger_alert.sh b/provision-contest/ansible/roles/keepalived/files/alerting/bin/trigger_alert.sh index 6c5c6fb9..72922d00 100755 --- a/provision-contest/ansible/roles/keepalived/files/alerting/bin/trigger_alert.sh +++ b/provision-contest/ansible/roles/keepalived/files/alerting/bin/trigger_alert.sh @@ -1,2 +1,7 @@ #!/bin/sh + +# This only copies the script below which contains alerting code +# A desktop autostart application started the alert_listener.sh +# script, which then detects the copy and executes it. + cp ~/bin/alerting.sh.template ~/alerting.sh diff --git a/provision-contest/ansible/roles/keepalived/handlers/main.yml b/provision-contest/ansible/roles/keepalived/handlers/main.yml index 47050fb0..730921ea 100644 --- a/provision-contest/ansible/roles/keepalived/handlers/main.yml +++ b/provision-contest/ansible/roles/keepalived/handlers/main.yml @@ -1,5 +1,5 @@ --- -# Define here handlers associated to this role. - -- name: restart keepalived service - service: name=keepalived state=restarted +- name: Restart keepalived service + service: + name: keepalived + state: restarted diff --git a/provision-contest/ansible/roles/keepalived/meta/main.yml b/provision-contest/ansible/roles/keepalived/meta/main.yml index 4a4f743a..a480e52b 100644 --- a/provision-contest/ansible/roles/keepalived/meta/main.yml +++ b/provision-contest/ansible/roles/keepalived/meta/main.yml @@ -2,3 +2,4 @@ --- dependencies: - role: domjudge_user + tags: domjudge_user diff --git a/provision-contest/ansible/roles/keepalived/tasks/main.yml b/provision-contest/ansible/roles/keepalived/tasks/main.yml index 8225b237..ae011b7f 100644 --- a/provision-contest/ansible/roles/keepalived/tasks/main.yml +++ b/provision-contest/ansible/roles/keepalived/tasks/main.yml @@ -1,11 +1,12 @@ --- # These tasks install keepalived with IP failover -- name: Install keepalived +- name: Install keepalived and beep (for alerting) apt: state: present pkg: - keepalived + - beep - name: Configure keepalived for domjudge IP failover template: @@ -14,7 +15,7 @@ mode: 0644 group: root owner: root - notify: restart keepalived service + notify: Restart keepalived service - name: Start but disable keepalived (so it does not fail back to primary) service: diff --git a/provision-contest/ansible/roles/keepalived/templates/keepalived.conf.j2 b/provision-contest/ansible/roles/keepalived/templates/keepalived.conf.j2 index 3d241f55..617cdf6c 100644 --- a/provision-contest/ansible/roles/keepalived/templates/keepalived.conf.j2 +++ b/provision-contest/ansible/roles/keepalived/templates/keepalived.conf.j2 @@ -1,17 +1,18 @@ vrrp_instance lb_ipv4 { state MASTER - interface {{ ansible_facts['default_ipv4']['interface'] }} + interface {{ REPLICATION_INTERFACE|default(ansible_facts['default_ipv4']['interface']) }} use_vmac virtual_router_id 32 - priority {{KEEPALIVED_PRIORITY}} + priority {{ KEEPALIVED_PRIORITY }} virtual_ipaddress { - {{DOMSERVER_IP}} + {{ DOMSERVER_IP }} } authentication { auth_type PASS - auth_pass {{REPLICATION_PASSWORD}} + auth_pass {{ REPLICATION_PASSWORD }} } - notify_backup /home/domjudge/bin/triggeralert.sh - notify_master /home/domjudge/bin/triggeralert.sh - notify_fault /home/domjudge/bin/triggeralert.sh + script_user domjudge domjudge + notify_backup /home/domjudge/bin/trigger_alert.sh + notify_master /home/domjudge/bin/trigger_alert.sh + notify_fault /home/domjudge/bin/trigger_alert.sh } diff --git a/provision-contest/ansible/roles/mysql_replication/meta/main.yml b/provision-contest/ansible/roles/mysql_replication/meta/main.yml index 2ecf9284..bbf7aabf 100644 --- a/provision-contest/ansible/roles/mysql_replication/meta/main.yml +++ b/provision-contest/ansible/roles/mysql_replication/meta/main.yml @@ -2,3 +2,4 @@ --- dependencies: - role: mysql_server + tags: mysql_server diff --git a/provision-contest/ansible/roles/mysql_replication/tasks/main.yml b/provision-contest/ansible/roles/mysql_replication/tasks/main.yml index 45f8b4a6..0f873b66 100644 --- a/provision-contest/ansible/roles/mysql_replication/tasks/main.yml +++ b/provision-contest/ansible/roles/mysql_replication/tasks/main.yml @@ -1,31 +1,43 @@ --- # These tasks configure a MySQL server for master/master replication -- name: install replication helper packages +- name: Install replication helper packages apt: state: present pkg: - mariadb-backup -- name: disable mysql listen on localhost only +- name: Disable mysql listen on localhost only replace: path: /etc/mysql/mariadb.conf.d/50-server.cnf regexp: '^bind-address\s*=' replace: '#bind-address =' -- name: add mysql config snippet for replication +- name: Set the replication interface variable + set_fact: + replication_interface: "{{ REPLICATION_INTERFACE|default(ansible_default_ipv4.interface) }}" + +- name: Set the variable for the IP address of the replication interface + set_fact: + replication_interface_ip_address: "{{ vars['ansible_' + replication_interface]['ipv4']['address'] }}" + +- name: Add mysql config snippet for replication template: src: mysql.replication.cnf.j2 dest: /etc/mysql/mariadb.conf.d/zzz_replication.cnf mode: 0644 group: root owner: root - notify: restart mysql + notify: Restart mysql -- name: copy script to setup replication - template: src=setup-replication.sh.j2 dest=/usr/local/sbin/setup-replication.sh mode=0755 +- name: Copy script to setup replication + template: + src: setup-replication.sh.j2 + dest: /usr/local/sbin/setup-replication.sh + mode: 0755 -- name: create mysql replication user +- name: Create mysql replication user + no_log: true mysql_user: name: replication host: '%' @@ -34,5 +46,5 @@ priv: '*.*:REPLICATION SLAVE' state: present -- name: make sure mysql is restarted +- name: Make sure mysql is restarted meta: flush_handlers diff --git a/provision-contest/ansible/roles/mysql_replication/templates/mysql.replication.cnf.j2 b/provision-contest/ansible/roles/mysql_replication/templates/mysql.replication.cnf.j2 index 9cb024e4..93f58de0 100644 --- a/provision-contest/ansible/roles/mysql_replication/templates/mysql.replication.cnf.j2 +++ b/provision-contest/ansible/roles/mysql_replication/templates/mysql.replication.cnf.j2 @@ -8,4 +8,4 @@ log-bin = /var/log/mysql/mysql-bin.log binlog_do_db = domjudge # Host specific replication configuration -server_id = {{ ansible_default_ipv4.address | regex_replace('^\d{2,3}\.\d+\.\d+\.', '') }} +server_id = {{ replication_interface_ip_address | ipaddr('int') }} diff --git a/provision-contest/ansible/roles/mysql_replication/templates/setup-replication.sh.j2 b/provision-contest/ansible/roles/mysql_replication/templates/setup-replication.sh.j2 index 5be5a45b..6bf515d1 100755 --- a/provision-contest/ansible/roles/mysql_replication/templates/setup-replication.sh.j2 +++ b/provision-contest/ansible/roles/mysql_replication/templates/setup-replication.sh.j2 @@ -1,14 +1,19 @@ -#!/bin/sh -e +#!/bin/bash -e # This script sets up MySQL master/master replication on two servers. usage() { cat < +Usage: $0 [OPTION]... -This script sets up MySQL master/master replication on two servers. -Run this script as root on the secondary server and specify the -primary one as argument. The databases on this server will be wiped! +Options: + -m Set up two-way master/master replication + -h Show this usage information + +This script sets up MySQL master/slave or master/master replication +on two servers. Run this script as root on the secondary server and +specify the primary one as argument. The databases on the secondary +will be wiped! This script should be run after ansible code has been run, so that MySQL replication configuration and users are present on both machines @@ -17,6 +22,11 @@ Running this script will synchronize the databases on this host to that on , including user credentials, so make sure that etc/dbpasswords.secret contains the same credentials on each machine. +The -m option should be used to set up two-way master/master replication, +which is normally used between the primary and a secondary hot-standby. +In case of replication to a disaster recovery laptop, this option should +normally not be used. + To recover a machine after it went down and a failover happened: 1. Bring the machine up again with the keepalived service stopped (it @@ -24,10 +34,10 @@ To recover a machine after it went down and a failover happened: 2. Make sure the machine is fully configured (using ansible). -3. Run this script as root on the machine with argument the active +3. Run this script as root on the failed machine with argument the active domserver. For example, if the active machine is domjudge-backup and this, failed machine is presumably domjudge-primary, then run: - setup-replication domjudge-backup + setup-replication -m domjudge-backup When this script completes successfully (should take ~1-3 minutes when using a Gbit network and about 1-5 GB database size), the two machines are running in master/master replication. If it fails, @@ -42,14 +52,34 @@ To recover a machine after it went down and a failover happened: on domjudge-primary, and check that the domjudge IP correctly returned to domjudge-primary again. + Alternatively, switch the effective roles of the two by decreasing + the priority in /etc/keepalived/keepalived.conf on domjudge-primary + and run on it: + service keepalived start + EOF } +SETUP_MASTER_MASTER=0 +while getopts 'mh' OPT ; do + case $OPT in + m) SETUP_MASTER_MASTER=1 ;; + h) SHOWHELP=1 ;; + \?) echo "Error: could not parse options."; exit 1 ;; + esac +done +shift $((OPTIND-1)) + PRIMARY=$1 +if [ -n "${SHOWHELP}" ]; then + usage + exit 0 +fi + if [ -z "$PRIMARY" ]; then - echo "Error: specify primary host to set up master/master replication with." + echo "Error: specify primary host to set up replication with." usage exit 1 fi @@ -60,8 +90,8 @@ if [ $(id -un) != 'root' ]; then exit 1 fi -if ip addr | grep '{{DOMSERVER_IP}}' >/dev/null 2>&1 ; then - echo "Error: this host '$(hostname)' has the primary IP '{{DOMSERVER_IP}}'." +if ip addr | grep '{{ DOMSERVER_IP }}' >/dev/null 2>&1 ; then + echo "Error: this host '$(hostname)' has the primary IP '{{ DOMSERVER_IP }}'." usage exit 1 fi @@ -96,9 +126,9 @@ get_field() check_status() { local sql_info="$(printf '%s\n' "$@")" - [ $(get_field 'Seconds_Behind_Master' "$sql_info") = '0' ] || return 1 - [ $(get_field 'Slave_SQL_Running' "$sql_info") = 'Yes' ] || return 1 - [ $(get_field 'Last_SQL_Errno' "$sql_info") = '0' ] || return 1 + [ "$(get_field 'Seconds_Behind_Master' "$sql_info")" = '0' ] || return 1 + [ "$(get_field 'Slave_SQL_Running' "$sql_info")" = 'Yes' ] || return 1 + [ "$(get_field 'Last_SQL_Errno' "$sql_info")" = '0' ] || return 1 return 0 } @@ -117,10 +147,12 @@ if check_replication ; then fi echo "Stopping PHP locally..." -service php7.4-fpm stop +service php{{ php_version.stdout }}-fpm stop -echo "Stopping replication on $PRIMARY..." -ssh "$PRIMARY" 'mysql -e "STOP SLAVE;"' +if [ "$SETUP_MASTER_MASTER" -eq 1 ]; then + echo "Stopping replication on $PRIMARY..." + ssh "$PRIMARY" 'mysql -e "STOP SLAVE;"' +fi PRIMARY_STATUS=$(ssh "$PRIMARY" "mysql -e 'SHOW MASTER STATUS\G'") PRIMARY_BINLOG=$(get_field 'File' "$PRIMARY_STATUS") @@ -149,9 +181,9 @@ service mysql start echo "Starting replication locally..." mysql -e " -CHANGE MASTER TO MASTER_HOST='{{SERVER_IP_PREFIX}}.${PRIMARY_SERVER_ID}', +CHANGE MASTER TO MASTER_HOST='${PRIMARY}', MASTER_USER='replication', - MASTER_PASSWORD='{{REPLICATION_PASSWORD}}', + MASTER_PASSWORD='{{ REPLICATION_PASSWORD }}', MASTER_LOG_FILE='${PRIMARY_BINLOG}', MASTER_LOG_POS=${PRIMARY_LOGPOS}; START SLAVE;" @@ -182,17 +214,19 @@ OUR_STATUS=$(mysql -e 'SHOW MASTER STATUS\G') OUR_BINLOG=$(get_field 'File' "$OUR_STATUS") OUR_LOGPOS=$(get_field 'Position' "$OUR_STATUS") -echo "Starting replication on $PRIMARY..." -ssh "$PRIMARY" "mysql -e ' -CHANGE MASTER TO MASTER_HOST=\"{{SERVER_IP_PREFIX}}.${OUR_SERVER_ID}\", - MASTER_USER=\"replication\", - MASTER_PASSWORD=\"{{REPLICATION_PASSWORD}}\", - MASTER_LOG_FILE=\"${OUR_BINLOG}\", - MASTER_LOG_POS=${OUR_LOGPOS}; -START SLAVE;'" +if [ "$SETUP_MASTER_MASTER" -eq 1 ]; then + echo "Starting replication on $PRIMARY..." + ssh "$PRIMARY" "mysql -e ' + CHANGE MASTER TO MASTER_HOST=\"{{ replication_interface_ip_address }}\", + MASTER_USER=\"replication\", + MASTER_PASSWORD=\"{{ REPLICATION_PASSWORD }}\", + MASTER_LOG_FILE=\"${OUR_BINLOG}\", + MASTER_LOG_POS=${OUR_LOGPOS}; + START SLAVE;'" +fi echo "Restarting PHP locally..." -service php7.4-fpm start +service php{{ php_version.stdout }}-fpm start if check_replication ; then echo "Replication setup done." diff --git a/provision-contest/ansible/roles/mysql_server/files/dump-db b/provision-contest/ansible/roles/mysql_server/files/dump-db index 0a8adee3..9fa7bcd2 100644 --- a/provision-contest/ansible/roles/mysql_server/files/dump-db +++ b/provision-contest/ansible/roles/mysql_server/files/dump-db @@ -1,5 +1,9 @@ #!/bin/sh +create_database_dump () { + sudo mysqldump --opt --skip-lock-tables domjudge | pv | gzip > "/home/domjudge/db-dumps/${1}.sql.gz" +} + if [ -z "$1" ] then echo "Usage dump-db [name]" @@ -7,4 +11,13 @@ then exit 1 fi -sudo mysqldump --opt --skip-lock-tables domjudge | pv | gzip > "/home/domjudge/db-dumps/${1}.sql.gz" +if [ -f "/home/domjudge/db-dumps/${1}.sql.gz" ]; then + while true; do + read -p "Overwrite existing database dump (y/N)? " yn + case $yn in + [Yy]* ) break ;; + ''|[Nn]* ) exit 0;; + esac + done +fi +create_database_dump $1 diff --git a/provision-contest/ansible/roles/mysql_server/handlers/main.yml b/provision-contest/ansible/roles/mysql_server/handlers/main.yml index 5ef1bb20..a3e1bb67 100644 --- a/provision-contest/ansible/roles/mysql_server/handlers/main.yml +++ b/provision-contest/ansible/roles/mysql_server/handlers/main.yml @@ -1,5 +1,6 @@ --- -# Define here handlers associated to this role. - -- name: restart mysql - service: name=mysql enabled=true state=restarted +- name: Restart mysql + service: + name: mysql + enabled: true + state: restarted diff --git a/provision-contest/ansible/roles/mysql_server/tasks/main.yml b/provision-contest/ansible/roles/mysql_server/tasks/main.yml index 26479b04..0e9a6a9a 100644 --- a/provision-contest/ansible/roles/mysql_server/tasks/main.yml +++ b/provision-contest/ansible/roles/mysql_server/tasks/main.yml @@ -1,7 +1,7 @@ --- # These tasks install and configure a MySQL server -- name: install mysql packages +- name: Install mysql packages apt: state: present pkg: @@ -9,7 +9,7 @@ - python3-mysqldb - mycli -- name: copy in MySQL config +- name: Copy in MySQL config copy: src: my.cnf dest: /root/.my.cnf @@ -17,7 +17,7 @@ owner: root group: root -- name: create directory for systemd mysql settings +- name: Create directory for systemd mysql settings file: path: /etc/systemd/system/mysql.service.d/ state: directory @@ -25,28 +25,33 @@ owner: root mode: 0755 -- name: update systemd so mysql has bigger limits +- name: Update systemd so mysql has bigger limits copy: src: mysql.override.cnf dest: /etc/systemd/system/mysql.service.d/override.conf mode: 0644 group: root owner: root - notify: restart mysql + notify: Restart mysql -- name: add mysql config snippet to increase limits +- name: Add mysql config snippet to increase limits copy: src: mysql.domjudge.cnf dest: /etc/mysql/mariadb.conf.d/zz_domjudge.cnf mode: 0644 owner: root group: root - notify: restart mysql + notify: Restart mysql -- name: make sure mysql is restarted +- name: Make sure mysql is restarted meta: flush_handlers -- name: create directory to store scripts & database dumps +- name: Make sure mysql runs + service: + name: mysql + state: started + +- name: Create directory to store scripts & database dumps file: path: /home/domjudge/{{ item }} owner: domjudge @@ -57,7 +62,7 @@ - db-dumps - bin -- name: copy database dump/load scripts +- name: Copy database dump/load scripts copy: src: "{{ item }}" dest: /home/domjudge/bin/{{ item }} diff --git a/provision-contest/ansible/roles/phpstorm/files/.idea/domjudge-checkout.iml b/provision-contest/ansible/roles/phpstorm/files/.idea/domjudge-checkout.iml index 74663487..330bd4c2 100644 --- a/provision-contest/ansible/roles/phpstorm/files/.idea/domjudge-checkout.iml +++ b/provision-contest/ansible/roles/phpstorm/files/.idea/domjudge-checkout.iml @@ -178,6 +178,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/provision-contest/ansible/roles/phpstorm/files/.idea/php.xml b/provision-contest/ansible/roles/phpstorm/files/.idea/php.xml index beca0f3f..9d627638 100644 --- a/provision-contest/ansible/roles/phpstorm/files/.idea/php.xml +++ b/provision-contest/ansible/roles/phpstorm/files/.idea/php.xml @@ -179,9 +179,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/provision-contest/ansible/roles/phpstorm/files/README.md b/provision-contest/ansible/roles/phpstorm/files/README.md index 389c3816..8c394418 100644 --- a/provision-contest/ansible/roles/phpstorm/files/README.md +++ b/provision-contest/ansible/roles/phpstorm/files/README.md @@ -1,12 +1,12 @@ Place in this directory: -* The unpacked PHPStorm .tar.gz file. Make sure the full version is correct set in variables.yml. +* The unpacked PHPStorm .tar.gz file. Make sure the full version is correct set in group_vars/all/all.yml. Download the .tar.gz from jetbrains.com. -* The PHPStorm configuration directory containing our configuration, as PhpStorm20xx.y. - Make sure the version is correctly set in variables.yml. +* The PHPStorm configuration directory containing our configuration from `.config/JetBrains/PhpStorm20xx.y`, as PhpStorm20xx.y. + Make sure the version is correctly set in group_vars/all/all.yml. Create the config by launching PHPStorm once on a machine and going through the wizard. Also install the plugins we use. Download the latest version of the following plugins and set them up: - https://plugins.jetbrains.com/plugin/8133-php-toolbox/versions - https://plugins.jetbrains.com/plugin/7320-php-annotations/versions - https://plugins.jetbrains.com/plugin/7219-symfony-support/versions - https://plugins.jetbrains.com/plugin/7499-gittoolbox/versions -* The PHPStorm local share directory containing our configuration, as PhpStorm20xx.y-local-share. \ No newline at end of file +* The PHPStorm local share directory containing our configuration from `.local/share/JetBrains/PhpStorm20xx.y`, as PhpStorm20xx.y-local-share. \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/_remote.repositories b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/_remote.repositories new file mode 100644 index 00000000..e910e8fd --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/_remote.repositories @@ -0,0 +1,3 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Oct 31 17:32:58 BDT 2022 +junit-bom-5.8.2.pom>central= diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/junit-bom-5.8.2.pom b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/junit-bom-5.8.2.pom new file mode 100644 index 00000000..c92aae6a --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/junit-bom-5.8.2.pom @@ -0,0 +1,159 @@ + + + + + + + + 4.0.0 + org.junit + junit-bom + 5.8.2 + pom + JUnit 5 (Bill of Materials) + This Bill of Materials POM can be used to ease dependency management when referencing multiple JUnit artifacts using Gradle or Maven. + https://junit.org/junit5/ + + + Eclipse Public License v2.0 + https://www.eclipse.org/legal/epl-v20.html + + + + + bechte + Stefan Bechtold + stefan.bechtold@me.com + + + jlink + Johannes Link + business@johanneslink.net + + + marcphilipp + Marc Philipp + mail@marcphilipp.de + + + mmerdes + Matthias Merdes + matthias.merdes@heidelpay.com + + + sbrannen + Sam Brannen + sam@sambrannen.com + + + sormuras + Christian Stein + sormuras@gmail.com + + + juliette-derancourt + Juliette de Rancourt + derancourt.juliette@gmail.com + + + + scm:git:git://github.com/junit-team/junit5.git + scm:git:git://github.com/junit-team/junit5.git + https://github.com/junit-team/junit5 + + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + + + org.junit.jupiter + junit-jupiter-api + 5.8.2 + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + + + org.junit.jupiter + junit-jupiter-migrationsupport + 5.8.2 + + + org.junit.jupiter + junit-jupiter-params + 5.8.2 + + + org.junit.platform + junit-platform-commons + 1.8.2 + + + org.junit.platform + junit-platform-console + 1.8.2 + + + org.junit.platform + junit-platform-engine + 1.8.2 + + + org.junit.platform + junit-platform-jfr + 1.8.2 + + + org.junit.platform + junit-platform-launcher + 1.8.2 + + + org.junit.platform + junit-platform-reporting + 1.8.2 + + + org.junit.platform + junit-platform-runner + 1.8.2 + + + org.junit.platform + junit-platform-suite + 1.8.2 + + + org.junit.platform + junit-platform-suite-api + 1.8.2 + + + org.junit.platform + junit-platform-suite-commons + 1.8.2 + + + org.junit.platform + junit-platform-suite-engine + 1.8.2 + + + org.junit.platform + junit-platform-testkit + 1.8.2 + + + org.junit.vintage + junit-vintage-engine + 5.8.2 + + + + diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/junit-bom-5.8.2.pom.sha1 b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/junit-bom-5.8.2.pom.sha1 new file mode 100644 index 00000000..3e210302 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/junit/junit-bom/5.8.2/junit-bom-5.8.2.pom.sha1 @@ -0,0 +1 @@ +90eab8a5a400f15b8e1cb6e65af0ceb616f23bba \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/_remote.repositories b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/_remote.repositories new file mode 100644 index 00000000..6fa130ef --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/_remote.repositories @@ -0,0 +1,4 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Oct 31 17:32:59 BDT 2022 +mariadb-java-client-3.0.7.jar>central= +mariadb-java-client-3.0.7.pom>central= diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.jar b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.jar new file mode 100644 index 00000000..58d75f34 Binary files /dev/null and b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.jar differ diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.jar.sha1 b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.jar.sha1 new file mode 100644 index 00000000..93f5f241 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.jar.sha1 @@ -0,0 +1 @@ +708456abf19170c3c80896826993eaa5f663ffa4 \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.pom b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.pom new file mode 100644 index 00000000..3a6be68d --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.pom @@ -0,0 +1,458 @@ + + + 4.0.0 + org.mariadb.jdbc + mariadb-java-client + jar + mariadb-java-client + 3.0.7 + JDBC driver for MariaDB and MySQL + https://mariadb.com/kb/en/mariadb/about-mariadb-connector-j/ + + + UTF-8 + 1.34 + 5.8.2 + 1.5.0 + 3.22.0 + 6.0.0 + 5.0.0 + UTF-8 + 1.3.0-alpha12 + 0.8.7 + 3.1.1 + 8.0.29 + 6.2.0 + + + + + LGPL-2.1 + + + + + mariadb.com + https://mariadb.com + + + + + mariadbJdbcDevelopers + mariadb jdbc developers + http://mariadb.org/ + + + + + scm:git:git://github.com/mariadb-corporation/mariadb-connector-j.git + https://github.com/mariadb-corporation/mariadb-connector-j + scm:git:git@github.com:MariaDB/mariadb-connector-j.git + + + + + JIRA + https://mariadb.atlassian.net/browse/CONJ + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + + + + + + org.junit + junit-bom + ${junit.version} + pom + import + + + + software.amazon.awssdk + bom + 2.17.24 + pom + import + + + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + true + + -Xlint:all,-options,-path,-processing + + 1.8 + 1.8 + + + + compile-java-8 + + compile + + + + compile-java-9 + compile + + compile + + + 9 + 9 + 9 + + ${project.basedir}/src/main/java9 + + true + + + + compile-java-11 + compile + + compile + + + 11 + 11 + 11 + + ${project.basedir}/src/main/java11 + + true + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + 1.8 + + org.mariadb.jdbc.client,org.mariadb.jdbc.codec,org.mariadb.jdbc.message,org.mariadb.jdbc.util,org.mariadb.jdbc.plugin.authentication.standard.ed25519,org.mariadb.jdbc.plugin.authentication.standard.ed25519.* + + + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + org.mariadb.jdbc + + javax.naming,javax.naming.ldap,javax.management,javax.sql,javax.net;resolution:=optional,javax.net.ssl;resolution:=optional,javax.transaction.xa;resolution:=optional,waffle.windows.auth;resolution:=optional,waffle.windows.auth.impl;resolution:=optional,org.ietf.jgss;resolution:=optional,javax.security.auth.login;resolution:=optional,javax.security.auth.x500;resolution:=optional,software.amazon.awssdk.*;resolution:=optional,org.slf4j;resolution:=optional + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + + jar + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + + deploy + + + + ossrh + https://oss.sonatype.org/ + false + 15 + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + verify + + sign + + + + + + + com.coveo + fmt-maven-plugin + 2.9.1 + + + + format + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + + biz.aQute.bnd + bnd-maven-plugin + ${bnd-maven-plugin.version} + + bnd.bnd + + + + + bnd-process + + + + + + + + + + + + com.github.waffle + waffle-jna + ${waffle-jna.version} + true + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + test + + + + org.slf4j + slf4j-api + 2.0.0-alpha5 + true + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + org.osgi + org.osgi.core + ${osgi.version} + provided + + + + org.osgi + org.osgi.compendium + ${osgi.compendium.version} + provided + + + + software.amazon.awssdk + rds + true + + + + + + + + default + + true + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + jacoco-initialize + + prepare-agent + + + + report + test + + report + + + + + + + + + + + bench + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + mysql + mysql-connector-java + ${mysql-connector-java.version} + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-source + generate-sources + + add-source + + + + src/benchmark/java + + + + + add-resource + generate-resources + + add-resource + + + + + src/benchmark/resources + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + benchmarks + + + org.openjdk.jmh.Main + + + + + + package + + shade + + + + + + + + + + diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.pom.sha1 b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.pom.sha1 new file mode 100644 index 00000000..2a985165 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/org/mariadb/jdbc/mariadb-java-client/3.0.7/mariadb-java-client-3.0.7.pom.sha1 @@ -0,0 +1 @@ +81f83a79a9d7626e800ee1d7f949f44934b7315c \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/_remote.repositories b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/_remote.repositories new file mode 100644 index 00000000..29095cd2 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/_remote.repositories @@ -0,0 +1,3 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Oct 31 17:32:58 BDT 2022 +aws-sdk-java-pom-2.17.24.pom>central= diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/aws-sdk-java-pom-2.17.24.pom b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/aws-sdk-java-pom-2.17.24.pom new file mode 100644 index 00000000..8c271b2b --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/aws-sdk-java-pom-2.17.24.pom @@ -0,0 +1,1200 @@ + + + + + 4.0.0 + software.amazon.awssdk + aws-sdk-java-pom + 2.17.24 + pom + AWS Java SDK :: Parent + The Amazon Web Services SDK for Java provides Java APIs + for building software on AWS' cost-effective, scalable, and reliable + infrastructure products. The AWS Java SDK allows developers to code + against APIs for all of Amazon's infrastructure web services (Amazon + S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon + AutoScaling, etc). + + https://aws.amazon.com/sdkforjava + + + Apache License, Version 2.0 + https://aws.amazon.com/apache2.0 + repo + + + + + amazonwebservices + Amazon Web Services + https://aws.amazon.com + + developer + + + + + + aws-sdk-java + core + services + services-custom + bom + bom-internal + codegen + http-client-spi + http-clients + codegen-maven-plugin + bundle + build-tools + metric-publishers + release-scripts + utils + codegen-lite + codegen-lite-maven-plugin + archetypes + third-party + test/http-client-tests + test/protocol-tests + test/protocol-tests-core + test/service-test-utils + test/test-utils + test/codegen-generated-classes-test + test/sdk-benchmarks + test/module-path-tests + test/tests-coverage-reporting + test/stability-tests + test/sdk-native-image-test + test/s3-benchmarks + test/auth-sts-testing + + + https://github.com/aws/aws-sdk-java-v2.git + + + ${project.version} + 2.17.23 + 2.12.3 + 2.12.3 + 2.12.3 + 1.0.1 + 3.12.0 + 2.18.0 + 1.7.30 + 1.2.17 + 2.5 + 3.5 + + + 4.1.66.Final + 3.4.6 + 1.3 + UTF-8 + 4.1.4 + 2.0.5 + 1.13.0 + 3.10.0 + 3.5.101 + 2.2.21 + 1.10 + 1.29 + 0.13.14 + + + 4.13.1 + 5.7.1 + 1.3 + 1.10.19 + 2.28.2 + 3.8.0 + 29.0-jre + 1.1 + 7.1.0 + 2.6 + 2.0.40.Final + 1.16.0 + 1.0.392 + + + 2.22.2 + 3.8.1 + 3.1.2 + 2.22.2 + 3.1.1 + 3.0.1 + yyyy + 3.1.1 + 1.6 + 8.42 + 0.8.5 + 1.6.8 + 1.6.0 + 2.8.2 + 3.0.0 + 0.14.4 + + + 2.4.0 + + + 3.0.7.RELEASE + 2.3.9 + 1.8.2 + + 1.8 + 4.5.13 + 4.4.11 + + + 1.0.3 + + ${skipTests} + + + + + + + + + + + + aws-sdk-java-${project.artifactId}-${awsjavasdk.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${jre.version} + ${jre.version} + UTF-8 + true + -proc:none + false + -proc:none + + software/amazon/awssdk/services/kinesis/model/SubscribeToShardEvent.java + software/amazon/awssdk/services/kinesis/KinesisAsyncClient.java + software/amazon/awssdk/services/kinesis/DefaultKinesisAsyncClient.java + software/amazon/awssdk/services/kinesis/DefaultKinesisBaseClientBuilder.java + software/amazon/awssdk/services/sms/model/InternalError.java + software/amazon/awssdk/services/kinesis/transform/SubscribeToShardResponseUnmarshaller.java + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.version} + + ${argLine} + + **/*StabilityTest.java + **/*StabilityTests.java + **/*CucumberTest.java + **/*IntegrationTest.java + **/*IntegrationTests.java + **/*IntegTest.java + **/*IntegrationTestCase.java + + + **/Test*.java + **/*Tests.java + **/*Test.java + **/*TestCase.java + + ${skip.unit.tests} + + + + + org.apache.maven.surefire + surefire-junit47 + ${maven.surefire.version} + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + add-integ-sources + generate-sources + + add-test-source + + + + ${basedir}/src/it/java + + + + + add-integ-resources + generate-sources + + add-test-resource + + + + + ${basedir}/src/it/resources + + + + + + add-generated-sources + generate-sources + + add-source + + + + ${basedir}/target/generated-sources/sdk + + + + + add-license-notice + generate-sources + + add-resource + + + + + ${maven.multiModuleProjectDirectory} + + LICENSE.txt + NOTICE.txt + + META-INF + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + software.amazon.awssdk + build-tools + 1.0 + + + + + findbugs + process-classes + + check + + + + + true + false + true + software/amazon/awssdk/spotbugs-suppressions.xml + Low + Max + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + software.amazon.awssdk + build-tools + 1.0 + + + + + checkstyle + validate + + check + + + + + software/amazon/awssdk/checkstyle.xml + software/amazon/awssdk/checkstyle-suppressions.xml + true + true + true + true + **/module-info.java + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + + analyze-only + + + + + true + true + + + junit:*:* + software.amazon.awssdk:test-utils + org.hamcrest:* + org.testng:testng + org.mockito:* + org.junit.platform:* + org.opentest4j:* + + + org.reactivestreams:reactive-streams + com.fasterxml.jackson.core:* + org.slf4j:slf4j-api + + + + com.typesafe.netty:* + software.amazon.awssdk:aws-sdk-java + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + default-prepare-agent + + prepare-agent + + + + jacoco-site + package + + report + + + + + pre-integration-test + integration-test + + prepare-agent + + + + post-integration-test + post-integration-test + + report + + + + + + software/amazon/awssdk/modulepath/tests/**/*.class + software/amazon/awssdk/nativeimagetest/**/*.class + software/amazon/awssdk/testutils/service/**/*.class + **/*Benchmark.class + + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-maven-plugin.version} + + + + ${project.groupId} + ${project.artifactId} + ${awsjavasdk.previous.version} + jar + + + + + ${project.build.directory}/aws-sdk-java-${project.artifactId}-${project.version}.${project.packaging} + + + + true + + *.internal.* + + + + codegen-lite-maven-plugin + codegen-maven-plugin + codegen + codegen-lite + .*tests* + .*test* + protocol-tests-core + tests-coverage-reporting + aws-sdk-java + archetype-lambda + archetype-app-quickstart + archetype-tools + sdk-benchmarks + bundle + s3-benchmarks + + aws-crt-client + s3-transfer-manager + + true + true + true + + + METHOD_NEW_DEFAULT + true + true + + + METHOD_ADDED_TO_INTERFACE + true + true + + + + + + + verify + + cmp + + + + + + + + + + + sonar-generate + + true + true + true + true + + + + + publishing + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + sonatype-nexus-staging + https://aws.oss.sonatype.org + + + + + + + + disable-java8-doclint + + [1.8,) + + + -Xdoclint:none + + + + + quick + + true + true + true + true + true + + + + + integration-tests + + + doRelease + + + + true + true + true + true + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + integration-test + + integration-test + verify + + + + ${argLine} + + **/*IntegrationTest.java + **/*IntegrationTests.java + **/*IntegTest.java + **/RunCucumberTest.java + + + **/SimpleMethodsIntegrationTest.java + + false + 2 + + + + + + + + + + stability-tests + + + doRelease + + + + true + true + true + true + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + integration-test + + integration-test + verify + + + -Dio.netty.leakDetection.level=advanced + + **/*StabilityTest.java + **/*StabilityTests.java + + false + 2 + + + + + + + + + + simple-method-integration-tests + + + doRelease + + + + true + true + true + true + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + integration-test + + integration-test + verify + + + + **/SimpleMethodsIntegrationTest.java + + false + 2 + + + + + + + + + + public-javadoc + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + 128m + 8g + false + public + false + true + false + true + true + + --allow-script-in-comments + + AWS SDK for Java - ${awsjavasdk.version} + UTF-8 + UTF-8 + AWS SDK for Java API Reference - ${awsjavasdk.version} + AWS SDK for Java + :*.codegen:software.amazon.awssdk.services.protocol* + + + Greengrass + software.amazon.awssdk.services.greengrass* + + + Athena + software.amazon.awssdk.services.athena* + + + Marketplace Entitlement + software.amazon.awssdk.services.marketplaceentitlement* + + + CodeStar + software.amazon.awssdk.services.codestar* + + + Lex Model Building + software.amazon.awssdk.services.lexmodelbuilding* + + + Resource Groups Tagging API + software.amazon.awssdk.services.resourcegroupstaggingapi* + + + S3 Control + software.amazon.awssdk.services.s3control* + + + S3 + software.amazon.awssdk.services.s3* + + + Glacier + software.amazon.awssdk.services.glacier* + + + DynamoDB + software.amazon.awssdk.services.dynamo* + + + EC2 + software.amazon.awssdk.services.ec2* + + + SQS + software.amazon.awssdk.services.sqs* + + + SNS + software.amazon.awssdk.services.sns* + + + Relational Database Service + software.amazon.awssdk.services.rds* + + + Route 53 + software.amazon.awssdk.services.route53* + + + Simple Workflow Service + + software.amazon.awssdk.services.swf*:software.amazon.awssdk.services.simpleworkflow* + + + Elastic MapReduce + software.amazon.awssdk.services.emr* + + + Simple Email Service + + software.amazon.awssdk.services.ses*:software.amazon.awssdk.services.simpleemail* + + + Elastic Load Balancing + software.amazon.awssdk.services.elasticloadbalancing* + + + CloudSearch + software.amazon.awssdk.services.cloudsearch* + + + CloudWatch + software.amazon.awssdk.services.cloudwatch* + + + CloudWatch Logs + software.amazon.awssdk.services.logs* + + + CloudWatch Events + software.amazon.awssdk.services.cloudwatchevents* + + + CloudFront + software.amazon.awssdk.services.cloudfront* + + + CloudDirectory + software.amazon.awssdk.services.clouddirectory* + + + Cognito + software.amazon.awssdk.services.cognito* + + + AutoScaling + software.amazon.awssdk.services.autoscaling* + + + Kinesis + software.amazon.awssdk.services.kinesis*:software.amazon.awssdk.services.firehose* + + + Redshift + software.amazon.awssdk.services.redshift* + + + ElastiCache + software.amazon.awssdk.services.elasticache* + + + Elastic Transcoder + software.amazon.awssdk.services.elastictranscoder* + + + OpsWorks + software.amazon.awssdk.services.opsworks* + + + CloudFormation + software.amazon.awssdk.services.cloudformation* + + + Data Pipeline + software.amazon.awssdk.services.datapipeline* + + + Direct Connect + software.amazon.awssdk.services.directconnect* + + + Elastic Beanstalk + software.amazon.awssdk.services.elasticbeanstalk* + + + Identity and Access Management + software.amazon.awssdk.services.iam* + + + Security Token Service + software.amazon.awssdk.services.sts* + + + Storage Gateway Service + software.amazon.awssdk.services.storagegateway* + + + Support + software.amazon.awssdk.services.support* + + + Transcribe Streaming + software.amazon.awssdk.services.transcribestreaming* + + + CloudTrail + software.amazon.awssdk.services.cloudtrail* + + + Config + software.amazon.awssdk.services.config* + + + Certificate Manager + software.amazon.awssdk.services.acm* + + + Key Management + software.amazon.awssdk.services.kms* + + + Lambda + software.amazon.awssdk.services.lambda* + + + EC2 Container Service + software.amazon.awssdk.services.ecs* + + + CloudHSM + software.amazon.awssdk.services.cloudhsm* + + + Simple Systems Management Service + software.amazon.awssdk.services.ssm* + + + WorkSpaces + software.amazon.awssdk.services.workspaces* + + + Machine Learning + software.amazon.awssdk.services.machinelearning* + + + Directory Service + software.amazon.awssdk.services.directory* + + + Elastic File System + software.amazon.awssdk.services.efs* + + + CodePipeline + software.amazon.awssdk.services.codepipeline* + + + CodeCommit + software.amazon.awssdk.services.codecommit* + + + Device Farm + software.amazon.awssdk.services.devicefarm* + + + Elasticsearch Service + software.amazon.awssdk.services.elasticsearch* + + + Marketplace Commerce Analytics + software.amazon.awssdk.services.marketplacecommerceanalytics* + + + WAF + software.amazon.awssdk.services.waf* + + + Inspector Service + software.amazon.awssdk.services.inspector* + + + IoT + software.amazon.awssdk.services.iot* + + + API Gateway + software.amazon.awssdk.services.apigateway* + + + EC2 Container Registry + software.amazon.awssdk.services.ecr* + + + GameLift + software.amazon.awssdk.services.gamelift* + + + Database Migration Service + software.amazon.awssdk.services.databasemigration* + + + Marketplace Metering Service + software.amazon.awssdk.services.marketplacemetering* + + + Cognito Identity Provider + software.amazon.awssdk.services.cognitoidp* + + + Application Discovery Service + software.amazon.awssdk.services.applicationdiscovery* + + + Application Auto Scaling + software.amazon.awssdk.services.applicationautoscaling* + + + Snowball + software.amazon.awssdk.services.snowball* + + + Service Catalog + software.amazon.awssdk.services.servicecatalog* + + + Budgets + software.amazon.awssdk.services.budgets* + + + Server Migration + software.amazon.awssdk.services.sms* + + + Rekognition + software.amazon.awssdk.services.rekognition* + + + Polly + software.amazon.awssdk.services.polly* + + + Lightsail + software.amazon.awssdk.services.lightsail* + + + AppStream + software.amazon.awssdk.services.appstream* + + + X-Ray + software.amazon.awssdk.services.xray* + + + OpsWorks for Chef Automate + software.amazon.awssdk.services.opsworkscm* + + + Pinpoint + software.amazon.awssdk.services.pinpoint* + + + Step Functions + + software.amazon.awssdk.services.sfn*:software.amazon.awssdk.services.stepfunctions* + + + Shield + software.amazon.awssdk.services.shield* + + + Health APIs and Notifications + software.amazon.awssdk.services.health* + + + Cost and Usage Report + software.amazon.awssdk.services.costandusagereport* + + + Code Build + software.amazon.awssdk.services.codebuild* + + + Batch + software.amazon.awssdk.services.batch* + + + Lex Runtime + software.amazon.awssdk.services.lexruntime* + + + Lex Model Building + software.amazon.awssdk.services.lexmodelbuilding* + + + Mechanical Turk Requester + software.amazon.awssdk.services.mechanicalturkrequester* + + + Organizations + software.amazon.awssdk.services.organizations* + + + WorkDocs + software.amazon.awssdk.services.workdocs* + + + CodeDeploy + software.amazon.awssdk.services.codedeploy* + + + Common + software.amazon.awssdk* + + +
AWS SDK for Java API Reference - ${project.version}]]>
+ + + + ]]> + +
+
+
+
+
+
+
diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/aws-sdk-java-pom-2.17.24.pom.sha1 b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/aws-sdk-java-pom-2.17.24.pom.sha1 new file mode 100644 index 00000000..48f1db16 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/aws-sdk-java-pom/2.17.24/aws-sdk-java-pom-2.17.24.pom.sha1 @@ -0,0 +1 @@ +1888d356c8d5904ea30964d1b2e12b472847ad68 \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/_remote.repositories b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/_remote.repositories new file mode 100644 index 00000000..000b0763 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/_remote.repositories @@ -0,0 +1,3 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Oct 31 17:32:58 BDT 2022 +bom-2.17.24.pom>central= diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/bom-2.17.24.pom b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/bom-2.17.24.pom new file mode 100644 index 00000000..a9330e00 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/bom-2.17.24.pom @@ -0,0 +1,1543 @@ + + + 4.0.0 + + software.amazon.awssdk + aws-sdk-java-pom + 2.17.24 + ../pom.xml + + bom + pom + AWS Java SDK :: Bill of Materials + The AWS SDK for Java - BOM module holds the dependency managements for individual java clients. + https://aws.amazon.com/sdkforjava + + true + + + + + + software.amazon.awssdk + codegen-lite-maven-plugin + ${awsjavasdk.version} + + + software.amazon.awssdk + codegen-lite + ${awsjavasdk.version} + + + software.amazon.awssdk + codegen-maven-plugin + ${awsjavasdk.version} + + + software.amazon.awssdk + codegen + ${awsjavasdk.version} + + + + software.amazon.awssdk + annotations + ${awsjavasdk.version} + + + software.amazon.awssdk + arns + ${awsjavasdk.version} + + + software.amazon.awssdk + json-utils + ${awsjavasdk.version} + + + software.amazon.awssdk + third-party-jackson-core + ${awsjavasdk.version} + + + software.amazon.awssdk + third-party-jackson-dataformat-cbor + ${awsjavasdk.version} + + + software.amazon.awssdk + auth + ${awsjavasdk.version} + + + software.amazon.awssdk + aws-core + ${awsjavasdk.version} + + + software.amazon.awssdk + profiles + ${awsjavasdk.version} + + + software.amazon.awssdk + aws-cbor-protocol + ${awsjavasdk.version} + + + software.amazon.awssdk + aws-json-protocol + ${awsjavasdk.version} + + + software.amazon.awssdk + aws-query-protocol + ${awsjavasdk.version} + + + software.amazon.awssdk + aws-xml-protocol + ${awsjavasdk.version} + + + software.amazon.awssdk + protocol-core + ${awsjavasdk.version} + + + software.amazon.awssdk + regions + ${awsjavasdk.version} + + + software.amazon.awssdk + sdk-core + ${awsjavasdk.version} + + + software.amazon.awssdk + http-client-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + apache-client + ${awsjavasdk.version} + + + software.amazon.awssdk + netty-nio-client + ${awsjavasdk.version} + + + software.amazon.awssdk + url-connection-client + ${awsjavasdk.version} + + + software.amazon.awssdk + utils + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudwatch-metric-publisher + ${awsjavasdk.version} + + + + software.amazon.awssdk + acm + ${awsjavasdk.version} + + + software.amazon.awssdk + acmpca + ${awsjavasdk.version} + + + software.amazon.awssdk + alexaforbusiness + ${awsjavasdk.version} + + + software.amazon.awssdk + amplify + ${awsjavasdk.version} + + + software.amazon.awssdk + apigateway + ${awsjavasdk.version} + + + software.amazon.awssdk + apigatewaymanagementapi + ${awsjavasdk.version} + + + software.amazon.awssdk + apigatewayv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + applicationautoscaling + ${awsjavasdk.version} + + + software.amazon.awssdk + applicationdiscovery + ${awsjavasdk.version} + + + software.amazon.awssdk + appmesh + ${awsjavasdk.version} + + + software.amazon.awssdk + appstream + ${awsjavasdk.version} + + + software.amazon.awssdk + appsync + ${awsjavasdk.version} + + + software.amazon.awssdk + athena + ${awsjavasdk.version} + + + software.amazon.awssdk + autoscaling + ${awsjavasdk.version} + + + software.amazon.awssdk + autoscalingplans + ${awsjavasdk.version} + + + software.amazon.awssdk + backup + ${awsjavasdk.version} + + + software.amazon.awssdk + batch + ${awsjavasdk.version} + + + software.amazon.awssdk + budgets + ${awsjavasdk.version} + + + software.amazon.awssdk + chime + ${awsjavasdk.version} + + + software.amazon.awssdk + cloud9 + ${awsjavasdk.version} + + + software.amazon.awssdk + clouddirectory + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudformation + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudfront + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudhsm + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudhsmv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudsearch + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudsearchdomain + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudtrail + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudwatch + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudwatchevents + ${awsjavasdk.version} + + + software.amazon.awssdk + cloudwatchlogs + ${awsjavasdk.version} + + + software.amazon.awssdk + codebuild + ${awsjavasdk.version} + + + software.amazon.awssdk + codecommit + ${awsjavasdk.version} + + + software.amazon.awssdk + codedeploy + ${awsjavasdk.version} + + + software.amazon.awssdk + codepipeline + ${awsjavasdk.version} + + + software.amazon.awssdk + codestar + ${awsjavasdk.version} + + + software.amazon.awssdk + cognitoidentity + ${awsjavasdk.version} + + + software.amazon.awssdk + cognitoidentityprovider + ${awsjavasdk.version} + + + software.amazon.awssdk + cognitosync + ${awsjavasdk.version} + + + software.amazon.awssdk + comprehend + ${awsjavasdk.version} + + + software.amazon.awssdk + comprehendmedical + ${awsjavasdk.version} + + + software.amazon.awssdk + config + ${awsjavasdk.version} + + + software.amazon.awssdk + connect + ${awsjavasdk.version} + + + software.amazon.awssdk + costandusagereport + ${awsjavasdk.version} + + + software.amazon.awssdk + costexplorer + ${awsjavasdk.version} + + + software.amazon.awssdk + databasemigration + ${awsjavasdk.version} + + + software.amazon.awssdk + datapipeline + ${awsjavasdk.version} + + + software.amazon.awssdk + datasync + ${awsjavasdk.version} + + + software.amazon.awssdk + dax + ${awsjavasdk.version} + + + software.amazon.awssdk + devicefarm + ${awsjavasdk.version} + + + software.amazon.awssdk + directconnect + ${awsjavasdk.version} + + + software.amazon.awssdk + directory + ${awsjavasdk.version} + + + software.amazon.awssdk + dlm + ${awsjavasdk.version} + + + software.amazon.awssdk + docdb + ${awsjavasdk.version} + + + software.amazon.awssdk + dynamodb + ${awsjavasdk.version} + + + software.amazon.awssdk + dynamodb-enhanced + ${awsjavasdk.version} + + + software.amazon.awssdk + ec2 + ${awsjavasdk.version} + + + software.amazon.awssdk + ecr + ${awsjavasdk.version} + + + software.amazon.awssdk + ecs + ${awsjavasdk.version} + + + software.amazon.awssdk + efs + ${awsjavasdk.version} + + + software.amazon.awssdk + eks + ${awsjavasdk.version} + + + software.amazon.awssdk + elasticache + ${awsjavasdk.version} + + + software.amazon.awssdk + elasticbeanstalk + ${awsjavasdk.version} + + + software.amazon.awssdk + elasticloadbalancing + ${awsjavasdk.version} + + + software.amazon.awssdk + elasticloadbalancingv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + elasticsearch + ${awsjavasdk.version} + + + software.amazon.awssdk + elastictranscoder + ${awsjavasdk.version} + + + software.amazon.awssdk + emr + ${awsjavasdk.version} + + + software.amazon.awssdk + firehose + ${awsjavasdk.version} + + + software.amazon.awssdk + fms + ${awsjavasdk.version} + + + software.amazon.awssdk + fsx + ${awsjavasdk.version} + + + software.amazon.awssdk + gamelift + ${awsjavasdk.version} + + + software.amazon.awssdk + glacier + ${awsjavasdk.version} + + + software.amazon.awssdk + globalaccelerator + ${awsjavasdk.version} + + + software.amazon.awssdk + glue + ${awsjavasdk.version} + + + software.amazon.awssdk + greengrass + ${awsjavasdk.version} + + + software.amazon.awssdk + guardduty + ${awsjavasdk.version} + + + software.amazon.awssdk + health + ${awsjavasdk.version} + + + software.amazon.awssdk + iam + ${awsjavasdk.version} + + + software.amazon.awssdk + inspector + ${awsjavasdk.version} + + + software.amazon.awssdk + iot + ${awsjavasdk.version} + + + software.amazon.awssdk + iot1clickdevices + ${awsjavasdk.version} + + + software.amazon.awssdk + iot1clickprojects + ${awsjavasdk.version} + + + software.amazon.awssdk + iotanalytics + ${awsjavasdk.version} + + + software.amazon.awssdk + iotdataplane + ${awsjavasdk.version} + + + software.amazon.awssdk + iotjobsdataplane + ${awsjavasdk.version} + + + software.amazon.awssdk + kafka + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesis + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesisanalytics + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesisanalyticsv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesisvideo + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesisvideoarchivedmedia + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesisvideomedia + ${awsjavasdk.version} + + + software.amazon.awssdk + kms + ${awsjavasdk.version} + + + software.amazon.awssdk + lambda + ${awsjavasdk.version} + + + software.amazon.awssdk + lexmodelbuilding + ${awsjavasdk.version} + + + software.amazon.awssdk + lexruntime + ${awsjavasdk.version} + + + software.amazon.awssdk + licensemanager + ${awsjavasdk.version} + + + software.amazon.awssdk + lightsail + ${awsjavasdk.version} + + + software.amazon.awssdk + machinelearning + ${awsjavasdk.version} + + + software.amazon.awssdk + macie + ${awsjavasdk.version} + + + software.amazon.awssdk + managedblockchain + ${awsjavasdk.version} + + + software.amazon.awssdk + marketplacecommerceanalytics + ${awsjavasdk.version} + + + software.amazon.awssdk + marketplaceentitlement + ${awsjavasdk.version} + + + software.amazon.awssdk + marketplacemetering + ${awsjavasdk.version} + + + software.amazon.awssdk + mediaconnect + ${awsjavasdk.version} + + + software.amazon.awssdk + mediaconvert + ${awsjavasdk.version} + + + software.amazon.awssdk + medialive + ${awsjavasdk.version} + + + software.amazon.awssdk + mediapackage + ${awsjavasdk.version} + + + software.amazon.awssdk + mediapackagevod + ${awsjavasdk.version} + + + software.amazon.awssdk + mediastore + ${awsjavasdk.version} + + + software.amazon.awssdk + mediastoredata + ${awsjavasdk.version} + + + software.amazon.awssdk + mediatailor + ${awsjavasdk.version} + + + software.amazon.awssdk + migrationhub + ${awsjavasdk.version} + + + software.amazon.awssdk + mobile + ${awsjavasdk.version} + + + software.amazon.awssdk + mq + ${awsjavasdk.version} + + + software.amazon.awssdk + mturk + ${awsjavasdk.version} + + + software.amazon.awssdk + neptune + ${awsjavasdk.version} + + + software.amazon.awssdk + opsworks + ${awsjavasdk.version} + + + software.amazon.awssdk + opsworkscm + ${awsjavasdk.version} + + + software.amazon.awssdk + organizations + ${awsjavasdk.version} + + + software.amazon.awssdk + pi + ${awsjavasdk.version} + + + software.amazon.awssdk + pinpoint + ${awsjavasdk.version} + + + software.amazon.awssdk + pinpointemail + ${awsjavasdk.version} + + + software.amazon.awssdk + pinpointsmsvoice + ${awsjavasdk.version} + + + software.amazon.awssdk + polly + ${awsjavasdk.version} + + + software.amazon.awssdk + pricing + ${awsjavasdk.version} + + + software.amazon.awssdk + quicksight + ${awsjavasdk.version} + + + software.amazon.awssdk + ram + ${awsjavasdk.version} + + + software.amazon.awssdk + rds + ${awsjavasdk.version} + + + software.amazon.awssdk + rdsdata + ${awsjavasdk.version} + + + software.amazon.awssdk + redshift + ${awsjavasdk.version} + + + software.amazon.awssdk + rekognition + ${awsjavasdk.version} + + + software.amazon.awssdk + resourcegroups + ${awsjavasdk.version} + + + software.amazon.awssdk + resourcegroupstaggingapi + ${awsjavasdk.version} + + + software.amazon.awssdk + robomaker + ${awsjavasdk.version} + + + software.amazon.awssdk + route53 + ${awsjavasdk.version} + + + software.amazon.awssdk + route53domains + ${awsjavasdk.version} + + + software.amazon.awssdk + route53resolver + ${awsjavasdk.version} + + + software.amazon.awssdk + s3 + ${awsjavasdk.version} + + + software.amazon.awssdk + s3control + ${awsjavasdk.version} + + + software.amazon.awssdk + sagemaker + ${awsjavasdk.version} + + + software.amazon.awssdk + sagemakerruntime + ${awsjavasdk.version} + + + software.amazon.awssdk + secretsmanager + ${awsjavasdk.version} + + + software.amazon.awssdk + securityhub + ${awsjavasdk.version} + + + software.amazon.awssdk + serverlessapplicationrepository + ${awsjavasdk.version} + + + software.amazon.awssdk + servicecatalog + ${awsjavasdk.version} + + + software.amazon.awssdk + servicediscovery + ${awsjavasdk.version} + + + software.amazon.awssdk + ses + ${awsjavasdk.version} + + + software.amazon.awssdk + sfn + ${awsjavasdk.version} + + + software.amazon.awssdk + shield + ${awsjavasdk.version} + + + software.amazon.awssdk + signer + ${awsjavasdk.version} + + + software.amazon.awssdk + sms + ${awsjavasdk.version} + + + software.amazon.awssdk + snowball + ${awsjavasdk.version} + + + software.amazon.awssdk + sns + ${awsjavasdk.version} + + + software.amazon.awssdk + sqs + ${awsjavasdk.version} + + + software.amazon.awssdk + ssm + ${awsjavasdk.version} + + + software.amazon.awssdk + storagegateway + ${awsjavasdk.version} + + + software.amazon.awssdk + sts + ${awsjavasdk.version} + + + software.amazon.awssdk + support + ${awsjavasdk.version} + + + software.amazon.awssdk + swf + ${awsjavasdk.version} + + + software.amazon.awssdk + textract + ${awsjavasdk.version} + + + software.amazon.awssdk + transcribe + ${awsjavasdk.version} + + + software.amazon.awssdk + transcribestreaming + ${awsjavasdk.version} + + + software.amazon.awssdk + transfer + ${awsjavasdk.version} + + + software.amazon.awssdk + translate + ${awsjavasdk.version} + + + software.amazon.awssdk + waf + ${awsjavasdk.version} + + + software.amazon.awssdk + workdocs + ${awsjavasdk.version} + + + software.amazon.awssdk + worklink + ${awsjavasdk.version} + + + software.amazon.awssdk + workmail + ${awsjavasdk.version} + + + software.amazon.awssdk + workspaces + ${awsjavasdk.version} + + + software.amazon.awssdk + xray + ${awsjavasdk.version} + + + software.amazon.awssdk + groundstation + ${awsjavasdk.version} + + + software.amazon.awssdk + iotthingsgraph + ${awsjavasdk.version} + + + software.amazon.awssdk + iotevents + ${awsjavasdk.version} + + + software.amazon.awssdk + ioteventsdata + ${awsjavasdk.version} + + + software.amazon.awssdk + personalizeruntime + ${awsjavasdk.version} + + + software.amazon.awssdk + personalize + ${awsjavasdk.version} + + + software.amazon.awssdk + personalizeevents + ${awsjavasdk.version} + + + software.amazon.awssdk + servicequotas + ${awsjavasdk.version} + + + software.amazon.awssdk + applicationinsights + ${awsjavasdk.version} + + + software.amazon.awssdk + ec2instanceconnect + ${awsjavasdk.version} + + + software.amazon.awssdk + eventbridge + ${awsjavasdk.version} + + + software.amazon.awssdk + lakeformation + ${awsjavasdk.version} + + + software.amazon.awssdk + forecast + ${awsjavasdk.version} + + + software.amazon.awssdk + forecastquery + ${awsjavasdk.version} + + + software.amazon.awssdk + qldb + ${awsjavasdk.version} + + + software.amazon.awssdk + qldbsession + ${awsjavasdk.version} + + + software.amazon.awssdk + workmailmessageflow + ${awsjavasdk.version} + + + software.amazon.awssdk + codestarnotifications + ${awsjavasdk.version} + + + software.amazon.awssdk + savingsplans + ${awsjavasdk.version} + + + software.amazon.awssdk + sso + ${awsjavasdk.version} + + + software.amazon.awssdk + ssooidc + ${awsjavasdk.version} + + + software.amazon.awssdk + marketplacecatalog + ${awsjavasdk.version} + + + software.amazon.awssdk + sesv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + dataexchange + ${awsjavasdk.version} + + + software.amazon.awssdk + migrationhubconfig + ${awsjavasdk.version} + + + software.amazon.awssdk + connectparticipant + ${awsjavasdk.version} + + + software.amazon.awssdk + wafv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + appconfig + ${awsjavasdk.version} + + + software.amazon.awssdk + iotsecuretunneling + ${awsjavasdk.version} + + + software.amazon.awssdk + elasticinference + ${awsjavasdk.version} + + + software.amazon.awssdk + imagebuilder + ${awsjavasdk.version} + + + software.amazon.awssdk + schemas + ${awsjavasdk.version} + + + software.amazon.awssdk + accessanalyzer + ${awsjavasdk.version} + + + software.amazon.awssdk + computeoptimizer + ${awsjavasdk.version} + + + software.amazon.awssdk + networkmanager + ${awsjavasdk.version} + + + software.amazon.awssdk + kendra + ${awsjavasdk.version} + + + software.amazon.awssdk + frauddetector + ${awsjavasdk.version} + + + software.amazon.awssdk + codegurureviewer + ${awsjavasdk.version} + + + software.amazon.awssdk + codeguruprofiler + ${awsjavasdk.version} + + + software.amazon.awssdk + outposts + ${awsjavasdk.version} + + + software.amazon.awssdk + sagemakera2iruntime + ${awsjavasdk.version} + + + software.amazon.awssdk + ebs + ${awsjavasdk.version} + + + software.amazon.awssdk + kinesisvideosignaling + ${awsjavasdk.version} + + + software.amazon.awssdk + detective + ${awsjavasdk.version} + + + software.amazon.awssdk + codestarconnections + ${awsjavasdk.version} + + + software.amazon.awssdk + synthetics + ${awsjavasdk.version} + + + software.amazon.awssdk + iotsitewise + ${awsjavasdk.version} + + + software.amazon.awssdk + macie2 + ${awsjavasdk.version} + + + software.amazon.awssdk + codeartifact + ${awsjavasdk.version} + + + software.amazon.awssdk + honeycode + ${awsjavasdk.version} + + + software.amazon.awssdk + ivs + ${awsjavasdk.version} + + + software.amazon.awssdk + braket + ${awsjavasdk.version} + + + software.amazon.awssdk + identitystore + ${awsjavasdk.version} + + + software.amazon.awssdk + appflow + ${awsjavasdk.version} + + + software.amazon.awssdk + redshiftdata + ${awsjavasdk.version} + + + software.amazon.awssdk + ssoadmin + ${awsjavasdk.version} + + + software.amazon.awssdk + timestreamwrite + ${awsjavasdk.version} + + + software.amazon.awssdk + timestreamquery + ${awsjavasdk.version} + + + software.amazon.awssdk + s3outposts + ${awsjavasdk.version} + + + software.amazon.awssdk + metrics-spi + ${awsjavasdk.version} + + + software.amazon.awssdk + databrew + ${awsjavasdk.version} + + + software.amazon.awssdk + servicecatalogappregistry + ${awsjavasdk.version} + + + software.amazon.awssdk + networkfirewall + ${awsjavasdk.version} + + + software.amazon.awssdk + mwaa + ${awsjavasdk.version} + + + software.amazon.awssdk + devopsguru + ${awsjavasdk.version} + + + software.amazon.awssdk + sagemakerfeaturestoreruntime + ${awsjavasdk.version} + + + software.amazon.awssdk + appintegrations + ${awsjavasdk.version} + + + software.amazon.awssdk + ecrpublic + ${awsjavasdk.version} + + + software.amazon.awssdk + amplifybackend + ${awsjavasdk.version} + + + software.amazon.awssdk + connectcontactlens + ${awsjavasdk.version} + + + software.amazon.awssdk + lookoutvision + ${awsjavasdk.version} + + + software.amazon.awssdk + customerprofiles + ${awsjavasdk.version} + + + software.amazon.awssdk + emrcontainers + ${awsjavasdk.version} + + + software.amazon.awssdk + sagemakeredge + ${awsjavasdk.version} + + + software.amazon.awssdk + healthlake + ${awsjavasdk.version} + + + software.amazon.awssdk + auditmanager + ${awsjavasdk.version} + + + software.amazon.awssdk + amp + ${awsjavasdk.version} + + + software.amazon.awssdk + greengrassv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + iotwireless + ${awsjavasdk.version} + + + software.amazon.awssdk + iotfleethub + ${awsjavasdk.version} + + + software.amazon.awssdk + iotdeviceadvisor + ${awsjavasdk.version} + + + software.amazon.awssdk + location + ${awsjavasdk.version} + + + software.amazon.awssdk + wellarchitected + ${awsjavasdk.version} + + + software.amazon.awssdk + lexruntimev2 + ${awsjavasdk.version} + + + software.amazon.awssdk + lexmodelsv2 + ${awsjavasdk.version} + + + software.amazon.awssdk + fis + ${awsjavasdk.version} + + + software.amazon.awssdk + lookoutmetrics + ${awsjavasdk.version} + + + software.amazon.awssdk + mgn + ${awsjavasdk.version} + + + software.amazon.awssdk + lookoutequipment + ${awsjavasdk.version} + + + software.amazon.awssdk + nimble + ${awsjavasdk.version} + + + software.amazon.awssdk + finspacedata + ${awsjavasdk.version} + + + software.amazon.awssdk + finspace + ${awsjavasdk.version} + + + software.amazon.awssdk + ssmincidents + ${awsjavasdk.version} + + + software.amazon.awssdk + ssmcontacts + ${awsjavasdk.version} + + + software.amazon.awssdk + applicationcostprofiler + ${awsjavasdk.version} + + + software.amazon.awssdk + apprunner + ${awsjavasdk.version} + + + software.amazon.awssdk + proton + ${awsjavasdk.version} + + + software.amazon.awssdk + route53recoveryreadiness + ${awsjavasdk.version} + + + software.amazon.awssdk + route53recoverycontrolconfig + ${awsjavasdk.version} + + + software.amazon.awssdk + route53recoverycluster + ${awsjavasdk.version} + + + software.amazon.awssdk + chimesdkmessaging + ${awsjavasdk.version} + + + software.amazon.awssdk + chimesdkidentity + ${awsjavasdk.version} + + + software.amazon.awssdk + snowdevicemanagement + ${awsjavasdk.version} + + + software.amazon.awssdk + memorydb + ${awsjavasdk.version} + + + + diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/bom-2.17.24.pom.sha1 b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/bom-2.17.24.pom.sha1 new file mode 100644 index 00000000..9f4405d1 --- /dev/null +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/MariaDB Connector J/3.0.7/software/amazon/awssdk/bom/2.17.24/bom-2.17.24.pom.sha1 @@ -0,0 +1 @@ +27d428dcc8d658caa04d531b938cbfb5758d463c \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/jdbc-drivers.xml b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/jdbc-drivers.xml index b9f79f8f..6250f17a 100644 --- a/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/jdbc-drivers.xml +++ b/provision-contest/ansible/roles/phpstorm/files/jdbc-drivers/jdbc-drivers.xml @@ -25,6 +25,10 @@ md5=""/> + + + + @@ -101,7 +105,8 @@ - + + @@ -133,6 +138,10 @@ + + + + @@ -187,6 +196,30 @@ + + + + + + + + + + + + + + + + + + + + + @@ -236,7 +269,8 @@ - + + @@ -344,7 +378,21 @@ - + + + + + + + + + + + + + + + @@ -387,6 +435,30 @@ md5=""/> + + + + + + + + + + + + @@ -414,6 +486,11 @@ md5=""/> + + + + + @@ -511,6 +588,43 @@ url="https://cache-redirector.jetbrains.com/download.jetbrains.com/idea/jdbc-drivers/MSSQL/9/sqlserver9-win-auth.jar" md5=""/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -623,6 +737,14 @@ url="https://cache-redirector.jetbrains.com/download.jetbrains.com/idea/jdbc-drivers/Redshift/license2.txt" md5=""/> + + + + @@ -644,9 +766,17 @@ md5=""/> + + + + + + + + + + + + + + + + + + + @@ -772,124 +920,67 @@ + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - - + - - + - - - + - - - + - - - + - @@ -910,6 +1001,14 @@ url="https://cache-redirector.jetbrains.com/download.jetbrains.com/idea/jdbc-drivers/couchbase/license.txt" md5=""/> + + + + @@ -922,6 +1021,21 @@ + + + + + + + + + + + + + + + @@ -929,6 +1043,10 @@ + + + + @@ -974,6 +1092,10 @@ + + + + @@ -1037,6 +1159,12 @@ + + + + + + @@ -1078,5 +1206,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/provision-contest/ansible/roles/phpstorm/meta/main.yml b/provision-contest/ansible/roles/phpstorm/meta/main.yml index 4a4f743a..a480e52b 100644 --- a/provision-contest/ansible/roles/phpstorm/meta/main.yml +++ b/provision-contest/ansible/roles/phpstorm/meta/main.yml @@ -2,3 +2,4 @@ --- dependencies: - role: domjudge_user + tags: domjudge_user diff --git a/provision-contest/ansible/roles/phpstorm/tasks/main.yml b/provision-contest/ansible/roles/phpstorm/tasks/main.yml index fb0b1f4e..9310d34b 100644 --- a/provision-contest/ansible/roles/phpstorm/tasks/main.yml +++ b/provision-contest/ansible/roles/phpstorm/tasks/main.yml @@ -22,7 +22,7 @@ owner: false use_ssh_args: true -- name: fix ownership of PHPStorm config +- name: Fix ownership of PHPStorm config file: path: "/home/domjudge/.config/JetBrains/PhpStorm{{ PHPSTORM_VERSION }}" recurse: true @@ -45,7 +45,7 @@ recursive: true use_ssh_args: true -- name: fix ownership of PHPStorm local share +- name: Fix ownership of PHPStorm local share file: path: "/home/domjudge/.local/share/JetBrains/PhpStorm{{ PHPSTORM_VERSION }}" recurse: true @@ -59,7 +59,7 @@ owner: false use_ssh_args: true -- name: fix ownership of PHPStorm config +- name: Fix ownership of PHPStorm config file: path: "{{ DJ_DIR }}/.idea" recurse: true diff --git a/provision-contest/ansible/roles/phpstorm/templates/jetbrains-phpstorm.desktop.j2 b/provision-contest/ansible/roles/phpstorm/templates/jetbrains-phpstorm.desktop.j2 index 0c21c06e..a51cea95 100644 --- a/provision-contest/ansible/roles/phpstorm/templates/jetbrains-phpstorm.desktop.j2 +++ b/provision-contest/ansible/roles/phpstorm/templates/jetbrains-phpstorm.desktop.j2 @@ -2,8 +2,8 @@ Version=1.0 Type=Application Name=PHPStorm -Icon=/phpstorm/PhpStorm-{{ PHPSTORM_FULL_VERSION }}/bin/phpstorm.png -Exec="/phpstorm/PhpStorm-{{ PHPSTORM_FULL_VERSION }}/bin/phpstorm.sh" %f +Icon=/opt/phpstorm/PhpStorm-{{ PHPSTORM_FULL_VERSION }}/bin/phpstorm.png +Exec="/opt/phpstorm/PhpStorm-{{ PHPSTORM_FULL_VERSION }}/bin/phpstorm.sh" %f Comment=PHPStorm (UDTC) Categories=Development;IDE;PHP Terminal=false diff --git a/provision-contest/ansible/roles/presadmin/files/presadmin.desktop b/provision-contest/ansible/roles/presadmin/files/presadmin.desktop new file mode 100644 index 00000000..79451040 --- /dev/null +++ b/provision-contest/ansible/roles/presadmin/files/presadmin.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Name=presadmin +Type=Application +Exec=/usr/local/bin/presadmin diff --git a/provision-contest/ansible/roles/presadmin/tasks/main.yml b/provision-contest/ansible/roles/presadmin/tasks/main.yml new file mode 100644 index 00000000..37f22bc9 --- /dev/null +++ b/provision-contest/ansible/roles/presadmin/tasks/main.yml @@ -0,0 +1,56 @@ +--- +# These tasks configure the presentation admin + +- name: Get the presentation admin release + uri: + url: https://api.github.com/repos/icpctools/icpctools/releases?per_page=1 + method: GET + return_content: true + status_code: 200 + body_format: json + register: latest_pres_admin_release_array + check_mode: no + +- name: Set pres admin latest version + set_fact: + pres_admin_version: "{{ latest_pres_admin_release_array.json[0].name | replace('v', '') }}" + +- name: Set pres admin minor version + set_fact: + pres_admin_version_minor: "{{ pres_admin_version | regex_replace('\\.\\d+$', '') }}" + +- name: Create presentation admin directory + file: + path: /home/domjudge/presadmin + state: directory + owner: domjudge + group: domjudge + mode: 0755 + +- name: Download and unpack presentation admin + unarchive: + src: https://github.com/icpctools/icpctools/releases/download/v{{ pres_admin_version }}/presentationAdmin-{{ pres_admin_version }}.zip + dest: /home/domjudge/presadmin + remote_src: true + owner: domjudge + group: domjudge + +- name: Find the presentation admin user + set_fact: + presentation_admin_account: "{{ CDS_ACCOUNTS | selectattr('username', '==', 'presAdmin') }}" + +- name: Add presentation admin script + template: + src: presadmin.j2 + dest: /usr/local/bin/presadmin + owner: domjudge + group: domjudge + mode: 0755 + +- name: Add presentation admin autostart + copy: + src: presadmin.desktop + dest: /home/domjudge/.config/autostart/presadmin.desktop + owner: domjudge + group: domjudge + mode: 0755 diff --git a/provision-contest/ansible/roles/presadmin/templates/presadmin.j2 b/provision-contest/ansible/roles/presadmin/templates/presadmin.j2 new file mode 100644 index 00000000..2f5dcd40 --- /dev/null +++ b/provision-contest/ansible/roles/presadmin/templates/presadmin.j2 @@ -0,0 +1 @@ +/home/domjudge/presadmin/presAdmin.sh https://{{ CDS_HOSTNAME }} {{ presentation_admin_account[0].username }} {{ presentation_admin_account[0].password }} diff --git a/provision-contest/ansible/roles/presclient/files/presclient.desktop b/provision-contest/ansible/roles/presclient/files/presclient.desktop new file mode 100644 index 00000000..19e75d04 --- /dev/null +++ b/provision-contest/ansible/roles/presclient/files/presclient.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Name=presclient +Type=Application +Exec=/usr/local/bin/presclient diff --git a/provision-contest/ansible/roles/presclient/tasks/main.yml b/provision-contest/ansible/roles/presclient/tasks/main.yml new file mode 100644 index 00000000..1575a3d3 --- /dev/null +++ b/provision-contest/ansible/roles/presclient/tasks/main.yml @@ -0,0 +1,56 @@ +--- +# These tasks configure the presentation clients + +- name: Get the presentation client release + uri: + url: https://api.github.com/repos/icpctools/icpctools/releases?per_page=1 + method: GET + return_content: true + status_code: 200 + body_format: json + register: latest_pres_client_release_array + check_mode: no + +- name: Set pres client latest version + set_fact: + pres_client_version: "{{ latest_pres_client_release_array.json[0].name | replace('v', '') }}" + +- name: Set pres client minor version + set_fact: + pres_client_version_minor: "{{ pres_client_version | regex_replace('\\.\\d+$', '') }}" + +- name: Create presentation client directory + file: + path: /home/domjudge/presclient + state: directory + owner: domjudge + group: domjudge + mode: 0755 + +- name: Download and unpack presentation client + unarchive: + src: https://github.com/icpctools/icpctools/releases/download/v{{ pres_client_version }}/presentations-{{ pres_client_version }}.zip + dest: /home/domjudge/presclient + remote_src: true + owner: domjudge + group: domjudge + +- name: Find the presentation user + set_fact: + presentation_account: "{{ CDS_ACCOUNTS | selectattr('username', '==', 'presentation') }}" + +- name: Add presentation client script + template: + src: presclient.j2 + dest: /usr/local/bin/presclient + owner: domjudge + group: domjudge + mode: 0755 + +- name: Add presentation client autostart + copy: + src: presclient.desktop + dest: /home/domjudge/.config/autostart/presclient.desktop + owner: domjudge + group: domjudge + mode: 0755 diff --git a/provision-contest/ansible/roles/presclient/templates/presclient.j2 b/provision-contest/ansible/roles/presclient/templates/presclient.j2 new file mode 100644 index 00000000..23bec2db --- /dev/null +++ b/provision-contest/ansible/roles/presclient/templates/presclient.j2 @@ -0,0 +1 @@ +/home/domjudge/presclient/client.sh https://{{ CDS_HOSTNAME }}/api/contests/{{ PRESCLIENT_CONTEST }} {{ presentation_account[0].username }} {{ presentation_account[0].password }} --name {{ ansible_hostname }} diff --git a/provision-contest/ansible/roles/prometheus_target_all/defaults/main.yml b/provision-contest/ansible/roles/prometheus_target_all/defaults/main.yml index 0686e6c9..b009bed1 100644 --- a/provision-contest/ansible/roles/prometheus_target_all/defaults/main.yml +++ b/provision-contest/ansible/roles/prometheus_target_all/defaults/main.yml @@ -1 +1,4 @@ prom: true +GROUP_PREFIXES: + - 'online-' + - '' diff --git a/provision-contest/ansible/roles/prometheus_target_all/files/.gitignore b/provision-contest/ansible/roles/prometheus_target_all/files/.gitignore new file mode 100644 index 00000000..40a4a51a --- /dev/null +++ b/provision-contest/ansible/roles/prometheus_target_all/files/.gitignore @@ -0,0 +1,3 @@ +*.key +*.crt +promtail-linux-amd64.zip diff --git a/provision-contest/ansible/roles/prometheus_target_all/files/71-push.conf b/provision-contest/ansible/roles/prometheus_target_all/files/71-push.conf new file mode 100644 index 00000000..ec38fdb5 --- /dev/null +++ b/provision-contest/ansible/roles/prometheus_target_all/files/71-push.conf @@ -0,0 +1 @@ +*.* @127.0.0.1:1514 diff --git a/provision-contest/ansible/roles/prometheus_target_all/files/promtail.service b/provision-contest/ansible/roles/prometheus_target_all/files/promtail.service index 10606214..10a56c25 100644 --- a/provision-contest/ansible/roles/prometheus_target_all/files/promtail.service +++ b/provision-contest/ansible/roles/prometheus_target_all/files/promtail.service @@ -4,7 +4,7 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/promtail-linux-amd64 --config.file /etc/promtail/promtail-local-config.yaml +ExecStart=/usr/bin/promtail-linux-amd64 --config.file /etc/promtail/promtail-local-config.yml [Install] WantedBy=multi-user.target diff --git a/provision-contest/ansible/roles/prometheus_target_all/handlers/main.yml b/provision-contest/ansible/roles/prometheus_target_all/handlers/main.yml index 4d60f810..fa9d6a7d 100644 --- a/provision-contest/ansible/roles/prometheus_target_all/handlers/main.yml +++ b/provision-contest/ansible/roles/prometheus_target_all/handlers/main.yml @@ -1,7 +1,19 @@ --- -- name: restart promtail +- name: Restart rsyslog + service: + name: rsyslog + enabled: true + state: restarted + +- name: Restart promtail service: name: promtail enabled: true state: restarted daemon_reload: true + +- name: Restart node-exporter + service: + name: prometheus-node-exporter + enabled: true + state: restarted diff --git a/provision-contest/ansible/roles/prometheus_target_all/tasks/main.yml b/provision-contest/ansible/roles/prometheus_target_all/tasks/main.yml index 6716f037..07661ce4 100644 --- a/provision-contest/ansible/roles/prometheus_target_all/tasks/main.yml +++ b/provision-contest/ansible/roles/prometheus_target_all/tasks/main.yml @@ -1,54 +1,120 @@ --- # This tasks gathers default system metrics. -- name: install required packages +- name: Install required packages apt: state: present pkg: - prometheus-node-exporter -# Setup promtail which sends our logs -- name: Install promtail - unarchive: - src: https://github.com/grafana/loki/releases/download/v2.5.0/promtail-linux-amd64.zip - dest: /usr/bin/ - remote_src: true - owner: domjudge - group: domjudge - when: prom - -- name: Dir for promtail settings +- name: Collect prometheus settings file: - state: directory - path: /etc/promtail + path: /etc/prometheus owner: root group: root mode: 0755 - when: prom + state: directory -- name: Set promtail settings +- name: Install SSL server certificates copy: - src: promtail-local-config.yaml - dest: /etc/promtail/ + src: "node_exporter.{{ item }}" + dest: "/etc/prometheus/node_exporter.{{ item }}" owner: root group: root mode: 0644 - when: prom - notify: restart promtail + loop: + - crt + - key -- name: Setup promtail systemd +- name: Get HTPassword + delegate_to: localhost + become: false + shell: "echo {{ PROMETHEUS_PASS }} | htpasswd -inBC 10 \"\" | tr -d ':\n'" + register: htpassd_shell + +- name: Store HTPassword for nginx wrapper copy: - src: promtail.service - dest: /etc/systemd/system/ - mode: 0655 + content: "prometheus:{{ htpassd_shell.stdout }}" + dest: /etc/prometheus/.htpasswd owner: root group: root - when: prom - notify: restart promtail + mode: 0644 + +- name: Set certificate to encrypt node_exporter traffic + template: + owner: prometheus + group: prometheus + mode: 0644 + src: web.yml.j2 + dest: /etc/prometheus/prometheus-authentication.yml + +- name: Scrape with TLS encryption + lineinfile: + dest: /etc/default/prometheus-node-exporter + state: present + regexp: '^ARGS=""' + line: 'ARGS="--web.config /etc/prometheus/prometheus-authentication.yml"' + notify: Restart node-exporter -- name: Start promtail service - service: - name: promtail - state: started - enabled: true +# Setup promtail which sends our logs +- name: Setup promtail to ship logs to loki (and grafana) when: prom + block: + - name: Install promtail + unarchive: + src: promtail-linux-amd64.zip + dest: /usr/bin/ + remote_src: false + owner: domjudge + group: domjudge + when: ICPC_IMAGE + + - name: Install promtail + unarchive: + src: https://github.com/grafana/loki/releases/download/v3.0.1/promtail-linux-amd64.zip + dest: /usr/bin/ + remote_src: true + owner: domjudge + group: domjudge + when: not ICPC_IMAGE + + - name: Dir for promtail settings + file: + state: directory + path: /etc/promtail + owner: root + group: root + mode: 0755 + + - name: Set promtail settings + template: + src: promtail-local-config.yml.j2 + dest: /etc/promtail/promtail-local-config.yml + owner: root + group: root + mode: 0644 + notify: Restart promtail + + - name: Setup promtail push + copy: + src: 71-push.conf + dest: /etc/rsyslog.d/ + mode: 0655 + owner: root + group: root + notify: Restart rsyslog + + - name: Setup promtail systemd + copy: + src: promtail.service + dest: /etc/systemd/system/ + mode: 0655 + owner: root + group: root + notify: Restart promtail + + - name: Start promtail service + service: + name: promtail + state: started + enabled: true diff --git a/provision-contest/ansible/roles/prometheus_target_all/templates/promtail-local-config.yaml.j2 b/provision-contest/ansible/roles/prometheus_target_all/templates/promtail-local-config.yaml.j2 deleted file mode 100644 index af053e76..00000000 --- a/provision-contest/ansible/roles/prometheus_target_all/templates/promtail-local-config.yaml.j2 +++ /dev/null @@ -1,20 +0,0 @@ -server: - http_listen_port: 9080 - #grpc_listen_port: 0 - -positions: - filename: /var/log/positions.yaml - -clients: -{% for host in groups["grafana"] %} - - url: https://{{ hostvars[host].ansible_host }} -{% endfor %} - -scrape_configs: -- job_name: system - static_configs: - - targets: - - localhost - labels: - job: varlogs - __path__: /var/log/*log diff --git a/provision-contest/ansible/roles/prometheus_target_all/templates/promtail-local-config.yml.j2 b/provision-contest/ansible/roles/prometheus_target_all/templates/promtail-local-config.yml.j2 new file mode 100644 index 00000000..6e6c6d35 --- /dev/null +++ b/provision-contest/ansible/roles/prometheus_target_all/templates/promtail-local-config.yml.j2 @@ -0,0 +1,37 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 19080 + +positions: + filename: /var/tmp/promtail-syslog-positions.yml + +clients: +{% for host in groups["grafana"] %} + - url: http://{{ hostvars[host].ansible_host }}:3100/loki/api/v1/push +{% endfor %} + +scrape_configs: + - job_name: system + static_configs: + - labels: + __path__: /var/log/**/*log +{% for group_prefix in GROUP_PREFIXES %} +{% if ansible_fqdn in groups[group_prefix+'judgehost'] %} + - job_name: judgehostlogs + static_configs: + - labels: + __path__: '/opt/domjudge/output/log/*' +{% endif %} +{% if ansible_fqdn in groups[group_prefix+'domserver'] %} + - job_name: webapplogs + static_configs: + - labels: + __path__: '/opt/domjudge/webapp/var/log/*' +{% endif %} +{% endfor %} + # See: https://alexandre.deverteuil.net/post/syslog-relay-for-loki/ + - job_name: syslog + syslog: + listen_address: 0.0.0.0:1514 + labels: + job: syslog diff --git a/provision-contest/ansible/roles/prometheus_target_all/templates/web.yml.j2 b/provision-contest/ansible/roles/prometheus_target_all/templates/web.yml.j2 new file mode 100644 index 00000000..4d1a35ac --- /dev/null +++ b/provision-contest/ansible/roles/prometheus_target_all/templates/web.yml.j2 @@ -0,0 +1,5 @@ +basic_auth_users: + prometheus: {{ htpassd_shell.stdout }} +tls_server_config: + cert_file: /etc/prometheus/node_exporter.crt + key_file: /etc/prometheus/node_exporter.key diff --git a/provision-contest/ansible/roles/prometheus_target_web/files/.gitignore b/provision-contest/ansible/roles/prometheus_target_web/files/.gitignore index 403c59b9..599e1479 100644 --- a/provision-contest/ansible/roles/prometheus_target_web/files/.gitignore +++ b/provision-contest/ansible/roles/prometheus_target_web/files/.gitignore @@ -1 +1,2 @@ /php-fpm_exporter*.tar.gz +/php-fpm.tar.gz diff --git a/provision-contest/ansible/roles/prometheus_target_web/files/nginx-status.conf b/provision-contest/ansible/roles/prometheus_target_web/files/nginx-status.conf deleted file mode 100644 index 5617c246..00000000 --- a/provision-contest/ansible/roles/prometheus_target_web/files/nginx-status.conf +++ /dev/null @@ -1,9 +0,0 @@ -server { - listen 8787; - listen [::]:8787; - server_name _default_; - - location = /basic_status { - stub_status; - } -} diff --git a/provision-contest/ansible/roles/prometheus_target_web/files/php-fpm-exporter.service b/provision-contest/ansible/roles/prometheus_target_web/files/php-fpm-exporter.service index 94e5185e..9d6ca255 100644 --- a/provision-contest/ansible/roles/prometheus_target_web/files/php-fpm-exporter.service +++ b/provision-contest/ansible/roles/prometheus_target_web/files/php-fpm-exporter.service @@ -4,7 +4,7 @@ Documentation=https://github.com/hipages/php-fpm_exporter [Service] User=www-data Restart=always -ExecStart=/usr/bin/php-fpm_exporter server --phpfpm.fix-process-count --phpfpm.scrape-uri unix:///var/run/php-fpm-domjudge.sock;/fpm_status +ExecStart=/usr/bin/php-fpm_exporter server --web.listen-address :19253 --phpfpm.fix-process-count --phpfpm.scrape-uri unix:///var/run/php-fpm-domjudge.sock;/fpm_status ExecReload=/bin/kill -HUP $MAINPID TimeoutStopSec=20s SendSIGKILL=no diff --git a/provision-contest/ansible/roles/prometheus_target_web/handlers/main.yml b/provision-contest/ansible/roles/prometheus_target_web/handlers/main.yml index b5739a5f..b3fbd2e0 100644 --- a/provision-contest/ansible/roles/prometheus_target_web/handlers/main.yml +++ b/provision-contest/ansible/roles/prometheus_target_web/handlers/main.yml @@ -1,12 +1,19 @@ --- -- name: restart php-exporter - service: name=php-fpm-exporter enabled=true state=restarted +- name: Restart php-exporter + service: + name: php-fpm-exporter + enabled: true + state: restarted + daemon_reload: true -- name: restart nginx - service: name=nginx enabled=true state=restarted +- name: Restart nginx-exporter + service: + name: prometheus-nginx-exporter + enabled: true + state: restarted -- name: restart nginx-exporter - service: name=prometheus-nginx-exporter enabled=true state=restarted - -- name: restart mysqld-exporter - service: name=prometheus-mysqld-exporter enabled=true state=restarted +- name: Restart mysqld-exporter + service: + name: prometheus-mysqld-exporter + enabled: true + state: restarted diff --git a/provision-contest/ansible/roles/prometheus_target_web/meta/main.yml b/provision-contest/ansible/roles/prometheus_target_web/meta/main.yml new file mode 100644 index 00000000..24a09a19 --- /dev/null +++ b/provision-contest/ansible/roles/prometheus_target_web/meta/main.yml @@ -0,0 +1,5 @@ +# Role dependencies +--- +dependencies: + - role: prometheus_target_all + tags: prometheus_target_all diff --git a/provision-contest/ansible/roles/prometheus_target_web/tasks/main.yml b/provision-contest/ansible/roles/prometheus_target_web/tasks/main.yml index 77660578..609c7db8 100644 --- a/provision-contest/ansible/roles/prometheus_target_web/tasks/main.yml +++ b/provision-contest/ansible/roles/prometheus_target_web/tasks/main.yml @@ -5,8 +5,8 @@ apt: state: present pkg: - - prometheus-mysqld-exporter - notify: restart nginx-exporter + - prometheus-nginx-exporter + notify: Restart nginx-exporter - name: Expose MariaDB metrics when: MARIADB @@ -14,13 +14,21 @@ state: present pkg: - prometheus-mysqld-exporter - notify: restart mysqld-exporter + notify: Restart mysqld-exporter + +- name: Scrape mysql exporter with TLS encryption + lineinfile: + dest: /etc/default/prometheus-mysqld-exporter + state: present + regexp: '^ARGS=""' + line: 'ARGS="--web.config.file /etc/prometheus/prometheus-authentication.yml"' + notify: Restart mysqld-exporter # Gather PHP-FPM statistics # The exporter from this is currently not in deb sources # so we need to download this from GitHub see the README in files - name: Install PHP-fpm exporter binary - when: FPM + when: FPM and not WF_RESTRICTED_NETWORK ansible.builtin.unarchive: src: https://github.com/hipages/php-fpm_exporter/releases/download/v2.0.4/php-fpm_exporter_2.0.4_linux_amd64.tar.gz dest: /usr/bin/ @@ -28,30 +36,52 @@ exclude: - LICENSE - README.md - notify: restart php-exporter + notify: Restart php-exporter + +- name: Install PHP-fpm exporter binary (local cache) + when: FPM and WF_RESTRICTED_NETWORK + block: + - name: Copy local php-fpm deb to monitoring host + copy: + src: php-fpm.tar.gz + dest: /srv/php-fpm.tar.gz + + - name: Install local archive on remote + ansible.builtin.unarchive: + src: /srv/php-fpm.tar.gz + dest: /usr/bin/ + remote_src: true + exclude: + - LICENSE + - README.md + notify: Restart php-exporter - name: Export PHP-FPM metrics when: FPM synchronize: src: php-fpm-exporter.service dest: /etc/systemd/system/php-fpm-exporter.service - notify: restart php-exporter + notify: Restart php-exporter # Gather NGINX statistics, # Observe that we use the observed process itself in the monitoring - name: Get NGINX status - synchronize: - src: nginx-status.conf + template: + src: nginx-status.conf.j2 dest: /etc/nginx/sites-enabled/nginx-status.conf - notify: restart nginx + mode: 0644 + notify: Restart nginx +# In the future add: --web.config /etc/prometheus/prometheus-authentication.yml"' +# see: https://github.com/nginxinc/nginx-prometheus-exporter +# The version at the WFLuxor in the repository is not new enough - name: Prometheus nginx exporter lineinfile: dest: /etc/default/prometheus-nginx-exporter state: present regexp: '^ARGS=""' - line: 'ARGS="-nginx.scrape-uri=http://localhost:8787/basic_status"' - notify: restart nginx-exporter + line: 'ARGS="-web.listen-address=127.0.0.1:19113 -nginx.scrape-uri=http://localhost:8787/basic_status"' + notify: Restart nginx-exporter - name: Create storage dir for exporter settings when: MARIADB @@ -71,4 +101,4 @@ dest: /var/lib/prometheus/.my.cnf owner: prometheus mode: 0644 - notify: restart mysqld-exporter + notify: Restart mysqld-exporter diff --git a/provision-contest/ansible/roles/prometheus_target_web/templates/nginx-status.conf.j2 b/provision-contest/ansible/roles/prometheus_target_web/templates/nginx-status.conf.j2 new file mode 100644 index 00000000..e5397fdb --- /dev/null +++ b/provision-contest/ansible/roles/prometheus_target_web/templates/nginx-status.conf.j2 @@ -0,0 +1,34 @@ +server { + listen 127.0.0.1:8787; + server_name _default_; + + location = /basic_status { + stub_status; + } +} + +server { + listen 0.0.0.0:9113 ssl; + ssl_certificate /etc/prometheus/node_exporter.crt; + ssl_certificate_key /etc/prometheus/node_exporter.key; + ssl_protocols TLSv1.3; + + auth_basic "Prometheus scraping"; + auth_basic_user_file /etc/prometheus/.htpasswd; + location / { + proxy_pass http://127.0.0.1:19113; + } +} + +server { + listen 0.0.0.0:9253 ssl; + ssl_certificate /etc/prometheus/node_exporter.crt; + ssl_certificate_key /etc/prometheus/node_exporter.key; + ssl_protocols TLSv1.3; + + auth_basic "Prometheus scraping"; + auth_basic_user_file /etc/prometheus/.htpasswd; + location / { + proxy_pass http://127.0.0.1:19253; + } +} diff --git a/provision-contest/ansible/roles/scoreboard/handlers/main.yml b/provision-contest/ansible/roles/scoreboard/handlers/main.yml deleted file mode 100644 index 1d442fdc..00000000 --- a/provision-contest/ansible/roles/scoreboard/handlers/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# Define here handlers associated to this role. - -- name: restart nginx - service: name=nginx enabled=true state=restarted diff --git a/provision-contest/ansible/roles/scoreboard/tasks/main.yml b/provision-contest/ansible/roles/scoreboard/tasks/main.yml index f5726d64..94c2dd60 100644 --- a/provision-contest/ansible/roles/scoreboard/tasks/main.yml +++ b/provision-contest/ansible/roles/scoreboard/tasks/main.yml @@ -1,7 +1,7 @@ --- # These tasks configure a static scoreboard -- name: create directories for styling, scripts, fonts and flags +- name: Create directories for styling, scripts, fonts and flags file: path: /home/domjudge/scoreboard/{{ item }} state: directory @@ -14,7 +14,15 @@ - webfonts - flags -- name: download required assets +- name: Grant www-data permisions to domjudge homedir + acl: + path: /home/domjudge + entity: www-data + etype: user + permissions: rwx + state: present + +- name: Download required assets get_url: url: "{{ DOMSERVER_URL }}/{{ item }}" dest: /home/domjudge/scoreboard/{{ item }} @@ -26,6 +34,7 @@ - css/bootstrap.min.css - css/fontawesome-all.min.css - js/jquery.min.js + - js/jquery.debounce.min.js - js/bootstrap.bundle.min.js - js/domjudge.js - style_domjudge.css @@ -38,7 +47,7 @@ - webfonts/fa-v4compatibility.ttf - webfonts/fa-v4compatibility.woff2 -- name: install contest images +- name: Install contest images synchronize: src: files/domjudge-public/ dest: "/home/domjudge/scoreboard/" @@ -47,7 +56,7 @@ become: true become_user: domjudge -- name: download and unpack flag icons package +- name: Download and unpack flag icons package unarchive: src: https://github.com/lipis/flag-icons/archive/refs/tags/3.5.0.zip dest: /tmp @@ -55,7 +64,7 @@ owner: domjudge group: domjudge -- name: copy flag icon SVG's +- name: Copy flag icon SVG's copy: src: /tmp/flag-icons-3.5.0/flags/ dest: /home/domjudge/scoreboard/flags/ @@ -64,7 +73,7 @@ group: domjudge mode: 0755 -- name: create bin directory +- name: Create bin directory file: path: /home/domjudge/bin state: directory @@ -72,7 +81,7 @@ group: domjudge mode: 0755 -- name: install update scoreboard script +- name: Install update scoreboard script template: src: update-scoreboard.sh.j2 dest: "/home/domjudge/bin/update-scoreboard.sh" @@ -81,34 +90,34 @@ mode: 0755 # Ansible cron task defaults to * for all cron parts -- name: set up cron to update scoreboard +- name: Set up cron to update scoreboard cron: name: update public scoreboard job: /home/domjudge/bin/update-scoreboard.sh user: domjudge -- name: install nginx +- name: Install nginx apt: state: present pkg: - nginx -- name: add scoreboard nginx conf +- name: Add scoreboard nginx conf template: src: scoreboard.conf.j2 dest: /etc/nginx/sites-available/scoreboard.conf mode: 0644 - notify: restart nginx + notify: Restart nginx -- name: enable nginx conf for static scoreboard +- name: Enable nginx conf for static scoreboard file: src: /etc/nginx/sites-available/scoreboard.conf dest: /etc/nginx/sites-enabled/scoreboard.conf state: link - notify: restart nginx + notify: Restart nginx -- name: disable default nginx site +- name: Disable default nginx site file: path: /etc/nginx/sites-enabled/default state: absent - notify: restart nginx + notify: Restart nginx diff --git a/provision-contest/ansible/roles/ssh/defaults/main.yml b/provision-contest/ansible/roles/ssh/defaults/main.yml new file mode 100644 index 00000000..c54730df --- /dev/null +++ b/provision-contest/ansible/roles/ssh/defaults/main.yml @@ -0,0 +1,11 @@ +github_ssh_user: + - meisterT + - nickygerritsen + - thijskh + - tuupke + - ubergeek42 + - vmcj + +ssh_public_keys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICd/P3Mu0X0hE9yc+k4cU/eT67S4F4E8Lmxk4yYDM8qL jaap@diamond" + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINrtJzsraIV4SFFkeu5HtxwRZKBOwn2dL/g1CJhaCwJs jaap@tiger" diff --git a/provision-contest/ansible/roles/ssh/files/.gitignore b/provision-contest/ansible/roles/ssh/files/.gitignore index ac18eab4..14aa70a0 100644 --- a/provision-contest/ansible/roles/ssh/files/.gitignore +++ b/provision-contest/ansible/roles/ssh/files/.gitignore @@ -1 +1 @@ -/id_rsa* +/id_ed25519* diff --git a/provision-contest/ansible/roles/ssh/files/config b/provision-contest/ansible/roles/ssh/files/config index 9ba9ad25..774a3847 100644 --- a/provision-contest/ansible/roles/ssh/files/config +++ b/provision-contest/ansible/roles/ssh/files/config @@ -1,7 +1,10 @@ +# This file is managed by ansible, so any changes you make here will +# get overwritten in the next ansible run. If you want to make local +# changes, do this in the following include snippet. +Include ~/.ssh/config-local + # Defaults used if no other host definitions apply. # Add more host specific settings above here. Host * - NoHostAuthenticationForLocalhost yes - ForwardAgent yes - ForwardX11 yes - ForwardX11Trusted yes + NoHostAuthenticationForLocalhost yes + ForwardAgent yes diff --git a/provision-contest/ansible/roles/ssh/meta/main.yml b/provision-contest/ansible/roles/ssh/meta/main.yml index 4a4f743a..a480e52b 100644 --- a/provision-contest/ansible/roles/ssh/meta/main.yml +++ b/provision-contest/ansible/roles/ssh/meta/main.yml @@ -2,3 +2,4 @@ --- dependencies: - role: domjudge_user + tags: domjudge_user diff --git a/provision-contest/ansible/roles/ssh/tasks/add-auth-keys.yml b/provision-contest/ansible/roles/ssh/tasks/add-auth-keys.yml new file mode 100644 index 00000000..ac30db53 --- /dev/null +++ b/provision-contest/ansible/roles/ssh/tasks/add-auth-keys.yml @@ -0,0 +1,13 @@ +- name: "Ensure authorized_keys for {{ user_item }} contains keys from GitHub user accounts." + authorized_key: + user: "{{ user_item }}" + key: "https://github.com/{{ item }}.keys" + comment: "{{ item }}" + loop: "{{ github_ssh_user }}" + when: not WF_RESTRICTED_NETWORK or WF_GREEN + +- name: "Ensure authorized_keys for {{ user_item }} contains listed public keys." + authorized_key: + user: "{{ user_item }}" + key: "{{ item }}" + loop: "{{ ssh_public_keys }}" diff --git a/provision-contest/ansible/roles/ssh/tasks/main.yml b/provision-contest/ansible/roles/ssh/tasks/main.yml index 45e6a52c..d76681fc 100644 --- a/provision-contest/ansible/roles/ssh/tasks/main.yml +++ b/provision-contest/ansible/roles/ssh/tasks/main.yml @@ -18,8 +18,8 @@ mode: 0600 loop: - config - - id_rsa - - id_rsa.pub + - id_ed25519 + - id_ed25519.pub - name: Copy shared SSH private/public key and config to root copy: @@ -28,14 +28,21 @@ mode: 0600 loop: - config - - id_rsa - - id_rsa.pub + - id_ed25519 + - id_ed25519.pub - name: Add SSH key to authorized keys of domjudge and root authorized_key: user: "{{ item }}" state: present - key: "{{ lookup('file', 'id_rsa.pub') }}" + key: "{{ lookup('file', 'id_ed25519.pub') }}" loop: - domjudge - root + +- include_tasks: add-auth-keys.yml + loop: + - domjudge + - root + loop_control: + loop_var: user_item diff --git a/provision-contest/ansible/roles/ssh/vars/.gitignore b/provision-contest/ansible/roles/ssh/vars/.gitignore new file mode 100644 index 00000000..1cda54be --- /dev/null +++ b/provision-contest/ansible/roles/ssh/vars/.gitignore @@ -0,0 +1 @@ +*.yml diff --git a/provision-contest/ansible/roles/ssl/handlers/main.yml b/provision-contest/ansible/roles/ssl/handlers/main.yml index cf4078cf..3a5c2355 100644 --- a/provision-contest/ansible/roles/ssl/handlers/main.yml +++ b/provision-contest/ansible/roles/ssl/handlers/main.yml @@ -1,5 +1,3 @@ --- -# Define here handlers associated to this role. - -- name: update-ca-certificates +- name: Update-ca-certificates command: update-ca-certificates diff --git a/provision-contest/ansible/roles/ssl/tasks/main.yml b/provision-contest/ansible/roles/ssl/tasks/main.yml index 691c5210..13d28702 100644 --- a/provision-contest/ansible/roles/ssl/tasks/main.yml +++ b/provision-contest/ansible/roles/ssl/tasks/main.yml @@ -1,7 +1,7 @@ --- # These tasks install SSL certificates/keys -- name: install SSL server certificates +- name: Install SSL server certificates copy: src: "{{ item }}" dest: /etc/ssl/certs/ @@ -10,9 +10,9 @@ mode: 0644 with_fileglob: - "*.crt" - notify: update-ca-certificates + notify: Update-ca-certificates -- name: create CA certificates shared directory +- name: Create CA certificates shared directory file: dest: /usr/local/share/ca-certificates state: directory @@ -20,7 +20,7 @@ group: root mode: 0755 -- name: install SSL server certificates into CA certificates +- name: Install SSL server certificates into CA certificates copy: src: "{{ item }}" dest: /usr/local/share/ca-certificates @@ -29,9 +29,9 @@ mode: 0644 with_fileglob: - "*.crt" - notify: update-ca-certificates + notify: Update-ca-certificates -- name: install SSL private key files +- name: Install SSL private key files copy: src: "{{ item }}" dest: /etc/ssl/private/ @@ -40,5 +40,5 @@ mode: 0600 with_fileglob: - "*.key" - notify: update-ca-certificates + notify: Update-ca-certificates when: INSTALL_SSL_PRIVATE_KEYS is defined and INSTALL_SSL_PRIVATE_KEYS diff --git a/provision-contest/ansible/roles/system_fixes/handlers/main.yml b/provision-contest/ansible/roles/system_fixes/handlers/main.yml index 572a8e29..a1b9179d 100644 --- a/provision-contest/ansible/roles/system_fixes/handlers/main.yml +++ b/provision-contest/ansible/roles/system_fixes/handlers/main.yml @@ -1,5 +1,6 @@ --- -# Define here handlers associated to this role. - -- name: update dconf +- name: Update dconf command: dconf update + +- name: Reboot the machine + reboot: diff --git a/provision-contest/ansible/roles/system_fixes/tasks/main.yml b/provision-contest/ansible/roles/system_fixes/tasks/main.yml index 115a45cf..358bf659 100644 --- a/provision-contest/ansible/roles/system_fixes/tasks/main.yml +++ b/provision-contest/ansible/roles/system_fixes/tasks/main.yml @@ -1,18 +1,26 @@ --- # These tasks perform miscellaneous fixes to the base system. -- name: set timezone +- name: Check installed PHP version + command: php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' + register: php_version + changed_when: false + check_mode: no + ignore_errors: "{{ ansible_check_mode }}" + +- name: Set timezone timezone: name: "{{ TIMEZONE }}" -- name: set PHP timezone for CLI +- name: Set PHP timezone for CLI lineinfile: - dest: /etc/php/7.4/cli/php.ini + dest: "/etc/php/{{ php_version.stdout }}/cli/php.ini" state: present regexp: 'date\.timezone\s*=' line: 'date.timezone = {{ TIMEZONE }}' + ignore_errors: "{{ ansible_check_mode }}" -- name: enable bash completion globally +- name: Enable bash completion globally blockinfile: path: /etc/bash.bashrc insertafter: "# enable bash completion in interactive shells" @@ -32,4 +40,36 @@ owner: root group: root mode: 0755 - notify: update dconf + notify: Update dconf + when: GRAPHICAL + +- name: Configure network for static IP address + when: STATIC_IP_ENABLED + block: + - name: Configure static IP address + template: + src: static_ip.j2 + dest: /etc/network/interfaces.d/{{ STATIC_IP_INTERFACE }} + notify: Reboot the machine + + - name: Load configuration from /etc/network/interfaces.d + lineinfile: + path: /etc/network/interfaces + line: 'source-directory /etc/network/interfaces.d' + notify: Reboot the machine + + - name: Use 8.8.8.8 as DNS server + lineinfile: + path: /etc/systemd/resolved.conf + regexp: '^#?DNS=' + line: 'DNS=8.8.8.8' + notify: Reboot the machine + +- name: Enable etckeeper autocommits every hour + file: + src: /etc/cron.daily/etckeeper + dest: /etc/cron.hourly/etckeeper + state: link + +- name: Flush handlers + meta: flush_handlers diff --git a/provision-contest/ansible/roles/system_fixes/templates/static_ip.j2 b/provision-contest/ansible/roles/system_fixes/templates/static_ip.j2 new file mode 100644 index 00000000..78993a29 --- /dev/null +++ b/provision-contest/ansible/roles/system_fixes/templates/static_ip.j2 @@ -0,0 +1,5 @@ +allow-hotplug {{ STATIC_IP_INTERFACE }} +iface {{ STATIC_IP_INTERFACE }} inet static + address {{ ansible_host }} + netmask {{ STATIC_IP_NETMASK }} + gateway {{ STATIC_IP_GATEWAY }} diff --git a/provision-contest/ansible/scoreboard.yml b/provision-contest/ansible/scoreboard.yml index 221ceaf7..3b0ce472 100644 --- a/provision-contest/ansible/scoreboard.yml +++ b/provision-contest/ansible/scoreboard.yml @@ -1,11 +1,13 @@ --- # This playbook installs the static scoreboard -- name: setup static scoreboard +- name: Setup static scoreboard hosts: scoreboard vars: host_type: scoreboard become: true + handlers: + - import_tasks: handlers.yml roles: - role: base_packages tags: base_packages diff --git a/provision-contest/create_samples_zip.py b/provision-contest/create_samples_zip.py new file mode 100755 index 00000000..45b69529 --- /dev/null +++ b/provision-contest/create_samples_zip.py @@ -0,0 +1,57 @@ +#!/usr/bin/python3 +import sys +import os +import yaml +import zipfile +import argparse + +DEFAULT_OUTPUT = 'samps.zip' + +def add_to_zip(zipf, source, dest, predicate=None): + for root, dirs, files in os.walk(source): + for fname in files: + path = os.path.join(root, fname) + if predicate is not None and not predicate(path): + continue + zipf.write(path, os.path.join(dest, os.path.relpath(path, source))) + +def public_sample_file(path): + testcase, ext = os.path.splitext(path) + + if ext not in {'.in', '.ans', '.interaction'}: + return False + + if ext != '.interaction' and os.path.exists(testcase + '.interaction'): + return False + + return True + +def main(): + parser = argparse.ArgumentParser(prog = 'create_samples_zip', description = 'generate the samples.zip file from a contest archive') + parser.add_argument('-o', '--output', default = DEFAULT_OUTPUT, help = 'the output path for the samples zip') + parser.add_argument('config_path', help = 'the path to the contest archive') + args = parser.parse_args() + + config_path = args.config_path + output_path = args.output + if os.path.isdir(output_path): + output_path = os.path.join(output_path, DEFAULT_OUTPUT) + + with open(os.path.join(config_path, 'problemset.yaml'), 'r') as f: + problemset = yaml.load(f, Loader=yaml.Loader) + + with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for problem in problemset['problems']: + + add_to_zip(zipf, + os.path.join(config_path, problem['short-name'], 'data', 'sample'), + problem['letter'], + public_sample_file) + + add_to_zip(zipf, + os.path.join(config_path, problem['short-name'], 'attachments'), + os.path.join(problem['letter'], 'attachments')) + +if __name__ == '__main__': + main() + diff --git a/provision-contest/disable-turboboost_ht b/provision-contest/disable-turboboost_ht index 272d0ee2..07ec3812 100755 --- a/provision-contest/disable-turboboost_ht +++ b/provision-contest/disable-turboboost_ht @@ -4,53 +4,109 @@ set -eu -o pipefail shopt -s extglob declare -A core_ids +declare -a disabled_cores -for cpu in /sys/devices/system/cpu/cpu* ; do - [[ $(basename $cpu) =~ ^cpu[0-9]+$ ]] || continue - - # Reenable stuff in case we are rerunning this script. - [ -f $cpu/online ] && echo 1 > $cpu/online - if [ -f $cpu/cpufreq/scaling_governor ]; then - chmod u+w $cpu/cpufreq/scaling_governor - fi - - # Set governor to performance and do not allow changes later on. - if [ -f $cpu/cpufreq/scaling_governor ]; then - echo performance > $cpu/cpufreq/scaling_governor - chmod a-w $cpu/cpufreq/scaling_governor - fi - - # Disable all but one thread on each core. - core_id=$(cat $cpu/topology/core_id) - if [[ ${core_ids[$core_id]:-} ]]; then - echo 0 > $cpu/online - else - core_ids[$core_id]=1 - fi +disable_cpu () { + cpu="$1" + echo 0 > $cpu/online + disabled_cores+=("${cpu##*/}") +} + +store_isolcpus_fact="/var/tmp/isolcpus" + +# Learn all efficency cores if they exist +cpu_list=() +if [ -f /sys/devices/cpu_atom/cpus ]; then + range="$(cat /sys/devices/cpu_atom/cpus)" + IFS='-' read -r start end <<< "$range" + # shellcheck disable=SC2207,SC2175 + cpu_list=($(eval echo {$start..$end})) +fi + +# shellcheck disable=SC2012 +for cpu in $(ls -1d /sys/devices/system/cpu/cpu* | sort --version-sort) ; do + [[ $(basename $cpu) =~ ^cpu[0-9]+$ ]] || continue + + # Reenable stuff in case we are rerunning this script. + [ -f $cpu/online ] && echo 1 > $cpu/online + if [ -f $cpu/cpufreq/scaling_governor ]; then + chmod u+w $cpu/cpufreq/scaling_governor + fi + for boundary in min max; do + if [ -f $cpu/cpufreq/scaling_${boundary}_freq ]; then + chmod u+w $cpu/cpufreq/scaling_${boundary}_freq + fi + done + + # Set governor to performance and do not allow changes later on. + if [ -f $cpu/cpufreq/scaling_governor ]; then + echo performance > $cpu/cpufreq/scaling_governor + chmod a-w $cpu/cpufreq/scaling_governor + fi + + # Hardware maximum performance. + if [ -f $cpu/cpufreq/scaling_min_freq ] && [ -f $cpu/cpufreq/scaling_max_freq ]; then + cp $cpu/cpufreq/scaling_{max,min}_freq + chmod a-w $cpu/cpufreq/scaling_{max,min}_freq + fi + + # Disable all but one thread on each core per socket. Both core_id and physical_package_id + # are numbers so it must be ensured that for the following examples are seen as distinct: + # - core_id=1, physical_package=11 + # - core_id=11, physical_package=1 + # Simple concatenation would result in the string '111' for both cores. Though `cat` + # adds a newline after each file, we do not want to rely on `cat` to always add this + # 'delimiter'. + core_id=$(cat $cpu/topology/core_id | tr -d '\n')'-'$(cat $cpu/topology/physical_package_id | tr -d '\n') + + # Disable all efficiency cores + found=0 + for efficiency_core in "${cpu_list[@]}"; do + if [[ "$cpu" == "/sys/devices/system/cpu/cpu$efficiency_core" ]]; then + disable_cpu $cpu + found=1 + break + fi + done + if [ "$found" -eq "1" ]; then + continue + fi + + if [[ ${core_ids[$core_id]:-} ]]; then + disable_cpu $cpu + else + core_ids[$core_id]=1 + fi done +if [ -n "${store_isolcpus_fact}" ]; then + csv_string=$(IFS=, ; echo "${disabled_cores[*]}") + echo "$csv_string" > "$store_isolcpus_fact" +fi + DIR_INTEL=/sys/devices/system/cpu/intel_pstate DIR_AMD=/sys/devices/system/cpu/cpufreq +# Same for some ARM CPUs +FILE_AMD=$DIR_AMD/boost if [ -d $DIR_INTEL ]; then - # now disable turbo boost - FILE=$DIR_INTEL/no_turbo - echo -n 1 > $FILE || echo "Could not write to '$FILE', ignoring for now..." - if [ $(cat $FILE) -ne 1 ]; then - echo "Error: turboboost still enabled!" - exit 1 - fi - - # increase freq from powersaving to normal, but don't overclock - echo 100 > $DIR_INTEL/min_perf_pct - echo 100 > $DIR_INTEL/max_perf_pct -elif [ -d $DIR_AMD ]; then - # now disable boosting - FILE=$DIR_AMD/boost - echo -n 0 > $FILE || echo "Could not write to '$FILE', ignoring for now..." - if [ $(cat $FILE) -ne 0 ]; then - echo "Error: turboboost still enabled!" - exit 1 - fi + # now disable turbo boost + FILE=$DIR_INTEL/no_turbo + echo -n 1 > $FILE || echo "Could not write to '$FILE', ignoring for now..." + if [ $(cat $FILE) -ne 1 ]; then + echo "Error: turboboost still enabled!" + exit 1 + fi + + # increase freq from powersaving to normal, but don't overclock + echo 100 > $DIR_INTEL/min_perf_pct + echo 100 > $DIR_INTEL/max_perf_pct +elif [ -f $FILE_AMD ]; then + # now disable boosting + echo -n 0 > $FILE_AMD || echo "Could not write to '$FILE_AMD', ignoring for now..." + if [ $(cat $FILE_AMD) -ne 0 ]; then + echo "Error: turboboost still enabled!" + exit 1 + fi else - echo "Warning: kernel (turbo) boost config not found in '$DIR_INTEL' or '$DIR_AMD'." + echo "Warning: kernel (turbo) boost config not found in '$DIR_INTEL' or '$DIR_AMD'." fi diff --git a/provision-contest/dump-api.sh b/provision-contest/dump-api.sh index 4eec3f3c..f04f21f1 100755 --- a/provision-contest/dump-api.sh +++ b/provision-contest/dump-api.sh @@ -6,10 +6,10 @@ baseurl="https://domjudge/api/contests" mkdir -p "$where" -for endpoint in judgement-types languages problems groups organizations teams state submissions judgements runs clarifications scoreboard; do +for endpoint in judgement-types languages problems groups organizations teams state submissions judgements runs clarifications scoreboard awards; do http GET "$baseurl/$contest/$endpoint" | python -mjson.tool > "$where/${endpoint}.json" done endpoint=contest http GET "$baseurl/$contest" | python -mjson.tool > "$where/${endpoint}.json" endpoint=event-feed -http GET "$baseurl/$contest/${endpoint}?stream=0" > "$where/${endpoint}.json" +http GET "$baseurl/$contest/${endpoint}?stream=0" > "$where/${endpoint}.ndjson" diff --git a/provision-contest/replay.py b/provision-contest/replay.py index e945e60a..31f84145 100755 --- a/provision-contest/replay.py +++ b/provision-contest/replay.py @@ -1,15 +1,16 @@ #!/usr/bin/python3 +import argparse from halo import Halo import json -from pprint import pprint from zipfile import ZipFile from datetime import datetime +from datetime import timedelta +from itertools import dropwhile import time import sys import random import requests -import re import logging logging.basicConfig( @@ -18,15 +19,28 @@ format='%(asctime)s - \x1b[34;1m%(message)s\x1b[0m', ) -if len(sys.argv) == 1 or len(sys.argv) > 4: - print(f'Usage: {sys.argv[0]} [ []') - sys.exit(-1) - -api_url = sys.argv[1] -if len(sys.argv) >= 3: - contest = sys.argv[2] +parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, + description='Replay a contest.') + +parser.add_argument('api_url', help='Contest API URL.') +parser.add_argument('-c', '--contest', help='''submit for contest with ID CONTEST. + Mandatory when more than one contest is active.''') +parser.add_argument('-s', '--submissionid', help='submission to start at.') +parser.add_argument('--insecure', help='do not verify SSL certificate', action='store_true') +parser.add_argument('-r', '--no_remap_teams', help='do not remap team ID\'s to team ID\'s of contest from API.', action='store_true') +parser.add_argument('-I', '--ignore_teamids', help='Completely randomize teamids during replay, not storing any mapping.', action='store_true') +parser.add_argument('-i', '--internal_data_source', help='The API uses an internal API source.', action='store_true') +parser.add_argument('-f', '--simulation_speed', help='Speed up replay speed by this factor.') + +args = parser.parse_args() + +api_url = args.api_url +verify = not args.insecure +if args.contest: + contest = args.contest else: - contests = requests.get(f'{api_url}/contests').json() + contests = requests.get(f'{api_url}/contests', verify=verify).json() if len(contests) == 1: contest = contests[0]['id'] else: @@ -40,17 +54,60 @@ submissions = json.load(open('submissions.json')) logging.info(f'Loaded {len(submissions)} submissions.') -contest_data = requests.get(f'{api_url}/contests/{contest}').json() +problem_data = requests.get(f'{api_url}/contests/{contest}/problems', verify=verify).json() +known_problem_ids = set([p['id'] for p in problem_data]) +used_problem_ids = set([s['problem_id'] for s in submissions]) +unknown_problem_ids = used_problem_ids.difference(known_problem_ids) +if unknown_problem_ids: + if len(unknown_problem_ids) == len(used_problem_ids): + logging.critical('None of the used problem IDs is known.') + sys.exit(-1) + logging.error(f'Some problem IDs are used but not known: {unknown_problem_ids}') + +contest_data = requests.get(f'{api_url}/contests/{contest}', verify=verify).json() +if 'code' in contest_data and 'message' in contest_data and contest_data['code'] != 200: + code = contest_data['code'] + msg = contest_data['message'] + logging.critical(f'Failed to retrieve contest, HTTP status code: {code}, message: {msg}') + sys.exit(-1) + +while not contest_data['start_time']: + logging.info(f'Start time unknown - contest delayed.') + time_diff = 30 + spinner = Halo(spinner='dots', text=f'Sleeping for ~{str(round(time_diff,2))}s while waiting for contest start to be set.') + spinner.start() + time.sleep(time_diff) + spinner.stop() + contest_data = requests.get(f'{api_url}/contests/{contest}', verify=verify).json() + contest_start = datetime.strptime(contest_data['start_time'], '%Y-%m-%dT%H:%M:%S%z').timestamp() +contest_start_obj = datetime.strptime(contest_data['start_time'], '%Y-%m-%dT%H:%M:%S%z') contest_duration = (datetime.strptime(contest_data['duration'], '%H:%M:%S.000') - datetime(1900, 1, 1)).total_seconds() +if args.no_remap_teams: + if args.ignore_teamids: + logging.critical('Cannot specify --no_remap_teams and --ignore_teamids at the same time.') + sys.exit(-1) + + logging.info('Keeping original team IDs.') +else: + # Get the teams from the contest + team_data = requests.get(f'{api_url}/contests/{contest}/teams', verify=verify).json() + team_ids = [team['id'] for team in team_data if not team['hidden']] + + if not team_ids: + logging.critical('Contest has no teams, can\'t submit.') + sys.exit(-1) + + logging.info(f'Remapping teams to {len(team_ids)} teams from API.') + now = time.time() orig_contest_duration = 5 * 60 * 60 first_submission_time = 0 -if len(sys.argv) == 4: - skip_to_submission = int(sys.argv[3]) - submissions = submissions[skip_to_submission:] +if args.submissionid: + skip_to_submission = args.submissionid + submissions = list(dropwhile(lambda s: s['id'] != skip_to_submission, submissions)) logging.info(f'Skipped to submission {skip_to_submission}, {len(submissions)} remaining.') first_submission_time = (datetime.strptime(submissions[0]['contest_time'][:-4], '%H:%M:%S') - datetime(1900, 1, 1)).total_seconds() orig_contest_duration -= first_submission_time @@ -65,39 +122,36 @@ logging.info('Contest will be started at ' + time.strftime('%X %x %Z', time.localtime(contest_start)) + '.') simulation_speed = orig_contest_duration/contest_duration +if args.simulation_speed: + simulation_speed = float(args.simulation_speed) logging.info(f'Simulation speed: {simulation_speed}') -# problems.json is necessary to map from problem id to label. -problems = json.load(open('problems.json')) -summary = ', '.join([p['label'] for p in problems]) -logging.info(f'Loaded {len(problems)} problems ({summary}).') - -# We will submit under different user accounts, parse all teams out them. -accounts_tsv = open('accounts.tsv') -accounts = dict() -num_teams = 0 -for line in accounts_tsv: - credentials = line.rstrip('\n').split('\t') - if credentials[0] != 'team': - continue - num_teams += 1 - accounts[num_teams] = (credentials[2], credentials[3]) +if args.internal_data_source: + # problems.json is necessary to map from problem id to label. + problems = json.load(open('problems.json')) + summary = ', '.join([p['label'] for p in problems]) + logging.info(f'Loaded {len(problems)} problems ({summary}).') -problem_to_label = dict() -for problem in problems: - problem_to_label[problem['id']] = problem['label'] + problem_to_label = dict() + for problem in problems: + problem_to_label[problem['id']] = problem['label'] team_problem_team_map = dict() submissions_api_url = f'{api_url}/contests/{contest}/submissions' for submission in submissions: + if not submission['id']: + continue times = submission['contest_time'].split(':') - orig_submission_time = int(times[0])*3600 + int(times[1])*60 + float(times[2]) + hours = int(times[0]) + minutes = int(times[1]) + seconds = float(times[2]) + orig_submission_time = hours*3600 + minutes*60 + seconds new_submission_time = orig_submission_time/simulation_speed time_from_start = time.time() - contest_start time_diff = new_submission_time - time_from_start if time_diff > 0: - logging.info(f'Waiting for simulated contest time of {times[0]}:{times[1]}:{times[2]}.') + logging.info(f'Waiting for simulated contest time of {hours}:{minutes:02}:{seconds:06.3f}.') spinner = Halo(spinner='dots', text=f'Sleeping for ~{str(round(time_diff,2))}s') spinner.start() while time_diff > 1: @@ -113,16 +167,21 @@ # all the time. team_id = submission['team_id'] problem_id = submission['problem_id'] - # TODO: make this configurable or detect it. - # With internal data source: - # problem_label = problem_to_label[problem_id] - # With external data source: - problem_label = problem_id - if team_id not in team_problem_team_map: - team_problem_team_map[team_id] = dict() - if problem_label not in team_problem_team_map[team_id]: - team_problem_team_map[team_id][problem_label] = random.randint(1, num_teams) - team_id = team_problem_team_map[team_id][problem_label] + if args.internal_data_source: + problem_label = problem_to_label[problem_id] + else: + problem_label = problem_id + if not args.no_remap_teams: + if args.ignore_teamids: + team_id = random.choice(team_ids) + else: + if team_id not in team_problem_team_map: + team_problem_team_map[team_id] = dict() + if problem_label not in team_problem_team_map[team_id]: + team_problem_team_map[team_id][problem_label] = random.choice(team_ids) + team_id = team_problem_team_map[team_id][problem_label] + else: + team_id = submission['team_id'] files = [] problem_zip = ZipFile(submission['id'] + ".zip") @@ -130,21 +189,25 @@ file_tuple = ('code[]', (name, problem_zip.read(name), 'text/plain')) files.insert(len(files), file_tuple) first_filename = files[0][1][0] - username = accounts[team_id][0] # We don't allow python2 anymore, let's rewrite it as python3 and try our # best. language_id = submission['language_id'] if language_id == 'python2': language_id = 'python3' - data = {'problem_id': problem_label, 'language_id': language_id} + + data = { + 'problem_id': problem_label, + 'language_id': language_id, + 'team_id': team_id + } if 'entry_point' in submission: data['entry_point'] = submission['entry_point'] - logging.info(f'Submitting problem {problem_label} ({first_filename}) on behalf of user {username}.') + logging.info(f'Submitting problem {problem_label} ({first_filename}) on behalf of team {team_id}.') r = requests.post( submissions_api_url, data = data, - auth = accounts[team_id], files = files, + verify = verify, ) if r.status_code == 200: sid = r.json()['id'] diff --git a/run_html_validation.sh b/run_html_validation.sh deleted file mode 100755 index 60611f06..00000000 --- a/run_html_validation.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/sh -# -# Cronjob script to automatically run HTML validation of webpages -# on an live, publicly accessible DOMjudge installation. - -set -e - -#DEBUG=1 - -# Optionally specify a user and password when the web interface is -# password protected: -#USER=jury -#PASS=passwordhere - -LIVESYSTEMDIR=~/system -LIVEURLPREFIX="https://${USER:+$USER:$PASS@}www.domjudge.org/domjudge/" - -VNUCHECKER=~/vnu_html_checker/vnu.jar - -[ "$DEBUG" ] && set -x -quiet() -{ - if [ "$DEBUG" ]; then - "$@" - else - "$@" > /dev/null 2>&1 - fi -} - -TEMPDIR=$(mktemp -d /tmp/dj_html_validate-XXXXXX) - -cd ~ - -# Validate DOMjudge webpages running from uptodate git checkout -# (we cannot use a fresh checkout due to missing website config) -cd $LIVESYSTEMDIR && git pull -q --rebase --autostash - -URLS=' -. -public/ -public/problems.php -public/team.php?id=2 -team -team/clarification.php -team/clarification.php?id=137 -team/problems.php -team/scoreboard.php -team/scoreboard.php?categoryid[]=1&filter=filter -team/submission_details.php?id=998 -team/submission_details.php?id=1163 -team/team.php?id=2 -jury/ -jury/auditlog.php -jury/balloons.php -jury/checkconfig.php -jury/check_judgings.php -jury/clarification.php -jury/clarification.php?id=107 -jury/clarifications.php -jury/config.php -jury/contest.php?cmd=add -jury/contest.php?id=2 -jury/contests.php -jury/edit_source.php?id=1&rank=0 -jury/executables.php -jury/executable.php?id=c -jury/genpasswds.php -jury/impexp.php -jury/impexp_contestyaml.php -jury/index.php -jury/internal_errors.php -jury/internal_error.php?id=1 -jury/judgehost.php?id=judgehost1 -jury/judgehosts.php?cmd=edit&referrer=judgehosts.php -jury/judgehosts.php -jury/judgehost_restriction.php?cmd=add -jury/judgehost_restriction.php?id=1 -jury/judgehost_restrictions.php -jury/language.php?cmd=add -jury/language.php?id=c -jury/languages.php -jury/problem.php?id=7 -jury/problem.php?id=7&cmd=edit -jury/problems.php -jury/refresh_cache.php -jury/rejudging.php?id=1 -jury/rejudgings.php -jury/scoreboard.php -jury/scoreboard.php?country[]=NLD -jury/show_source.php?id=1 -jury/show_source.php?id=3 -jury/submission.php?id=1 -jury/submission.php?id=91 -jury/submission.php?id=94 -jury/submissions.php?view[0] -jury/submissions.php?view[1] -jury/submissions.php?view[2] -jury/team.php?id=2 -jury/team.php?id=2&cmd=edit -jury/team_affiliations.php -jury/team_affiliation.php?id=1 -jury/team_categories.php -jury/team_category.php?id=1 -jury/teams.php -jury/testcase.php?probid=10 -jury/user.php?id=1 -jury/users.php -api/' - -OFS="$IFS" -IFS=' -' - -check_html () -{ - set +e - url="$LIVEURLPREFIX$1" - TEMP="$TEMPDIR/$(echo "$1" | sed 's!/!_!g').html" - curl -s -L -g ${USER:+-u$USER:$PASS} "$url" > "$TEMP" - # Filter out some unfixable errors and normalize file/URL output: - java -jar $VNUCHECKER "$TEMP" 2>&1 | \ - grep -v 'error: Attribute .*sorttable_customkey.* not allowed on element' | \ - sed -e 's!^"file.*validate-[a-zA-Z0-9]*/!!' \ - -e 's!^\(api\|jury\|team\|public\)_!\1/!' \ - -e 's/\.html":/ /g;s/%3F/?/g;s/%26/\&/g;s/%3D/=/g;s/%5B/[/g;s/%5D/]/g' - set -e -} - -for i in $URLS ; do - check_html "$i" -done -IFS="$OFS" - -[ "$DEBUG" ] || rm -rf "$TEMPDIR" - -exit 0 diff --git a/scoreboard_xml2csv.xslt b/scoreboard_xml2csv.xslt deleted file mode 100644 index e1cbbef3..00000000 --- a/scoreboard_xml2csv.xslt +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/submit_coverity_scan.sh b/submit_coverity_scan.sh index b0380f49..9f2be1c6 100755 --- a/submit_coverity_scan.sh +++ b/submit_coverity_scan.sh @@ -94,7 +94,7 @@ git_commit() quietfilter() { if [ "$QUIET" ]; then - grep -vE '(^Coverity Build Capture|^Internal version numbers:|^[[:space:]]*$|compilation units \(100%\)|PHP compilation units \([8-9][0-9]%\)|JavaScript compilation units \(9[0-9]%\)|^Looking for translation units|^The cov-build utility completed successfully\.|^Build successfully submitted\.|^\[STATUS\] |^\*+$|^\|[0-9-]+\|$|^\[WARNING\] (Path .* looks like an idir\.|No source file matches in filesystem capture search directory:|Filesystem capture was enabled but yielded no source file matches\.))' || true + grep -vE '(^Coverity Build Capture|^Internal version numbers:|^[[:space:]]*$|compilation units \(100%\)|PHP compilation units \([3-9][0-9]%\)|(JavaScript|Python) compilation units \(9[0-9]%\)|^Looking for translation units|^The cov-build utility completed successfully\.|^Build successfully submitted\.|^\[STATUS\] |^\*+$|^\|[0-9-]+\|$|^\[WARNING\] (Path .* looks like an idir\.|No source file matches in filesystem capture search directory:|Filesystem capture was enabled but yielded no source file matches\.)|For more details, please look at:|/cov-int/build-log\.txt|^Attempting to detect unconfigured compilers in build)' || true else cat fi diff --git a/update_demoweb.sh b/update_demoweb.sh index aa040a15..fceedc27 100755 --- a/update_demoweb.sh +++ b/update_demoweb.sh @@ -19,7 +19,7 @@ make QUIET=1 maintainer-clean make QUIET=1 MAINT_CXFLAGS='-g -O2 -Wall -fPIE -Wformat -Wformat-security' \ inplace-conf >/dev/null 2>&1 || true make QUIET=1 inplace-install docs >/dev/null 2>&1 -composer auto-scripts >/dev/null 2>&1 +(cd webapp && composer auto-scripts >/dev/null 2>&1) # Reset database to known good state: mysql $MYSQLOPTS "$DBNAME" < domjudge_demo_remove.sql diff --git a/update_docs.sh b/update_docs.sh index 19430d46..3784e400 100755 --- a/update_docs.sh +++ b/update_docs.sh @@ -5,6 +5,14 @@ JSON="${WEBSERVER_PATH}/versions.json" TMPDIR=$(mktemp -d -t 'update_docs-XXXXXX') DOC_REPO="$TMPDIR/domjudge" + +cleanup() +{ + rm -rf "$TMPDIR" +} + +trap cleanup EXIT + git clone https://github.com/DOMjudge/domjudge.git "$DOC_REPO" for version in $(jq -r -c '.[]' < "${JSON}") ; do @@ -26,5 +34,3 @@ for version in $(jq -r -c '.[]' < "${JSON}") ; do cp -r doc/manual/build/html/* "${WEBSERVER_PATH}/${version}/" ) done - -rm -rf "$TMPDIR" diff --git a/website/.well-known/security.txt b/website/.well-known/security.txt new file mode 100644 index 00000000..e7261021 --- /dev/null +++ b/website/.well-known/security.txt @@ -0,0 +1,2 @@ +Contact: mailto:admin@domjudge.org +Expires: 2023-11-06T23:00:00.000Z diff --git a/website/DOMjudgelogo.svg b/website/DOMjudgelogo.svg index 06e2512c..75dde9b7 100644 --- a/website/DOMjudgelogo.svg +++ b/website/DOMjudgelogo.svg @@ -50,7 +50,7 @@ inkscape:label="DOMjudgelogo" transform="matrix(1.3333333,0,0,-1.3333333,-142.66667,1122.5195)">DOM DOM judge SOCAL (since 2013), RMRC (since 2014), South Pacific Regional -
  • ICPC World Finals (since 2012, in 2018 and 2019 as main system)
  • +
  • ICPC Championship: + NAC (in 2023)
  • +
  • ICPC World Finals (since 2012, and since 2018 as main system)
  • DOMjudge has also been adapted for use in numerous online contests, as @@ -54,7 +56,7 @@ If you are using (an adapted version of) DOMjudge, we would be glad to hear abou

    The following is required to run DOMjudge:

    • At least one machine running Linux, with root access
    • -
    • Apache (or Nginx) web server with PHP >= 7.1.3 and +
    • Apache (or Nginx) web server with PHP >= 7.4.0 and PHP command line interface
    • MySQL or MariaDB database server version 5.5.3 or newer
    • Compilers for the languages you want to support
    • diff --git a/website/chat.shtml b/website/chat.shtml index 0ac4213d..c8b4c65b 100644 --- a/website/chat.shtml +++ b/website/chat.shtml @@ -11,10 +11,42 @@ this link to sign up.

      + +

      Netiquette

      - Once you have joined the Slack workspace, feel free to ask any DOMjudge-related - questions you might have. Please do note that it may sometimes take a bit longer - than a few minutes to get a response, partly because people might be in different timezones. -

      + Once you have joined the Slack workspace, feel free to ask any + DOMjudge-related questions you might have. Please do take some + common internet etiquette in consideration: +

      +

      +
      Don't demand (quick) answers
      +
      + We're all volunteers doing this in our free time. Although we + do our best to respond to questions, people may not always be + available due to timezone differences or simply other reasons. +
      + +
      Ask questions in public channels
      +
      + Do not ask questions privately, unless you're invited to or + you know that that person knows the answer and this is not + relevant to others. +
      + +
      Provide clear details
      +
      + When you run into a problem or error, provide as detailed as + possible how you got there and any exact errors you got. The + more details you provide, the better and quicker we can help. +
      + +
      Prefer text over screenshots
      +
      + When possible, copy&paste error messages, configuration + data, etc. as code quoted text instead of + screenshots. This makes it easier to read on any device and + copy&paste. +
      +
    diff --git a/website/checktestdata.shtml b/website/checktestdata.shtml index fe750a0e..c0215a0d 100644 --- a/website/checktestdata.shtml +++ b/website/checktestdata.shtml @@ -24,8 +24,8 @@ example grammar scripts for more details.

    The checktestdata sources can be found at GitHub. To build checktestdata from repository sources requires -Flexc++ and -Bisonc++. +Flexc++ and +Bisonc++. Alternatively, the release branch from the checktestdata repository contains pregenerated parser source files, which do not require Flexc++/Bisonc++. Finally, Debian packages can be installed diff --git a/website/demo.shtml b/website/demo.shtml index 27125619..2d463d54 100644 --- a/website/demo.shtml +++ b/website/demo.shtml @@ -27,14 +27,28 @@ populated with data from a live contest.

    Public interface
    Shows the scoreboard and the problemset.
    Team interface
    -
    Shows your submissions, submission details, clarifications, the problemset and the scoreboard. Log in with username and password team. -As this is a demo, you can't actually submit solutions.
    +
    Shows your submissions, submission details, clarifications, the +problemset and the scoreboard. Log in with username and password team. +You can submit solutions through the web interface, but as this is a demo, +these will not get judged. The submit client is also not available.
    Jury/admin interface
    The full set of options available to judges and administrators. Log in with username and password jury or username and password admin respectively. The admin interface is a superset of the jury interface with additional options to configure the contests. There are no judgehosts running, so things like rejudging do not have much effect. Team source code and testdata in/output has been replaced with a placeholder, so the source formatting and testdata diff functionality is not useful.
    +
    API documentation
    +
    Documentation on how to use DOMjudge from scripts. This is used by our submit client and the configure-domjudge & import-contest scripts. Also useful if you want to use only parts of DOMjudge, for example as you use your own custom scoreboard or another submission method. Some of the tools also work via this API.
    +
    Contest API
    +
    The contest API itself, which adheres to the Contest API specification. +Note that we do not implement all optional details of the API, some +new specifications from the draft version may be work in progress. +We also have some private additions; additional properties in +endpoints specified by the standard can be disabled by passing the URL +parameter strict=1. +Various endpoints are available (or provide full access) only with +admin credentials, which have to be provided via HTTP basic auth; +username and password are admin.

    The data in the system is reset to a known good state every night, so feel diff --git a/website/development.shtml b/website/development.shtml index e15ae649..201c2ecf 100644 --- a/website/development.shtml +++ b/website/development.shtml @@ -1,12 +1,12 @@

    DOMjudge is open source, free software and has an active development -community with contributors from around the world. Question, suggestion +community with contributors from around the world. Question, suggestion, bug or patch? All those are most welcome via the channels below.

    Source code

    -

    The DOMjudge sources are maintained +

    The DOMjudge sources are maintained on GitHub. You can get the main repository with the following command: diff --git a/website/documentation.shtml b/website/documentation.shtml index f37341eb..83c72ba4 100644 --- a/website/documentation.shtml +++ b/website/documentation.shtml @@ -5,9 +5,8 @@

    DOMjudge manual
    The complete reference for those setting up and running the DOMjudge system as well as the jury.
    -
    Team manual
    -
    Concise manual for participating teams. Only available in Portable -Document Format (PDF).
    +
    Team manual
    +
    Concise manual for participating teams. Available also as PDF in the DOMjudge tarball.

    API documentation

    @@ -18,7 +17,7 @@ state as well as perform certain actions, such as submit solutions and control contest state (with admin role).

    The DOMjudge API is an implementation of the ICPC -Contest API +Contest API (except that it does not (yet) implement the optional team-members endpoint). It also has some extensions, see the internal API documentation.

    diff --git a/website/download.shtml b/website/download.shtml index 11c08693..e4ee2456 100644 --- a/website/download.shtml +++ b/website/download.shtml @@ -54,7 +54,7 @@ The packages are built and tested on Debian stable, but also on newer v

    To install these packages, add our repository GPG key directly to APT with

    -
    curl -o - https://www.domjudge.org/repokey.asc | sudo apt-key add -
    +
    curl -o - https://www.domjudge.org/repokey.asc | sudo tee /etc/apt/trusted.gpg.d/domjudge.org.asc

    or first check our signatures on the key for authenticity. Then add the following lines to /etc/apt/sources.list:

    diff --git a/website/header.shtml b/website/header.shtml
    index 690f8f53..1f35c97d 100644
    --- a/website/header.shtml
    +++ b/website/header.shtml
    @@ -1,7 +1,7 @@
    -
     
    @@ -19,8 +19,9 @@
     
     
    + width="80" height="169" id="domjudgeLogo" class="float-end d-none d-md-block">
    diff --git a/website/index.shtml b/website/index.shtml index d53e4b6f..f765dc24 100644 --- a/website/index.shtml +++ b/website/index.shtml @@ -1,7 +1,8 @@ -
    -
    +
    +
    +

    DOMjudge

    DOMjudge is an automated system to run programming contests, like the @@ -11,8 +12,10 @@ system to plug in languages and has a feature-rich interface for the judges. The system has been used in many live contests and is free, open source software that you can adapt to your needs.

    -

    Learn more »

    -
    + +

    Learn more »

    +
    +
    diff --git a/website/style_domjudge.css b/website/style_domjudge.css index 37d14eb2..0c592489 100644 --- a/website/style_domjudge.css +++ b/website/style_domjudge.css @@ -14,3 +14,7 @@ kbd { color: inherit; background-color: transparent; } + +a { + text-decoration: none; +} diff --git a/website/tools.shtml b/website/tools.shtml index c81c97ea..822e1ce6 100644 --- a/website/tools.shtml +++ b/website/tools.shtml @@ -23,10 +23,18 @@ and testing commands while working on an ICPC-style problem. Ideally one should never have to manually run any compilation or testing command themselves.
    The Kattis Problem Tool is - another collection of tools to createproblem packages. DOMjudge is able to + another collection of tools to create problem packages. DOMjudge is able to import this format.

    +

    Polygon2DOMjudge

    + +

    Polygon2DOMjudge + is a tool that converts problem packages exported from the + Codeforces Polygon Platform + into the DOMjudge Problem Package format. +

    +

    Contest as a service

    Cloudcontest

    @@ -45,8 +53,9 @@

    ICPC Tools

    ICPC Tools is a set of (free to use) - tools that can work with a CLICS compatible API, like DOMjudge provides. It has a - scoreboard display and resolver, balloons utility and various other presentation tools.

    + tools that can work with a + CLICS compatible API, like DOMjudge provides. It has a scoreboard display and + resolver, balloons utility and various other presentation tools.

    DOMjura

    @@ -69,4 +78,16 @@ is useful to keep problem data secret and to make sharing credentials easier on the organizer.

    +

    DOMjudge for teaching

    + +

    DOMjudge is mostly focused on being a contest system for short running contests. Some people use the +automatic judging for teaching. When possible we can incorporate such extensions to provide easier access +through the API and UI with feature requests. Since not everything will make it into our releases, we link here to +some extensions to help with this use case. +

      +
    • Isolate this is an alternative to our judgedaemon with chroot.
    • +
    • DOMtutor
    • +
    +

    +