322 lines
12 KiB
Bash
Executable file
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
|