agama/live/live-root/usr/bin/agama-issue-generator
Ladislav Slezák 655882fa54
Rename "root" to "live-root" to avoid confusion
...with "root/root".
2025-04-23 13:26:35 +02:00

322 lines
12 KiB
Bash
Executable file

#!/bin/bash
# This is a helper script which generates issue file displayed at the console
# before logging in. Partly inspired by the issue-generator package
# (https://github.com/thkukuk/issue-generator).
#
# Generates several issue files:
# - Welcome message with Agama version number (--welcome option)
# - Agama SSL certificate fingerprints (--ssl option)
# - SSH host key fingerprints (--ssh option)
# - Agama access URL for all network devices (--watch-network option)
# NOTE: in this case the script does not finish, it watches the changes in
# the NetworkManager configuration and updates the URL if needed
# - Agama access URL using the mDNS (Avahi) URL (--watch-avahi option),
# NOTE: in this case the script does not finish, it watches the changes in
# the Avahi service and updates the URL if needed
#
# issue location for the Agama SSL certificate fingerprints, it is generated as
# the last one and is used as a trigger to allow refreshing all other generated
# issues
CERT_ISSUE=/run/issue.d/50-agama-ssl-certificate.issue
# a helper function which generates the Agama welcome message displayed at the
# console
generate_welcome() {
# get the latest version of any Agama package (except the integration tests, it lives in a
# separate git repository and has different number of commits than the rest)
AGAMA_VERSION=$(rpm -qa | grep agama | grep -v agama-integration-tests | xargs rpm -q --queryformat \
"%{VERSION}\n" | sed -e "s/\\.devel/+/" -e 's/+0$//' | sort -V | tail -n 1)
ISSUE=/run/issue.d/10-agama-welcome.issue
printf "Welcome to \\\\e{lightgreen}Agama\\\\e{reset} installer version %s! (\\\\l)\n\n" "$AGAMA_VERSION" > "$ISSUE"
}
# a helper function which displays the SSH host key fingerprints at the console
generate_ssh_fingerprints() {
FINGERPRINTS=$(find /etc/ssh -type f -name "ssh_host_*_key" -exec ssh-keygen -l -f \{\} \; | cut -d ' ' -f 2,4 | sed -e "s/^/ /")
ISSUE=/run/issue.d/30-live-ssh-fingerprints.issue
printf "SSH host key fingerprints:\n%s\n\n" "$FINGERPRINTS" > "$ISSUE"
}
# a helper function which generates the Agama SSL certificate fingerprints
# displayed at the console
generate_certificate_fingerprints() {
AGAMA_CERT=/etc/agama.d/ssl/cert.pem
# delete the previous file if it is there
rm -f "$CERT_ISSUE"
if [ -e "$AGAMA_CERT" ]; then
SHA256=$(openssl x509 -fingerprint -sha256 -noout -in "$AGAMA_CERT" | sed -e "s/^sha256 Fingerprint=//")
SHA1=$(openssl x509 -fingerprint -sha1 -noout -in "$AGAMA_CERT" | sed -e "s/^sha1 Fingerprint=//")
if [ -n "$SHA256" ] && [ -n "$SHA1" ]; then
printf "Agama installer SSL certificate fingerprints:\n SHA256: %s\n SHA1: %s\n\n" "$SHA256" "$SHA1" \
> "$CERT_ISSUE"
fi
fi
# tell agetty to use the issues from /run
touch /run/issue
# reload the issues to activate the changes
touch /run/agetty.reload
}
# message file for the Agama mDNS URL
AVAHI_MESSAGE=/run/issue.d/70-agama-connect-avahi.message
# symlink for the issue file, created only when network is available
AVAHI_ISSUE=/run/issue.d/70-agama-connect-avahi.issue
# issue file with Agama URLs
URL_ISSUES="/run/issue.d/70-agama-connect-urls.issue"
# issue displayed when there is no network connection
DISCONNECTED_ISSUE="/run/issue.d/70-agama-disconnected.issue"
# helper function, build the Agama URL messages or display a warning when
# network is not available
write_url_headers() {
# generate a header and footer around the Agama URL issues
ISSUE_HEADER=/run/issue.d/69-agama-connect.issue
ISSUE_FOOTER=/run/issue.d/71-agama-connect.issue
if [ -e "$URL_ISSUES" ]; then
# if Avahi URL is set then display it as well
if [ -e "$AVAHI_MESSAGE" ]; then
ln -sf "$AVAHI_MESSAGE" "$AVAHI_ISSUE"
fi
rm -f "$DISCONNECTED_ISSUE"
# at least one address present, display the header and footer
echo "Connect to the Agama installer using these URLs:" > "$ISSUE_HEADER"
echo > "$ISSUE_FOOTER"
else
# no network, delete the header, footer and the Avahi issue symlink
rm -f "$ISSUE_HEADER" "$ISSUE_FOOTER" "$AVAHI_ISSUE"
# display a warning message
printf "\\\\e{brown}Network is not available, the Agama installer cannot \
be used remotely.\\\\e{reset}\n\n" > "$DISCONNECTED_ISSUE"
fi
}
# a helper function which generates the mDNS URL for accessing the Agama server
# displayed at the console
generate_avahi_url() {
# track the name, update the issue file only if the name is changed
OLDNAME=""
# watch for a systemd signal describing the status message change from the Avahi daemon
dbus-monitor --system "sender='org.freedesktop.systemd1',\
interface='org.freedesktop.DBus.Properties',\
path='/org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice',type=signal" \
2> /dev/null | while read -r line;
do
AVAHINAME=$(echo "$line" | grep "Server startup complete. Host name is" \
| sed -e "s/^.*Server startup complete. Host name is \(.*\)\. Local.*$/\\1/")
# mDNS host name found and it is different than the previous one (or the initial value)
if [ -n "$AVAHINAME" ] && [ "$AVAHINAME" != "$OLDNAME" ]; then
OLDNAME="$AVAHINAME"
echo " https://$AVAHINAME" > "$AVAHI_MESSAGE"
write_url_headers
# reload if not in the initial state
if [ -e "$CERT_ISSUE" ]; then
touch /run/agetty.reload
fi
fi
# daemon stopped, remove the message file
if echo "$line" | grep -q "avahi-daemon .* exiting"; then
OLDNAME=""
rm -f "$AVAHI_MESSAGE"
write_url_headers
touch /run/agetty.reload
fi
done
}
# function for centering text
# $1 - the text
# $2 - requested width
function center_text() {
LEN=${#1}
PADDING_LEN=$(($2 - LEN))
PADDING_LEN=$((PADDING_LEN / 2))
PADDING="$(printf '%*s' $PADDING_LEN)"
echo "$PADDING$1$PADDING"
}
# generate QR codes for the access URLs
function create_qr_codes() {
ADDRESSES=("$@")
# width of the generated QR code
QR_WIDTH=30
# check if serial console is used, get the terminal size (width)
TERM_WIDTH=$(stty -F /dev/ttyS0 size 2> /dev/null | cut -d " " -f 2)
# otherwise check the first console size
if [ -z "$TERM_WIDTH" ]; then
TERM_WIDTH=$(stty -F /dev/tty1 size 2> /dev/null | cut -d " " -f 2)
echo "Linux console width $TERM_WIDTH"
else
echo "Serial console width $TERM_WIDTH"
fi
# display QR codes only if the terminal is bigger than the 80x24(25) default
if [ -n "$TERM_WIDTH" ] && [ "$TERM_WIDTH" -gt 80 ]; then
# compute how much QR codes can fit on the screen side-by-side
QR_NUM=$(( TERM_WIDTH / QR_WIDTH ))
# split the list into the IPv4 and IPv6 addresses, display QR codes only for the IPv4 addresses
IP4ADDR=()
IP6ADDR=()
for ADDR in "${ADDRESSES[@]}"; do [[ "$ADDR" =~ : ]] && IP6ADDR+=("$ADDR") || IP4ADDR+=("$ADDR"); done
# split the IPv4 list into 2 parts, for the first part display the QR codes,
QR_ADDRESSES=("${IP4ADDR[@]:0:$QR_NUM}")
SZ="${#IP4ADDR[@]}"
# for the rest display just the text URL as the list is too long
REST_ADDRESSES=("${IP4ADDR[@]:$QR_NUM:$SZ}")
else
QR_ADDRESSES=()
REST_ADDRESSES=("${ADDRESSES[@]}")
fi
if [ -n "${REST_ADDRESSES[*]}" ]; then
printf " https://%s\n" "${REST_ADDRESSES[@]}" >> "$URL_ISSUES"
fi
if [ -n "${IP6ADDR[*]}" ]; then
printf " https://[%s]\n" "${IP6ADDR[@]}" >> "$URL_ISSUES"
fi
if [ -z "${QR_ADDRESSES[*]}" ]; then
return 0
fi
# temporary file for generated QR code
QR_TEMP=$(mktemp)
# temporary file for merged QR codes (displayed side-by-side)
QR_RESULT=$(mktemp)
# copy of the merged QR codes (the merged file cannot be used as input and
# output at the same time)
QR_RESULT_COPY=$(mktemp)
# label with URLs displayed below the QR codes
LABEL=""
# generate the QR codes and merge them side-by-side
for ADDR in "${QR_ADDRESSES[@]}"; do
cp "$QR_RESULT" "$QR_RESULT_COPY"
URL="https://$ADDR"
echo "Rendering QR code for $URL"
# force (the -v option) using at least symbol version 2 (QR size 25x25),
# for short addresses (like https://1.1.1.1) it would be enough using
# version 1 (QR size 21x21), but longer addresses need version 2 and
# putting different sizes side-by-side breaks formatting, see `man
# qrencode` and https://www.qrcode.com/en/about/version.html
qrencode -t ANSIUTF8 -m 2 -v 2 -o "$QR_TEMP" "$URL"
# put the QR codes side-by-side
paste -d " " "$QR_RESULT_COPY" "$QR_TEMP" > "$QR_RESULT"
PADDED_URL=$(center_text "$URL" "$QR_WIDTH")
LABEL="$LABEL$PADDED_URL "
done
cat "$QR_RESULT" >> "$URL_ISSUES"
echo "$LABEL" >> "$URL_ISSUES"
# delete the temporary files
rm -f "$QR_TEMP" "$QR_RESULT_COPY" "$QR_RESULT"
}
# helper function, write the issue with the currently available URLs for
# accessing Agama from outside
build_addresses() {
ADDRESSES=()
readarray -t CONNECTIONS < <(busctl -j get-property org.freedesktop.NetworkManager /org/freedesktop/NetworkManager org.freedesktop.NetworkManager ActiveConnections | jq --raw-output ".data[]")
for CONNECTION in "${CONNECTIONS[@]}"; do
TYPE=$(busctl -j get-property org.freedesktop.NetworkManager "$CONNECTION" org.freedesktop.NetworkManager.Connection.Active Type 2> /dev/null | jq --raw-output ".data")
# ignore loopbacks, we need external addresses
if [ "$TYPE" != "loopback" ]; then
IP4CONFIG=$(busctl -j get-property org.freedesktop.NetworkManager "$CONNECTION" org.freedesktop.NetworkManager.Connection.Active Ip4Config 2> /dev/null | jq --raw-output ".data")
mapfile -t IP4ADDRESSES < <(busctl -j get-property org.freedesktop.NetworkManager "$IP4CONFIG" org.freedesktop.NetworkManager.IP4Config AddressData 2> /dev/null | jq --raw-output ".data[].address.data")
ADDRESSES+=("${IP4ADDRESSES[@]}")
IP6CONFIG=$(busctl -j get-property org.freedesktop.NetworkManager "$CONNECTION" org.freedesktop.NetworkManager.Connection.Active Ip6Config 2> /dev/null | jq --raw-output ".data")
# ignore IPv6 link local addresses starting with "fe80:", they are not supported by browsers
mapfile -t IP6ADDRESSES < <(busctl -j get-property org.freedesktop.NetworkManager "$IP6CONFIG" org.freedesktop.NetworkManager.IP6Config AddressData 2> /dev/null | jq --raw-output ".data[].address.data" | grep -i -v ^fe80:)
ADDRESSES+=("${IP6ADDRESSES[@]}")
fi
done
# remove duplicates
readarray -t ADDRESSES < <(printf "%s\n" "${ADDRESSES[@]}" | sort -u)
echo "Found external addresses: ${ADDRESSES[*]}"
# delete the old file
rm -f "$URL_ISSUES"
if [ -n "${ADDRESSES[*]}" ]; then
create_qr_codes "${ADDRESSES[@]}"
fi
write_url_headers
# reload if not in the initial state
if [ -e "$CERT_ISSUE" ]; then
touch /run/agetty.reload
fi
}
# a helper function which generates the URLs for accessing the Agama server
# displayed at the console
generate_network_url() {
# build a message with the current URLs
build_addresses
# watch for IP address changes in the NetworkManager service
dbus-monitor --system "sender='org.freedesktop.NetworkManager',\
interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',\
type=signal" 2> /dev/null | while read -r line; do
# some IP4 configuration has been changed, rebuild the URLs
if echo "$line" | grep -q -E 'string "org.freedesktop.NetworkManager.IP(4|6)Config"'; then
echo "Network configuration changed"
build_addresses
fi
done
}
# wait until the SSL fingreprint issue is created with a time limit passed as
# the second argument (in seconds)
wait_for_ssl_issue() {
for _ in $(seq 1 "$1"); do
[ -f "$CERT_ISSUE" ] && exit 0
sleep 1
done
}
# make sure the parent directory for the issues exists
mkdir -p /run/issue.d
# create the issue file for specified item
if [ "$1" = "--welcome" ]; then
generate_welcome
elif [ "$1" = "--ssh" ]; then
generate_ssh_fingerprints
elif [ "$1" = "--ssl" ]; then
generate_certificate_fingerprints
elif [ "$1" = "--wait-for-ssl" ]; then
wait_for_ssl_issue "$2"
elif [ "$1" = "--watch-network" ]; then
generate_network_url "$2" "$3"
elif [ "$1" = "--watch-avahi" ]; then
generate_avahi_url
else
echo "Missing or incorrect argument"
exit 1
fi