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

View file

@ -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

View file

@ -0,0 +1,3 @@
# Disable automount
[org/gnome/desktop/media-handling]
automount = false

View file

@ -0,0 +1,3 @@
# Disable blank screen
[org/gnome/desktop/session]
idle-delay = uint32 0

View file

@ -0,0 +1,3 @@
# Disable lock on blank screen
[org/gnome/desktop/screensaver]
lock-enabled = false

View file

@ -0,0 +1,4 @@
# Disable suspend when inactive
[org/gnome/settings-daemon/plugins/power]
sleep-inactive-ac-type = 'nothing'
sleep-inactive-battery-type = 'nothing'

View file

@ -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

View file

@ -0,0 +1,2 @@
[org/gnome/shell]
enabled-extensions = ['contest-lock@soi.ch', 'user-indicator@soi.ch']

View file

@ -0,0 +1,3 @@
# Disable locking the screen
[org/gnome/desktop/lockdown]
disable-lock-screen = true

View file

@ -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"
}
]
}
}

View file

@ -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;
}
});

View file

@ -0,0 +1,2 @@
PasswordAuthentication no
AllowUsers root

View file

@ -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

View file

@ -0,0 +1,2 @@
[Time]
NTP=contest.soi.ch

View file

@ -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 ""

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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();
}

View file

@ -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": ""
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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": ""
}

View file

@ -0,0 +1,4 @@
.panel-bin {
padding-left: 12px;
padding-right: 12px;
}

View file

@ -0,0 +1,12 @@
# Remote access
openssh-server
rsync
# Firewall
nftables
# Reboot with kexec
kexec-tools
# For importing client certificate
libnss3-tools