#!/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 if grep -q "\binst.remote=0\b" /run/agama/cmdline.d/agama.conf; then LOCAL_ONLY=1 else LOCAL_ONLY=0 fi # 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() { if [ "$LOCAL_ONLY" = "1" ]; then echo "Remote access to the Agama installer is disabled, not generating Agama SSL fingerprints" exit 0 fi 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() { if [ "$LOCAL_ONLY" = "1" ]; then echo "Remote access to the Agama installer is disabled, skipping Avahi URL generation" exit 0 fi # 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=() IP6ADDR=() # put IPv6 addresses into a separate list to display [] around them for ADDR in "${ADDRESSES[@]}"; do [[ "$ADDR" =~ : ]] && IP6ADDR+=("$ADDR") || REST_ADDRESSES+=("$ADDR") done 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 echo >> "$URL_ISSUES" 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" echo >> "$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() { if [ "$LOCAL_ONLY" = "1" ]; then echo "Remote access to the Agama installer is disabled, skipping network URL generation" # display a warning message printf "\\\\e{brown}Remote access to the Agama installer is disabled, it can be used only locally.\\\\e{reset}\n\n" > "$DISCONNECTED_ISSUE" exit 0 fi # 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