Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions docs/development/img.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,20 @@ The boot menu cannot edit the entries: their kernel command line, root hash incl
part of the UKI. With Secure Boot off, a UEFI shell can still start the UKI with another
command line (see [What Rite Live proves](#what-rite-live-proves)).

The session starts on its own as the `rite` user. Its start screen shows the `rite`
version, the ceremony definition with its SHA-256, where the outputs go, and the `rite run`
command for the ceremony. Pressing Enter starts `rite run` with that ceremony, and
`rite run` asks for any parameter the ceremony leaves open. For other flags, such as
`--dry-run` or `--role`, the operator types the word `shell` at the prompt and runs the
command by hand.
Before the session, the setup screen lets the operator choose a keyboard layout and a
time zone. US and UTC are the defaults. The layout is loaded and tested before it is
kept. The layout applies to the text console and both terminals; the time zone applies
to times shown by the session, including `date`. Transcripts always use UTC. The
displayed time comes from the machine clock. If it is wrong, power off from the setup
screen and correct it in the firmware settings. Setup runs once per boot. The session
start screen shows the selected layout and time zone.

The session then starts as the `rite` user. Its start screen shows the `rite` version,
the ceremony definition and its SHA-256, where outputs go, and the `rite run` command
for the ceremony. It also shows the keyboard layout and time zone in use. Pressing
Enter starts `rite run` with that ceremony, and `rite run` asks for any parameter the
ceremony leaves open. For other flags, such as `--dry-run` or `--role`, the operator
types the word `shell` at the prompt and runs the command by hand.

When the outputs partition is missing or does not mount, the start screen says so and
offers no Enter shortcut: the ceremony's outputs would be lost at power off, so running it
Expand Down Expand Up @@ -339,8 +347,6 @@ driver. A ceremony that needs an air gap establishes it physically, by leaving o
cable, and records it as a witnessed confirmation step, such as `verify_air_gap` in
`examples/showcase/demo.rite.yaml`.

The keyboard layout is US.

### Known hardware issues

- **Secure Boot.** The setting is named differently from one firmware to another. On ASUS
Expand Down Expand Up @@ -447,7 +453,8 @@ any interaction with the boot process. Observation does not prove what booted.
| `img/mkosi/mkosi.extra/` | Files copied into the image, at their paths in it |
| `.../efi/loader/loader.conf` | Boot menu: timeout, no editing |
| `.../usr/lib/systemd/system/rite-session.service`, `rite-shell.service`, `.../etc/pam.d/rite-session` | The session on tty1 and the shell on tty2, run as `rite` |
| `.../usr/lib/rite/rite-kiosk` | Starts the session or the shell in cage and foot, or on the text console |
| `.../usr/lib/systemd/system/rite-setup.service`, `.../usr/lib/rite/rite-setup`, `setup-values` | The setup screen: keyboard layout and time zone, chosen before the session |
| `.../usr/lib/rite/rite-kiosk` | Starts the session or the shell in cage and foot, or on the text console, with the layout and zone chosen at setup |
| `.../usr/lib/rite/rite-session`, `rite-shell` | The operator's start screen, and the second terminal |
| `.../usr/lib/systemd/system/rite-ceremony-load.service`, `.../usr/lib/rite/load-ceremony` | Copies the ceremony layer into memory and records its hash |
| `.../usr/lib/rite/mount-outputs`, `boot-partition` | Mounts `RITE-OUT` at `/rite/outputs`; finds a partition on the boot disk |
Expand Down
6 changes: 6 additions & 0 deletions img/mkosi/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ Packages=
# The boot loader and the UKI stub, which mkosi takes from the image.
systemd-boot-efi

# rite-setup uses whiptail for menus, ckbcomp to convert XKB layouts,
# and loadkeys to load the resulting console keymap.
whiptail
console-setup
kbd

# Pager, for man and the shell.
less

Expand Down
15 changes: 15 additions & 0 deletions img/mkosi/mkosi.extra/usr/lib/rite/rite-kiosk
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@

program=$1

# Apply the settings chosen in rite-setup: cage reads the XKB variables and
# session programs read TZ. The settings file is root-owned before the session
# starts. If it is absent or invalid, the session uses US and UTC.
# shellcheck disable=SC1091
source /usr/lib/rite/setup-values
if [[ -r $SETTINGS ]]; then
while IFS='=' read -r key value; do
case $key in
layout) valid_layout "$value" && export XKB_DEFAULT_LAYOUT=$value ;;
variant) valid_variant "$value" && export XKB_DEFAULT_VARIANT=$value ;;
timezone) valid_zone "$value" && export TZ=$value ;;
esac
done < "$SETTINGS"
fi

if ! grep -qwF 'rite.console' /proc/cmdline; then
# cage takes the display, which only the terminal on screen can do: the
# second terminal starts cage when the operator first switches to it.
Expand Down
6 changes: 6 additions & 0 deletions img/mkosi/mkosi.extra/usr/lib/rite/rite-session
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ else
echo "WARNING: no RITE-OUT partition. Outputs in $OUTPUTS are lost at power off."
fi
echo ""
# Set by rite-kiosk from the choice made in rite-setup.
# shellcheck disable=SC1091
source /usr/lib/rite/setup-values
echo "Keyboard: $(layout_name "${XKB_DEFAULT_LAYOUT:-us}" "${XKB_DEFAULT_VARIANT:-}")"
echo "Time zone: ${TZ:-UTC}"
echo ""
echo "Terminals: Ctrl+Alt+F2 opens a second terminal, Ctrl+Alt+F1 returns here."
echo ""

Expand Down
198 changes: 198 additions & 0 deletions img/mkosi/mkosi.extra/usr/lib/rite/rite-setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/bin/bash
# /usr/lib/rite/rite-setup
# The keyboard layout and time zone, chosen from menus on tty1 before the
# sessions start. Nothing typed by the operator becomes a setting. A layout
# is loaded into the console and tested before it is kept. The settings go to
# /run/rite-setup/settings, where rite-kiosk reads them for cage (XKB) and
# the session programs (TZ). If setup fails, the session uses US and UTC.
# This script always exits 0.

# Ctrl-C, Ctrl-\ and Ctrl-Z do nothing: a stopped script would hold the boot.
trap '' INT QUIT TSTP

# shellcheck disable=SC1091
source /usr/lib/rite/setup-values

COUNTRIES=/usr/share/zoneinfo/iso3166.tab
TITLE="Rite Live"

layout=us
variant=
zone=UTC

# whiptail draws on stdout and prints the choice on stderr; the swap returns
# the choice on stdout and draws on stderr, which is the terminal.
ask() {
whiptail --title "$TITLE" "$@" 3>&1 1>&2 2>&3
}

message() {
ask --msgbox "$1" 10 64
}

save() {
if ! printf 'layout=%s\nvariant=%s\ntimezone=%s\n' "$layout" "$variant" "$zone" \
> "$SETTINGS.new" || ! mv -f "$SETTINGS.new" "$SETTINGS"; then
rm -f "$SETTINGS.new" "$SETTINGS"
layout=us
variant=
zone=UTC
load_keymap us ""
message "Could not save the settings. The sessions will use US and UTC."
fi
}

# Loads layout $1, variant $2 into the console keymap, which every terminal
# shares.
load_keymap() {
local args=(-layout "$1") status
[[ -n $2 ]] && args+=(-variant "$2")
ask --infobox "Loading the keyboard layout..." 7 50
ckbcomp "${args[@]}" 2>/dev/null | loadkeys -q 2>/dev/null
status=("${PIPESTATUS[@]}")
[[ ${status[0]} -eq 0 && ${status[1]} -eq 0 ]]
}

# ckbcomp warns and falls back to the base layout for an unknown variant.
variant_listed() {
[[ -z $2 || -n $(xkb_description "$1" "$2") ]]
}

# Every layout of the XKB rules, then its variants. Prints "layout variant".
other_layout() {
local name rest items=() choice
while read -r name rest; do
items+=("$name" "$rest")
done < <(xkb_section layout)
choice=$(ask --notags --menu "Keyboard layout:" 22 72 14 "${items[@]}") || return 1

# A variant line reads "name layout: description".
items=("-" "No variant")
while read -r name rest; do
[[ ${rest%%:*} == "$choice" ]] && items+=("$name" "${rest#*: }")
done < <(xkb_section variant)
if [[ ${#items[@]} -eq 2 ]]; then
echo "$choice"
return 0
fi
rest=$(ask --notags --menu "Variant for $(layout_name "$choice" ""):" 22 72 14 \
"${items[@]}") || return 1
[[ $rest == - ]] && rest=""
echo "$choice $rest"
}

change_keyboard() {
local choice new_layout new_variant new_name current_name typed
# Widely used layouts, by their names in the XKB rules.
choice=$(ask --notags --default-item "$layout${variant:+ $variant}" \
--menu "Keyboard layout:" 20 60 11 \
"gb" "English (UK)" \
"us" "English (US)" \
"fr" "French" \
"ch fr" "French (Switzerland)" \
"de" "German" \
"ch" "German (Switzerland)" \
"it" "Italian" \
"jp" "Japanese" \
"br" "Portuguese (Brazil)" \
"es" "Spanish" \
"other" "Other layouts...") || return 0
if [[ $choice == other ]]; then
choice=$(other_layout) || return 0
fi
read -r new_layout new_variant <<< "$choice"
new_name=$(layout_name "$new_layout" "$new_variant")
current_name=$(layout_name "$layout" "$variant")
if ! valid_layout "$new_layout" || ! valid_variant "$new_variant" \
|| ! variant_listed "$new_layout" "$new_variant"; then
message "Layout $new_name is not supported. Keeping $current_name."
return 0
fi

if ! load_keymap "$new_layout" "$new_variant"; then
if load_keymap "$layout" "$variant"; then
message "Could not load $new_name. Keeping $current_name."
else
message "Could not load $new_name or restore $current_name. The keyboard may not work. Power off and restart."
fi
return 0
fi
typed=$(ask --inputbox "Type anything to check the layout, then press Enter." \
8 60) || typed=""
if ask --yesno "You typed:

$typed

Keep this layout: $new_name?" 12 60; then
layout=$new_layout
variant=$new_variant
save
elif ! load_keymap "$layout" "$variant"; then
# The console still has the new layout: keep it, so that the settings
# and the start screen say what the keyboard does.
layout=$new_layout
variant=$new_variant
save
message "Could not restore $current_name. Keeping $new_name."
fi
}

# UTC or a region, then a zone of that region, each shown with its country.
change_zone() {
local choice items=("UTC" "") code name comment
local -A country
while IFS= read -r name; do
items+=("$name" "")
done < <(grep -v '^#' "$ZONE_TAB" | cut -f3 | cut -d/ -f1 | sort -u)
choice=$(ask --default-item "${zone%%/*}" --menu "Choose a region:" 20 50 12 \
"${items[@]}") || return 0

if [[ $choice != UTC ]]; then
while IFS=$'\t' read -r code name; do
country[$code]=$name
done < <(grep -v '^#' "$COUNTRIES")
items=()
while IFS=$'\t' read -r code _ name comment; do
[[ $name == "$choice"/* ]] || continue
items+=("$name" "${country[$code]:-$code}${comment:+, $comment}")
done < <(grep -v '^#' "$ZONE_TAB" | sort -t$'\t' -k3)
choice=$(ask --default-item "$zone" --menu "Choose a time zone:" 22 76 14 \
"${items[@]}") || return 0
fi
if ! valid_zone "$choice"; then
message "Time zone $choice is not supported. Keeping $zone."
return 0
fi
zone=$choice
save
}

while true; do
choice=$(ask --notags --nocancel --default-item start --menu "\
Keyboard: $(layout_name "$layout" "$variant")
Time zone: $zone
Time: $(date -u '+%Y-%m-%d %H:%M UTC'), $(TZ=$zone date '+%H:%M %Z') local

Check the clock against a watch.
If it is wrong, power off and correct it in the firmware settings." 19 76 5 \
"start" "Continue" \
"keyboard" "Change the keyboard layout" \
"zone" "Change the time zone" \
"time" "Refresh the time" \
"poweroff" "Power off") || choice=start
case $choice in
keyboard) change_keyboard ;;
zone) change_zone ;;
time) ;;
poweroff)
if ask --defaultno --yesno "Power off the machine?" 8 40; then
# polkit lets the rite user power off (10-rite.rules).
systemctl poweroff && exit 0
message "Could not power off the machine. Try again."
fi
;;
*) break ;;
esac
done
clear
exit 0
56 changes: 56 additions & 0 deletions img/mkosi/mkosi.extra/usr/lib/rite/setup-values
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# shellcheck shell=bash
# /usr/lib/rite/setup-values
# The keyboard layout and time zone values: their checks and the names the
# screens show. Sourced by rite-setup, which writes them, and by rite-kiosk and
# rite-session, which read them back.

# Used by the scripts that source this file.
# shellcheck disable=SC2034
SETTINGS=/run/rite-setup/settings
XKB_RULES=/usr/share/X11/xkb/rules/evdev.lst
ZONE_TAB=/usr/share/zoneinfo/zone.tab

valid_layout() {
[[ $1 =~ ^[a-z0-9_]+$ ]]
}

# Some variants have capitals: de T3, jp OADG109A.
valid_variant() {
[[ -z $1 || $1 =~ ^[A-Za-z0-9_][A-Za-z0-9_-]*$ ]]
}

# UTC, or a zone of zone.tab, which lists one or more zones per country.
valid_zone() {
[[ $1 == UTC ]] && return 0
[[ $1 =~ ^[A-Za-z0-9_+-]+(/[A-Za-z0-9_+-]+)*$ ]] \
&& grep -v '^#' "$ZONE_TAB" | cut -f3 | grep -qxF -- "$1"
}

# Prints the entries of section $1 of the XKB rules ("layout", "variant"), one
# per line: the name, then the description. A variant's description starts
# with its layout: "fr ch: French (Switzerland)".
xkb_section() {
awk -v want="$1" '/^! / { section = $2; next } NF && section == want {
sub(/^ +/, ""); print }' "$XKB_RULES"
}

# Prints the description of layout $1 with variant $2 from the XKB rules, such
# as "French (Switzerland)", or nothing if the rules do not list them. awk reads
# the file itself: a lookup that stops early must not leave a writer on a closed
# pipe, since systemd services ignore SIGPIPE.
xkb_description() {
awk -v layout="$1" -v variant="$2" '
/^! / { section = $2; next }
variant == "" && section == "layout" && $1 == layout {
sub(/^ *[^ ]+ +/, ""); print; exit }
variant != "" && section == "variant" && $1 == variant && $2 == layout ":" {
sub(/^ *[^ ]+ +[^ ]+ +/, ""); print; exit }' "$XKB_RULES"
}

# Prints the name of layout $1 with variant $2, or the codes if the XKB rules
# do not list it.
layout_name() {
local name
name=$(xkb_description "$1" "$2")
echo "${name:-$1${2:+ ($2)}}"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Units of the Live system. mkosi applies the presets at the end of the
# build (systemctl preset-all); Debian's default enables every other unit,
# systemd-networkd and systemd-resolved among them.
enable rite-setup.service
enable rite-session.service
enable rite-shell.service
enable rite-ceremony-load.service
40 changes: 40 additions & 0 deletions img/mkosi/mkosi.extra/usr/lib/systemd/system/rite-setup.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# The keyboard layout and time zone, chosen on tty1 before the sessions start
# (rite-setup), as the rite user. The sessions start whether setup succeeds or
# not. On failure, the stop command removes the settings and restores the US
# console keymap.
[Unit]
Description=Rite keyboard layout and time zone
After=systemd-user-sessions.service
Before=rite-session.service rite-shell.service
ConditionPathExists=/dev/tty1

[Service]
Type=oneshot
# Once per boot.
RemainAfterExit=yes
# Kernel messages would print over the menus.
ExecStartPre=+/usr/bin/dmesg --console-off
ExecStart=/usr/lib/rite/rite-setup
# Restore kernel messages and protect settings before the sessions start.
# On failure, the stop command also restores the US keymap.
ExecStartPost=+/usr/bin/dmesg --console-on
ExecStartPost=+/usr/bin/chown -R root:root /run/rite-setup
ExecStopPost=+/usr/bin/dmesg --console-on
ExecStopPost=+/usr/bin/chown -R root:root /run/rite-setup
ExecStopPost=+/bin/sh -c 'test "$SERVICE_RESULT" = success || { rm -f /run/rite-setup/settings; ckbcomp us | loadkeys -q; }'
User=rite
# Loading the console keymap takes CAP_SYS_TTY_CONFIG, even on the process's
# own terminal.
AmbientCapabilities=CAP_SYS_TTY_CONFIG
RuntimeDirectory=rite-setup
RuntimeDirectoryPreserve=yes
TimeoutStartSec=infinity
TTYPath=/dev/tty1
TTYReset=yes
TTYVHangup=yes
StandardInput=tty
StandardOutput=tty
StandardError=tty

[Install]
WantedBy=multi-user.target
Loading