Initial commit
This commit is contained in:
commit
968d09e362
|
@ -0,0 +1,5 @@
|
||||||
|
/os/config/
|
||||||
|
/osbuild/
|
||||||
|
|
||||||
|
/contestops/certs/
|
||||||
|
/contestops/local.known_hosts
|
|
@ -0,0 +1,15 @@
|
||||||
|
The Gnome Shell extensions, which can be found under the path
|
||||||
|
/os/layers/contestant/includes.chroot/usr/share/gnome-shell/extensions,
|
||||||
|
are distributed under the terms of the GNU General Public License, version 2 or later.
|
||||||
|
See https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/COPYING for the license text.
|
||||||
|
|
||||||
|
The rest of the repository is distributed under the MIT license, see below.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright © 2024 Jan Schär
|
||||||
|
|
||||||
|
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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"title": "SOI Finals 2024 · Day 1",
|
||||||
|
"message": "",
|
||||||
|
"startTime": "2024-01-01T10:00:00+01:00"
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
stofl;Mouse Stofl
|
||||||
|
binna1;Mouse Binna
|
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
||||||
|
contestant01
|
||||||
|
contestant02
|
||||||
|
contestant03
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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)
|
|
@ -0,0 +1,213 @@
|
||||||
|
#!/bin/env python3
|
||||||
|
|
||||||
|
# Build a live ISO.
|
||||||
|
# Run this from an empty working directory,
|
||||||
|
# or a directory where you have run this before to reuse caches.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import pathlib
|
||||||
|
import tomllib
|
||||||
|
import hashlib
|
||||||
|
import datetime
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
DISTRIBUTION = "bookworm"
|
||||||
|
|
||||||
|
VARIANT_LABEL = {
|
||||||
|
"contestant": "contest",
|
||||||
|
"training-live": "live",
|
||||||
|
"training-installer": "install",
|
||||||
|
}
|
||||||
|
|
||||||
|
VARIANT_BOOT_OPTIONS = {
|
||||||
|
"contestant": [
|
||||||
|
dict(label="SOI contest", cmdline="boot=live toram"),
|
||||||
|
dict(label="SOI contest, run from drive", cmdline="boot=live"),
|
||||||
|
dict(label="SOI contest, fail-safe mode", cmdline="@LB_BOOTAPPEND_LIVE_FAILSAFE@"),
|
||||||
|
],
|
||||||
|
"training-live": [
|
||||||
|
dict(label="SOI live system, run from RAM", cmdline="boot=live toram"),
|
||||||
|
dict(label="SOI live system, run from drive", cmdline="boot=live"),
|
||||||
|
dict(label="SOI live system, fail-safe mode", cmdline="@LB_BOOTAPPEND_LIVE_FAILSAFE@"),
|
||||||
|
],
|
||||||
|
"training-installer": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
VARIANT_EXTRA_LB_CONFIG = {
|
||||||
|
"training-installer": [
|
||||||
|
"--debian-installer", "live",
|
||||||
|
# Linux headers are needed for VirtualBox DKMS.
|
||||||
|
"--linux-packages", "linux-image linux-headers",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
VARIANT_EXTRA_BOOTSTRAP = {
|
||||||
|
# Fasttrack keyring is needed for VirtualBox.
|
||||||
|
"training-installer": ",fasttrack-archive-keyring",
|
||||||
|
}
|
||||||
|
|
||||||
|
DOWNLOADS = [
|
||||||
|
dict(
|
||||||
|
name="soi-header.tar.gz",
|
||||||
|
url="https://git.soi.ch/SOI/soi-header/archive/19ddcef24eb55bdb5ddb817c1d91bfa04c8cb8dd.tar.gz",
|
||||||
|
sha256="38042587982af4e9431aea461e5c345bde358bcc79f0a0eadcf5b3ed77aeb8ab",
|
||||||
|
),
|
||||||
|
# From https://soi.ch/wiki/soi-codeblocks/#install-the-soi-project-template
|
||||||
|
dict(
|
||||||
|
name="soi_template_codeblocks_ubuntu.zip",
|
||||||
|
url="https://soi.ch/media/files/soi_template_codeblocks_ubuntu_RzdvSho.zip",
|
||||||
|
sha256="3f4cae26bbb0cbdfd4cf9f94bfce14988e395f847948d9b349c12d0b980386e9",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def run(args, check=True, **kwargs):
|
||||||
|
print(f"> {' '.join(args)}")
|
||||||
|
subprocess.run(args, check=check, **kwargs)
|
||||||
|
|
||||||
|
def edit_file(filename, fn):
|
||||||
|
with open(filename) as f:
|
||||||
|
content = f.read()
|
||||||
|
content = fn(content)
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
def sha256sum(filename):
|
||||||
|
with open(filename, "rb", buffering=0) as f:
|
||||||
|
return hashlib.file_digest(f, "sha256").hexdigest()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"variant",
|
||||||
|
choices=["contestant", "training-live", "training-installer"],
|
||||||
|
help="Variant to build.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
script_dir = pathlib.Path(__file__).parent.resolve()
|
||||||
|
chroot_includes = pathlib.Path("config/includes.chroot")
|
||||||
|
|
||||||
|
with open(script_dir / "config/config.toml", "rb") as f:
|
||||||
|
config = tomllib.load(f)
|
||||||
|
|
||||||
|
# Remove files generated by previous build, but keep cache.
|
||||||
|
run("lb clean".split())
|
||||||
|
run("rm -rf .build config udeb-build".split())
|
||||||
|
|
||||||
|
# Download files.
|
||||||
|
run("mkdir -p downloads".split())
|
||||||
|
for download in DOWNLOADS:
|
||||||
|
filename = f'downloads/{download["name"]}'
|
||||||
|
try:
|
||||||
|
if sha256sum(filename) == download["sha256"]:
|
||||||
|
continue
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
print(f'> Downloading {download["url"]}')
|
||||||
|
urllib.request.urlretrieve(download["url"], filename)
|
||||||
|
if sha256sum(filename) != download["sha256"]:
|
||||||
|
raise Exception(f"Downloaded file {filename} has wrong hash.")
|
||||||
|
|
||||||
|
# Create base configuration directory.
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"lb", "config",
|
||||||
|
"--clean",
|
||||||
|
"--ignore-system-defaults",
|
||||||
|
"--mode", "debian",
|
||||||
|
"--distribution", DISTRIBUTION,
|
||||||
|
"--archive-areas", "main contrib non-free non-free-firmware",
|
||||||
|
"--firmware-chroot", "false",
|
||||||
|
"--firmware-binary", "false",
|
||||||
|
"--apt-recommends", "false",
|
||||||
|
# isc-dhcp-client and ifupdown are obsoleted by network-manager,
|
||||||
|
# but they still have Priority: important.
|
||||||
|
# We need ca-certificates for fetching https packages repos.
|
||||||
|
"--debootstrap-options", "--exclude=isc-dhcp-client,isc-dhcp-common,ifupdown --include=ca-certificates" +
|
||||||
|
VARIANT_EXTRA_BOOTSTRAP.get(args.variant, ""),
|
||||||
|
"--loadlin", "false",
|
||||||
|
"--iso-volume", f"SOI {VARIANT_LABEL[args.variant]} @ISOVOLUME_TS@",
|
||||||
|
"--bootappend-live", "boot=live toram",
|
||||||
|
]
|
||||||
|
+ VARIANT_EXTRA_LB_CONFIG.get(args.variant, [])
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add our own configuration on top.
|
||||||
|
run(["cp", "-rT", f"{script_dir}/layers/participant", "config"])
|
||||||
|
run(["cp", "-rT", f"{script_dir}/layers/{args.variant}", "config"])
|
||||||
|
|
||||||
|
if args.variant == "training-installer":
|
||||||
|
# Insert admin password into preseed.
|
||||||
|
edit_file("config/includes.installer/preseed.cfg",
|
||||||
|
lambda s: s.replace("@install_admin_password@", config["install_admin_password"]))
|
||||||
|
|
||||||
|
# Copy inventory file.
|
||||||
|
run("mkdir -p config/includes.binary/install".split())
|
||||||
|
run(["cp", f"{script_dir}/config/installer-inventory.txt", "config/includes.binary/install/inventory.txt"])
|
||||||
|
|
||||||
|
# Insert build date in login screen logo.
|
||||||
|
edit_file("config/includes.chroot/usr/local/share/images/login-screen-logo.svg",
|
||||||
|
lambda s: s.replace("@date@", datetime.date.today().isoformat()))
|
||||||
|
|
||||||
|
# Build and install custom udeb packages for installer.
|
||||||
|
run(["cp", "-rT", f"{script_dir}/installer-udeb", "udeb-build"])
|
||||||
|
run("dpkg-buildpackage --build=all".split(), cwd="udeb-build/inventory-hostname")
|
||||||
|
run("mkdir -p config/packages.binary".split())
|
||||||
|
run("cp udeb-build/inventory-hostname_0_all.udeb config/packages.binary/".split())
|
||||||
|
|
||||||
|
# Copy the source lists. The installer deletes everything in sources.list.d,
|
||||||
|
# so we need to copy them somewhere else and restore them after the install.
|
||||||
|
for listpath in pathlib.Path('config/archives').glob('*.list.chroot'):
|
||||||
|
run(["cp", str(listpath), f"config/includes.chroot/usr/local/share/target-sources/{listpath.name.removesuffix('.chroot')}"])
|
||||||
|
elif args.variant == "contestant":
|
||||||
|
# Insert root password into hook script.
|
||||||
|
edit_file("config/hooks/live/2010-contestant.hook.chroot",
|
||||||
|
lambda s: s.replace("@contestant_root_password@", config["contestant_root_password"]))
|
||||||
|
|
||||||
|
# Copy authorized_keys.
|
||||||
|
run("mkdir -p config/includes.chroot/root/.ssh".split())
|
||||||
|
run(["cp", f"{script_dir}/config/contestant_authorized_keys", "config/includes.chroot/root/.ssh/authorized_keys"])
|
||||||
|
|
||||||
|
# Configure boot options.
|
||||||
|
grub_boot_options = '\n'.join(
|
||||||
|
f'menuentry "{option["label"]}" {{\n'
|
||||||
|
f' linux @KERNEL_LIVE@ {option["cmdline"]}\n'
|
||||||
|
f' initrd @INITRD_LIVE@\n'
|
||||||
|
f'}}\n'
|
||||||
|
for option in VARIANT_BOOT_OPTIONS[args.variant]
|
||||||
|
)
|
||||||
|
with open("/usr/share/live/build/bootloaders/grub-pc/grub.cfg") as f:
|
||||||
|
grub_cfg = f.read()
|
||||||
|
grub_cfg = grub_cfg.replace("@LINUX_LIVE@", grub_boot_options)
|
||||||
|
with open("config/bootloaders/grub-pc/grub.cfg", "w") as f:
|
||||||
|
f.write(grub_cfg)
|
||||||
|
|
||||||
|
syslinux_boot_options = ''.join(
|
||||||
|
f"label live-{i}\n"
|
||||||
|
f"\tmenu label {option['label']}\n"
|
||||||
|
+ (f"\tmenu default\n" if i == 0 else "") +
|
||||||
|
f"\tlinux @LINUX@\n"
|
||||||
|
f"\tinitrd @INITRD@\n"
|
||||||
|
f"\tappend {option['cmdline'].replace('@LB_BOOTAPPEND_LIVE_FAILSAFE@', '@APPEND_LIVE_FAILSAFE@')}\n"
|
||||||
|
f"\n"
|
||||||
|
for i, option in enumerate(VARIANT_BOOT_OPTIONS[args.variant])
|
||||||
|
)
|
||||||
|
pathlib.Path("config/bootloaders/syslinux_common").mkdir(parents=True, exist_ok=True)
|
||||||
|
with open("config/bootloaders/syslinux_common/live.cfg.in", "w") as f:
|
||||||
|
f.write(syslinux_boot_options)
|
||||||
|
|
||||||
|
# Install soi header.
|
||||||
|
(chroot_includes / "usr/local/include").mkdir(parents=True, exist_ok=True)
|
||||||
|
run(["tar", "--overwrite", "-xf", f"downloads/soi-header.tar.gz", "-C", f"{chroot_includes}/usr/local/include", "--strip-components=2", "soi-header/include/"])
|
||||||
|
|
||||||
|
# Install codeblocks template.
|
||||||
|
(chroot_includes / "usr/share/codeblocks/templates/wizard").mkdir(parents=True, exist_ok=True)
|
||||||
|
run(["unzip", "-o", f"downloads/soi_template_codeblocks_ubuntu.zip", "-d", f"{chroot_includes}/usr/share/codeblocks/templates/wizard/"])
|
||||||
|
|
||||||
|
# Start the build.
|
||||||
|
run("lb build".split())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Example password: soi
|
||||||
|
# Create a hash with mkpasswd
|
||||||
|
install_admin_password = "$y$j9T$h5VhMd4KkdmbxdZD1gO0N/$1hvwZgO8pQw13Xd6jaNXbtkbqVOC4W/ia/KXOcCGYvB"
|
||||||
|
|
||||||
|
# Example password: soi
|
||||||
|
contestant_root_password = "$y$j9T$h5VhMd4KkdmbxdZD1gO0N/$1hvwZgO8pQw13Xd6jaNXbtkbqVOC4W/ia/KXOcCGYvB"
|
|
@ -0,0 +1 @@
|
||||||
|
# Add your ssh authorized keys here
|
|
@ -0,0 +1,2 @@
|
||||||
|
DEMO123 debian01
|
||||||
|
SOME_SERIAL_NUMBER some-hostname
|
|
@ -0,0 +1,5 @@
|
||||||
|
inventory-hostname (0) UNRELEASED; urgency=low
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- Jan Schär <jan@soi.ch> Fri, 02 Feb 2024 21:37:43 +0000
|
|
@ -0,0 +1,12 @@
|
||||||
|
Source: inventory-hostname
|
||||||
|
Section: debian-installer
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Jan Schär <jan@soi.ch>
|
||||||
|
Build-Depends: debhelper-compat (= 13)
|
||||||
|
|
||||||
|
Package: inventory-hostname
|
||||||
|
Package-Type: udeb
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}
|
||||||
|
XB-Installer-Menu-Item: 1650
|
||||||
|
Description: Configure hostname using inventory list.
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/bin/sh -e
|
||||||
|
. /usr/share/debconf/confmodule
|
||||||
|
db_capb backup
|
||||||
|
|
||||||
|
log() {
|
||||||
|
logger -t inventory-hostname "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
INVENTORY_FILE=/cdrom/install/inventory.txt
|
||||||
|
|
||||||
|
SERIAL=$(cat /sys/class/dmi/id/product_serial)
|
||||||
|
|
||||||
|
if [ -z "$SERIAL" ]; then
|
||||||
|
log "Warning: No serial number found, skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$INVENTORY_FILE" ]; then
|
||||||
|
log "Warning: No inventory file found at $INVENTORY_FILE, skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SET_HOSTNAME=$(sed -n -e "s/^${SERIAL}\s\+\([0-9A-Za-z.-]\+\)\$/\1/p" "$INVENTORY_FILE")
|
||||||
|
|
||||||
|
if [ -z "$SET_HOSTNAME" ]; then
|
||||||
|
db_subst inventory-hostname/get_hostname SERIAL "$SERIAL"
|
||||||
|
if ! db_input high inventory-hostname/get_hostname; then
|
||||||
|
# question not asked
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if ! db_go; then
|
||||||
|
exit 10 # back up
|
||||||
|
fi
|
||||||
|
|
||||||
|
db_get inventory-hostname/get_hostname
|
||||||
|
SET_HOSTNAME="$RET"
|
||||||
|
elif [ "$(echo "$SET_HOSTNAME" | wc -l)" != "1" ]; then
|
||||||
|
log "Warning: Multiple inventory entries found for serial number $SERIAL, skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
db_set netcfg/get_hostname "$SET_HOSTNAME"
|
||||||
|
db_fset netcfg/get_hostname seen true
|
|
@ -0,0 +1,15 @@
|
||||||
|
Template: debian-installer/inventory-hostname/title
|
||||||
|
Type: text
|
||||||
|
Description: Set hostname from inventory list
|
||||||
|
|
||||||
|
Template: inventory-hostname/get_hostname
|
||||||
|
Type: string
|
||||||
|
Default: debian
|
||||||
|
Description: Hostname:
|
||||||
|
WARNING: The serial number of this machine was not found in the inventory list.
|
||||||
|
Are you sure you want to install on this machine?
|
||||||
|
If yes, you may want to add this machine to the inventory.
|
||||||
|
.
|
||||||
|
Serial number: ${SERIAL}
|
||||||
|
.
|
||||||
|
You can manually enter a hostname.
|
|
@ -0,0 +1,3 @@
|
||||||
|
#! /usr/bin/make -f
|
||||||
|
%:
|
||||||
|
dh $@
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Set root password.
|
||||||
|
chpasswd --encrypted <<< 'root:@contestant_root_password@'
|
||||||
|
|
||||||
|
# Set chromium homepage.
|
||||||
|
sed -i 's|"homepage": ".*"|"homepage": "https://contest.soi.ch/"|' /etc/chromium/master_preferences
|
||||||
|
sed -i 's|"homepage_is_newtabpage": true,|"homepage_is_newtabpage": false,|' /etc/chromium/master_preferences
|
||||||
|
|
||||||
|
# Disable Bluetooth.
|
||||||
|
systemctl disable bluetooth.service
|
||||||
|
|
||||||
|
# Disable sleep.
|
||||||
|
systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
|
||||||
|
|
||||||
|
# Disable panels in gnome-control-center.
|
||||||
|
DISABLE_DESKTOP="dpkg-statoverride --force-statoverride-add --update --add root root 640"
|
||||||
|
$DISABLE_DESKTOP /usr/share/applications/gnome-bluetooth-panel.desktop
|
||||||
|
$DISABLE_DESKTOP /usr/share/applications/gnome-online-accounts-panel.desktop
|
||||||
|
$DISABLE_DESKTOP /usr/share/applications/gnome-sharing-panel.desktop
|
||||||
|
|
||||||
|
# Enable the live system configuration script at boot.
|
||||||
|
systemctl enable live-config.service
|
||||||
|
|
||||||
|
# Disable kexec-tools services.
|
||||||
|
# We want to load kexec manually, and execution of kexec is already done by systemd.
|
||||||
|
systemctl disable kexec-load.service
|
||||||
|
systemctl disable kexec.service
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Disable automount
|
||||||
|
[org/gnome/desktop/media-handling]
|
||||||
|
automount = false
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Disable blank screen
|
||||||
|
[org/gnome/desktop/session]
|
||||||
|
idle-delay = uint32 0
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Disable lock on blank screen
|
||||||
|
[org/gnome/desktop/screensaver]
|
||||||
|
lock-enabled = false
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Disable suspend when inactive
|
||||||
|
[org/gnome/settings-daemon/plugins/power]
|
||||||
|
sleep-inactive-ac-type = 'nothing'
|
||||||
|
sleep-inactive-battery-type = 'nothing'
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Disable "Updates available" notifications and auto updates.
|
||||||
|
# Updates which require reboot are useless on live systems,
|
||||||
|
# and other updates would be installed on each boot.
|
||||||
|
[org/gnome/software]
|
||||||
|
allow-updates = false
|
|
@ -0,0 +1,2 @@
|
||||||
|
[org/gnome/shell]
|
||||||
|
enabled-extensions = ['contest-lock@soi.ch', 'user-indicator@soi.ch']
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Disable locking the screen
|
||||||
|
[org/gnome/desktop/lockdown]
|
||||||
|
disable-lock-screen = true
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"policies": {
|
||||||
|
"OverrideFirstRunPage": "",
|
||||||
|
"NoDefaultBookmarks": true,
|
||||||
|
"DisableProfileImport": true,
|
||||||
|
"Preferences": {
|
||||||
|
"datareporting.policy.dataSubmissionPolicyBypassNotification": true,
|
||||||
|
"security.default_personal_cert": "Select Automatically"
|
||||||
|
},
|
||||||
|
"Homepage": {
|
||||||
|
"URL": "https://contest.soi.ch/",
|
||||||
|
"StartPage": "homepage"
|
||||||
|
},
|
||||||
|
"DisplayBookmarksToolbar": true,
|
||||||
|
"Bookmarks": [
|
||||||
|
{
|
||||||
|
"Title": "Contest",
|
||||||
|
"URL": "https://contest.soi.ch/",
|
||||||
|
"Placement": "toolbar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Show a root password prompt for these actions:
|
||||||
|
// - change network settings
|
||||||
|
// - hibernate
|
||||||
|
// - change package download proxy
|
||||||
|
// - mount removable storage, perform other disk operations
|
||||||
|
|
||||||
|
polkit.addRule(function (action, subject) {
|
||||||
|
if (
|
||||||
|
action.id.indexOf("org.freedesktop.ModemManager1.") === 0 ||
|
||||||
|
action.id.indexOf("org.freedesktop.NetworkManager.") === 0 ||
|
||||||
|
action.id === "org.freedesktop.login1.hibernate" ||
|
||||||
|
action.id === "org.freedesktop.packagekit.system-network-proxy-configure" ||
|
||||||
|
action.id.indexOf("org.freedesktop.udisks2.") === 0
|
||||||
|
) {
|
||||||
|
return polkit.Result.AUTH_ADMIN;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
PasswordAuthentication no
|
||||||
|
AllowUsers root
|
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=custom configuration of live system during boot.
|
||||||
|
Before=basic.target
|
||||||
|
After=local-fs.target systemd-tmpfiles-setup.service
|
||||||
|
DefaultDependencies=no
|
||||||
|
ConditionKernelCommandLine=boot=live
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/local/bin/live-config
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=basic.target
|
|
@ -0,0 +1,2 @@
|
||||||
|
[Time]
|
||||||
|
NTP=contest.soi.ch
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This tool installs the client certificate in Firefox and Chromium.
|
||||||
|
|
||||||
|
username="$1"
|
||||||
|
|
||||||
|
userhome="/home/$username"
|
||||||
|
certificate="$userhome/.config/clientcert.p12"
|
||||||
|
|
||||||
|
runuser -u "$username" -- mkdir -p "$userhome/.config"
|
||||||
|
mv "$userhome/clientcert.p12" "$certificate"
|
||||||
|
chown "$username:$username" "$certificate"
|
||||||
|
|
||||||
|
# Delete all Firefox data
|
||||||
|
rm -rf "$userhome/.mozilla/"
|
||||||
|
|
||||||
|
# Create an empty profile
|
||||||
|
runuser -u "$username" -- mkdir -p "$userhome/.mozilla/firefox/main"
|
||||||
|
|
||||||
|
# Tell Firefox to use this profile
|
||||||
|
cat > "$userhome/.mozilla/firefox/profiles.ini" <<EOF
|
||||||
|
[Profile0]
|
||||||
|
Name=main
|
||||||
|
IsRelative=1
|
||||||
|
Path=main
|
||||||
|
|
||||||
|
[General]
|
||||||
|
StartWithLastProfile=1
|
||||||
|
Version=2
|
||||||
|
|
||||||
|
[Install3B6073811A6ABF12]
|
||||||
|
Default=main
|
||||||
|
Locked=1
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chown "$username:$username" "$userhome/.mozilla/firefox/profiles.ini"
|
||||||
|
|
||||||
|
# Create a certificate database
|
||||||
|
runuser -u "$username" -- certutil -d "sql:$userhome/.mozilla/firefox/main/" -N --empty-password
|
||||||
|
|
||||||
|
# Import the client certificate
|
||||||
|
runuser -u "$username" -- pk12util -d "sql:$userhome/.mozilla/firefox/main/" -i "$certificate" -K "" -W ""
|
||||||
|
|
||||||
|
# Do the same for the NSS shared certificate database, used by Chromium
|
||||||
|
rm -rf "$userhome/.pki/"
|
||||||
|
runuser -u "$username" -- mkdir -p "$userhome/.pki/nssdb"
|
||||||
|
runuser -u "$username" -- certutil -d "sql:$userhome/.pki/nssdb/" -N --empty-password
|
||||||
|
runuser -u "$username" -- pk12util -d "sql:$userhome/.pki/nssdb/" -i "$certificate" -K "" -W ""
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
LIVE_HOSTNAME=debian
|
||||||
|
LIVE_USERNAME=contestant
|
||||||
|
LIVE_USER_FULLNAME="Contestant"
|
||||||
|
|
||||||
|
# Set hostname.
|
||||||
|
echo "${LIVE_HOSTNAME}" > /etc/hostname
|
||||||
|
hostname "${LIVE_HOSTNAME}"
|
||||||
|
|
||||||
|
# Create hosts file.
|
||||||
|
cat > /etc/hosts <<EOF
|
||||||
|
127.0.0.1 localhost ${LIVE_HOSTNAME}
|
||||||
|
::1 localhost ip6-localhost ip6-loopback
|
||||||
|
fe00::0 ip6-localnet
|
||||||
|
ff00::0 ip6-mcastprefix
|
||||||
|
ff02::1 ip6-allnodes
|
||||||
|
ff02::2 ip6-allrouters
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create ssh host key.
|
||||||
|
ssh-keygen -q -f /etc/ssh/ssh_host_ed25519_key -N "" -t ed25519
|
||||||
|
|
||||||
|
# Create user.
|
||||||
|
adduser --disabled-password --gecos "$LIVE_USER_FULLNAME" "$LIVE_USERNAME"
|
||||||
|
|
||||||
|
# Enable auto login.
|
||||||
|
sed -i \
|
||||||
|
-e "s/^[# ]*AutomaticLoginEnable *=.*/AutomaticLoginEnable = true/g" \
|
||||||
|
-e "s/^[# ]*AutomaticLogin *=.*/AutomaticLogin = $LIVE_USERNAME/g" \
|
||||||
|
-e "s/^[# ]*TimedLoginEnable *=.*/TimedLoginEnable = true/g" \
|
||||||
|
-e "s/^[# ]*TimedLogin *=.*/TimedLogin = $LIVE_USERNAME/g" \
|
||||||
|
-e "s/^[# ]*TimedLoginDelay *=.*/TimedLoginDelay = 5/g" \
|
||||||
|
/etc/gdm3/daemon.conf
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Reboot with kexec.
|
||||||
|
# This has the advantage that we don't need to go through the system boot menu,
|
||||||
|
# which is especially useful when the boot menu is password protected.
|
||||||
|
# However, we currently can't preserve the squashfs in RAM across kexec,
|
||||||
|
# so the boot USB stick needs to be plugged in before rebooting.
|
||||||
|
|
||||||
|
kexec --kexec-file-syscall --load /vmlinuz --initrd=/initrd.img --append="$(cat /proc/cmdline)"
|
||||||
|
|
||||||
|
if XDG_RUNTIME_DIR="/run/user/$(id -u contestant)" runuser -u contestant -- zenity --question --title="Reboot?" --text="Press Enter after inserting the boot USB stick."
|
||||||
|
then
|
||||||
|
reboot
|
||||||
|
fi
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||||
|
<TITLE>Bookmarks</TITLE>
|
||||||
|
<H1>Bookmarks</H1>
|
||||||
|
<DL><p>
|
||||||
|
<DT><H3 PERSONAL_TOOLBAR_FOLDER="true">Bookmarks Bar</H3>
|
||||||
|
<DL><p>
|
||||||
|
<DT><A HREF="https://contest.soi.ch/" ICON="data:image/png;base64,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">Contest</A>
|
||||||
|
</DL><p>
|
||||||
|
</DL><p>
|
|
@ -0,0 +1,428 @@
|
||||||
|
// Portions of this file are taken from GNOME Shell and adapted.
|
||||||
|
// Because of that, this gnome extension is distributed under
|
||||||
|
// the terms of the GNU General Public License, version 2 or later.
|
||||||
|
|
||||||
|
const {
|
||||||
|
AccountsService, Atk, Clutter, Gio,
|
||||||
|
GLib, Graphene, Meta, Shell, St,
|
||||||
|
} = imports.gi;
|
||||||
|
|
||||||
|
const Background = imports.ui.background;
|
||||||
|
const Layout = imports.ui.layout;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
|
||||||
|
// half of the time for which a frame is displayed
|
||||||
|
const HALF_FRAME_TIME_MS = 8;
|
||||||
|
|
||||||
|
const BLUR_BRIGHTNESS = 0.55;
|
||||||
|
const BLUR_SIGMA = 60;
|
||||||
|
|
||||||
|
const POINTER_HIDE_TIMEOUT = 10 * GLib.USEC_PER_SEC;
|
||||||
|
|
||||||
|
let actor;
|
||||||
|
let lockDialog;
|
||||||
|
let labelCountdown;
|
||||||
|
let labelTitle;
|
||||||
|
let labelMessage;
|
||||||
|
let labelUser;
|
||||||
|
|
||||||
|
const bgManagers = [];
|
||||||
|
let backgroundGroup;
|
||||||
|
|
||||||
|
let cursorTracker;
|
||||||
|
let motionId = 0;
|
||||||
|
let lastMotionTime = 0;
|
||||||
|
let pointerHidden = false;
|
||||||
|
let pointerHideId = 0;
|
||||||
|
|
||||||
|
let user;
|
||||||
|
|
||||||
|
let grab;
|
||||||
|
let countdownTimeoutId = 0;
|
||||||
|
|
||||||
|
let configFile;
|
||||||
|
let configMonitor;
|
||||||
|
let config;
|
||||||
|
let startTime;
|
||||||
|
|
||||||
|
let isExtensionEnabled = false;
|
||||||
|
let isActive = false;
|
||||||
|
let isShellReady = false;
|
||||||
|
let isActiveChanging = false;
|
||||||
|
|
||||||
|
function extLog (msg) {
|
||||||
|
log(`[contest-lock] ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function extLogError (msg) {
|
||||||
|
printerr(`[contest-lock] Error: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig () {
|
||||||
|
configFile.load_contents_async(null, (obj, res) => {
|
||||||
|
// If there is a poblem with the config file, log an error and keep
|
||||||
|
// using the old config.
|
||||||
|
let newConfig;
|
||||||
|
try {
|
||||||
|
const [ok, bytes] = configFile.load_contents_finish(res);
|
||||||
|
// TextDecoder is used in upstream gnome-shell, but not yet
|
||||||
|
// supported in current Debian.
|
||||||
|
const contentStr = imports.byteArray.toString(bytes);
|
||||||
|
//const contentStr = new TextDecoder().decode(bytes);
|
||||||
|
newConfig = JSON.parse(contentStr);
|
||||||
|
} catch (err) {
|
||||||
|
logError(err, '[contest-lock] config file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(typeof newConfig === 'object' && newConfig != null)) {
|
||||||
|
extLogError('config file: invalid format');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof newConfig.title !== 'string') {
|
||||||
|
extLogError('config file: "title" must be a string');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof newConfig.message !== 'string') {
|
||||||
|
extLogError('config file: "message" must be a string');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof newConfig.startTime !== 'string' ||
|
||||||
|
!/^\d{4,}-\d\d-\d\dT\d\d:\d\d:\d\d\+\d\d:\d\d$/.test(newConfig.startTime)
|
||||||
|
) {
|
||||||
|
extLogError('config file: "startTime" must be a string with format 0000-00-00T00:00:00+00:00');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
extLog('Loaded new config.')
|
||||||
|
config = newConfig;
|
||||||
|
startTime = (new Date(newConfig.startTime)).getTime();
|
||||||
|
|
||||||
|
updateConfig();
|
||||||
|
syncActive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncActive () {
|
||||||
|
if (isActiveChanging) return;
|
||||||
|
let beforeStart = false;
|
||||||
|
if (startTime != null) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeToStart = startTime - now.getTime() - HALF_FRAME_TIME_MS;
|
||||||
|
beforeStart = timeToStart > 0;
|
||||||
|
}
|
||||||
|
// ignore disable event when active
|
||||||
|
if (beforeStart && isShellReady && (isExtensionEnabled || isActive)) {
|
||||||
|
activate();
|
||||||
|
} else {
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConfig () {
|
||||||
|
if (labelTitle != null) {
|
||||||
|
labelTitle.text = config.title;
|
||||||
|
}
|
||||||
|
if (labelMessage != null) {
|
||||||
|
labelMessage.text = config.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser () {
|
||||||
|
if (labelUser != null) {
|
||||||
|
const realName = user.get_real_name();
|
||||||
|
if (realName != null) labelUser.text = realName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCountdown () {
|
||||||
|
countdownTimeoutId = 0;
|
||||||
|
const now = new Date();
|
||||||
|
const nowTime = now.getTime() + HALF_FRAME_TIME_MS;
|
||||||
|
const timeToStart = startTime - nowTime;
|
||||||
|
const beforeStart = timeToStart > 0;
|
||||||
|
if (!beforeStart) {
|
||||||
|
deactivate();
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
const allSecondsToStart = Math.floor(timeToStart / 1000);
|
||||||
|
const secondsToStart = allSecondsToStart % 60
|
||||||
|
const allMinutesToStart = Math.floor(allSecondsToStart / 60);
|
||||||
|
const minutesToStart = allMinutesToStart % 60;
|
||||||
|
const hoursToStart = Math.floor(allMinutesToStart / 60);
|
||||||
|
|
||||||
|
let hoursString = '';
|
||||||
|
if (hoursToStart !== 0) hoursString = `${hoursToStart}∶`;
|
||||||
|
labelCountdown.text = hoursString +
|
||||||
|
minutesToStart.toString().padStart(2, '0') + '∶' +
|
||||||
|
secondsToStart.toString().padStart(2, '0');
|
||||||
|
|
||||||
|
// Force a redraw of the entire label widget. Without this, there sometimes
|
||||||
|
// appears a small artifact to the right of the text, which is only visible
|
||||||
|
// every other second. This seems to be a bug in the rendering engine itself.
|
||||||
|
labelCountdown.queue_redraw();
|
||||||
|
|
||||||
|
const nextUpdateTime = 1000 - nowTime % 1000
|
||||||
|
countdownTimeoutId = GLib.timeout_add(
|
||||||
|
GLib.PRIORITY_HIGH,
|
||||||
|
nextUpdateTime,
|
||||||
|
updateCountdown
|
||||||
|
);
|
||||||
|
GLib.Source.set_name_by_id(countdownTimeoutId, '[contest-lock] updateCountdown');
|
||||||
|
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBackgrounds () {
|
||||||
|
if (!isActive) return;
|
||||||
|
while (bgManagers.length) bgManagers.pop().destroy();
|
||||||
|
backgroundGroup.destroy_all_children();
|
||||||
|
|
||||||
|
for (let monitorIndex = 0; monitorIndex < Main.layoutManager.monitors.length; monitorIndex++) {
|
||||||
|
const monitor = Main.layoutManager.monitors[monitorIndex];
|
||||||
|
const widget = new St.Widget({
|
||||||
|
style_class: 'screen-shield-background',
|
||||||
|
x: monitor.x,
|
||||||
|
y: monitor.y,
|
||||||
|
width: monitor.width,
|
||||||
|
height: monitor.height,
|
||||||
|
effect: new Shell.BlurEffect({
|
||||||
|
name: 'blur',
|
||||||
|
brightness: BLUR_BRIGHTNESS,
|
||||||
|
sigma: BLUR_SIGMA,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const bgManager = new Background.BackgroundManager({
|
||||||
|
container: widget,
|
||||||
|
monitorIndex,
|
||||||
|
controlPosition: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
bgManagers.push(bgManager);
|
||||||
|
|
||||||
|
backgroundGroup.add_child(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerHideTimer () {
|
||||||
|
if (pointerHideId !== 0) {
|
||||||
|
GLib.source_remove(pointerHideId);
|
||||||
|
pointerHideId = 0;
|
||||||
|
}
|
||||||
|
if (!isActive) return GLib.SOURCE_REMOVE;
|
||||||
|
|
||||||
|
const timeToHide = lastMotionTime + POINTER_HIDE_TIMEOUT - GLib.get_monotonic_time();
|
||||||
|
if (timeToHide <= 0) {
|
||||||
|
cursorTracker.set_pointer_visible(false);
|
||||||
|
pointerHidden = true;
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerHideId = GLib.timeout_add(
|
||||||
|
GLib.PRIORITY_HIGH,
|
||||||
|
timeToHide / 1000 + 20,
|
||||||
|
pointerHideTimer
|
||||||
|
);
|
||||||
|
GLib.Source.set_name_by_id(pointerHideId, '[contest-lock] pointerHide');
|
||||||
|
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activate () {
|
||||||
|
if (isActive) return;
|
||||||
|
isActiveChanging = true;
|
||||||
|
isActive = true;
|
||||||
|
|
||||||
|
grab = Main.pushModal(Main.uiGroup, { actionMode: Shell.ActionMode.LOCK_SCREEN });
|
||||||
|
if (typeof grab === 'boolean') { // gnome 38
|
||||||
|
if (!grab) {
|
||||||
|
grab = Main.pushModal(Main.uiGroup, {
|
||||||
|
options: Meta.ModalOptions.POINTER_ALREADY_GRABBED,
|
||||||
|
actionMode: Shell.ActionMode.LOCK_SCREEN
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!grab) {
|
||||||
|
extLogError('Failed to activate: Could not obtain keyboard grab.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grab = Main.uiGroup;
|
||||||
|
} else if ((grab.get_seat_state() & Clutter.GrabState.KEYBOARD) === 0) {
|
||||||
|
Main.popModal(grab);
|
||||||
|
grab = null;
|
||||||
|
extLogError('Failed to activate: Could not obtain keyboard grab.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.show();
|
||||||
|
|
||||||
|
Main.sessionMode.pushMode('unlock-dialog');
|
||||||
|
|
||||||
|
backgroundGroup = new Clutter.Actor();
|
||||||
|
|
||||||
|
motionId = global.stage.connect('captured-event', (stage, event) => {
|
||||||
|
if (event.type() === Clutter.EventType.MOTION) {
|
||||||
|
lastMotionTime = GLib.get_monotonic_time();
|
||||||
|
if (pointerHidden) {
|
||||||
|
cursorTracker.set_pointer_visible(true);
|
||||||
|
pointerHidden = false;
|
||||||
|
pointerHideTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Clutter.EVENT_PROPAGATE;
|
||||||
|
});
|
||||||
|
cursorTracker.set_pointer_visible(false);
|
||||||
|
pointerHidden = true;
|
||||||
|
|
||||||
|
labelCountdown = new St.Label({
|
||||||
|
style_class: 'contest-lock-countdown',
|
||||||
|
x_align: Clutter.ActorAlign.CENTER,
|
||||||
|
});
|
||||||
|
labelTitle = new St.Label({
|
||||||
|
style_class: 'contest-lock-title',
|
||||||
|
x_align: Clutter.ActorAlign.CENTER,
|
||||||
|
});
|
||||||
|
labelMessage = new St.Label({
|
||||||
|
style_class: 'contest-lock-message',
|
||||||
|
x_align: Clutter.ActorAlign.CENTER,
|
||||||
|
});
|
||||||
|
labelUser = new St.Label({
|
||||||
|
style_class: 'contest-lock-user',
|
||||||
|
x_align: Clutter.ActorAlign.CENTER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stack = new St.BoxLayout({
|
||||||
|
style_class: 'contest-lock-stack',
|
||||||
|
vertical: true,
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true,
|
||||||
|
x_align: Clutter.ActorAlign.CENTER,
|
||||||
|
y_align: Clutter.ActorAlign.CENTER,
|
||||||
|
});
|
||||||
|
stack.add_child(labelUser);
|
||||||
|
stack.add_child(labelCountdown);
|
||||||
|
stack.add_child(labelTitle);
|
||||||
|
stack.add_child(labelMessage);
|
||||||
|
|
||||||
|
const mainBox = new St.BoxLayout();
|
||||||
|
mainBox.add_constraint(new Layout.MonitorConstraint({ primary: true }));
|
||||||
|
mainBox.add_child(stack);
|
||||||
|
|
||||||
|
lockDialog = new St.Widget({
|
||||||
|
name: 'contestLockDialog',
|
||||||
|
accessible_role: Atk.Role.WINDOW,
|
||||||
|
visible: false,
|
||||||
|
reactive: true,
|
||||||
|
can_focus: true,
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true,
|
||||||
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
||||||
|
});
|
||||||
|
lockDialog.add_child(backgroundGroup);
|
||||||
|
lockDialog.add_child(mainBox);
|
||||||
|
|
||||||
|
updateConfig();
|
||||||
|
updateUser();
|
||||||
|
updateCountdown();
|
||||||
|
updateBackgrounds();
|
||||||
|
|
||||||
|
// countdown may have just expired before we called updateCountdown
|
||||||
|
if (!isActive) return;
|
||||||
|
|
||||||
|
actor.add_child(lockDialog);
|
||||||
|
lockDialog.show();
|
||||||
|
|
||||||
|
extLog('Activated.')
|
||||||
|
isActiveChanging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivate () {
|
||||||
|
if (!isActive) return;
|
||||||
|
isActiveChanging = true;
|
||||||
|
isActive = false;
|
||||||
|
|
||||||
|
if (Main.sessionMode.currentMode === 'unlock-dialog') {
|
||||||
|
Main.sessionMode.popMode('unlock-dialog');
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.popModal(grab);
|
||||||
|
grab = null;
|
||||||
|
|
||||||
|
if (countdownTimeoutId !== 0) {
|
||||||
|
GLib.source_remove(countdownTimeoutId);
|
||||||
|
countdownTimeoutId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.hide();
|
||||||
|
labelCountdown = null;
|
||||||
|
labelTitle = null;
|
||||||
|
labelMessage = null;
|
||||||
|
labelUser = null;
|
||||||
|
while (bgManagers.length) bgManagers.pop().destroy();
|
||||||
|
lockDialog.destroy();
|
||||||
|
lockDialog = null;
|
||||||
|
|
||||||
|
if (motionId) {
|
||||||
|
global.stage.disconnect(motionId);
|
||||||
|
motionId = 0;
|
||||||
|
}
|
||||||
|
cursorTracker.set_pointer_visible(true);
|
||||||
|
|
||||||
|
extLog('Deactivated.');
|
||||||
|
isActiveChanging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init (extension) {
|
||||||
|
actor = Main.layoutManager.screenShieldGroup;
|
||||||
|
|
||||||
|
const userName = GLib.get_user_name();
|
||||||
|
user = AccountsService.UserManager.get_default().get_user(userName);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
user.connect('notify::is-loaded', updateUser);
|
||||||
|
user.connect('changed', updateUser);
|
||||||
|
updateUser();
|
||||||
|
|
||||||
|
Main.layoutManager.connect('monitors-changed', updateBackgrounds);
|
||||||
|
|
||||||
|
cursorTracker = Meta.CursorTracker.get_for_display(global.display);
|
||||||
|
|
||||||
|
if (!Main.layoutManager._startingUp) {
|
||||||
|
isShellReady = true;
|
||||||
|
} else {
|
||||||
|
Main.layoutManager.connect('startup-complete', () => {
|
||||||
|
isShellReady = true;
|
||||||
|
syncActive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: When we drop compatibility with gnome <42, remove this code,
|
||||||
|
// rename the stylesheet back to stylesheet.css (so that it is loaded
|
||||||
|
// by the extension system) and add a session-modes property which
|
||||||
|
// includes unlock-dialog to metadata.json.
|
||||||
|
const theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
|
||||||
|
const stylesheetFile = extension.dir.get_child('stylesheet-always.css');
|
||||||
|
theme.load_stylesheet(stylesheetFile);
|
||||||
|
|
||||||
|
// TODO: When we drop compatibility with gnome <42, remove this code.
|
||||||
|
// gnome 38 has a bug that causes extensions to break when running
|
||||||
|
// `dconf update` while the screen is locked.
|
||||||
|
Main.extensionManager.reloadExtension = function () {};
|
||||||
|
|
||||||
|
configFile = Gio.File.new_for_path('/etc/contest-lock.json');
|
||||||
|
configMonitor = configFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
|
||||||
|
configMonitor.set_rate_limit(1000);
|
||||||
|
configMonitor.connect('changed', loadConfig);
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable () {
|
||||||
|
isExtensionEnabled = true;
|
||||||
|
syncActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable () {
|
||||||
|
isExtensionEnabled = false;
|
||||||
|
syncActive();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extension-id": "contest-lock",
|
||||||
|
"uuid": "contest-lock@soi.ch",
|
||||||
|
"name": "Contest lock screen",
|
||||||
|
"description": "A custom lock screen for contests.",
|
||||||
|
"shell-version": [ "3.38", "42", "43" ],
|
||||||
|
"url": ""
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
.contest-lock-stack {
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
spacing: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contest-lock-countdown {
|
||||||
|
font-size: 64pt;
|
||||||
|
font-weight: 300;
|
||||||
|
font-feature-settings: "tnum"; /* tabular figures */
|
||||||
|
}
|
||||||
|
|
||||||
|
.contest-lock-title {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contest-lock-user {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contest-lock-message {
|
||||||
|
padding-top: 24px;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
const { St, Clutter, GLib, Gio, AccountsService } = imports.gi;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
|
||||||
|
let panelBin;
|
||||||
|
let userLabel;
|
||||||
|
|
||||||
|
let user;
|
||||||
|
|
||||||
|
function updateUser () {
|
||||||
|
const realName = user.get_real_name();
|
||||||
|
if (realName != null) userLabel.text = realName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init () {
|
||||||
|
panelBin = new St.Bin({
|
||||||
|
style_class: 'panel-bin',
|
||||||
|
});
|
||||||
|
userLabel = new St.Label({
|
||||||
|
text: 'No user',
|
||||||
|
y_align: Clutter.ActorAlign.CENTER,
|
||||||
|
});
|
||||||
|
panelBin.set_child(userLabel);
|
||||||
|
|
||||||
|
const userName = GLib.get_user_name();
|
||||||
|
user = AccountsService.UserManager.get_default().get_user(userName);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
user.connect('notify::is-loaded', updateUser);
|
||||||
|
user.connect('changed', updateUser);
|
||||||
|
updateUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable () {
|
||||||
|
Main.panel._rightBox.insert_child_at_index(panelBin, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable () {
|
||||||
|
Main.panel._rightBox.remove_child(panelBin);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extension-id": "user-indicator",
|
||||||
|
"uuid": "user-indicator@soi.ch",
|
||||||
|
"name": "User indicator",
|
||||||
|
"description": "Shows the user's real name in the top bar.",
|
||||||
|
"shell-version": [ "3.38", "42", "43" ],
|
||||||
|
"url": ""
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
.panel-bin {
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Remote access
|
||||||
|
openssh-server
|
||||||
|
rsync
|
||||||
|
|
||||||
|
# Firewall
|
||||||
|
nftables
|
||||||
|
|
||||||
|
# Reboot with kexec
|
||||||
|
kexec-tools
|
||||||
|
|
||||||
|
# For importing client certificate
|
||||||
|
libnss3-tools
|
|
@ -0,0 +1,62 @@
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
McvDuGKIXk111Z3nrF4DeIIc/U6ICQ==
|
||||||
|
=CCk2
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -0,0 +1 @@
|
||||||
|
deb http://download.sublimetext.com/ apt/stable/
|
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Version: GnuPG v1.4.7 (GNU/Linux)
|
||||||
|
|
||||||
|
mQENBFYxWIwBCADAKoZhZlJxGNGWzqV+1OG1xiQeoowKhssGAKvd+buXCGISZJwT
|
||||||
|
LXZqIcIiLP7pqdcZWtE9bSc7yBY2MalDp9Liu0KekywQ6VVX1T72NPf5Ev6x6DLV
|
||||||
|
7aVWsCzUAF+eb7DC9fPuFLEdxmOEYoPjzrQ7cCnSV4JQxAqhU4T6OjbvRazGl3ag
|
||||||
|
OeizPXmRljMtUUttHQZnRhtlzkmwIrUivbfFPD+fEoHJ1+uIdfOzZX8/oKHKLe2j
|
||||||
|
H632kvsNzJFlROVvGLYAk2WRcLu+RjjggixhwiB+Mu/A8Tf4V6b+YppS44q8EvVr
|
||||||
|
M+QvY7LNSOffSO6Slsy9oisGTdfE39nC7pVRABEBAAG0N01pY3Jvc29mdCAoUmVs
|
||||||
|
ZWFzZSBzaWduaW5nKSA8Z3Bnc2VjdXJpdHlAbWljcm9zb2Z0LmNvbT6JATUEEwEC
|
||||||
|
AB8FAlYxWIwCGwMGCwkIBwMCBBUCCAMDFgIBAh4BAheAAAoJEOs+lK2+EinPGpsH
|
||||||
|
/32vKy29Hg51H9dfFJMx0/a/F+5vKeCeVqimvyTM04C+XENNuSbYZ3eRPHGHFLqe
|
||||||
|
MNGxsfb7C7ZxEeW7J/vSzRgHxm7ZvESisUYRFq2sgkJ+HFERNrqfci45bdhmrUsy
|
||||||
|
7SWw9ybxdFOkuQoyKD3tBmiGfONQMlBaOMWdAsic965rvJsd5zYaZZFI1UwTkFXV
|
||||||
|
KJt3bp3Ngn1vEYXwijGTa+FXz6GLHueJwF0I7ug34DgUkAFvAs8Hacr2DRYxL5RJ
|
||||||
|
XdNgj4Jd2/g6T9InmWT0hASljur+dJnzNiNCkbn9KbX7J/qK1IbR8y560yRmFsU+
|
||||||
|
NdCFTW7wY0Fb1fWJ+/KTsC4=
|
||||||
|
=J6gs
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -0,0 +1 @@
|
||||||
|
deb http://packages.microsoft.com/repos/code stable main
|
|
@ -0,0 +1,31 @@
|
||||||
|
set timeout=5
|
||||||
|
|
||||||
|
# Everything below is copied from the default config.
|
||||||
|
|
||||||
|
set default=0
|
||||||
|
|
||||||
|
if [ x$feature_default_font_path = xy ] ; then
|
||||||
|
font=unicode
|
||||||
|
else
|
||||||
|
font=$prefix/unicode.pf2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copied from the netinst image
|
||||||
|
if loadfont $font ; then
|
||||||
|
set gfxmode=800x600
|
||||||
|
set gfxpayload=keep
|
||||||
|
insmod efi_gop
|
||||||
|
insmod efi_uga
|
||||||
|
insmod video_bochs
|
||||||
|
insmod video_cirrus
|
||||||
|
else
|
||||||
|
set gfxmode=auto
|
||||||
|
insmod all_video
|
||||||
|
fi
|
||||||
|
|
||||||
|
insmod gfxterm
|
||||||
|
insmod png
|
||||||
|
|
||||||
|
source /boot/grub/theme.cfg
|
||||||
|
|
||||||
|
terminal_output gfxterm
|
|
@ -0,0 +1,33 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">
|
||||||
|
<rect x="0" y="0" width="640" height="480" fill="#000"/>
|
||||||
|
<g transform="translate(90, 90) scale(4)">
|
||||||
|
<circle r="13.75" fill="none" stroke="#303030" stroke-width="2.5"/>
|
||||||
|
<path fill="none" stroke="#1eadf5" stroke-width="2" d="M7.6121 0 0 -7.6121 13.2727 -15.7298M-7.6121 0 7.6121 0 0 7.6121 -7.6121 0 -14.3693 13.5411"/>
|
||||||
|
<g fill="#1862ff">
|
||||||
|
<circle r="3" cy="7.6121"/>
|
||||||
|
<circle r="3" cx="7.6121"/>
|
||||||
|
<circle r="3" cy="-7.6121"/>
|
||||||
|
<circle r="3" cx="-7.6121"/>
|
||||||
|
<circle r="3" cx="13.2727" cy="-15.7298"/>
|
||||||
|
<circle r="3" cx="-14.3693" cy="13.5411"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g style="font-family: 'DejaVu Sans'; fill: #fff;">
|
||||||
|
<text style="font-weight:bold;font-size:20px;"
|
||||||
|
x="191" y="48">@PROJECT@ @VERSION@ (@DISTRIBUTION@)</text>
|
||||||
|
<text style="font-weight:bold;font-size:20px;"
|
||||||
|
x="191" y="68">@ARCHITECTURE@</text>
|
||||||
|
<text style="font-weight:bold;font-size:16px;"
|
||||||
|
x="191" y="108">Built: @YEAR@-@MONTH@-@DAY@ @HOUR@:@MINUTE@:@SECOND@ @TIMEZONE@</text>
|
||||||
|
<text style="font-weight:normal;font-size:10px;"
|
||||||
|
x="191" y="140">linux: @LINUX_VERSIONS@</text>
|
||||||
|
<!--<text style="font-weight:normal;font-size:10px;"
|
||||||
|
x="191" y="156">live-build: @LIVE_BUILD_VERSION@</text>
|
||||||
|
<text style="font-weight:normal;font-size:10px;"
|
||||||
|
x="191" y="172">live-boot: @LIVE_BOOT_VERSION@</text>
|
||||||
|
<text style="font-weight:normal;font-size:10px;"
|
||||||
|
x="191" y="188">live-config: @LIVE_CONFIG_VERSION@</text>
|
||||||
|
<text style="font-weight:normal;font-size:10px;"
|
||||||
|
x="191" y="204">live-tools: @LIVE_TOOLS_VERSION@</text>-->
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,47 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Update dconf database after having put files in /etc/dconf/.
|
||||||
|
dconf update
|
||||||
|
|
||||||
|
# Configure timezone.
|
||||||
|
TIMEZONE=Europe/Zurich
|
||||||
|
echo "$TIMEZONE" > /etc/timezone
|
||||||
|
ln -sf /usr/share/zoneinfo/"$TIMEZONE" /etc/localtime
|
||||||
|
|
||||||
|
# Install VS Code extensions.
|
||||||
|
VSCODE_EXTENSIONS="ms-python.python ms-vscode.cpptools swissolyinfo.soicode"
|
||||||
|
mkdir /etc/skel/.vscode
|
||||||
|
chown nobody:nogroup /etc/skel/.vscode
|
||||||
|
for ext in $VSCODE_EXTENSIONS; do
|
||||||
|
runuser -u nobody -- code --user-data-dir=/tmp/vsc.tmp \
|
||||||
|
--extensions-dir=/etc/skel/.vscode/extensions \
|
||||||
|
--install-extension="$ext"
|
||||||
|
done
|
||||||
|
chown -R root:root /etc/skel/.vscode
|
||||||
|
rm -rf /tmp/vsc.tmp
|
||||||
|
|
||||||
|
# Enable codeblocks template.
|
||||||
|
sed -i 's|// project wizards|RegisterWizard(wizProject, _T("soi"), _T("A SOI task"), _T("Console"));|' /usr/share/codeblocks/templates/wizard/config.script
|
||||||
|
|
||||||
|
# Add a default keyring to avoid a prompt to create one when launching Chromium.
|
||||||
|
mkdir -p /etc/skel/.local/share/keyrings/
|
||||||
|
chmod og= /etc/skel/.local/share/keyrings/
|
||||||
|
echo -n "Default_keyring" > /etc/skel/.local/share/keyrings/default
|
||||||
|
cat > /etc/skel/.local/share/keyrings/Default_keyring.keyring <<EOF
|
||||||
|
[keyring]
|
||||||
|
display-name=Default keyring
|
||||||
|
ctime=0
|
||||||
|
mtime=0
|
||||||
|
lock-on-idle=false
|
||||||
|
lock-after=false
|
||||||
|
EOF
|
||||||
|
chmod og= /etc/skel/.local/share/keyrings/Default_keyring.keyring
|
||||||
|
|
||||||
|
# Patch bug in live-boot.
|
||||||
|
# This is fixed upstream: https://salsa.debian.org/live-team/live-boot/-/commit/2cb049fb7502d11f344d14c567aab2592f19e77b
|
||||||
|
# Once we pull in the fix through an upgrade, we can remove the patch here.
|
||||||
|
if [ -f /lib/live/boot/9990-toram-todisk.sh ]; then
|
||||||
|
sed -i 's|dev="/dev/shm"|dev="tmpfs"|g' /lib/live/boot/9990-toram-todisk.sh
|
||||||
|
fi
|
|
@ -0,0 +1,5 @@
|
||||||
|
[org/gnome/desktop/background]
|
||||||
|
picture-uri = 'file:///usr/local/share/backgrounds/wallpaper-soi.svg'
|
||||||
|
picture-uri-dark = 'file:///usr/local/share/backgrounds/wallpaper-soi-dark.svg'
|
||||||
|
picture-options = 'zoom'
|
||||||
|
primary-color = '#e6e6e6'
|
|
@ -0,0 +1,2 @@
|
||||||
|
[org/gnome/shell]
|
||||||
|
favorite-apps = ['firefox-esr.desktop', 'gnome-terminal.desktop', 'nautilus.desktop']
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Configure the default keyboard layouts shown in the switcher
|
||||||
|
# According to Wikipedia, the Italian-speaking part of Switzerland also uses the ch+fr layout.
|
||||||
|
# The first option is the default.
|
||||||
|
[org/gnome/desktop/input-sources]
|
||||||
|
sources = [('xkb', 'ch'), ('xkb', 'ch+fr'), ('xkb', 'us')]
|
|
@ -0,0 +1,2 @@
|
||||||
|
user-db:user
|
||||||
|
system-db:local
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-80 -80 160 160" width="3840" height="3840">
|
||||||
|
<title>Swiss Olympiad in Informatics</title>
|
||||||
|
<rect fill="#2a2a2a" x="-100" y="-100" width="200" height="200"/>
|
||||||
|
<circle r="13.75" fill="none" stroke="#101010" stroke-width="2.5"/>
|
||||||
|
<path fill="none" stroke="#1eadf5" stroke-width="2" d="M7.6121 0 0 -7.6121 13.2727 -15.7298M-7.6121 0 7.6121 0 0 7.6121 -7.6121 0 -14.3693 13.5411"/>
|
||||||
|
<g fill="#1862ff">
|
||||||
|
<circle r="3" cy="7.6121"/>
|
||||||
|
<circle r="3" cx="7.6121"/>
|
||||||
|
<circle r="3" cy="-7.6121"/>
|
||||||
|
<circle r="3" cx="-7.6121"/>
|
||||||
|
<circle r="3" cx="13.2727" cy="-15.7298"/>
|
||||||
|
<circle r="3" cx="-14.3693" cy="13.5411"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 654 B |
|
@ -0,0 +1,14 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-80 -80 160 160" width="3840" height="3840">
|
||||||
|
<title>Swiss Olympiad in Informatics</title>
|
||||||
|
<rect fill="#e6e6e6" x="-100" y="-100" width="200" height="200"/>
|
||||||
|
<circle r="13.75" fill="none" stroke="#303030" stroke-width="2.5"/>
|
||||||
|
<path fill="none" stroke="#1eadf5" stroke-width="2" d="M7.6121 0 0 -7.6121 13.2727 -15.7298M-7.6121 0 7.6121 0 0 7.6121 -7.6121 0 -14.3693 13.5411"/>
|
||||||
|
<g fill="#1862ff">
|
||||||
|
<circle r="3" cy="7.6121"/>
|
||||||
|
<circle r="3" cx="7.6121"/>
|
||||||
|
<circle r="3" cy="-7.6121"/>
|
||||||
|
<circle r="3" cx="-7.6121"/>
|
||||||
|
<circle r="3" cx="13.2727" cy="-15.7298"/>
|
||||||
|
<circle r="3" cx="-14.3693" cy="13.5411"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 654 B |
|
@ -0,0 +1,3 @@
|
||||||
|
live-boot
|
||||||
|
|
||||||
|
# This file overrides a default list to remove live-config.
|
|
@ -0,0 +1,41 @@
|
||||||
|
# firmware
|
||||||
|
firmware-linux amd64-microcode intel-microcode
|
||||||
|
firmware-iwlwifi firmware-brcm80211 firmware-realtek
|
||||||
|
|
||||||
|
# system
|
||||||
|
systemd-timesyncd
|
||||||
|
locales
|
||||||
|
zstd
|
||||||
|
wpasupplicant
|
||||||
|
wireless-regdb
|
||||||
|
|
||||||
|
# desktop
|
||||||
|
gnome-core
|
||||||
|
xdg-user-dirs-gtk
|
||||||
|
network-manager-gnome
|
||||||
|
# needed for ejecting UBS sticks in nautilus
|
||||||
|
eject
|
||||||
|
|
||||||
|
# shell utilities
|
||||||
|
htop unzip
|
||||||
|
|
||||||
|
# software for participants
|
||||||
|
firefox-esr chromium-l10n
|
||||||
|
codeblocks emacs geany gedit joe kate kdevelop nano vim vim-gtk3
|
||||||
|
gcc g++ gdb ddd valgrind python3 pypy3
|
||||||
|
evince gnome-terminal konsole xterm byobu make cmake
|
||||||
|
nautilus-extension-gnome-terminal
|
||||||
|
file-roller
|
||||||
|
# for drawing on screenshots
|
||||||
|
drawing
|
||||||
|
# documentation
|
||||||
|
info manpages-dev gcc-doc gdb-doc
|
||||||
|
# from third-party repositories
|
||||||
|
sublime-text code
|
||||||
|
# requested by participants (gnome-tweaks can be used e.g. to change the function of Caps Lock key)
|
||||||
|
gnome-tweaks fonts-firacode
|
||||||
|
|
||||||
|
# translations
|
||||||
|
manpages-de manpages-fr manpages-it
|
||||||
|
gcc-12-locales
|
||||||
|
firefox-esr-l10n-de firefox-esr-l10n-fr firefox-esr-l10n-it
|
|
@ -0,0 +1 @@
|
||||||
|
locales locales/locales_to_be_generated multiselect en_US.UTF-8 UTF-8, de_CH.UTF-8 UTF-8, fr_CH.UTF-8 UTF-8, it_CH.UTF-8 UTF-8
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Fasttrack is needed for VirtualBox.
|
||||||
|
deb https://fasttrack.debian.net/debian-fasttrack/ @DISTRIBUTION@-fasttrack main contrib
|
|
@ -0,0 +1 @@
|
||||||
|
ethdetect
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Install the noauth PAM profile.
|
||||||
|
groupadd noauth
|
||||||
|
pam-auth-update --enable noauth
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "inventory-hostname" >> .disk/udeb_include
|
|
@ -0,0 +1,2 @@
|
||||||
|
[org/gnome/login-screen]
|
||||||
|
logo = '/usr/local/share/images/login-screen-logo.svg'
|
|
@ -0,0 +1,3 @@
|
||||||
|
user-db:user
|
||||||
|
system-db:gdm
|
||||||
|
file-db:/usr/share/gdm/greeter-dconf-defaults
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Connecting to a WiFi in the gnome-shell quick settings prompts for an admin
|
||||||
|
// password without this rule.
|
||||||
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/7378
|
||||||
|
|
||||||
|
polkit.addRule(function (action, subject) {
|
||||||
|
if (action.id === "org.freedesktop.NetworkManager.settings.modify.system") {
|
||||||
|
return polkit.Result.YES;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Set up apt lists.
|
||||||
|
cp -rT /usr/local/share/target-sources /etc/apt/sources.list.d
|
||||||
|
rm /etc/apt/sources.list
|
||||||
|
|
||||||
|
USERNAME=soi
|
||||||
|
USER_FULLNAME="SOI"
|
||||||
|
# Password: soi
|
||||||
|
USER_PASSWORD='$y$j9T$h5VhMd4KkdmbxdZD1gO0N/$1hvwZgO8pQw13Xd6jaNXbtkbqVOC4W/ia/KXOcCGYvB'
|
||||||
|
|
||||||
|
# Create user.
|
||||||
|
adduser --disabled-password --gecos "$USER_FULLNAME" "$USERNAME"
|
||||||
|
usermod -p "$USER_PASSWORD" "$USERNAME"
|
||||||
|
adduser "$USERNAME" noauth
|
|
@ -0,0 +1,18 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-260 -20 520 40" width="1300" height="100">
|
||||||
|
<title>Swiss Olympiad in Informatics</title>
|
||||||
|
<circle r="13.75" fill="none" stroke="#101010" stroke-width="2.5"/>
|
||||||
|
<path fill="none" stroke="#1eadf5" stroke-width="2" d="M7.6121 0 0 -7.6121 13.2727 -15.7298M-7.6121 0 7.6121 0 0 7.6121 -7.6121 0 -14.3693 13.5411"/>
|
||||||
|
<g fill="#1862ff">
|
||||||
|
<circle r="3" cy="7.6121"/>
|
||||||
|
<circle r="3" cx="7.6121"/>
|
||||||
|
<circle r="3" cy="-7.6121"/>
|
||||||
|
<circle r="3" cx="-7.6121"/>
|
||||||
|
<circle r="3" cx="13.2727" cy="-15.7298"/>
|
||||||
|
<circle r="3" cx="-14.3693" cy="13.5411"/>
|
||||||
|
</g>
|
||||||
|
<g style="font-family: 'DejaVu Sans'; font-size: 5px; fill: #fff;">
|
||||||
|
<text x="40" y="-9">This laptop is property of the Swiss Olympiad in Informatics. Contact: info@soi.ch</text>
|
||||||
|
<text x="40" y="1">Dieser Laptop ist Eigentum der Schweizer Informatikolympiade. Kontakt: info@soi.ch</text>
|
||||||
|
<text x="40" y="11">Software version: @date@</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 937 B |
|
@ -0,0 +1,11 @@
|
||||||
|
Types: deb deb-src
|
||||||
|
URIs: http://deb.debian.org/debian
|
||||||
|
Suites: bookworm bookworm-updates
|
||||||
|
Components: main contrib non-free non-free-firmware
|
||||||
|
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||||
|
|
||||||
|
Types: deb deb-src
|
||||||
|
URIs: http://deb.debian.org/debian-security
|
||||||
|
Suites: bookworm-security
|
||||||
|
Components: main contrib non-free non-free-firmware
|
||||||
|
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
|
@ -0,0 +1,6 @@
|
||||||
|
Name: Accept noauth users without any authentication
|
||||||
|
Default: yes
|
||||||
|
Priority: 512
|
||||||
|
Auth-Type: Primary
|
||||||
|
Auth:
|
||||||
|
[success=end default=ignore] pam_succeed_if.so user ingroup noauth
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Disable mountmedia, because that can make the cdrom mount fail.
|
||||||
|
# Not sure why there are two conflicting mounting mechanisms in Debian Installer
|
||||||
|
# (mountmedia with /media and cdrom-detect with /cdrom).
|
||||||
|
exit 1
|
|
@ -0,0 +1,28 @@
|
||||||
|
#_preseed_V1
|
||||||
|
|
||||||
|
d-i debian-installer/language string en
|
||||||
|
d-i debian-installer/country string CH
|
||||||
|
d-i debian-installer/locale string en_US.UTF-8
|
||||||
|
|
||||||
|
d-i keyboard-configuration/xkb-keymap select ch
|
||||||
|
|
||||||
|
d-i hw-detect/load_firmware boolean false
|
||||||
|
|
||||||
|
d-i netcfg/enable boolean false
|
||||||
|
d-i netcfg/get_domain string
|
||||||
|
|
||||||
|
d-i passwd/root-login boolean false
|
||||||
|
d-i passwd/user-fullname string Admin
|
||||||
|
d-i passwd/username string superstofl
|
||||||
|
d-i passwd/user-password-crypted password @install_admin_password@
|
||||||
|
|
||||||
|
d-i partman-auto/method string regular
|
||||||
|
d-i partman-auto/init_automatically_partition select some_device
|
||||||
|
d-i partman-auto/choose_recipe select atomic
|
||||||
|
d-i partman/choose_partition select finish
|
||||||
|
|
||||||
|
d-i apt-setup/use_mirror boolean false
|
||||||
|
|
||||||
|
d-i grub-installer/only_debian boolean true
|
||||||
|
|
||||||
|
d-i preseed/late_command string in-target /usr/local/bin/install-config
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This file overrides a default list to remove live-config.
|
||||||
|
# For the installer, we don't need any live packages,
|
||||||
|
# they would just be removed again by the installer.
|
|
@ -0,0 +1,16 @@
|
||||||
|
sudo
|
||||||
|
|
||||||
|
# Make Secure Boot work
|
||||||
|
grub-efi-amd64-signed
|
||||||
|
|
||||||
|
# Firmware updates through gnome-software
|
||||||
|
fwupd fwupd-signed
|
||||||
|
|
||||||
|
# Low battery charge notifications, battery info
|
||||||
|
gnome-power-manager
|
||||||
|
|
||||||
|
# Run virtual machines with Gnome Boxes
|
||||||
|
gnome-boxes qemu-system-x86 qemu-utils libvirt-daemon-system
|
||||||
|
|
||||||
|
# Run virtual machines with VirtualBox
|
||||||
|
virtualbox-qt
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Enable the live system configuration script at boot.
|
||||||
|
systemctl enable live-config.service
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Disable lock on blank screen
|
||||||
|
[org/gnome/desktop/screensaver]
|
||||||
|
lock-enabled = false
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Disable "Updates available" notifications and auto updates.
|
||||||
|
# Updates which require reboot are useless on live systems,
|
||||||
|
# and other updates would be installed on each boot.
|
||||||
|
[org/gnome/software]
|
||||||
|
allow-updates = false
|
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=custom configuration of live system during boot.
|
||||||
|
Before=basic.target
|
||||||
|
After=local-fs.target systemd-tmpfiles-setup.service
|
||||||
|
DefaultDependencies=no
|
||||||
|
ConditionKernelCommandLine=boot=live
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/local/bin/live-config
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=basic.target
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
LIVE_HOSTNAME=debian
|
||||||
|
|
||||||
|
LIVE_USERNAME=soi
|
||||||
|
LIVE_USER_FULLNAME="SOI live"
|
||||||
|
# Password: soi
|
||||||
|
LIVE_USER_PASSWORD='$y$j9T$h5VhMd4KkdmbxdZD1gO0N/$1hvwZgO8pQw13Xd6jaNXbtkbqVOC4W/ia/KXOcCGYvB'
|
||||||
|
|
||||||
|
# Set hostname.
|
||||||
|
echo "${LIVE_HOSTNAME}" > /etc/hostname
|
||||||
|
hostname "${LIVE_HOSTNAME}"
|
||||||
|
|
||||||
|
# Create hosts file.
|
||||||
|
cat > /etc/hosts <<EOF
|
||||||
|
127.0.0.1 localhost ${LIVE_HOSTNAME}
|
||||||
|
::1 localhost ip6-localhost ip6-loopback
|
||||||
|
fe00::0 ip6-localnet
|
||||||
|
ff00::0 ip6-mcastprefix
|
||||||
|
ff02::1 ip6-allnodes
|
||||||
|
ff02::2 ip6-allrouters
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create user.
|
||||||
|
adduser --disabled-password --gecos "$LIVE_USER_FULLNAME" "$LIVE_USERNAME"
|
||||||
|
usermod -p "$LIVE_USER_PASSWORD" "$LIVE_USERNAME"
|
||||||
|
adduser "$LIVE_USERNAME" sudo
|
||||||
|
|
||||||
|
# Disable sudo password prompt.
|
||||||
|
cat > /etc/sudoers.d/10_customize <<EOF
|
||||||
|
# Do not ask for password
|
||||||
|
Defaults !authenticate
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Enable auto login.
|
||||||
|
sed -i \
|
||||||
|
-e "s/^[# ]*AutomaticLoginEnable *=.*/AutomaticLoginEnable = true/g" \
|
||||||
|
-e "s/^[# ]*AutomaticLogin *=.*/AutomaticLogin = $LIVE_USERNAME/g" \
|
||||||
|
-e "s/^[# ]*TimedLoginEnable *=.*/TimedLoginEnable = true/g" \
|
||||||
|
-e "s/^[# ]*TimedLogin *=.*/TimedLogin = $LIVE_USERNAME/g" \
|
||||||
|
-e "s/^[# ]*TimedLoginDelay *=.*/TimedLoginDelay = 5/g" \
|
||||||
|
/etc/gdm3/daemon.conf
|
|
@ -0,0 +1,4 @@
|
||||||
|
sudo
|
||||||
|
|
||||||
|
# Show progress while copying squashfs to RAM.
|
||||||
|
rsync
|
|
@ -0,0 +1,128 @@
|
||||||
|
# OS build system
|
||||||
|
|
||||||
|
This is a system for building a customized OS for SOI, based on [Debian Live].
|
||||||
|
|
||||||
|
[Debian Live]: https://live-team.pages.debian.net/live-manual/html/live-manual/index.en.html
|
||||||
|
|
||||||
|
## Variants
|
||||||
|
|
||||||
|
There are multiple variants of the OS for different use cases.
|
||||||
|
|
||||||
|
- `training-live` is a live system for training.
|
||||||
|
- `training-installer` is an installer, which writes the OS to disk instead of running it directly.
|
||||||
|
This is intended for installing the laptops owned by SOI only.
|
||||||
|
Installation is offline and mostly automated.
|
||||||
|
- `contestant` is a live system for contests.
|
||||||
|
It has additional configuration useful for contests.
|
||||||
|
|
||||||
|
The live systems copy the entire OS to RAM while booting, so you can remove the USB stick after booting is finished.
|
||||||
|
That way, you only need a small number of USB sticks for booting many computers.
|
||||||
|
|
||||||
|
All variants support Secure Boot.
|
||||||
|
However, VirtualBox (contained in the installer variant) only works with Secure Boot disabled.
|
||||||
|
|
||||||
|
## How to build an ISO
|
||||||
|
|
||||||
|
We run the build inside a Docker container, so you need Docker installed on your host.
|
||||||
|
Building works on Linux hosts, other OSes are untested.
|
||||||
|
|
||||||
|
First, obtain the configuration files and put them in the folder `config`.
|
||||||
|
These files contain secrets and are thus not committed to the repository.
|
||||||
|
If you want to create your own config, see the folder `config-example` for examples.
|
||||||
|
|
||||||
|
Run the following commands in the repository root folder.
|
||||||
|
The `--privileged` flag is needed for mounting `/proc` and similar in the target system root.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p osbuild/build
|
||||||
|
sudo docker pull debian:bookworm
|
||||||
|
sudo docker run --rm -it --privileged --mount type=bind,source="$(pwd)",target=/work --workdir /work debian:bookworm
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the container, run the following commands.
|
||||||
|
Replace `training-live` with the variant you want to build.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get update
|
||||||
|
# python3: for build script
|
||||||
|
# ca-certificates: for downloading files over https
|
||||||
|
# rsync, cpio: used by live-build
|
||||||
|
# unzip: for codeblocks plugin
|
||||||
|
# build-essential, debhelper: for building custom udeb
|
||||||
|
apt-get install --no-install-recommends python3 ca-certificates live-build rsync cpio unzip build-essential debhelper
|
||||||
|
cd osbuild/build
|
||||||
|
python3 ../../os/build.py training-live
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the build is finished, you will find the ISO at `osbuild/build/live-image-amd64.hybrid.iso`.
|
||||||
|
|
||||||
|
## Testing in a VM
|
||||||
|
|
||||||
|
During development, it's convenient to test the OS in a virtual machine.
|
||||||
|
Install QEMU on your host.
|
||||||
|
The following commands should be run outside the docker container.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# training-live, legacy and EFI boot:
|
||||||
|
kvm -m 8G -smp 4 -vga virtio -cdrom training-live.iso
|
||||||
|
kvm -m 8G -smp 4 -vga virtio -cdrom training-live.iso -bios /usr/share/ovmf/OVMF.fd
|
||||||
|
|
||||||
|
# training-installer:
|
||||||
|
qemu-img create -f qcow2 installtarget.qcow2 20G
|
||||||
|
kvm -m 8G -smp 4 -vga virtio -drive file=training-installer.iso,if=virtio,format=raw,readonly=on -drive file=installtarget.qcow2,if=virtio -bios /usr/share/ovmf/OVMF.fd -smbios type=1,serial=DEMO123
|
||||||
|
|
||||||
|
# contestant:
|
||||||
|
# Add your ssh key to os/config/contestant_authorized_keys
|
||||||
|
kvm -m 8G -smp 4 -vga virtio -cdrom contestant.iso -bios /usr/share/ovmf/OVMF.fd -nic user,model=virtio-net-pci,hostfwd=tcp:127.0.0.1:2222-:22
|
||||||
|
ssh -o "UserKnownHostsFile ./local.known_hosts" -p 2222 root@localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
The configuration is split into layers, which are applied depending on the variant.
|
||||||
|
Here is a list of features.
|
||||||
|
|
||||||
|
- `participant` (all variants)
|
||||||
|
- various code editors and other tools
|
||||||
|
- VS Code extensions
|
||||||
|
- SOI header
|
||||||
|
- Code::Blocks template
|
||||||
|
- wallpaper
|
||||||
|
- default favorite apps
|
||||||
|
- default list of keyboard layouts
|
||||||
|
- timezone
|
||||||
|
- list of locales
|
||||||
|
- bootloader background image
|
||||||
|
- `training-live`
|
||||||
|
- disable lock on blank screen
|
||||||
|
- disable software update notifications
|
||||||
|
- automatic login
|
||||||
|
- sudo without password
|
||||||
|
- `training-installer`
|
||||||
|
- disable network detection to speed up install
|
||||||
|
- preseed most installer questions
|
||||||
|
- look up hostname in inventory file from serial number (inventory-hostname udeb)
|
||||||
|
- login screen logo
|
||||||
|
- login without password for `noauth` group
|
||||||
|
- create an admin user with sudo rights and password
|
||||||
|
- create a participant user without password
|
||||||
|
- install packages for firmware updates and power manager
|
||||||
|
- install Gnome Boxes and VirtualBox for running virtual machines
|
||||||
|
- `contestant`
|
||||||
|
- disable bluetooth
|
||||||
|
- disable sleep
|
||||||
|
- disable lock on blank screen
|
||||||
|
- disable software update notifications
|
||||||
|
- disable some panels in gnome-control-center
|
||||||
|
- disable automatic mounting of storage media
|
||||||
|
- polkit rules which block changing network settings and mounting storage media (it prompts for the root password)
|
||||||
|
- configure NTP
|
||||||
|
- install and configure ssh server
|
||||||
|
- set root password
|
||||||
|
- set `authorized_keys` for root
|
||||||
|
- automatic login
|
||||||
|
- set browser homepage and bookmarks to https://contest.soi.ch
|
||||||
|
- Gnome Shell extension which displays the user name in the top bar
|
||||||
|
- contest lock Gnome Shell extension
|
||||||
|
- some management scripts to be run via ssh
|
||||||
|
- some packages for contest admin
|
Loading…
Reference in New Issue