diff --git a/README.md b/README.md index c8f85e8..4ae4720 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ to test any **HTTP** services they're running. Add the following line to ``` 192.168.56.56 sr-proxy sr-proxy.local 192.168.56.57 sr-compsvc sr-compsvc.local +192.168.56.58 sr-kitsvc sr-kitsvc.local ``` You'll then be able to access the machines as if they were hosted. For example diff --git a/Vagrantfile b/Vagrantfile index 28d7e17..f0f2031 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -15,6 +15,7 @@ Vagrant.configure(2) do |config| ansible.groups = { "webproxies" => ["sr-proxy"], "competitorsvcs" => ["sr-compsvc"], + "kitsvcs" => ["sr-kitsvc"], } end @@ -35,4 +36,14 @@ Vagrant.configure(2) do |config| compsrv.vm.network "private_network", ip: "192.168.56.57" compsrv.vm.hostname = "sr-compsvc.local" end + + config.vm.define "sr-kitsvc" do |kitsrv| + kitsrv.vm.box = "ubuntu/jammy64" + + # This name is what's looked up in the Ansible host_vars. + kitsrv.vm.define "sr-kitsvc" + + kitsrv.vm.network "private_network", ip: "192.168.56.58" + kitsrv.vm.hostname = "sr-kitsvc.local" + end end diff --git a/group_vars/all.yml b/group_vars/all.yml index 5982e59..cacc9cc 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -28,6 +28,11 @@ enable_competition_homepage: false enable_competitor_services_proxy: true competitor_services_proxy_hostname: competitorsvcs.studentrobotics.org +# We typically only host the kit services for the duration of the +# competition year. +enable_kit_services_proxy: true +kit_services_proxy_hostname: kitsvcs.studentrobotics.org + firewall_allowed_tcp_ports: - "22" - "80" diff --git a/host_vars/kitsvcs.studentrobotics.org.yml b/host_vars/kitsvcs.studentrobotics.org.yml new file mode 100644 index 0000000..0c3e7b5 --- /dev/null +++ b/host_vars/kitsvcs.studentrobotics.org.yml @@ -0,0 +1,19 @@ +--- +canonical_hostname: kitsvcs.studentrobotics.org +secondary_hostnames: + # Include our primary canonical hostname so that requests via the proxy there + # aren't redirected. This is needed (rather than overriding the Host header + # with the actual domain) so that urls generated by services hosted services + # include the right domain. + - studentrobotics.org + +add_hsts_header: true +certbot_certs: + - domains: + - "{{ canonical_hostname }}" + +users: + - jhoward + - jsedensmith + - kkwiatek + - plaw diff --git a/host_vars/sr-kitsvc.yml b/host_vars/sr-kitsvc.yml new file mode 100644 index 0000000..c902e3e --- /dev/null +++ b/host_vars/sr-kitsvc.yml @@ -0,0 +1,11 @@ +--- +# This is a dev VM created by Vagrant. + +canonical_hostname: sr-kitsvc +secondary_hostnames: + # See explanation in host_vars/kitsvcs.studentrobotics.org.yml for why + # we include the proxy hostname here. + - sr-proxy + +add_hsts_header: false +certbot_create_if_missing: false diff --git a/hosts b/hosts index 278afd1..dfd9812 100644 --- a/hosts +++ b/hosts @@ -6,3 +6,6 @@ monty.studentrobotics.org [competitorsvcs] competitorsvcs.studentrobotics.org + +[kitsvcs] +kitsvcs.studentrobotics.org diff --git a/playbook.yml b/playbook.yml index e9981dc..7590f43 100644 --- a/playbook.yml +++ b/playbook.yml @@ -21,3 +21,10 @@ - competitor-services-nginx - code-submitter - discord-gated-entry + +- name: Kit services + hosts: kitsvcs + roles: + # TODO: Give this role a less machine-specific name + - competitor-services-nginx + - helpdesk-system diff --git a/roles/helpdesk-system/README.md b/roles/helpdesk-system/README.md new file mode 100644 index 0000000..92faa3b --- /dev/null +++ b/roles/helpdesk-system/README.md @@ -0,0 +1,5 @@ +# Helpdesk System + +App for managing a competition helpdesk. + +This is a deployment of . diff --git a/roles/helpdesk-system/handlers/main.yml b/roles/helpdesk-system/handlers/main.yml new file mode 100644 index 0000000..ff7540b --- /dev/null +++ b/roles/helpdesk-system/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Restart helpdesk-system + service: + name: helpdesk-system + state: restarted diff --git a/roles/helpdesk-system/tasks/main.yml b/roles/helpdesk-system/tasks/main.yml new file mode 100644 index 0000000..32e07ee --- /dev/null +++ b/roles/helpdesk-system/tasks/main.yml @@ -0,0 +1,116 @@ +- name: Install virtualenv system dependencies + apt: + pkg: + - python3-virtualenv + - python3-wheel + +- name: Create install directory + file: + path: "{{ install_dir }}" + state: directory + owner: www-data + mode: "755" + +- name: Create secrets directory + file: + path: "{{ helpdesk_secrets_dir }}" + state: directory + owner: www-data + mode: "0700" + +- name: Download + git: + repo: https://github.com/srobo/helpdesk-system + dest: "{{ install_dir }}" + force: true + version: dfbc34b66bfb6d6c9a811e141d59d81aad46dab1 + notify: + Restart helpdesk-system + register: helpdesk_system_repo + become_user: www-data + +- name: Generate secret key + copy: + force: false + content: "{{ lookup('community.general.random_string', length=50) }}" + dest: "{{ helpdesk_secrets_dir }}/secret-key.txt" + owner: www-data + mode: "0600" + notify: + Restart helpdesk-system + +- name: Generate volunteer signup code + copy: + force: false + content: "{{ lookup('community.general.random_string', length=10, ignore_similar_chars=True, special=False, upper=False) }}" + dest: "{{ helpdesk_secrets_dir }}/volunteer-signup-code.txt" + owner: www-data + mode: "0600" + notify: + Restart helpdesk-system + +- name: Install configuration + template: + src: configuration.py + dest: "{{ install_dir }}/helpdesk/helpdesk/configuration.py" + owner: www-data + mode: "0600" + notify: + Restart helpdesk-system + +- name: Install virtual environment + pip: + virtualenv: "{{ venv_dir }}" + requirements: "{{ install_dir }}/requirements.txt" + notify: + Restart helpdesk-system + become_user: www-data + when: helpdesk_system_repo.changed # noqa: no-handler - Use a handler to ensure execution order + +- name: Install deploy requirements + pip: + # Latest at the time of writing. Don't actually care about the version, + # only that we pin it for stability. + name: gunicorn==21.2.0 + virtualenv: "{{ venv_dir }}" + notify: + Restart helpdesk-system + become_user: www-data + +- name: Install systemd service + template: + src: helpdesk-system.service + dest: /etc/systemd/system/helpdesk-system.service + mode: "0644" + notify: + Restart helpdesk-system + +- name: Install nginx config + template: + src: nginx.conf + dest: /etc/nginx/locations-enabled/helpdesk-system + mode: "0644" + notify: + Reload nginx + +- name: Run migrations # noqa: no-changed-when - We want to always run this (it handles its own idempotency) + community.general.django_manage: + command: migrate --noinput + app_path: "{{ install_dir }}/helpdesk" + virtualenv: "{{ venv_dir }}" + become_user: www-data + when: helpdesk_system_repo.changed # noqa: no-handler - Use a handler to ensure execution order + +- name: Collect static # noqa: no-changed-when - We want to always run this (it handles its own idempotency) + community.general.django_manage: + command: collectstatic --noinput + app_path: "{{ install_dir }}/helpdesk" + virtualenv: "{{ venv_dir }}" + become_user: www-data + when: helpdesk_system_repo.changed # noqa: no-handler - Use a handler to ensure execution order + +- name: Enable service + service: + name: helpdesk-system + state: started + enabled: true diff --git a/roles/helpdesk-system/templates/configuration.py b/roles/helpdesk-system/templates/configuration.py new file mode 100644 index 0000000..95d5b55 --- /dev/null +++ b/roles/helpdesk-system/templates/configuration.py @@ -0,0 +1,51 @@ +from pathlib import Path + +SECRETS_DIR = Path("{{ helpdesk_secrets_dir }}") + +######################### +# # +# Required settings # +# # +######################### + +# Allow all hostnames - this validation is done by nginx instead. +ALLOWED_HOSTS = ["*"] + +# Database configuration. See the Django documentation for a complete list of available parameters: +# https://docs.djangoproject.com/en/stable/ref/settings/#databases +DATABASE = { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "db.sqlite", + "CONN_MAX_AGE": 300, # Max database connection age +} + +# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file. +# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and +# symbols. Helpdesk will not run without this defined. For more information, see +# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY +SECRET_KEY = SECRETS_DIR.joinpath("secret-key.txt").read_text() + +######################### +# # +# Optional settings # +# # +######################### + +BASE_PATH = "helpdesk/" + +# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal +# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging +# on a production system. +DEBUG = False + +EMAIL = { + 'BACKEND': 'django.core.mail.backends.console.EmailBackend' +} + +# Title of the System +SYSTEM_TITLE = "Helpdesk" + +# Time zone (default: UTC) +TIME_ZONE = "Europe/London" + +VOLUNTEER_SIGNUP_CODE = SECRETS_DIR.joinpath("volunteer-signup-code.txt").read_text() diff --git a/roles/helpdesk-system/templates/helpdesk-system.service b/roles/helpdesk-system/templates/helpdesk-system.service new file mode 100644 index 0000000..df2be96 --- /dev/null +++ b/roles/helpdesk-system/templates/helpdesk-system.service @@ -0,0 +1,15 @@ +[Unit] +Description=Helpdesk System +After=network.target + +[Service] +User=www-data + +Type=simple + +WorkingDirectory={{ install_dir }}/helpdesk +RuntimeDirectory=helpdesk-system +ExecStart={{ venv_dir }}/bin/gunicorn helpdesk.wsgi:application --bind unix:/var/run/helpdesk-system/helpdesk-system.socket --forwarded-allow-ips='*' --access-logfile - --workers="{{ ansible_processor_nproc * 2 + 1 }}" --max-requests=500 --max-requests-jitter=20 --timeout=30 + +[Install] +WantedBy=multi-user.target diff --git a/roles/helpdesk-system/templates/nginx.conf b/roles/helpdesk-system/templates/nginx.conf new file mode 100644 index 0000000..398955e --- /dev/null +++ b/roles/helpdesk-system/templates/nginx.conf @@ -0,0 +1,12 @@ +location /helpdesk/ { + proxy_pass http://unix:/var/run/helpdesk-system/helpdesk-system.socket; + proxy_pass_request_headers on; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $host; +} + +location /helpdesk/static { + alias {{ install_dir }}/helpdesk/static; +} diff --git a/roles/helpdesk-system/vars/main.yml b/roles/helpdesk-system/vars/main.yml new file mode 100644 index 0000000..8c056a1 --- /dev/null +++ b/roles/helpdesk-system/vars/main.yml @@ -0,0 +1,3 @@ +install_dir: /srv/helpdesk-system +venv_dir: "{{ install_dir }}/venv" +helpdesk_secrets_dir: "{{ secrets_dir }}/helpdesk-system" diff --git a/roles/srobo-nginx/templates/nginx.conf b/roles/srobo-nginx/templates/nginx.conf index 830f833..648aca4 100644 --- a/roles/srobo-nginx/templates/nginx.conf +++ b/roles/srobo-nginx/templates/nginx.conf @@ -128,6 +128,18 @@ http { } {% endif %} + {% if enable_kit_services_proxy %} + location /helpdesk/ { + # When the proxied service is not available NGINX will refuse to start. + # Use a variable to trick it into connecting lazily and thus always + # starting up, even if in a degraded mode. + set $kitsvcs '{{ kit_services_proxy_hostname }}'; + proxy_pass https://$kitsvcs$request_uri; + # Note: don't set a Host header as we want the helpdesk system to use our + # public hostname, not the hostname of the underlying machine. + } + {% endif %} + {% if enable_srcomp_proxy %} location /comp-api/ { # When the proxied service is not available NGINX will refuse to start. diff --git a/roles/users/files/authorized_keys/kkwiatek b/roles/users/files/authorized_keys/kkwiatek new file mode 100644 index 0000000..544d09d --- /dev/null +++ b/roles/users/files/authorized_keys/kkwiatek @@ -0,0 +1,3 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBjJRX+kUOwOioFgUp8A1OyR3450Lp0K4FB/ZW7aDOJL +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxOqjTyfHm5elbi5YqvZlCTC3X8lsY/rHsoXcfzix5b +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII+BGrcVW5MGbrBgYabpPlD5QvpRjJvPTjCahxWlHLga