Initial commit

This commit is contained in:
Jan Schär 2024-05-09 22:45:53 +02:00
commit 968d09e362
88 changed files with 2323 additions and 0 deletions

20
contestops/assign-user.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
machinename="$1"
username="$2"
machineusername=contestant
userline=$(grep "^$username;" contestants.csv)
if [ $? -ne 0 ]; then
echo "User $username not found"
exit 1
fi
fullname=$(echo "$userline" | cut "-d;" -f2)
# Set real name of machine user
ssh -F local.ssh_config "$machinename" chfn --full-name "\"$fullname\"" $machineusername
# Install client certificate
scp -F local.ssh_config "certs/$username.p12" "$machinename:/home/$machineusername/clientcert.p12"
ssh -F local.ssh_config "$machinename" install-client-cert $machineusername

25
contestops/backup-create.sh Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
sleep_secs=120
do_backup() {
for host in $(cat hostlist); do
echo $host
target=backups/$host/$(date --iso-8601=seconds)
mkdir -p $target
rsync -e "ssh -F local.ssh_config" --recursive --links --perms --times --verbose --prune-empty-dirs --exclude ".*" --exclude "/snap" --exclude "Screenshot from *" --max-size 200K $host:/home/contestant/ $target
done
}
if [ "$1" == timer ]; then
while true; do
do_backup
echo
echo "Finished, next backup in $sleep_secs seconds."
echo
echo
sleep $sleep_secs
done
else
do_backup
fi

9
contestops/config-hosts Normal file
View file

@ -0,0 +1,9 @@
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
89.58.34.6 contest.soi.ch
2a03:4000:64:8::1 contest.soi.ch

View file

@ -0,0 +1,48 @@
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
ct state invalid drop
ct state { established, related } accept
# Accept loopback
iif lo accept
# Accept ICMP
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Accept incoming connections to these ports
tcp dport { ssh } accept
reject
}
chain forward {
type filter hook forward priority 0;
reject
}
chain output {
type filter hook output priority 0;
ct state invalid drop
ct state { established, related } accept
# Accept loopback
oif lo accept
# Accept outgoing connections to these addresses
ip daddr { 89.58.34.6 } tcp dport { https } accept
ip daddr { 89.58.34.6 } udp dport { ntp } accept
ip6 daddr { 2a03:4000:64:8::1 } tcp dport { https } accept
ip6 daddr { 2a03:4000:64:8::1 } udp dport { ntp } accept
# Accept any connections by root user
#meta skuid root accept
reject
}
}

View file

@ -0,0 +1,22 @@
#!/bin/bash
set -ex
# Disable WiFi.
parallel-ssh -x "-F local.ssh_config" -h hostlist nmcli radio wifi off
# Create hosts file so we don't need DNS.
parallel-scp -x "-F local.ssh_config" -h hostlist ./config-hosts /etc/hosts
# Configure firewall.
parallel-scp -x "-F local.ssh_config" -h hostlist ./config-nftables.conf /etc/nftables.conf
parallel-ssh -x "-F local.ssh_config" -h hostlist systemctl enable nftables.service
# For some unknown reason nft gets stuck the first time it is run.
parallel-ssh -x "-F local.ssh_config" -h hostlist --par 30 systemctl start nftables.service
# Uncomment these lines if machines have 4K displays. This scales display to 2x.
# parallel-scp -x "-F local.ssh_config" -h hostlist ./set-display-scale.py /usr/local/bin/set-display-scale.py
# parallel-ssh -x "-F local.ssh_config" -h hostlist 'DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u contestant)/bus" runuser -u contestant -- python3 /usr/local/bin/set-display-scale.py'
# Configure contest lock screen.
parallel-scp -x "-F local.ssh_config" -h hostlist ./contest-lock.json /etc/contest-lock.json

View file

@ -0,0 +1,5 @@
{
"title": "SOI Finals 2024 · Day 1",
"message": "",
"startTime": "2024-01-01T10:00:00+01:00"
}

View file

@ -0,0 +1,2 @@
stofl;Mouse Stofl
binna1;Mouse Binna
1 stofl Mouse Stofl
2 binna1 Mouse Binna

60
contestops/create-certs.sh Executable file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# install cfssl
set -e
usernames=$(cat contestants.csv | cut "-d;" -f1)
mkdir -p certs
cd certs
cat <<EOF > ca.json
{
"CN": "SOI Contest Root CA",
"key": {
"algo": "rsa",
"size": 2048
}
}
EOF
if [ ! -f ca.pem ]; then
cfssl gencert -initca ca.json | cfssljson -bare ca
fi
cat <<EOF >client-config.json
{
"signing": {
"default": {
"expiry": "438000h"
},
"profiles": {
"client": {
"usages": ["signing", "key encipherment", "digital signature", "client auth"],
"expiry": "438000h"
}
}
}
}
EOF
for username in $usernames; do
cat <<EOF >client-csr-$username.json
{
"CN": "$username",
"key": {
"algo": "rsa",
"size": 2048
}
}
EOF
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=client-config.json -profile=client client-csr-$username.json | cfssljson --bare $username-cert
openssl pkcs12 -export -in $username-cert.pem -inkey $username-cert-key.pem -out $username.p12 -passout pass:
done

3
contestops/hostlist Normal file
View file

@ -0,0 +1,3 @@
contestant01
contestant02
contestant03

View file

@ -0,0 +1,202 @@
# ssh config for room INF3 at EPFL
Host contestant01
HostName 128.178.158.101
Host contestant02
HostName 128.178.158.102
Host contestant03
HostName 128.178.158.103
Host contestant04
HostName 128.178.158.104
Host contestant05
HostName 128.178.158.105
Host contestant06
HostName 128.178.158.106
Host contestant07
HostName 128.178.158.107
Host contestant08
HostName 128.178.158.108
Host contestant09
HostName 128.178.158.109
Host contestant10
HostName 128.178.158.110
Host contestant11
HostName 128.178.158.111
Host contestant12
HostName 128.178.158.112
Host contestant13
HostName 128.178.158.113
Host contestant14
HostName 128.178.158.114
Host contestant15
HostName 128.178.158.115
Host contestant16
HostName 128.178.158.116
Host contestant17
HostName 128.178.158.117
Host contestant18
HostName 128.178.158.118
Host contestant19
HostName 128.178.158.119
Host contestant20
HostName 128.178.158.120
Host contestant21
HostName 128.178.158.121
Host contestant22
HostName 128.178.158.122
Host contestant23
HostName 128.178.158.123
Host contestant24
HostName 128.178.158.124
Host contestant25
HostName 128.178.158.125
Host contestant26
HostName 128.178.158.126
Host contestant27
HostName 128.178.158.127
Host contestant28
HostName 128.178.158.128
Host contestant29
HostName 128.178.158.129
Host contestant30
HostName 128.178.158.130
Host contestant31
HostName 128.178.158.131
Host contestant32
HostName 128.178.158.132
Host contestant33
HostName 128.178.158.133
Host contestant34
HostName 128.178.158.134
Host contestant35
HostName 128.178.158.135
Host contestant36
HostName 128.178.158.136
Host contestant37
HostName 128.178.158.137
Host contestant38
HostName 128.178.158.138
Host contestant39
HostName 128.178.158.139
Host contestant40
HostName 128.178.158.140
Host contestant41
HostName 128.178.158.141
Host contestant42
HostName 128.178.158.142
Host contestant43
HostName 128.178.158.143
Host contestant44
HostName 128.178.158.144
Host contestant45
HostName 128.178.158.145
Host contestant46
HostName 128.178.158.146
Host contestant47
HostName 128.178.158.147
Host contestant48
HostName 128.178.158.148
Host contestant49
HostName 128.178.158.149
Host contestant50
HostName 128.178.158.150
Host contestant51
HostName 128.178.158.151
Host contestant52
HostName 128.178.158.152
Host contestant53
HostName 128.178.158.153
Host contestant54
HostName 128.178.158.154
Host contestant55
HostName 128.178.158.155
Host contestant56
HostName 128.178.158.156
Host contestant57
HostName 128.178.158.157
Host contestant58
HostName 128.178.158.158
Host contestant59
HostName 128.178.158.159
Host contestant60
HostName 128.178.158.160
Host contestant61
HostName 128.178.158.161
Host contestant62
HostName 128.178.158.162
Host contestant63
HostName 128.178.158.163
Host contestant64
HostName 128.178.158.164
Host vm
HostName localhost
Port 2222
Host *
User root
UserKnownHostsFile ./local.known_hosts
HashKnownHosts no

View file

@ -0,0 +1,17 @@
Host contestant01
HostName 10.42.0.101
Host contestant02
HostName 10.42.0.102
Host contestant03
HostName 10.42.0.103
Host vm
HostName localhost
Port 2222
Host *
User root
UserKnownHostsFile ./local.known_hosts
HashKnownHosts no

184
contestops/readme.md Normal file
View file

@ -0,0 +1,184 @@
# Contest ops
Here are instructions and various scripts and files for running contests.
The setup consists of a machine for each contestant, a machine running the grader, and an admin machine.
All these should be connected through a network, preferably wired.
The grader can be a machine accessible over the internet or in the local network.
## Grader setup
Install an ntp server on the grader machine.
This ensures that the contestant machine clocks are synchronized with the grader clock.
If a firewall is enabled, you may need to open the NTP port.
```bash
sudo apt install ntpsec
```
Configure the grader to accept client certificates.
The CA certificate (`certs/ca.pem`) is generated as part of the admin setup.
## Contestant machine setup
Obtain the contestant ISO, or build it yourself.
Flash the ISO to an USB stick.
All data on the stick will be lost.
For example, with the Gnome Disks utility, select the USB stick, open the menu on the right of the title bar, and click "Restore Disk Image...".
Boot the contestant machine from the USB stick.
Insert the stick and power on the machine.
Then repeatedly press a key to enter the boot menu (which key depends on the model, e.g. F12).
The boot menu may be password protected on machines in computer rooms; in that case you need to know the password.
The OS is loaded into RAM during boot, so you can remove the stick once the boot is finished and boot the next machine.
## Network setup
If there is not already an existing network, you need to set it up yourself.
Connect all contestant machines and the admin machine to a network switch with LAN cables.
If you use multiple switches, don't forget to also link the switches together.
If the grader must be accessed over the internet, you can connect the admin machine to WiFi or USB tethering with a phone.
You can then share the internet with the local network.
If you have Gnome, go to Network settings, click on the gear on the Ethernet connection, go to IPv4 tab, and select "Shared to other computers".
If you have docker installed, this doesn't work yet, because docker blocks routing.
You can fix it by running the following commands.
```bash
sudo iptables -I DOCKER-USER -i en+ -j ACCEPT
sudo iptables -I DOCKER-USER -o en+ -j ACCEPT
```
## Admin setup
This guide assumes that the admin machine is running Debian, Ubuntu or similar.
Invent a password for root on the machines.
Create a password hash for it with the following command.
Put the hash in the `contest_root_password` variable in `os/config/config.toml`.
This must be done before building the ISO.
```bash
sudo apt install whois
mkpasswd
```
Install parallel-ssh.
```bash
sudo apt install pssh
```
Edit `contestants.csv` and fill in the username and real name of each contestant.
Run the script to create a CA and client certificates.
```bash
sudo apt install golang-cfssl
./create-certs.sh
```
Edit `local.ssh_config` and create an entry with hostname and IP address for each contestant machine.
You can get the IP address by running `ip addr` in a terminal on the contestant machine.
Edit `hostlist` and add the hostnames of all contestant machines.
Get ssh host keys.
After rebooting machines, delete `local.known_hosts` and run this command again.
```bash
parallel-ssh -x "-F local.ssh_config" -h hostlist -O StrictHostKeyChecking=accept-new true
```
Test time synchronization.
```bash
parallel-ssh -x "-F local.ssh_config" -h hostlist -i date
```
Edit `config-hosts` and `config-nftables.conf` to fill in the correct IP addresses for the grader.
You can look these up with `host contest.soi.ch`.
Edit `contest-lock.json` to fill in the title and start time of the contest.
Apply the configuration to machines.
If the script gets stuck, press Ctrl+C and run it again.
```bash
./configure-machines.sh
```
Assign users to machines.
```bash
./assign-user.sh contestant01 stofl
./assign-user.sh contestant02 binna1
```
Start periodic backup of contestant machines.
```bash
./backup-create.sh timer
```
## Restore machine from backup
Because machines run from RAM, they will lose all files after rebooting.
Therefore, backups are especially important.
To restore a backup to a spare machine, use the following commands.
Prepare in advance by keeping the user to machine assignment nearby for reference, and
replacing `contestant03` in the commands below with the spare machine hostname.
```bash
./assign-user.sh contestant03 <username>
rsync -e "ssh -F local.ssh_config" -av --chown contestant:contestant backups/contestantxx/xxxx/ contestant03:/home/contestant/
```
## Contest lock screen
The contest lock screen is a gnome extension which can lock the screen and show a countdown until the contest starts.
The screen is unlocked when the contest starts.
The lock screen also displays the user name and a title.
It is configured in the file `/etc/contest-lock.json`.
It watches this file, and when it changes the new configuration is instantly applied.
If there is an error in the config file, it will continue to use the old config and print a message.
To see the logs, run this on a contestant machine:
```bash
journalctl -f -o cat /usr/bin/gnome-shell
```
An additional text can be shown with the `message` field. It can contain newlines (`\n`).
In case there is a problem with the contest lock screen and you can't fix it, the backup solution is to turn off `AutomaticLoginEnable` and set a password instead, that you announce when the contest starts.
```bash
parallel-ssh -x "-F local.ssh_config" -h hostlist 'chpasswd <<< contestant:stofl'
```
**Development notes**
Links:
- https://www.codeproject.com/Articles/5271677/How-to-Create-A-GNOME-Extension
- https://gjs.guide/
Regular lock screen (contest-lock is based on this):
- https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/screenShield.js
- https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/unlockDialog.js
Developer commands:
- Open the gnome-shell developer tools: Press Alt+F2, enter `lg`.
## Problems and solutions
Here are solutions to recurring problems.
**User indicator does not appear.**
Fixed by adding the gnome shell version from `gnome-shell --version` to the list of supported versions: `shell-version` in `os/layers/contestant/includes.chroot/usr/share/gnome-shell/extensions/user-indicator@soi.ch/metadata.json`.
The same applies for the contest-lock extension.

View file

@ -0,0 +1,37 @@
#!/usr/bin/python3
# This script sets the display scale factor to 2, for use on 4K displays where everything is too small without scaling.
import dbus
new_scale = 2.0
# https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
bus_name = "org.gnome.Mutter.DisplayConfig"
object_path = "/org/gnome/Mutter/DisplayConfig"
session_bus = dbus.SessionBus()
display_config_object = session_bus.get_object(bus_name, object_path)
display_config_intf = dbus.Interface(display_config_object, dbus_interface=bus_name)
serial, physical_monitors, logical_monitors, properties = display_config_intf.GetCurrentState()
current_mode_id = {}
for ((connector, _, _, _), modes, monitor_properties) in physical_monitors:
for (mode_id, width, height, rate, preferred_scale, supported_scales, mode_properties) in modes:
if mode_properties.get("is-current", False):
current_mode_id[connector] = mode_id
scaled_logical_monitors = [
(layout_x, layout_y, new_scale, transform, primary, [
(connector, current_mode_id[connector], {}) for (connector, _, _, _) in monitors
])
for (layout_x, layout_y, scale, transform, primary, monitors, monitor_properties) in logical_monitors
]
apply_properties = {}
if "layout-mode" in properties and properties.get("supports-changing-layout-mode", False):
apply_properties["layout-mode"] = properties["layout-mode"]
method = 1 # temporary
display_config_intf.ApplyMonitorsConfig(serial, method, scaled_logical_monitors, apply_properties)